(译)理解 LSTM 网络 (Understanding LSTM Networks by colah)

转自:https://blog.csdn.net/Jerr__y/article/details/58598296

@翻译:huangyongye

原文链接: Understanding LSTM Networks

前言:其实之前就已经用过 LSTM 了,是在深度学习框架 keras 上直接用的,但是到现在对LSTM详细的网络结构还是不了解,心里牵挂着难受呀!今天看了 tensorflow 文档上面推荐的这篇博文,看完这后,焕然大悟,对 LSTM 的结构理解基本上没有太大问题。此博文写得真真真好!!!为了帮助大家理解,也是怕日后自己对这些有遗忘的话可以迅速回想起来,所以打算对原文写个翻译。首先声明,由于本人水平有限,如有翻译不好或理解有误的多多指出!此外,本译文也不是和原文一字一句对应的,为了方便理解可能会做一些调整和修改。)

1. 循环神经网络(RNNs)

人们思考问题往往不是从零开始的。就好像你现在阅读这篇文章一样,你对每个词的理解都会依赖于你前面看到的一些词,而不是把你前面看的内容全部抛弃了,忘记了,再去理解这个单词。也就是说,人们的思维总是会有延续性的。

传统的神经网络是做不到这样的延续性(它们没办法保留对前文的理解),这似乎成了它们一个巨大的缺陷。举个例子,在观看影片中,你想办法去对每一帧画面上正在发生的事情做一个分类理解。目前还没有明确的办法利用传统的网络把对影片中前面发生的事件添加进来帮助理解后面的画面。

但是,循环神经网络可以做到。在RNNs的网络中,有一个循环的操作,使得它们能够保留之前学习到的内容。

 
Fig1. RNNs 网络结构 

在上图网络结构中,对于矩形块 A 的那部分,通过输入xtxt(t时刻的特征向量),它会输出一个结果htht(t时刻的状态或者输出)。网络中的循环结构使得某个时刻的状态能够传到下一个时刻。(译者注:因为当前时刻的状态会作为下一时刻输入的一部分)

这些循环的结构让 RNNs 看起来有些难以理解。但是,你稍微想一下就会发现,这似乎和普通的神经网络有不少相似之处呀。我们可以把 RNNs 看成是一个普通的网络做了多次复制后叠加在一起组成的。每一网络会把它的输出传递到下一个网络中。我们可以把 RNNs 在时间步上进行展开,就得到下图这样:

 
fig2. RNNs 展开网络结构

从 RNNs 链状的结构很容易理解到它是和序列信息相关的。这种结构似乎生来就是为了解决序列相关问题的。

而且,它们的的确确非常管用!在最近的几年中,人们利用 RNNs 不可思议地解决了各种各样的问题:语音识别,语言模型,翻译,图像(添加)字幕,等等。关于RNNs在这些方面取得的惊人成功,我们可以看 Andrej Karpathy 的博客: The Unreasonable Effectiveness of Recurrent Neural Networks.

RNNs 能够取得这样的成功,主要还是 LSTMs 的使用。这是一种比较特殊的 RNNs,而且对于很多任务,它比普通的 RNNs 效果要好很多很多!基本上现在所使用的循环神经网络用的都是 LSTMs,这也正是本文后面所要解释的网络。

2. 长时期依赖存在的问题

RNNs 的出现,主要是因为它们能够把以前的信息联系到现在,从而解决现在的问题。比如,利用前面的画面,能够帮助我们理解当前画面的内容。如果 RNNs 真的可以做到这个,那么它肯定是对我们的任务有帮助的。但是它真的可以 做到吗,恐怕还得看实际情况呀!

有时候,我们在处理当前任务的时候,只需要看一下比较近的一些信息。比如在一个语言模型中,我们要通过上文来预测一下个词可能会是什么,那么当我们看到“ the clouds are in the ?”时,不需要更多的信息,我们就能够自然而然的想到下一个词应该是“sky”。在这样的情况下,我们所要预测的内容和相关信息之间的间隔很小,这种情况下 RNNs 就能够利用过去的信息, 很容易的实现。

 
fig2. 短期依赖

但是,有些情况是需要更多的上下文信息。比如我们要预测“I grew up in France … (此处省略1万字)… I speak ?”这个预测的词应该是 Franch,但是我们是要通过很长很长之前提到的信息,才能做出这个正确的预测的呀,普通的 RNNs 很难做到这个。

随着预测信息和相关信息间的间隔增大, RNNs 很难去把它们关联起来了。

 
fig3. 长期依赖

从理论上来讲,通过选择合适的参数,RNNs 确实是可以把这种长时期的依赖关系(“long-term dependencies”) 联系起来,并解决这类问题的。但遗憾的是在实际中, RNNs 无法解决这个问题。 Hochreiter (1991) [German] 和 Bengio, et al. (1994) 曾经对这个问题进行过深入的研究,发现 RNNs 的确很难解决这个问题。

但是非常幸运,LSTMs 能够帮我们解决这个问题。

3. LSTM 网络

长短期记忆网络(Long Short Term Memory networks) - 通常叫做 “LSTMs” —— 是 RNN 中一个特殊的类型。由Hochreiter & Schmidhuber (1997)提出,广受欢迎,之后也得到了很多人们的改进调整。LSTMs 被广泛地用于解决各类问题,并都取得了非常棒的效果。

明确来说,设计 LSTMs 主要是为了避免前面提到的 长时期依赖 (long-term dependency )的问题。它们的本质就是能够记住很长时期内的信息,而且非常轻松就能做到。

所有循环神经网络结构都是由完全相同结构的(神经网络)模块进行复制而成的。在普通的RNNs 中,这个模块结构非常简单,比如仅是一个单一的 tanh 层。

 
fig4. 普通 RNNs 内部结构

LSTMs 也有类似的结构(译者注:唯一的区别就是中间部分)。但是它们不再只是用一个单一的 tanh 层,而是用了四个相互作用的层。 

 
fig5. LSTM 内部结构

别担心,别让这个结构给吓着了,下面根据这个结构,我们把它解剖开,一步一步地来理解它(耐心看下去,你一定可以理解的)。现在,我们先来定义一下用到的符号: 

 
fig6. 符号说明

在网络结构图中,每条线都传递着一个向量,从一个节点中输出,然后输入到另一个节点中。粉红色的圆圈表示逐点操作,比如向量相加;黄色的矩形框表示的是一个神经网络层(就是很多个神经节点);合并的线表示把两条线上所携带的向量进行合并(比如一个带 ht1ht−1,另一个带 xtxt , 那么合并后的输出就是[ht1,xt][ht−1,xt]); 分开的线表示将线上传递的向量复制一份,传给两个地方。

3.1 LSTMs 的核心思想

LSTMs 最关键的地方在于 cell(整个绿色的框就是一个 cell) 的状态 和 结构图上面的那条横穿的水平线。

cell 状态的传输就像一条传送带,向量从整个 cell 中穿过,只是做了少量的线性操作。这种结构能够很轻松地实现信息从整个 cell 中穿过而不做改变。(译者注:这样我们就可以实现了长时期的记忆保留了) 

 
fig7. 传送带结构

若只有上面的那条水平线是没办法实现添加或者删除信息的。而是通过一种叫做 门(gates) 的结构来实现的。

 可以实现选择性地让信息通过,主要是通过一个 sigmoid 的神经层 和一个逐点相乘的操作来实现的。 

 
fig8. 门结构(sigmoid 层)

sigmoid 层输出(是一个向量)的每个元素都是一个在 0 和 1 之间的实数,表示让对应信息通过的权重(或者占比)。比如, 0 表示“不让任何信息通过”, 1 表示“让所有信息通过”。

每个 LSTM 有三个这样的门结构,来实现保护和控制信息。(译者注:分别是 “forget gate layer”, 遗忘门; “input gate layer”,传入门; “output gate layer”, 输出门)

3.2 逐步理解 LSTM

(好了,终于来到最激动的时刻了)

3.2.1 遗忘门

首先是 LSTM 要决定让那些信息继续通过这个 cell,这是通过一个叫做“forget gate layer ”的sigmoid 神经层来实现的。它的输入是ht1ht−1xtxt,输出是一个数值都在 0,1 之间的向量(向量长度和 cell 的状态 Ct1Ct−1 一样),表示让 Ct1Ct−1 的各部分信息通过的比重。 0 表示“不让任何信息通过”, 1 表示“让所有信息通过”。

回到我们上面提到的语言模型中,我们要根据所有的上文信息来预测下一个词。这种情况下,每个 cell 的状态中都应该包含了当前主语的性别信息(保留信息),这样接下来我们才能够正确地使用代词。但是当我们又开始描述一个新的主语时,就应该把上文中的主语性别给忘了才对(忘记信息)。 

 
fig9. 遗忘门 (forget gates)

3.2.2 传入门

下一步是决定让多少新的信息加入到 cell 状态 中来。实现这个需要包括两个 步骤:首先,一个叫做“input gate layer ”的 sigmoid 层决定哪些信息需要更新;一个 tanh 层生成一个向量,也就是备选的用来更新的内容,Ct~Ct~ 。在下一步,我们把这两部分联合起来,对 cell 的状态进行一个更新。

 
fig10. 传入门 (input gates)

在我们的语言模型的例子中,我们想把新的主语性别信息添加到 cell 状态中,来替换掉老的状态信息。 
有了上述的结构,我们就能够更新 cell 状态了, 即把Ct1Ct−1更新为 CtCt。 从结构图中应该能一目了然, 首先我们把旧的状态 Ct1Ct−1ftft相乘, 把一些不想保留的信息忘掉。然后加上itCt~it∗Ct~。这部分信息就是我们要添加的新内容。

 
fig11. 更新 cell 状态

3.2.3 输出门

最后,我们需要来决定输出什么值了。这个输出主要是依赖于 cell 的状态CtCt,但是又不仅仅依赖于 CtCt,而是需要经过一个过滤的处理。首先,我们还是使用一个 sigmoid 层来(计算出)决定CtCt中的哪部分信息会被输出。接着,我们把CtCt通过一个 tanh 层(把数值都归到 -1 和 1 之间),然后把 tanh 层的输出和 sigmoid 层计算出来的权重相乘,这样就得到了最后输出的结果。

在语言模型例子中,假设我们的模型刚刚接触了一个代词,接下来可能要输出一个动词,这个输出可能就和代词的信息相关了。比如说,这个动词应该采用单数形式还是复数的形式,那么我们就得把刚学到的和代词相关的信息都加入到 cell 状态中来,才能够进行正确的预测。

 
fig12. cell 输出

4. LSTM 的变种 GRU

原文这部分介绍了 LSTM 的几个变种,还有这些变形的作用。在这里我就不再写了。有兴趣的可以直接阅读原文。

下面主要讲一下其中比较著名的变种 GRU(Gated Recurrent Unit ),这是由 Cho, et al. (2014) 提出。在 GRU 中,如 fig.13 所示,只有两个门:重置门(reset gate)和更新门(update gate)。同时在这个结构中,把细胞状态和隐藏状态进行了合并。最后模型比标准的 LSTM 结构要简单,而且这个结构后来也非常流行。 

 
fig13. GRU结构

其中, rtrt 表示重置门,ztzt 表示更新门。重置门决定是否将之前的状态忘记。(作用相当于合并了 LSTM 中的遗忘门和传入门)当 rtrt 趋于 0 的时候,前一个时刻的状态信息 ht1ht−1 会被忘掉,隐藏状态 ht~ht~ 会被重置为当前输入的信息。更新门决定是否要将隐藏状态更新为新的状态ht~ht~(作用相当于 LSTM 中的输出门) 。

和 LSTM 比较一下:

  • (1) GRU 少一个门,同时少了细胞状态 CtCt
  • (2) 在 LSTM 中,通过遗忘门和传入门控制信息的保留和传入;GRU 则通过重置门来控制是否要保留原来隐藏状态的信息,但是不再限制当前信息的传入。
  • (3) 在 LSTM 中,虽然得到了新的细胞状态 CtCt,但是还不能直接输出,而是需要经过一个过滤的处理: ht=ottanh(Ct)ht=ot∗tanh(Ct); 同样,在 GRU 中, 虽然 (2) 中我们也得到了新的隐藏状态 ht~ht~, 但是还不能直接输出,而是通过更新门来控制最后的输出: ht=(1zt)ht1+ztht~ht=(1−zt)∗ht−1+zt∗ht~ 。

后记:好了,到这里对一般形式的 LSTM 的结构讲解已经结束了,原文后面对 LSTM 的各种变形讲解也比较简单,在这里我就不再写了,有兴趣的可以直接阅读原文。上面我结合了原论文比较详细地介绍了一下 GRU,个人水平有限,难免也会出错。其实英语理解能力还行的话建议还是阅读原文比较好,有些东西翻译过来连我自己都不知道该怎么表达了,如果您觉得有什么地方不对,欢迎指出。翻译完后才发现网上已经有很多翻译的版本了,所以参考那些版本又做了一些调整,主要是参考了[译] 理解 LSTM 网络这篇文章。后面如果有时间的话,我应该会写个用 TensorFlow 来实现 LSTM 的例子,敬请期待,哈哈哈!

(LSTM 的例子还真写好了,欢迎戳: TensorFlow入门(五)多层 LSTM 通俗易懂版

 

TensorFlow入门(五)多层 LSTM 通俗易懂版

前言: 根据我本人学习 TensorFlow 实现 LSTM 的经历,发现网上虽然也有不少教程,其中很多都是根据官方给出的例子,用多层 LSTM 来实现 PTBModel 语言模型,比如: 
tensorflow笔记:多层LSTM代码分析 
但是感觉这些例子还是太复杂了,所以这里写了个比较简单的版本,虽然不优雅,但是还是比较容易理解。

如果你想了解 LSTM 的原理的话(前提是你已经理解了普通 RNN 的原理),可以参考我前面翻译的博客: 
(译)理解 LSTM 网络 (Understanding LSTM Networks by colah)

如果你想了解 RNN 原理的话,可以参考 AK 的博客: 
The Unreasonable Effectiveness of Recurrent Neural Networks

很多朋友提到多层怎么理解,所以自己做了一个示意图,希望帮助初学者更好地理解 多层RNN.

 
图1 3层RNN按时间步展开

本例不讲原理。通过本例,你可以了解到单层 LSTM 的实现,多层 LSTM 的实现。输入输出数据的格式。 RNN 的 dropout layer 的实现。

# -*- coding:utf-8 -*-
import tensorflow as tf
import numpy as np
from tensorflow.contrib import rnn
from tensorflow.examples.tutorials.mnist import input_data

# 设置 GPU 按需增长
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config=config)

# 首先导入数据,看一下数据的形式
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
print mnist.train.images.shape

Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz

(55000, 784)

1. 首先设置好模型用到的各个超参数

lr = 1e-3
# 在训练和测试的时候,我们想用不同的 batch_size.所以采用占位符的方式
batch_size = tf.placeholder(tf.int32)  # 注意类型必须为 tf.int32
# 在 1.0 版本以后请使用 :
# keep_prob = tf.placeholder(tf.float32, [])
# batch_size = tf.placeholder(tf.int32, [])

# 每个时刻的输入特征是28维的,就是每个时刻输入一行,一行有 28 个像素
input_size = 28
# 时序持续长度为28,即每做一次预测,需要先输入28行
timestep_size = 28
# 每个隐含层的节点数
hidden_size = 256
# LSTM layer 的层数
layer_num = 2
# 最后输出分类类别数量,如果是回归预测的话应该是 1
class_num = 10

_X = tf.placeholder(tf.float32, [None, 784])
y = tf.placeholder(tf.float32, [None, class_num])
keep_prob = tf.placeholder(tf.float32)

2. 开始搭建 LSTM 模型,其实普通 RNNs 模型也一样

# 把784个点的字符信息还原成 28 * 28 的图片
# 下面几个步骤是实现 RNN / LSTM 的关键
####################################################################
# **步骤1:RNN 的输入shape = (batch_size, timestep_size, input_size) 
X = tf.reshape(_X, [-1, 28, 28])

# **步骤2:定义一层 LSTM_cell,只需要说明 hidden_size, 它会自动匹配输入的 X 的维度
lstm_cell = rnn.BasicLSTMCell(num_units=hidden_size, forget_bias=1.0, state_is_tuple=True)

# **步骤3:添加 dropout layer, 一般只设置 output_keep_prob
lstm_cell = rnn.DropoutWrapper(cell=lstm_cell, input_keep_prob=1.0, output_keep_prob=keep_prob)

# **步骤4:调用 MultiRNNCell 来实现多层 LSTM
mlstm_cell = rnn.MultiRNNCell([lstm_cell] * layer_num, state_is_tuple=True)

# **步骤5:用全零来初始化state
init_state = mlstm_cell.zero_state(batch_size, dtype=tf.float32)

# **步骤6:方法一,调用 dynamic_rnn() 来让我们构建好的网络运行起来
# ** 当 time_major==False 时, outputs.shape = [batch_size, timestep_size, hidden_size] 
# ** 所以,可以取 h_state = outputs[:, -1, :] 作为最后输出
# ** state.shape = [layer_num, 2, batch_size, hidden_size], 
# ** 或者,可以取 h_state = state[-1][1] 作为最后输出
# ** 最后输出维度是 [batch_size, hidden_size]
# outputs, state = tf.nn.dynamic_rnn(mlstm_cell, inputs=X, initial_state=init_state, time_major=False)
# h_state = outputs[:, -1, :]  # 或者 h_state = state[-1][1]

# *************** 为了更好的理解 LSTM 工作原理,我们把上面 步骤6 中的函数自己来实现 ***************
# 通过查看文档你会发现, RNNCell 都提供了一个 __call__()函数(见最后附),我们可以用它来展开实现LSTM按时间步迭代。
# **步骤6:方法二,按时间步展开计算
outputs = list()
state = init_state
with tf.variable_scope('RNN'):
    for timestep in range(timestep_size):
        if timestep > 0:
            tf.get_variable_scope().reuse_variables()
        # 这里的state保存了每一层 LSTM 的状态
        (cell_output, state) = mlstm_cell(X[:, timestep, :], state)
        outputs.append(cell_output)
h_state = outputs[-1]

3. 设置 loss function 和 优化器,展开训练并完成测试

# 上面 LSTM 部分的输出会是一个 [hidden_size] 的tensor,我们要分类的话,还需要接一个 softmax 层
# 首先定义 softmax 的连接权重矩阵和偏置
# out_W = tf.placeholder(tf.float32, [hidden_size, class_num], name='out_Weights')
# out_bias = tf.placeholder(tf.float32, [class_num], name='out_bias')
# 开始训练和测试
W = tf.Variable(tf.truncated_normal([hidden_size, class_num], stddev=0.1), dtype=tf.float32)
bias = tf.Variable(tf.constant(0.1,shape=[class_num]), dtype=tf.float32)
y_pre = tf.nn.softmax(tf.matmul(h_state, W) + bias)


# 损失和评估函数
cross_entropy = -tf.reduce_mean(y * tf.log(y_pre))
train_op = tf.train.AdamOptimizer(lr).minimize(cross_entropy)

correct_prediction = tf.equal(tf.argmax(y_pre,1), tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))


sess.run(tf.global_variables_initializer())
for i in range(2000):
    _batch_size = 128
    batch = mnist.train.next_batch(_batch_size)
    if (i+1)%200 == 0:
        train_accuracy = sess.run(accuracy, feed_dict={
            _X:batch[0], y: batch[1], keep_prob: 1.0, batch_size: _batch_size})
        # 已经迭代完成的 epoch 数: mnist.train.epochs_completed
        print "Iter%d, step %d, training accuracy %g" % ( mnist.train.epochs_completed, (i+1), train_accuracy)
    sess.run(train_op, feed_dict={_X: batch[0], y: batch[1], keep_prob: 0.5, batch_size: _batch_size})

# 计算测试数据的准确率
print "test accuracy %g"% sess.run(accuracy, feed_dict={
    _X: mnist.test.images, y: mnist.test.labels, keep_prob: 1.0, batch_size:mnist.test.images
Iter0, step 200, training accuracy 0.851562
Iter0, step 400, training accuracy 0.960938
Iter1, step 600, training accuracy 0.984375
Iter1, step 800, training accuracy 0.960938
Iter2, step 1000, training accuracy 0.984375
Iter2, step 1200, training accuracy 0.9375
Iter3, step 1400, training accuracy 0.96875
Iter3, step 1600, training accuracy 0.984375
Iter4, step 1800, training accuracy 0.992188
Iter4, step 2000, training accuracy 0.984375
test accuracy 0.9858

我们一共只迭代不到5个epoch,在测试集上就已经达到了0.9825的准确率,可以看出来 LSTM 在做这个字符分类的任务上还是比较有效的,而且我们最后一次性对 10000 张测试图片进行预测,才占了 725 MiB 的显存。而我们在之前的两层 CNNs 网络中,预测 10000 张图片一共用了 8721 MiB 的显存,差了整整 12 倍呀!! 这主要是因为 RNN/LSTM 网络中,每个时间步所用的权值矩阵都是共享的,可以通过前面介绍的 LSTM 的网络结构分析一下,整个网络的参数非常少。

4. 可视化看看 LSTM 的是怎么做分类的

毕竟 LSTM 更多的是用来做时序相关的问题,要么是文本,要么是序列预测之类的,所以很难像 CNNs 一样非常直观地看到每一层中特征的变化。在这里,我想通过可视化的方式来帮助大家理解 LSTM 是怎么样一步一步地把图片正确的给分类。

import matplotlib.pyplot as plt

看下面我找了一个字符 3

print mnist.train.labels[4]
  • 1
[ 0.  0.  0.  1.  0.  0.  0.  0.  0.  0.]

我们先来看看这个字符样子,上半部分还挺像 2 来的

X3 = mnist.train.images[4]
img3 = X3.reshape([28, 28])
plt.imshow(img3, cmap='gray')
plt.show()



我们看看在分类的时候,一行一行地输入,分为各个类别的概率会是什么样子的。

X3.shape = [-1, 784]
y_batch = mnist.train.labels[0]
y_batch.shape = [-1, class_num]

X3_outputs = np.array(sess.run(outputs, feed_dict={
            _X: X3, y: y_batch, keep_prob: 1.0, batch_size: 1}))
print X3_outputs.shape
X3_outputs.shape = [28, hidden_size]
print X3_outputs.shape
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
(28, 1, 256)
(28, 256)
h_W = sess.run(W, feed_dict={
            _X:X3, y: y_batch, keep_prob: 1.0, batch_size: 1})
h_bias = sess.run(bias, feed_dict={
            _X:X3, y: y_batch, keep_prob: 1.0, batch_size: 1})
h_bias.shape = [-1, 10]

bar_index = range(class_num)
for i in xrange(X3_outputs.shape[0]):
    plt.subplot(7, 4, i+1)
    X3_h_shate = X3_outputs[i, :].reshape([-1, hidden_size])
    pro = sess.run(tf.nn.softmax(tf.matmul(X3_h_shate, h_W) + h_bias))
    plt.bar(bar_index, pro[0], width=0.2 , align='center')
    plt.axis('off')
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这里写图片描述

在上面的图中,为了更清楚地看到线条的变化,我把坐标都去了,每一行显示了 4 个图,共有 7 行,表示了一行一行读取过程中,模型对字符的识别。可以看到,在只看到前面的几行像素时,模型根本认不出来是什么字符,随着看到的像素越来越多,最后就基本确定了它是字符 3.

好了,本次就到这里。有机会再写个优雅一点的例子,哈哈。其实学这个 LSTM 还是比较困难的,当时写 多层 CNNs 也就半天到一天的时间基本上就没啥问题了,但是这个花了我大概整整三四天,而且是在我对原理已经很了解(我自己觉得而已。。。)的情况下,所以学会了感觉还是有点小高兴的~

17-04-19补充几个资料: 
recurrent_network.py 一个简单的 tensorflow LSTM 例子。 
Tensorflow下构建LSTM模型进行序列化标注 介绍非常好的一个 NLP 开源项目。(例子中有些函数可能在新版的 tensorflow 中已经更新了,但并不影响理解)

5. 附:BASICLSTM.__call__()

'''code: https://github.com/tensorflow/tensorflow/blob/master/tensorflow/contrib/rnn/python/ops/core_rnn_cell_impl.py'''

  def __call__(self, inputs, state, scope=None):
      """Long short-term memory cell (LSTM)."""
      with vs.variable_scope(scope or "basic_lstm_cell"):
          # Parameters of gates are concatenated into one multiply for efficiency.
          if self._state_is_tuple:
              c, h = state
          else:
              c, h = array_ops.split(value=state, num_or_size_splits=2, axis=1)
          concat = _linear([inputs, h], 4 * self._num_units, True, scope=scope)

          # ** 下面四个 tensor,分别是四个 gate 对应的权重矩阵
          # i = input_gate, j = new_input, f = forget_gate, o = output_gate
          i, j, f, o = array_ops.split(value=concat, num_or_size_splits=4, axis=1)

          # ** 更新 cell 的状态: 
          # ** c * sigmoid(f + self._forget_bias) 是保留上一个 timestep 的部分旧信息
          # ** sigmoid(i) * self._activation(j)  是有当前 timestep 带来的新信息
          new_c = (c * sigmoid(f + self._forget_bias) + sigmoid(i) *
               self._activation(j))

          # ** 新的输出
          new_h = self._activation(new_c) * sigmoid(o)

          if self._state_is_tuple:
              new_state = LSTMStateTuple(new_c, new_h)
          else:
              new_state = array_ops.concat([new_c, new_h], 1)
          # ** 在(一般都是) state_is_tuple=True 情况下, new_h=new_state[1]
          # ** 在上面博文中,就有 cell_output = state[1]
          return new_h, new_state
  • 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
  • 30
  • 31
  • 32

本文代码:https://github.com/yongyehuang/Tensorflow-Tutorial







猜你喜欢

转载自blog.csdn.net/qq_27009517/article/details/80038128