上文给出了一个LSTM使用的具体例子,但其中依旧存在一些东西说的不是很清楚明白,接下来,我们会针对LSTM使用中更加细致的一些东西,做一些介绍。
本人目前使用的基本都是TensorFlow进行开发。
lstm_cell = tf.nn.rnn_cell.LSTMCell(n_hidden, forget_bias=1.0, name='basic_lstm_cell')
outputs, states = tf.contrib.rnn.static_rnn(lstm_cell, x1, dtype=tf.float32)
假如我们以此种方式运行一个LSTM,毫无疑问,需要对outpus,和states 做一个深入的了解,因为LSTMCell只是神经网络的一层,通常我们后面都会有其他的全连接神经网络与之相连,完成分类或者其他的任务。
问题1:outputs和states的深入学习和理解
其次,我们上述代码使用了静态运行的方式,有没有其他的运行方式呢?
问题2:循环神经网络的其他运行方式
第三个问题,有多少种LSTMCell我们可以拿来用?还记得有一种叫做GRU的东西么?其与LSTMCell非常相像,但是结构比LSTMCell要简单,使用方式基本一致。
首先,我们从static_rnn方法的基本参数:
def static_rnn(cell, inputs, initial_state=None,dtype=None,sequence_
length=None, scope=None):
cell :即我们上面生成好的Cell类对象
inputs:输入数据,一定是list或者是二维张量,类比我们前面的x1,其就是28个元素的list
initital_state : 即初始状态,系统会初始化为全零的隐藏层状态,通常我们不用自行初始化
返回值有两个,一个是结果即outputs,一个是cell状态;我们只关注结果,而实际上结果也是一个list,输入是多少个时序,list里就会输出多少个元素。
类比于白话RNN系列(五)中,其outputs实际上是一个length=28的list,每个元素均为128 * 128 的张量;前面的128实际上代表的批次大小,而后面的128 则是代表我们输出的数据,这个128维的向量会与后续的全连接神经网络共同产出分类结果数据。
我们之所以取outputs[-1],原因在于其最是最后一个循环的输出,才是我们真正需要的数据。
而对于states,实际上是一个LSTMStateTuple类型的数据,本身是个元组;元组里面包含两个元素:c和h,c表示的就是最后时刻cell的内部状态值,h表示的就是最后时刻隐层状态值。
隐约有种感觉,outputs[-1]和states的第二个元素应该是相同的东西,都代表最后时刻的隐层状态值;事实证明,的确如此:
state:[[ 0.01089936 0.02731343 0.05962883 ... 0.11460225 0.09394393
0.08642814]
[ 0.05554791 -0.06378083 0.08341898 ... 0.09763484 0.01406891
0.00135677]
[-0.00513092 -0.0474266 0.07704069 ... 0.10713644 0.00992752
0.03562667]
...
[ 0.05851342 -0.0413289 0.08804315 ... 0.10628442 0.01292721
0.01195676]
[ 0.0014212 -0.00056065 0.04524097 ... 0.01346213 -0.00850293
-0.00390543]
[ 0.01522607 0.03220217 0.06999128 ... 0.06230348 0.04305534
0.05264532]]
out-1:[[ 0.01089936 0.02731343 0.05962883 ... 0.11460225 0.09394393
0.08642814]
[ 0.05554791 -0.06378083 0.08341898 ... 0.09763484 0.01406891
0.00135677]
[-0.00513092 -0.0474266 0.07704069 ... 0.10713644 0.00992752
0.03562667]
...
[ 0.05851342 -0.0413289 0.08804315 ... 0.10628442 0.01292721
0.01195676]
[ 0.0014212 -0.00056065 0.04524097 ... 0.01346213 -0.00850293
-0.00390543]
[ 0.01522607 0.03220217 0.06999128 ... 0.06230348 0.04305534
0.05264532]]
这个结果可以通过在原有代码中嵌入状态输出和outputs[-1]输出得到:如下
out = sess.run(outputs, feed_dict={x: batch_x, y: batch_y})
sta = sess.run(states, feed_dict={x: batch_x, y: batch_y})
print('state:' + str(sta[1]))
print('out-1:' + str(out[-1]))
因此,稍微总结下:outputs实际上是一个时序list,循环体循环多少次,则有多少个元素;而每个元素为一个张量,形如100* 128, 其中100代表批次大小,而128代表隐藏层的状态。
同样,states代表了隐藏层状态,是一个元组;并且,states[1]和outputs[-1]实质是一样的。
OK,这里分析的outputs和states是与static_run方法紧密相连的,我们看下其他的运行方式,会有什么不同的效果:
除了静态运行外,循环神经网络还存在动态运行的方式:
outputs, _ = tf.nn.dynamic_rnn(gru, x, dtype=tf.float32)
outputs = tf.transpose(outputs, [1, 0, 2])
上图即动态运行方式,我们看下dynamic_rnn的参数:
def dynamic_rnn (cell, inputs, sequence_length=None, initial_state=None,
dtype=None, parallel_iterations=None, swap_memory=False,
time_major=False, scope=None):
参数大致与static_run都是一致的,但有几个地方不同:
1.inputs:输入数据,是一个张量,一般是三维张量,[batch_size,max_time,...]。其中batch_size表示一次的批次数量,max_time表示时间序列总数,后面是具体数据。
类比,我们这里不需要对x进行unstack操作,直接输入128 * 784 (前面128是批次大小,后面784 是序列总长度)即可。
对于其输出,我们关注outputs即可,当time_major为默认值False时,input的shape为[batch_size,max_time,...]。如果是True,shape为[max_time,batch_size,...]; 因此,我们可以显式定义time_major=True,来保证取出的outputs[-1]是我们需要使用到的隐藏层状态。
在LSTM的具体使用中,我们一方面需要关注自己输入的张量维度,同时也要谨慎关注我们输出的张量维度,二者结合在一起,能够起到更好的效果。