美文网首页
第十一章 循环神经网络

第十一章 循环神经网络

作者: 晨光523152 | 来源:发表于2020-03-19 14:31 被阅读0次

    CNNs 利用数据的局部相关性和权值共享的思想大大减少了网络的参数量,非常适合于图片这种具有空间局部相关性的数据。

    然而除了空间维度之外,自然界还有一个时间维度

    e.g. 说话时发出的语音信号,随着时间变化的股市参数等等。这类数据并不一定具有局部相关性,同时数据在时间维度上的长度也是可变的,并不适合CNNs。

    11.1 序列表示方法

    具有先后顺序的数据一般叫做序列,比如随时间而变化的商品价格数据就是非常典型的序列。

    如果希望神经网络能够用于 nlp 任务,那么怎么把单词或字符转化为数值就变得关键(因为,神经网络是一系列矩阵相乘,相加等运算)。

    接下来探讨文本序列的表示方法,其他非数值类型的信号可以参考文本序列的表示方法。

    • one-hot 编码
      以英文句子为例,假设我们只考虑最常用的一万个单词,那么一个单词就可以表示为某位为1,其他位置为0的长度为10000的稀疏向量。
    一个one-hot 编码例子

    把文字编码为数值的算法叫做 Word embedding,它也表示经过这个过程后得到的词向量,具体表示算法还是词向量需要依语境定。

    one-hot 编码的向量是高维度而且极其稀疏的,大量位置为0,计算效率较低,同时也不利于神经网络的训练。

    one-hot 编码还有个问题,得到的向量没有数值关系,忽略了单词先天具有的语义相关性。e.g. like / dislike。

    一个衡量表示向量的尺度就是余弦相关度:

    余弦相关度

    11.1.1 Embedding 层

    在神经网络中,单词的表示向量可以直接通过训练的方式得到,把单词的表示层叫做 Embedding 层。

    Embedding 层负责把单词编码为某个向量vec,他接受的是采用数字编码的单词idx,如 2 表示 “I”,3 表示“me”等,系统总单词数量记作N_{vocab},输出长度为f的向量vec
    vec = f_{\theta}(idx| N_{vocab},f)

    Embedding 层通过一个 shape 为 [N_{vocab},f]的查询表 table,对于任意的idx,只需查询到对应位置上的向量返回即可:
    vec = table[idx]

    Embedding 层是可训练的,它可放置在神经网络之前,完成单词到向量的转换,得到的表示向量可以继续通过神经网络完成后续任务。

    TensorFlow中:

    layers.Embedding(N_vocab, f)
    

    11.1.2 预训练的词向量

    Embedding 层的查询表是随机初始化的,需要从零开始训练。

    可以使用预训练的 Word Embedding 模型来得到单词的表示方法,基于预训练模型的词向量相当于迁移了整个语义空间的知识,往往能得到更好的性能,e.g. Word2Vec,GloVe。

    通过设置 Embedding 层中不采用随机初始化的方式,而是使用预训练的词向量模型来帮助提升 NLP 任务的性能:

    图 1

    这里这个 load_embed 函数????

    11.2 循环神经网络

    以情感分析为例:


    情感分析

    通过 Embedding 层把句子转换为[b,s,f]的张量,b 是 batch,s 是句子长度,f 为词向量长度。

    由于输入的文本数据,传统 CNNs 并不能取得很好的效果。

    11.2.1 全连接层可行吗

    对于每一个词向量,分别使用一个全连接层提取语义特征。

    全连接层提取语义特征1 缺点

    11.2.2 共享权值

    上面使用s个全连接层来提取特征没有使用权值共享的思想。

    全连接层提取语义特征2

    虽然这种方法减少了参数量,并且让每个序列的输出长度都一样,但是还是将整个句子拆开来分别理解,无法获取整体的语义信息

    11.2.3 语义信息

    为了让网络能够按序提取词向量的语义信息,并累积成整个句子的语境信息,使用内存(Memory)机制。


    Memory机制

    除了参数W_{xh}之外,额外增加了一个W_{hh}参数,每个时间戳t上的状态张量h刷新机制为:
    h_{t} = \sigma(W_{xh}x_{t} + W_{hh}h_{t-1} + b)

    最后得到的h_{t}能较好地代表了句子的语义信息。

    11.2.4 循环神经网络

    在每一个时间戳,网络层接受当前时间戳的输入x_{t}和上一个时间戳的网络状态向量h_{t-1},经过:

    h_{t} = f_{\theta}(h_{t-1},x_{t})
    然后将h_{t}输入到下一层。

    展开的RNN模型

    在循环神经网络中,激活函数更多地采用 tanh 函数。

    11.4 RNN 层使用方法

    layers.SimpleRNNCell 来完成 \sigma(W_{xh}x_{t}+ W_{hh}h_{t-1}+b)计算

    还有一个是 layers.SimpleRNN。

    SimpleRNN 与 SimpleRNNCell 的区别在于:

    • 带 Cell 的层仅仅是完成了一个时间戳的前向计算,
    • 不带 Cell 的层一般是基于 Cell 层实现的,在其内部已经完成了多个时间戳的循环计算。

    11.4.1 SimpleRNNCell

    以某输入特征长度f=4,Cell 状态向量特征长度h=3为例:

    cell =layers.SimpleRNNCell(3)
    cell.build(input_shape=(None,4))
    cell.variables
    # 输出为
    [<tf.Variable 'kernel:0' shape=(4, 3) dtype=float32, numpy=
     array([[-0.3022833 ,  0.61006415, -0.62484777],
            [ 0.57937527, -0.38491082,  0.17247498],
            [ 0.15173519, -0.19038242,  0.46637845],
            [ 0.39670765, -0.5884889 ,  0.4437238 ]], dtype=float32)>,
     <tf.Variable 'recurrent_kernel:0' shape=(3, 3) dtype=float32, numpy=
     array([[ 0.63223445, -0.3049811 , -0.7122262 ],
            [ 0.77466154,  0.26471475,  0.5743045 ],
            [ 0.01338477, -0.9148294 ,  0.40361884]], dtype=float32)>,
     <tf.Variable 'bias:0' shape=(3,) dtype=float32, numpy=array([0., 0., 0.], dtype=float32)>]
    

    里面有三个变量,分别对应于W_{xh},W_{hh},b

    11.4.3 SimpleRNN 层

    通过 SimpleRNN 可以仅需一行代码即可完成整个前向运算过程,它默认返回最后一个时间戳上的输出,如果希望返回所有时间戳上的输出列表,可以设置 return_sequences=True 参数:

    layers.SimpleRNN(64, return_sequences = True)
    

    11.6.1 梯度裁剪

    梯度裁剪:将梯度张量的数值或者范数限制在某个较小的区间内,从而将远大于1的梯度值减少。

    常用的梯度裁剪方法:

    • 把张量W限制在一个区间内,e.g. w_{ij}\in [min,max]。通过tf.clio_by_value()实现。
    x = tf.random.normal([2,2])
    x
    ##输出
    <tf.Tensor: id=5, shape=(2, 2), dtype=float32, numpy=
    array([[-1.2057091,  0.7955832],
           [-0.7486858, -0.9154401]], dtype=float32)>
    tf.clip_by_value(x,0,0.5)
    ##输出
    <tf.Tensor: id=10, shape=(2, 2), dtype=float32, numpy=
    array([[0. , 0.5],
           [0. , 0. ]], dtype=float32)>
    
    • 通过限制梯度张量W的范数来实现梯度裁剪。通过tf.clip_by_norm()实现。
      e.g. 对W的二范数约束在[o,max]之间,如果||W||_{2}>max,则按照W^{'}=\frac{W}{||W||_{2}}*max裁剪。

    • 前面两种方式都是通过改变单个梯度张量来实现梯度裁剪,但是这可能会使得网络更新的方向发生改变。为了限制网络的梯度值并且同时不改变网络的更新方向,考虑所有参数的梯度W的范数,实现等比例的缩放。通过tf.clip_by_global_norm实现。
      \begin{split} globalnorm &= \sqrt{\sum_{i}||W^{i}||_{2}^{2}}\\ W^{i} &= \frac{W^{i}\times maxnorm}{max(globalnorm,maxnorm)} \end{split}
      其中W^{i}是指网络参数的第i个梯度,maxnorm是指指定的全局最大范数值。

    11.6.2 梯度弥散

    通过增大学习率,减少网络深度,添加 Skip Connection等措施来解决梯度弥散现象。

    11.7 RNN 短时记忆

    RNN 不能理解长句子,往往只能够从有限长度内的句子提取信息,而对于较长范围内的有用信息往往不能够很好的利用起来,这种现象叫做短时记忆

    (p.s 是不是能够理解为CNN那样的局部感受野,只关注到了一定范围的信息??)

    11.8 LSTM 原理

    在LSTM中,有两个状态向量chc作为LSTM的内部状态向量(可以理解为LSTM的内存Memory),h表示LSTM的输出向量。
    利用三个门控:输入门,遗忘门,输出门,来控制内部信息的流动。

    LSTM结构框图

    11.8.1 遗忘门

    遗忘门作用于LSTM状态向量c上面,用于控制上一个时间戳的记忆c_{t-1}对当前时间戳的影响。遗忘门的控制变量g_{f}由:
    g_{f}=\sigma(W_{f}\times [h_{t-1}, x_{t}]+b_{f})
    其中[h_{t-1},x_{t}]表示把两个向量连接成一个更长的向量W_{f},b_{f}是参数张量。\sigma一把选用 Sigmoid 函数。

    经过遗忘门后,LSTM的状态向量变为g_{f}\times c_{t-1}

    意义:

    • g_{f}=1时,遗忘门全部张开,LSTM接受上一个状态c_{t-1}的全部信息。
    • g_{f} = 0时,遗忘门关闭,忽略上一个状态的信息。
    遗忘门

    11.8.2 输入门

    输入门用于控制LSTM对输入的接收程度。
    计算方式为:
    \begin{split} \tilde{c_{t}}&=tanh(W_{c}\times [h_{t-1},x_{t}]+b_{c})\\ g_{i} &=\sigma(W_{i}\times [h_{t-1},x_{t}]+b_{i})\\ out &=g_{i}\times \tilde{c_{t}} \end{split}
    重点在于第二步吧,控制了\tilde{c_{t}}是否全部进入Memory内。

    输入门控制变量g_{i}的意义:

    • g_{i}=0时,LSTM不接受任何的新输入\tilde{c_{t}}
    • g_{i}=1时,LSTM全部接受新输入\tilde{c_{t}}
    输入门

    11.8.3 刷新Memory

    在遗忘门和输入门的控制下,LSTM有选择地读取了上一个时间戳的记忆c_{t-1}和当前时间戳的新输入\tilde{c_{t}},状态向量c_{t}的刷新方式为:
    c_{t} = g_{i}*\tilde{c_{t}} + g_{f}*c_{t-1}

    +号前面是输入门的输出,针对当前当前时间戳和上一个时间戳;
    +号后面是遗忘门的输出,针对上一个时间戳。

    11.8.4 输出门

    LSTM的内部状态向量c_{t}并不会直接用于输出,而是在输出门的作用下有选择的输出。
    输出门的门控变量g_{o}为:
    g_{o} = \sigma(W_{o}\times [h_{t-1},x_{t}]+b_{o})
    LSTM的输出由:
    h_{t} = g_{o}*tanh(c_{t})

    输出门的意义:

    • g_{o}=0时,输出关闭,LSTM的内部记忆被完全隔离,无法用作输出,此时输出为0的向量;
    • g_{o}=1时,输出完全打开,LSTM的状态向量c_{t}全部用于输出。
    输出门 输出门和遗忘门的典型行为

    (理解方式:输入门针对当前时间戳和上一个时间戳,遗忘门只针对上一个时间戳)

    11.9 LSTM层使用方法

    • 使用LSTMCell 来手动完成时间戳上面的循环计算;
    • 通过LSTM层方式一步完成前向运算。

    11.9.1 LSTMCell

    LSTM的状态变量List有两个,即[h_{t},c_{t}]

    调用cell完成前向运算时,返回两个元素,第一个元素为cell的输出,也就是h_{t}#,第二个元素为cell的更新后的状态List:[h_{t},c_{t}]$。

    x = tf.random.normal([2,80,100])
    xt = x[:,0,:]
    cell = tf.keras.layers.LSTMCell(64)
    state = [tf.zeros([2,64]),tf.zeros([2,64])]
    for xt in tf.unstack(x, axis = 1):
        out, state = cell(xt, state)
    

    11.9.2 LSTM层

    通过tf.keras.layers.LSTM 层可以方便的一次完成整个序列的运算。

    net = tf.keras.Sequential([
        tf.keras.layers.LSTM(64, return_sequences = True),
        tf.keras.layers.LSTM(64)
    ])
    out1 = net(x)
    

    11.10 GRU 简介

    LSTM的缺点:计算代价比较高,模型参数比较大。(优点:LSTM不容易出现梯度弥散现象)。并且,遗忘门是LSTM中最重要的门口,甚至发现只有遗忘门的简化网络在多个基准数据集上面超过标准LSTM。

    门控循环网络(GRU)是应用最广泛的变种之一。其把内部状态向量和输出向量合并,统一为状态向量h,门控数量也减少到两个:复位门和更新门

    GUU 网络结构

    11.10.1 复位门

    复位门用于控制上一个时间戳的状态h_{t-1}进入GRU的量。

    门控向量g_{r}由当前时间戳输入x_{t}和上一时间戳状态h_{t-1}变换得到:
    g_{r} = \sigma(W_{r}\times [h_{t-1},x_{t}] + b_{r})
    门控向量g_{r}只控制状态h_{t-1},而不会控制输入x_{t}
    \tilde{h_{t}}=tanh(W_{h}\times [g_{r} \times h_{t-1},x_{t}]+ b_{h})

    门控向量g_{r}的意义:

    • g_{r}=0时,新输入\tilde{h_{t}}全部来自于输入x_{t},不接受h_{t-1}此时相当于复位h_{t-1}

    • g_{r}=1时,h_{t-1}和输入x_{t}共同产生新输入\tilde{h_{t}}

      复位门结构

    11.10.2 更新门

    更新们用控制上一时间戳状态h_{t-1}和新输入\tilde{h_{t}}对新状态向量h_{t}的影响程度。

    更新门控向量g_{z}由:
    g_{z} = \sigma(W_{z}\times [h_{t-1},x_{t}] + b_{z})

    g_{z}用于控制新输入\tilde{h_{t}}信号,1-g_{z}用于控制状态h_{t-1}信号:
    h_{t}=(1-g_{z})\times h_{t-1} + g_{z} \times \tilde{h_{t}}

    意义:

    • g_{z} = 0时,h_{t}全部来自h_{t-1}
    • g_{z} = 1时,h_{t}全部来自\tilde{h_{t}}

    11.10.3 GUR 使用方法

    同样包括:GURCell,GUR层两种使用方法。

    参考资料:《TensorFlow深度学习》

    相关文章

      网友评论

          本文标题:第十一章 循环神经网络

          本文链接:https://www.haomeiwen.com/subject/jqgckhtx.html