基础的 RNN 网络结构如下图所示,上一个时间戳的状态向量 𝑡−1与当前时间戳的输入𝒙𝑡经过线性变换后,通过激活函数𝑡𝑎𝑛ℎ后得到新的状态向量 𝑡。
相对于基础的RNN网络只有一个状态向量 𝑡,LSTM 新增了一个状态向量𝑪𝑡,同时引入了门控(Gate)机制,通过门控单元来控制信息的遗忘和刷新,如下图所示:
在 LSTM 中,有两个状态向量𝒄和h ,其中𝒄作为LSTM 的内部状态向量,可以理解为LSTM 的内存状态向量Memory,而 h表示LSTM 的输出向量。相对于基础的RNN 来说,LSTM 把内部Memory 和输出分开为两个变量,同时利用三个门控:输入门(Input Gate)、遗忘门(Forget Gate)和输出门(Output Gate)来控制内部信息的流动。
门控机制可以理解为控制数据流通量的一种手段,类比于水阀门:当水阀门全部打开时,水流畅通无阻地通过;当水阀门全部关闭时,水流完全被隔断。在LSTM 中,阀门开和程度利用门控值向量𝒈表示,如下图所示,通过𝜎(𝒈)激活函数将门控制压缩到[0,1]之间区间,当𝜎(𝒈) = 0时,门控全部关闭,输出𝒐 = 0;当𝜎(𝒈) = 1时,门控全部打开,输出𝒐 = 𝒙。通过门控机制可以较好地控制数据的流量程度。
下面分别介绍三个门控的原理及作用
遗忘门
遗忘门作用于LSTM 状态向量𝒄上面用于控制上一个时间戳的记忆𝒄𝑡−1对当前时间戳的影响。遗忘门的控制变量𝒈𝑓由 𝒈𝑓 = 𝜎(𝑾𝑓[ h𝑡−1, 𝒙𝑡 ] + 𝒃𝑓 ) 产生,如图下图所示,其中𝑾𝑓和𝒃𝑓为遗忘门的参数张量,可由反向传播算法自动优化,𝜎为激活函数,一般使用Sigmoid 函数。当门控𝒈𝑓 = 1时,遗忘门全部打开,LSTM 接受上一个状态𝒄𝑡−1的所有信息;当门控𝒈𝑓 = 0时,遗忘门关闭,LSTM 直接忽略𝒄𝑡−1,输出为0的向量。这也是遗忘门的名字由来。
经过遗忘门后,LSTM 的状态向量变为𝒈𝒄。
输入门
输入门用于控制LSTM 对输入的接收程度。首先通过对当前时间戳的输入𝒙𝑡和上一个
时间戳的输出 𝑡−1做非线性变换得到新的输入变量
其中𝑾𝑐和𝒃𝑐为输入门的参数,需要通过反向传播算法自动优化,tanh 为激活函数,用于将
输入标准化到[−1,1]区间。𝒄 𝑡并不会全部刷新进入LSTM 的Memory,而是通过输入门控制
接受输入的量。输入门的控制变量同样来自于输入𝒙𝑡和输出 h 𝑡−1:
其中𝑾𝑖和𝒃𝑖为输入门的参数,需要通过反向传播算法自动优化,𝜎为激活函数,一般使用Sigmoid 函数。输入门控制变量𝒈𝑖决定了LSTM 对当前时间戳的新输入𝒄 𝑡的接受程度:当𝒈𝑖 = 0时,LSTM 不接受任何的新输入𝒄 𝑡;当𝒈𝑖 = 1时,LSTM 全部接受新输入𝒄 𝑡,如图所示。
经过输入门后,待写入Memory 的向量为𝒈𝑖 𝒄𝑡
刷新Memory
在遗忘门和输入门的控制下,LSTM 有选择地读取了上一个时间戳的记忆𝒄𝑡−1和当前
时间戳的新输入𝒄 𝑡,状态向量𝒄𝑡的刷新方式为:
得到的新状态向量𝒄𝑡即为当前时间戳的状态向量,如上图所示。
输出门
LSTM 的内部状态向量𝒄𝑡并不会直接用于输出,这一点和基础的RNN 不一样。基础的RNN 网络的状态向量 既用于记忆,又用于输出,所以基础的RNN 可以理解为状态向量𝒄和输出向量 是同一个对象。在LSTM 内部,状态向量并不会全部输出,而是在输出门的作用下有选择地输出。输出门的门控变量𝒈𝑜为:
其中𝑾𝑜和𝒃𝑜为输出门的参数,同样需要通过反向传播算法自动优化,𝜎为激活函数,一般使用Sigmoid 函数。当输出门𝒈𝑜 = 0时,输出关闭,LSTM 的内部记忆完全被隔断,无法用作输出,此时输出为0 的向量;当输出门𝒈𝑜 = 1时,输出完全打开,LSTM 的状态向量𝒄𝑡全部用于输出。LSTM 的输出由:
产生,即内存向量𝒄𝑡经过tanh激活函数后与输入门作用,得到LSTM 的输出。由于𝒈𝑜 ∈ [0,1],tanh(𝒄t) ∈ [−1,1],因此LSTM 的输出 𝑡 ∈ [−1,1]。
小结
LSTM 虽然状态向量和门控数量较多,计算流程相对复杂。但是由于每个门控功能清晰明确,每个状态的作用也比较好理解。这里将典型的门控行为列举出来,并解释其代码的LSTM 行为,如表所示。
输入门控 | 遗忘门控 | LSTM行为 |
0 | 1 | 只使用记忆 |
1 | 1 | 综合输入和记忆 |
0 | 0 | 清零记忆 |
1 | 0 | 输入覆盖记忆 |
LSTM实战
在上一篇的博客中我们使用了SimpleRNN层去完成了一个简单的情感分类问题,但是在实际的工程当中很少有人会直接使用SimpleRNN而是多去使用LSTM或者GRU. 我们只需要将上篇博客的内容稍作修改便可以转化为LSTM层的使用
LSTMCell
和SimpleRNN一样,Tensorflow提供了两个网络层的表达方式,一个是LSTMCell一个是LSTM
self.state0 = [tf.zeros([batchsz, units])]
self.state1 = [tf.zeros([batchsz, units])]
==>
self.state0 = [tf.zeros([batchsz, units]),tf.zeros([batchsz, units])]
self.state1 = [tf.zeros([batchsz, units]),tf.zeros([batchsz, units])]
self.rnn_cell0 = layers.SimpleRNNCell(units, dropout=0.5)
self.rnn_cell1 = layers.SimpleRNNCell(units, dropout=0.5)
==>
self.rnn_cell0 = layers.LSTMCell(units,dropout=0.5)
self.rnn_cell1 = layers.LSTMCell(units,dropout=0.5)
只要改这四行代码即可
LSTM层
如果是LSTM层就更简单了
self.rnn = keras.Sequential([
layers.LSTM(units,dropout=0.5,return_sequences=True,unroll=True),
layers.LSTM(units,dropout=0.5,unroll=True)
])
构建时候把SimpleRNN改成LSTM就ok了