第十一章 循环神经网络

2020-03-19  本文已影响0人  晨光523152

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

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

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

11.1 序列表示方法

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

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

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

一个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 的区别在于:

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的梯度值减少。

常用的梯度裁剪方法:

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)>

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}

意义:

遗忘门

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}的意义:

输入门

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})

输出门的意义:

输出门 输出门和遗忘门的典型行为

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

11.9 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}的意义:

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}}

意义:

11.10.3 GUR 使用方法

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

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

上一篇下一篇

猜你喜欢

热点阅读