本着“凡我不能创造的,我就不能理解”的思想,本系列文章会基于纯Python以及NumPy从零创建自己的深度学习框架,该框架类似PyTorch能实现自动求导。
要深入理解深度学习,从零开始创建的经验非常重要,从自己可以理解的角度出发,尽量不使用外部完备的框架前提下,实现我们想要的模型。本系列文章的宗旨就是通过这样的过程,让大家切实掌握深度学习底层实现,而不是仅做一个调包侠。
我们前面介绍的简单RNN存在一些问题,即很难保持远离当前位置的信息和梯度消失的问题。
LSTM被设计来解决上面的问题。通过使网络学到忘记不需要的信息,并记住将来作出决定所需要的信息,以明确地管理上下文信息。
LSTM将上下文管理问题分成两个子问题:从上下文中移除不再需要的信息,以及增加未来决定更可能需要的信息。

解决这两个问题的关键是学习如何管理这个上下文,而不是将策略硬编码到架构中。LSTM首先增加一个显示的上下文层到网络中(除了常用的RNN隐藏层),并通过使用专门的神经元,利用门来控制信息流进流出组成网络层的单元。这些门是通过使用在输入、前一个隐藏状态和前一个上下文状态上分别添加的额外权重操作来实现的。
LSTM中的这些门共用了同样的设计模式:每个包含一个前馈网络,接着是sigmoid激活函数,最后是一个与被门控层元素级乘法。
选择sigmoid作为激活函数基于它的输出是在0到1之间。与元素级乘法结合的效果类似于二元掩码(binary mask)。与掩码中接近1的值对应的门层中的值几乎原样通过;与较低值对应的值基本上被擦除。
我们第一个介绍的门为遗忘门(forget gate)。该门的目的是删除上下文不再需要的信息。遗忘门计算前一个隐藏状态和当前输入的加权和,并通过sigmoid进行转换。得到mask,该mask然后与上下文向量元素级相乘来移除不再需要的上下文信息。
f
t
=
σ
(
U
f
h
t
−
1
+
W
f
x
t
)
(1)
f_t = \sigma(U_f h_{t-1} + W_fx_t) \tag 1
ft=σ(Ufht−1+Wfxt)(1)
其中
U
f
U_f
Uf和
W
f
W_f
Wf为两个权重矩阵;
h
t
−
1
h_{t-1}
ht−1为前一个隐藏状态;
x
t
x_t
xt为当前输入;
σ
\sigma
σ为sigmoid函数,这里我们忽略偏置项。
k
t
=
c
t
−
1
⊙
f
t
(2)
k_t = c_{t-1} \odot f_t \tag 2
kt=ct−1⊙ft(2)
其中
c
t
−
1
c_{t-1}
ct−1代表之前的上下文层向量;两个向量的元素级乘法(由操作符
⊙
\odot
⊙表示,有时称为哈达玛积)是两个向量对应元素相乘。
遗忘门控制内存之中的上下文向量 c t − 1 c_{t-1} ct−1是否被遗忘掉。
类似地,输入门也通过前一个隐藏状态
h
t
−
1
h_{t-1}
ht−1和当前输入
x
t
x_t
xt计算:
i
t
=
σ
(
U
i
h
t
−
1
+
W
i
x
t
)
(3)
i_t = \sigma(U_ih_{t-1} + W_ix_t) \tag 3
it=σ(Uiht−1+Wixt)(3)
然后我们计算从前一个隐藏状态
h
t
−
1
h_{t-1}
ht−1和当前输入
x
t
x_t
xt中提取实际的信息——所有RNN网络使用的基本运算:
g
t
=
tanh
(
U
g
h
t
−
1
+
W
g
x
t
)
(4)
g_t = \tanh(U_gh_{t-1} + W_gx_t) \tag 4
gt=tanh(Ught−1+Wgxt)(4)
在简单RNN中,上面计算的结果就是当前时刻的隐藏状态了,但在LSTM中不是。LSTM中的隐藏状态是根据上下文状态
c
t
c_t
ct计算的。
j
t
=
g
t
⊙
i
t
(5)
j_t = g_t \odot i_t \tag 5
jt=gt⊙it(5)
乘上输入门用来控制 g t g_t gt(也称为候选值)能多大程度地存入当前上下文状态 c t c_t ct。
我们把上面得到的
j
t
j_t
jt和
k
t
k_t
kt加起来就得到了当前上下文状态
c
t
c_t
ct:
c
t
=
j
t
+
k
t
(6)
c_t = j_t + k_t \tag 6
ct=jt+kt(6)
最后一个门是输出门,它用于控制当前隐藏状态的哪些信息是需要的。
o
t
=
σ
(
U
o
h
t
−
1
+
W
o
x
t
)
(7)
o_t = \sigma(U_o h_{t-1} +W_o x_t) \tag 7
ot=σ(Uoht−1+Woxt)(7)
h t = o t ⊙ tanh ( c t ) (8) h_t = o_t \odot \tanh(c_t) \tag 8 ht=ot⊙tanh(ct)(8)

给定不同门的权值,LSTM接受前一时刻的上下文层和隐藏层以及当前输入向量作为输入。然后,它生成更新的上下文向量和隐藏向量作为输出。隐藏层 h t h_t ht可以用作堆叠RNN中后续层的输入,或者为网络的最后一层生成输出。
比如基于当前的隐藏状态,可以计算出当前时刻的输出
y
^
t
\hat y_t
y^t:
y
^
t
=
softmax
(
W
y
h
t
+
b
y
)
(9)
\hat y_t = \text{softmax}(W_y h_t + b_y) \tag{9}
y^t=softmax(Wyht+by)(9)
总之,LSTM计算 c t c_t ct和 h t h_t ht。 c t c_t ct就是长期记忆, h t h_t ht就是短期记忆。使用输入 x t x_t xt和 h t − 1 h_{t-1} ht−1更新长期记忆,在更新中,一些 c t c_t ct的特征由遗忘门 f t f_t ft清除,还有一些特征由输入门增加到 c t c_t ct中。
新的短期记忆是长期记忆经过 tanh \tanh tanh后乘以输出门得到的。注意,执行更新时,LSTM不会查看长期记忆 c t c_t ct。只修改它。 c t c_t ct也永远不会经历线性转换。这就是解决梯度消失和爆炸的原因。