第2章 自然语言和单词的分布式表示
第3章 word2vec
第4章 word2vec的高速化
第5章 RNN
第6章 Gated RNN
第7章 基于RNN生成文本
第8章 Attention
《深度学习进阶:自然语言处理》第6章 Gated RNN
第6章 Gated RNN
按照惯例,上一章介绍的RNN的原理也需要在实现上做一些处理。实际应用中,经常使用LSTM或GRU网络代替上一章介绍的简单的RNN。
LSTM和GRU中增加了一种名为“门”的结构。基于这个门,可以学习到时序数据的长期依赖关系。
6.1 RNN的问题
简单RNN之所以不擅长学习时序数据的长期依赖关系,是因为BPTT会发生梯度消失和梯度爆炸的问题。
6.1.1 RNN的复习
6.1.2 梯度消失和梯度爆炸
参数更新是依靠输出结果与标签数据的差异求得梯度,并通过反向传播将梯度传递给上游的参数,由此输出结果与标签数据的差异体现出的有效信息被参数学习到。但是随着链路的拉长,梯度在传递中变弱甚至消失,权重参数将无法更新。简单RNN不能很好地避免梯度消失(vanishing gradients)或梯度爆炸(exploding gradients)。
6.1.3 梯度消失和梯度爆炸的原因
反向传播的梯度流经tanh、“+”和MatMu(矩阵乘积)运算。“+”的反向传播将上游传来的梯度原样传给下游,因此梯度的值不变。所以造成梯度消失和梯度爆炸的原因需要从tanh和MatMul运算中寻找。
RNN使用的激活函数tanh,其输入输出对应关系是
y
=
tanh
x
y=\tanh x
y=tanhx,导数为
d
y
d
x
=
1
−
y
2
\frac {dy} {dx}=1-y^2
dxdy=1−y2。
从tanh的导数的图象可以看出tanh导数数值范围在0到1之间,所有的梯度经过tanh函数都会变小,经过次数过多,梯度可能会消失。可以使用ReLU代替tanh,可以抑制tanh引起的梯度消失问题。
现在来看MatMul运算。忽略tanh的影响,矩阵相乘运算的反向传播过程如图:
可以看出来源于后端的梯度
d
h
dh
dh需要反复与参数矩阵相乘。本书做了两个简单的实验。
第一个实验将
d
h
dh
dh设置为元素都是1的向量,参数矩阵
W
h
W_h
Wh生成随机矩阵,矩阵元素大小基本在(-2,2)。按照上图反复相乘,计算每次相乘结果矩阵的L2范数,绘制矩阵L2范数与次数的关系如下:
可知发生了梯度爆炸。
第二个实验和第一个相似,只是在参数矩阵随机初始化的时候将每个元素乘以0.5,使得所有原始基本都在(-1,1)之间。实验结果如下:
可知发生了梯度消失。
如果将参数矩阵看做是一个标量,另外一个数反复与这个标量相乘,如果标量是大于1的,结果将快速增长;如果标量是小于1的,结果将不断减小。
对于矩阵来说,如果矩阵的最大奇异值大于1,则可能会发生梯度爆炸;如果矩阵的最大奇异值小于1,则可能发生梯度消失。
6.1.4 梯度爆炸的对策
针对梯度爆炸,可以使用梯度裁剪(gradients clipping)
的方法。简单说就是当梯度矩阵
g
^
\hat{g}
g^的L2范数大于一个设定的阈值
t
h
r
e
s
h
o
l
d
threshold
threshold时,我们就将梯度的每个元素按照阈值与矩阵L2范数的比例进行缩小。
伪代码如下:
i f ∥ g ^ ∥ ≥ t h r e s h o l d : g ^ = t h r e s h o l d ∥ g ^ ∥ g ^ if \parallel \hat{g} \parallel \geq threshold:\\ \hat{g}=\frac {threshold}{\parallel \hat{g} \parallel}\hat{g} \hspace{-1cm} if∥g^∥≥threshold:g^=∥g^∥thresholdg^
6.2 梯度消失和LSTM
人们已经提出了诸多Gated RNN框架解决矩阵相乘过程中的梯度消失问题,具有代表性的有LSTM和GRU。本章介绍LSTM,Long Short-Term Memory(长短期记忆),意思是可以长(Long)时间维持短期记忆(Short-Term Memory)。
6.2.1 LSTM的接口
相比RNN,LSTM增加记忆单元 c c c。记忆单元仅在LSTM层内传递。
6.2.2 LSTM层的结构
c
t
c_t
ct存储了时刻
t
t
t时LSTM的记忆,保存了从过去到时刻
t
t
t的所有必要信息,训练时就是以此为目的。
c
t
−
1
c_{t−1}
ct−1、
h
t
−
1
h_{t−1}
ht−1和
x
t
x_t
xt作为输入,经过某种计算后输出当前的记忆单元
c
t
c_t
ct。而隐藏状态
h
t
h_t
ht是
c
t
c_t
ct经过激活函数转化后得到,所以两者的尺寸相同。
回头看本章开头说的,LSTM属于Gate RNN,LSTM提供的门不仅能控制开合,还能控制流量的大小,或者说开合程度。有专门的权重参数经过sigmoid函数用于控制门的开合程度,这些权重参数通过学习被更新。
6.2.3 输出门
上面说到输出
h
t
=
tanh
(
c
t
)
h_t=\tanh(c_t)
ht=tanh(ct),现在针对输出
h
t
h_t
ht施加门,称这个门为输出门(output gate)。
设定输出门的开合程度取决于输入
x
t
x_t
xt和上一个状态
h
t
−
1
h_{t-1}
ht−1,分别给两个输入配置权重矩阵
W
x
(
o
)
W_x^{(o)}
Wx(o)和
W
h
(
o
)
W_h^{(o)}
Wh(o),再加上偏置
b
(
o
)
b^{(o)}
b(o),上标o表示output。
输出门公式如下:
o
=
σ
(
x
t
W
x
(
o
)
+
h
t
−
1
W
h
(
o
)
+
b
(
o
)
)
(6.1)
o=\sigma(x_tW_x^{(o)}+h_{t-1}W_h^{(o)}+b^{(o)}) \tag{6.1}
o=σ(xtWx(o)+ht−1Wh(o)+b(o))(6.1)
上式中
σ
(
)
\sigma()
σ()表示sigmoid函数。
输出门
o
o
o与之前提到的
tanh
(
c
t
)
\tanh(c_t)
tanh(ct)的对应的元素的乘积(阿达玛Hadamard乘积)作为最终的状态
h
t
h_t
ht。
∘
\circ
∘表示哈达玛乘积。
o
o
o与
tanh
(
c
t
)
\tanh(c_t)
tanh(ct)形状相同。
h
t
=
o
∘
tanh
(
c
t
)
(6.2)
h_t=o\circ \tanh(c_t) \tag{6.2}
ht=o∘tanh(ct)(6.2)
tanh的输出是−1.0 ~ 1.0的实数。我们可以认为这个−1.0 ~ 1.0的数值表示某种被编码的“信息”的强弱(程度)。而 sigmoid 函数的输出是 0.0~1.0 的实数,表示数据流出的比例。因此,在大多数情况下,门使用 sigmoid 函数作为激活函数,而包含实质信息的数据则使用 tanh 函数作为激活函数。
6.2.4 遗忘门
必要的忘记是前进的需要。遗忘门(forget gate)施加在上一记忆单元
c
t
−
1
c_{t-1}
ct−1,其输出计算公式如下:
f
=
σ
(
x
t
W
x
(
f
)
+
h
t
−
1
W
h
(
f
)
+
b
(
f
)
)
(6.3)
f=\sigma(x_tW_x^{{(f)}}+h_{t-1}W_h^{(f)}+b^{(f)}) \tag {6.3}
f=σ(xtWx(f)+ht−1Wh(f)+b(f))(6.3)
上一记忆单元
c
t
−
1
c_{t-1}
ct−1与遗忘门
f
f
f进行哈达玛乘积运算之后输出本轮记忆单元
c
t
c_t
ct。
c
t
=
f
∘
c
t
−
1
c_t=f\circ c_{t-1}
ct=f∘ct−1
6.2.5 新的记忆单元
遗忘门从上一时刻的记忆单元中删除了应该忘记的东西,但是这样一
来,记忆单元只会忘记信息。现在我们还想向这个记忆单元添加一些应当记住的新信息。
基于tanh节点计算出的结果被加到上一时刻的记忆单元
c
t
−
1
c_{t−1}
ct−1上。这样一来,新的信息就被添加到了记忆单元中。这个tanh节点的作用不是门,而是将新的信息添加到记忆单元中。因此,它不用sigmoid函数作为激活函数,而是使用tanh函数。
g
=
tanh
(
x
t
W
x
(
g
)
+
h
t
−
1
W
h
(
g
)
+
b
(
g
)
)
(6.4)
g=\tanh(x_tW_x^{{(g)}}+h_{t-1}W_h^{(g)}+b^{(g)}) \tag {6.4}
g=tanh(xtWx(g)+ht−1Wh(g)+b(g))(6.4)
6.2.6 输入门
上节新的记忆单元
g
g
g需要添加门来控制,称为输入门(input gate)
,记作
i
i
i。输入门的结构与输出门相同,只是作用的目标是
g
g
g。
i = σ ( x t W x ( i ) + h t − 1 W h ( i ) + b ( i ) ) (6.5) i=\sigma(x_tW_x^{(i)}+h_{t-1}W_h^{(i)}+b^{(i)}) \tag {6.5} i=σ(xtWx(i)+ht−1Wh(i)+b(i))(6.5)
然后将
i
i
i与
g
g
g进行哈达玛乘积运算。
上面结构就是一个完整的LSTM结构。还有其他变体,本章介绍的LSTM是最具代表性的。
6.2.7 LSTM的梯度的流动
下面解释为什么LSTM结构能避免梯度消失。
观察上面记忆单元的反向传播,仅经过“+”、“x”。
“+”节点将上游传递来的梯度原样流出,梯度没有变化。
“x”节点使用的是哈达玛乘积,是对应的元素相乘,而且每次的门值是不同的,由此避免了发生梯度消失或梯度爆炸。
6.3 LSTM的实现
有时间单开本书的代码实战专栏。
6.4 使用LSTM的语言模型
下图左边是第5章的Time RNN的语言模型网络结构,右边是将左边的RNN替换成LSTM形成的Time LSTM语言模型。
6.5 进一步改进RNNLM
6.5.1 LSTM层的多层化
创建高精度模型时,加深LSTM层(叠加多个 LSTM层)的方法往往很有效。
上图显示了叠加两个LSTM层的例子。叠加多个LSTM层,从而学习更加复杂的模式。在PTB数据集上学习语言模型的情况下,当LSTM的层数为2~4时,可以获得比较好的结果。
6.5.2 基于Dropout抑制过拟合
通过加深层,可以创建表现力更强的模型,但是这样的模型往往会发生过拟合(overfitting)。RNN比常规的前馈神经网络更容易发生过拟合。
抑制过拟合已有既定的方法:一是增加训练数据;二是降低模型的复杂度。我们会优先考虑这两个方法。除此之外,对模型复杂度给予惩罚的正则化也很有效。
Dropout, 在训练时随机忽略层的一部分(比如50%)神经元,也可以被视为一种正则化。
Dropout随机选择一部分神经元,然后忽略它们,停止向前传递信号。这种“随机忽视”是一种制约,可以提高神经网络的泛化能力。
使用RNN的模型中,将Dropout层插入在LSTM层的时序方向上,随着时间的推移,信息会渐渐丢失,因Dropout产生的噪声会随时间成比例地积累。
所以应该在深度方向(垂直方向)上插入Dropout层。
另外,“变分Dropout”(variational dropout)除了深度方向,也能用在时间方向上,从而进一步提高语言模型的精度。如图 6-34 所示,它的机制是同一层的Dropout使用相同的mask。这里所说的mask是指决定是否传递数据的随机布尔值。不做赘述。
6.5.3 权重共享
改进语言模型有一个非常简单的技巧,那就是权重共享(weight tying)。
绑定(共享)Embedding层和Affine层的权重,大大减少学习的参数数量,还能收获抑制过拟合的好处。假设词汇量为 V,LSTM 的隐藏状态的维数为H,则Embedding层的权重形状为V×H,Affine层的权重形状为H×V。此时,如果要使用权重共享,只需将Embedding层权重的转置设置为Affine层的权重。
6.5.4 更好的RNNLM的实现
6.5.4 更好的RNNLM的实现
优化点:
- LSTM层的多层化(此处为 2 层)
- 使用Dropout(仅应用在深度方向上)
- 权重共享(Embedding层和Affine层的权重共享)
实现不做赘述。
优化后的结构在PTB上的学习需要相当长的时间。在用CPU运行的情况下,需要2天左右;而如果用GPU运行,则能在5小时左右完成。