回顾 RNN详解中,RNN的缺陷是无法做到长期依赖。为此我们引入LSTM(Long Short Term Memory networks(以下简称LSTM)),是一种特殊的RNN,主要是为了解决长期依赖问题。同时,介绍一种LSTM的变体GRU,简化了LSTM,提高运算速度。
LSTM引例
先来看这样一个例子:
我们希望RNN可以学习到喝咖啡和打王者荣耀之间的依赖关系,早上喝了咖啡,下午才有精力打王者荣耀,但是二者在时间上并不接近。如何把这个长时间依赖关系表达出来?原始RNN中理论上是可以将其表达出来,但由于上述所提到RNN的缺陷,原始RNN很难把这个依赖学习到。同样后边还有一个长时间依赖:中午如果打过王者荣耀,那么吃完晚饭就不打了,直接睡觉。这种长时间的依赖在序列数据中是很常见的,而LSTM可以很容易的学习到这种依赖。
LSTM原理
标准的RNN如下所示:
在标准RNN的结构上加了点东西,整体结构如下所示:
LSTM由三个门来控制细胞状态,这三个门分别称为忘记门、输入门和输出门。输入门控制当前计算的新状态以及以多大程度更新到记忆单元中;遗忘门控制前一步记忆单元中的信息以多大程度被遗忘掉;输出门控制当前的输出有多大程度取决于当前的记忆单元。接下来依次介绍:
遗忘门
主要决定决定细胞状态需要丢弃哪些信息。通过查看 h ( t − 1 ) h_{(t-1)} h(t−1)和 x t x_t xt的信息来输出一个0-1之间的向量,该向量中的数值表示状态 C t − 1 C_{t-1} Ct−1 中有多少信息保留或丢弃,0表示不保留,1表示都保留,遗忘门如下图所示:
f
t
=
σ
(
W
f
⋅
[
h
t
−
1
,
x
t
]
+
b
f
)
\mathbf{f}_t=\sigma(W_f\cdot[\mathbf{h}_{t-1},\mathbf{x}_t]+\mathbf{b}_f)
ft=σ(Wf⋅[ht−1,xt]+bf)
式中:
W
f
W_f
Wf是遗忘门的权重矩阵,
[
h
t
−
1
,
x
t
]
[\mathbf{h}_{t-1},\mathbf{x}_t]
[ht−1,xt]表示把两个向量连接成一个更长的向量,
b
f
b_f
bf是遗忘门的偏置项,
σ
\sigma
σ 是sigmoid函数。
其中
W
f
⋅
[
h
t
−
1
,
x
t
]
W_f\cdot[\mathbf{h}_{t-1},\mathbf{x}_t]
Wf⋅[ht−1,xt] 可以理解为:
[
W
f
]
[
h
t
−
1
x
t
]
=
[
W
f
h
W
f
x
]
[
h
t
−
1
x
t
]
=
W
f
h
h
t
−
1
+
W
f
x
x
t
\begin{aligned} \begin{bmatrix}W_f\end{bmatrix}\begin{bmatrix}\mathbf{h}_{t-1}\\ \mathbf{x}_t\end{bmatrix}&= \begin{bmatrix}W_{fh}&W_{fx}\end{bmatrix}\begin{bmatrix}\mathbf{h}_{t-1}\\ \mathbf{x}_t\end{bmatrix}\\ &=W_{fh}\mathbf{h}_{t-1}+W_{fx}\mathbf{x}_t \end{aligned}
[Wf][ht−1xt]=[WfhWfx][ht−1xt]=Wfhht−1+Wfxxt
输入门
主要决定给细胞状态添加哪些新的信息。输入门如下图所示:
(1)利用
h
t
−
1
\boldsymbol h_{t-1}
ht−1和
x
t
\boldsymbol x_t
xt通过一个称为输入门的操作来决定更新哪些信息。
i
t
=
σ
(
W
i
⋅
[
h
t
−
1
,
x
t
]
+
b
i
)
\mathbf{i}_t=\sigma(W_i\cdot[\mathbf{h}_{t-1},\mathbf{x}_t]+\mathbf{b}_i)
it=σ(Wi⋅[ht−1,xt]+bi)
(2)利过一个tanh层得到新的候选细胞信息
c
~
t
\mathbf{\tilde{c}}_t
c~t,这些信息可能会被更新到细胞信息中。
c
~
t
=
tanh
(
W
c
⋅
[
h
t
−
1
,
x
t
]
+
b
c
)
\mathbf{\tilde{c}}_t=\tanh(W_c\cdot[\mathbf{h}_{t-1},\mathbf{x}_t]+\mathbf{b}_c)
c~t=tanh(Wc⋅[ht−1,xt]+bc)
(3)计算当前时刻的单元状态
c
t
\boldsymbol c_{t}
ct ,更新的规则就是通过忘记门选择忘记旧细胞信息的一部分,通过输入门选择添加候选细胞信息的一部分得到新的细胞信息。如下图所示:
即:
c
t
=
f
t
∗
c
t
−
1
+
i
t
∗
c
~
t
\mathbf{c}_t=f_t*{\mathbf{c}_{t-1}}+i_t*{\mathbf{\tilde{c}}_t}
ct=ft∗ct−1+it∗c~t
我们就把LSTM关于当前的记忆
c
~
t
\mathbf{\tilde{c}}_t
c~t和长期的记忆
c
t
−
1
\mathbf{c}_{t-1}
ct−1组合在一起,形成了新的单元状态。
由于遗忘门的控制,它可以保存很久很久之前的信息,由于输入门的控制,它又可以避免当前无关紧要的内容进入记忆。接着,来看看输出门,
输出门
将输入经过一个称为输出门的sigmoid层得到判断条件,然后将细胞状态经过tanh层得到一个-1~1之间值的向量,该向量与输出门得到的判断条件相乘就得到了最终该RNN单元的输出。输出门如下图所示:
输出门控制了长期记忆对当前输出的影响
o
t
=
σ
(
W
o
⋅
[
h
t
−
1
,
x
t
]
+
b
o
)
\mathbf{o}_t=\sigma(W_o\cdot[\mathbf{h}_{t-1},\mathbf{x}_t]+\mathbf{b}_o)
ot=σ(Wo⋅[ht−1,xt]+bo)
LSTM最终的输出,是由输出门和单元状态共同确定的:
h
t
=
o
t
∗
tanh
(
c
t
)
\mathbf{h}_t=\mathbf{o}_t* \tanh(\mathbf{c}_t)
ht=ot∗tanh(ct)
LSTM总结
如何实现长期依赖?
在一个训练好的网络中,当输入序列没有重要信息时,LSTM遗忘门的值接近为1,输入门接近0,此时过去的记忆会被保存,从而实现了长期记忆;当输入的序列中出现了重要信息时,LSTM会将其存入记忆中,此时输入门的值会接近于1;当输入序列出现重要信息,且该信息意味着之前的记忆不再重要的时候,输入门接近1,遗忘门接近0,这样旧的记忆被遗忘,新的重要信息被记忆。经过这样的设计,整个网络更容易学习到序列之间的长期依赖。
如何避免梯度消失/爆炸?
在lstm中,状态 c \mathbf c c是通过累加的方式来计算的。不像RNN中的累乘的形式,这样的话,它的的导数也不是乘积的形式,这样就不会发生梯度消失的情况了。
双向LSTM
单向LSTM可以根据前面的信息推出后面,但有时此刻的信息还取决于后面的信息。如:
我今天不舒服,我打算____一天。只根据‘不舒服‘,可能推出我打算‘去医院‘,‘睡觉‘,‘请假‘等等,但如果加上后面的‘一天‘,能选择的范围就变小了,‘去医院‘这种就不能选了,而‘请假‘‘休息‘之类的被选择概率就会更大。
双向LSTM的隐藏层要保存两个值, A 参与正向计算, A’ 参与反向计算。最终的输出值 y 取决于 A 和 A’,如下图所示:
以 t = 2 t=2 t=2为例,输入 x 2 x_2 x2, A 2 A_2 A2是正向计算的结果, A ’ 2 A’_2 A’2是反向计算的结果,输出 y 2 y_2 y2同时取决于 A 2 , A ’ 2 A_2,A’_2 A2,A’2。
在某些任务中,双向的 lstm 要比单向的 lstm 的表现要好。
GRU
GRU(Gated Recurrent Unit)作为LSTM的一种变体,与LSTM有两个不同点:
(1)GRU将LSTM中的两个信息流简化成一个信息流,输入只有一个 h t \boldsymbol h_t ht。
(2)GRU将忘记门和输入门合成了一个单一的更新门,还引入了一个重置门。
如下图所示:
主要运算过程如下:
(1) 重置门:
r
t
=
σ
(
W
r
⋅
[
h
t
−
1
,
x
t
]
)
r_t = \sigma(W_r\cdot[h_{t-1},x_t])
rt=σ(Wr⋅[ht−1,xt])
等效计算:
r
t
=
σ
(
W
r
⋅
h
t
−
1
+
W
r
⋅
x
t
)
r_t = \sigma(W_r\cdot h_{t-1}+ W_r\cdot x_t)
rt=σ(Wr⋅ht−1+Wr⋅xt)
(2) 更新门:
z
t
=
σ
(
W
z
⋅
[
h
t
−
1
,
x
t
]
)
z_t = \sigma(W_z\cdot[h_{t-1},x_t])
zt=σ(Wz⋅[ht−1,xt])
(3) 计算当前时刻的单元状态,其中
r
t
r_t
rt用来控制需要保留多少之前的记忆,比如如果
r
t
r_t
rt为0,那么
h
~
t
\tilde h_t
h~t只包含当前词的信息:
h
~
t
=
tanh
(
W
⋅
[
r
t
∗
h
t
−
1
,
x
t
]
)
\tilde h_t = \tanh(W \cdot[r_t * h_{t-1},x_t])
h~t=tanh(W⋅[rt∗ht−1,xt])
(4) 最后
z
t
z_t
zt控制需要从前一时刻的隐藏层
h
t
−
1
h_{t-1}
ht−1中遗忘多少信息,需要加入多少当前 时刻的隐藏层信息
h
~
t
\tilde h_t
h~t ,最后得到
h
t
h_t
ht,直接得到最后输出的隐藏层信息, 需要注意这里与LSTM的区别是GRU中没有output gate:
h t = ( 1 − z t ) ∗ h t − 1 + z t ∗ h ~ t h_t = (1-z_t)*h_{t-1} + z_t*\tilde h_t ht=(1−zt)∗ht−1+zt∗h~t
相当于简化了LSTM,运算速度提高了很多,应用效果也没有差很多。
参考文章: