循环神经网络:RNN、LSTM


RNN#

  • 基本结构#

 NLP 问题中,通常需要处理的句子都是由 n 个词组成,可看做一串序列,RNN 正是可以处理序列类型数据的深度学习模型。

 如图所示,RNN 由多个相同的神经网络单元,即图中的方块连接而成,输入为每个单词的词向量,上一个单元的计算结果传递给下一个单元。经过串行计算后,每一个单元总是包含有之前所有单元的一些信息。

 t 时刻神经单元的计算包含两个元素,t-1 时刻的输出 $ h_{t-1} $ 和 t 时刻的输入 $ x_{t} $ 。计算后的结果 $ h_{t} $ 则传递给下一个单元,作为 t+1 时刻的一个输入。假设输入序列为 $ x_{1},x_{2},x_{3},…,x_{n} $ ,对应的中间状态为 $ h_{1},h_{2},h_{3},…,h_{n} $ ,输出为 $ y_{1},y_{2},y_{3},…,y_{n} $ 。

 计算过程为:

$$ h_{t} = f(Ux_{t} + Wh_{t-1} + b) $$

$$ y_{t} = g(Vh_{t} + c) $$

 其中,$U,W,V,b,c$ 为需要更新的共享参数,激活函数 $ f $ 一般为 tanh 函数。

  • 其他结构#

    • Many to One#

       当处理文本分类时,输入是一个文本序列,而输出可能只是一个类别,那么只需要对最后一个中间状态做计算并输出结果就可以了。如下图所示:

    ​ 计算过程:

    ​ $$ h_{t} = f(Ux_{t} + Wh_{t-1} + b) $$

    ​ $$ y = g(Vh_{4} + c) $$

    • One to Many#

      当处理 Image Caption 任务时,输入可能是一个向量,输出则是一个文本序列,如下图所示:

      计算过程:
      $$ h_{t} = f(Ux + Wh_{t-1} + b) $$
      $$ y_{t} = g(Vh_{t} + c) $$

    • Many to Many#

      当处理机器翻译时,输入一串文本序列,输出一串文本序列。如下图所示:

      该模型称为 Sequence to Sequence 模型,又称为 Encoder-Decoder 模型。

  • 梯度消失(爆炸)#

    假设有三个时间段的 RNN 模型,如下图所示:

    前向传播:

    $t_{3}$ 时刻的损失函数为 $L_{3}$,对共享参数 $W、U、V$ 求导:

    可见,共享参数 $W、U$ 的每次求导计算会涉及到整个序列。而 RNN 的神经单元只有一个 $tanh$ 激活函数,如下图所示:

    standard RNN

    即:

    反向传播求导过程会包含每一步求导的连乘,假如参数 W 也是一个比较小的数 0.02 ,当 t 很大时,上式就会趋于零,RNN 的梯度就会消失。反之,会梯度爆炸。

LSTM#

  • 长期依赖问题#

     若梯度消失,那么最前面的输入所蕴含的信息就无法传达到后面。比如要推测 I grew up in France… I speak fluent French. 的最后一个词 French 。那么肯定就要知道很靠前的 France 这个词的信息,但是它们相互相隔非常远,有可能获取不到,如下图所示:

    RNN-longtermdependencies

  • 基本结构#

    RNN 的神经网络单元不同的是,LSTM 每个单元输的出包括两部分:$C_{t}$ 和 $h_{t}$ ,同时引入了遗忘门、输入门和输出门,增加了很多计算。

    LSTM

    单元状态 $C_{t-1}$ 通过累加的方式记录了 t 时刻需要保存的信息,作用在整个神经单元,因此可以长距离传输信息,如下图所示:

    • 遗忘门#

      遗忘门用来丢弃上一时刻 $C_{t-1}$ 的部分信息,上一时刻的隐状态 $h_{t-1}$ 和当前时刻的输入 $x{t}$ 通过一个 $sigmoid$ 层,输出 $f_{t}$ 介于 01 之间,1 代表信息全部保留,0 代表全部丢弃。

    • 输入门#

      1.为了更新单元状态 $C_{t}$ ,将 $h_{t-1}$ 和 $x_{t}$ 传递给 $sigmoid$ 函数,输出 $i_{t}$ 同样介于 01 之间,决定将更新临时单元状态中的哪些值。

      2.为了协调神经单元,将 $h_{t-1}$ 和 $x_{t}$ 传递给 $tanh$ 函数,输出的临时单元状态 $\tilde{C_{t}}$ 介于 -11 之间。

      3.将 $i_{t}$ 和 $\tilde{C_{t}}$ 逐点相乘。

    • 单元状态 $C_{t}$#

      1.将 $C{t-1}$ 与 $f_{t}$ 逐点相乘,和接近 0 的值相乘,表示该词的作用不太大,会逐渐被遗忘;反之,该词的权重会变大,表示比较重要。

      2.将结果和输入门的输出逐点相加,将单词的向量加加减减,更新为新的值,构成当前时刻神经单元的所有信息 $C{t}$ 。

    • 输出门#

      1.将 $h_{t-1}$ 和 $x_{t}$ 传递给 $sigmoid$ 函数,输出 $o_{t}$ 同样介于 01 之间,决定 $C_{t}$ 的哪些部分需要输出。

      2.将 $C_{t}$ 传递给 $tanh$ 函数,与 $o_{t}$ 逐点相乘得到输出,该输出作为当前隐状态 $h_{t}$ 参与下一个神经单元进行计算。

伪代码#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def tanh(x):
return (np.exp(x) - np.exp(-x)) / (np.exp(x) + np.exp(-x))
def sigmoid(x):
return 1 / 1 + np.exp(x)

def forget_layer(com):
return sigmoid(com)
def input_layer(com):
return sigmoid(com)
def temp_ct_layer(com):
return tanh(com)
def output_layer(com):
return sigmoid(classmethod)

def LSTMCell(pre_ct, pre_ht, input):
combine = pre_ht + input
ft = forget_layer(combine)
it = input_layer(combine)
temp_ct = temp_ct_layer(combine)
ot = output_layer(combine)
ct = pre_ct * ft + temp_ct * it
ht = ot * tanh(ct)
return ct, ht

ct = [0, 0, 0]
ht = [0, 0, 0]
inputs = [...]
for input in inputs:
LSTMCell(ct, ht, input)

References#

Understanding LSTM Networks

The Unreasonable Effectiveness of Recurrent Neural Networks