前言:使用现在主流的深度学习框架实现循环神经网络是很简单方便的,所以我们很多时候可能忘了怎么去分析神经网络的输入,输出到底是什么,循环层各个节点之间维护的所谓的状态到底是什么?本文需要循环神经网络的基本知识和一些基本的理论,可以参见我前面的两篇文章:
https://blog.csdn.net/qq_27825451/article/details/88870027
写这篇文章的初衷是因为我看过很多的博文,很多都是转载一样的,有一部分原创的说明了一些基本的实现,但是感觉模棱两可,没有涉及到非常细节的地方(也很有可能是别人写的我看不懂),所以我决定自己写一篇,这篇文章只说一个点,那就是循环神经网络的每一个cell的输出和它们之间的状态到底是什么样子的,为什么是这个样子,鉴于水平有限,有不正确的地方,还希望有大佬指正,本文基于tensorflow来说明。
一、RNN的基本结构
二、一个例子彻底弄懂输出与状态的“维度”问题
(1)第一步:创建训练数据样本X
(2)第二步:创建一个运算图结构
(3)第三步:构建graph,进行测试
三、程序的运行结果分析
四、RNN输出值和状态值总结
五:补充,
一、RNN的基本结构
在很多的书本上或者博客里可以看见这样的结构图,如下:
这当然是没有错误的,但是缺点就是这个图是大神经过提炼的,太抽象化了,不太方便理解,那该怎么办呢,我这里在草稿本上化了一个草图,个人感觉更好理解:
画的不好看,但是下面的说明部分做了比较详细的解释。
二、一个例子彻底弄懂输出与状态的“维度”问题
本文使用的是tensorflow框架,版本为tensorflow1.9.里面的dynamic_rnn()函数,为什么要用这个函数,前面的那篇文章已经做了很详细的说明。
(1)第一步:创建训练数据样本X
import tensorflow as tf
import numpy as np
import pprint
train_X = np.array([
[[0, 1, 2], [9, 8, 7],[3,6,8]],
[[3, 4, 5], [1, 3, 5],[6,2,9]],
[[6, 7, 8], [6, 5, 4],[1,7,4]],
[[9, 0, 1], [3, 7, 4],[5,8,2]],
])
'''样本数据为(samples,timesteps,features)的形式,
其中samples=4,timesteps=3,features=3
'''
(2)第二步:创建一个运算图结构
#创建一个容纳训练数据的placeholder
X=tf.placeholder(tf.float32,shape=[None,3,3])
# tensorflow处理变长时间序列的处理方式,首先每一个循环的cell里面有5个神经元
basic_cell=tf.nn.rnn_cell.BasicRNNCell(num_units=5)
#使用dynamic_rnn创建一个动态计算的RNN
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
(3)第三步:构建graph,进行测试
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
outputs_val, states_val = sess.run(
[outputs, states], feed_dict={X:train_X})
pprint.pprint(outputs_val) #打印输出值和状态值
pprint.pprint(states_val)
print('==============================================')
print(np.shape(outputs_val)) 查看输出值和状态的维度
print(np.shape(states_val))
print('++++++++++++++++++++++++++++++++++++++++++++++++')
print(basic_cell.state_size)
print(basic_cell.output_size)
三、程序的运行结果分析
程序的运行结果如下:
array([
[[-0.6108298 , 0.0596378 , -0.37820065, 0.3211917 ,0.56089014],
[-0.99999994, 0.9993362 , 0.9778955 , 0.5386584 ,-0.9203638],
[-0.9997577 , 0.9915552 , -0.9343918 , -0.24285667,0.63978183]],
[[-0.9990114 , 0.9177295 , -0.06776464, 0.6655134 ,0.4589014 ],
[-0.98075587, 0.53816617, -0.05612217, -0.24713938,0.48741972],
[-0.99997896, -0.47020257, 0.9985639 , 0.99964374,0.99789286]],
[[-0.999998 , 0.9958609 , 0.2563717 , 0.85442424,0.34319228],
[-0.9999506 , 0.9972327 , 0.8965514 , -0.5725894 ,-0.9418285],
[-0.98404294, 0.99636745, -0.99936426, -0.98879707,-0.83194304]],
[[-0.99995047, 0.92161566, 0.9999575 , 0.9958721 ,-0.23263188],
[-0.99924177, 0.9996709 , -0.97150767, -0.9945894 ,-0.991192 ],
[-0.99982065, 0.99869967, -0.8663484 , -0.98502225,-0.98442185]]], dtype=float32)
array([[-0.9997577 , 0.9915552 , -0.9343918 , -0.24285667, 0.63978183],
[-0.99997896, -0.47020257, 0.9985639 , 0.99964374, 0.99789286],
[-0.98404294, 0.99636745, -0.99936426, -0.98879707, -0.83194304],
[-0.99982065, 0.99869967, -0.8663484 , -0.98502225, -0.98442185]],
dtype=float32)
==============================================
(4, 3, 5)
(4, 5)
++++++++++++++++++++++++++++++++++++++++++++++++
5
5
通过上面的测试,我们发现:
输出值Y的维度为(4,3,5)至于是为什么?上面的手写图中已经说明了;
状态值S的维度为(4,5),上面也已经说明,
理论上Y和S其实应该是一样的,但是tensorflow中对这个state的定义是最后一个时间步上面的那个输出才是S,我们从上面的结果也可以看出,每一个每一个输出y的最后一个向量都与S是相等的,如下标注:
array([
[[-0.6108298 , 0.0596378 , -0.37820065, 0.3211917 ,0.56089014],
[-0.99999994, 0.9993362 , 0.9778955 , 0.5386584 ,-0.9203638],
[-0.9997577 , 0.9915552 , -0.9343918 , -0.24285667,0.63978183]],
[[-0.9990114 , 0.9177295 , -0.06776464, 0.6655134 ,0.4589014 ],
[-0.98075587, 0.53816617, -0.05612217, -0.24713938,0.48741972],
[-0.99997896, -0.47020257, 0.9985639 , 0.99964374,0.99789286]],
[[-0.999998 , 0.9958609 , 0.2563717 , 0.85442424,0.34319228],
[-0.9999506 , 0.9972327 , 0.8965514 , -0.5725894 ,-0.9418285],
[-0.98404294, 0.99636745, -0.99936426, -0.98879707,-0.83194304]],
[[-0.99995047, 0.92161566, 0.9999575 , 0.9958721 ,-0.23263188],
[-0.99924177, 0.9996709 , -0.97150767, -0.9945894 ,-0.991192 ],
[-0.99982065, 0.99869967, -0.8663484 , -0.98502225,-0.98442185]]], dtype=float32)
array([[-0.9997577 , 0.9915552 , -0.9343918 , -0.24285667, 0.63978183],
[-0.99997896, -0.47020257, 0.9985639 , 0.99964374, 0.99789286],
[-0.98404294, 0.99636745, -0.99936426, -0.98879707, -0.83194304],
[-0.99982065, 0.99869967, -0.8663484 , -0.98502225, -0.98442185]],
dtype=float32)
四、RNN总结
本文的总结是在tensorflow的dynamic_rnn基础之上的,但是这是RNN运算过程的本质反映,都是通用的。
结论总结:
(1)RNN的输出维度,output_Y的维度为(samples,timesteps,num_units)
(2)RNN的状态维度,output_S的维度为(samples,num_units)
注意,output_S和output_Y本质上是一样的,只不过这里人为RNN中间的state是在RNN的cell之间传递的,并没有输出,所以不算输出值,只是把最后一个时间不上面的state作为了输出的state。
补充:看一下dynamic_rnn的定义
tf.nn.dynamic_rnn(
cell,
inputs,
sequence_length=None,
initial_state=None,
dtype=None,
parallel_iterations=None,
swap_memory=False,
time_major=False,
scope=None
)
'''
Defined in tensorflow/python/ops/rnn.py.
See the guide: Neural Network > Recurrent Neural Networks
Creates a recurrent neural network specified by RNNCell cell.
Performs fully dynamic unrolling of inputs.
'''
#Example:
#=====================================================================
# create a BasicRNNCell
rnn_cell = tf.nn.rnn_cell.BasicRNNCell(hidden_size)
# 'outputs' is a tensor of shape [batch_size, max_time, cell_state_size]
# defining initial state
initial_state = rnn_cell.zero_state(batch_size, dtype=tf.float32)
# 'state' is a tensor of shape [batch_size, cell_state_size]
outputs, state = tf.nn.dynamic_rnn(rnn_cell, input_data,
initial_state=initial_state,
dtype=tf.float32)
#======================================================================
# create 2 LSTMCells
rnn_layers = [tf.nn.rnn_cell.LSTMCell(size) for size in [128, 256]]
# create a RNN cell composed sequentially of a number of RNNCells
multi_rnn_cell = tf.nn.rnn_cell.MultiRNNCell(rnn_layers)
# 'outputs' is a tensor of shape [batch_size, max_time, 256]
# 'state' is a N-tuple where N is the number of LSTMCells containing a
# tf.contrib.rnn.LSTMStateTuple for each cell
outputs, state = tf.nn.dynamic_rnn(cell=multi_rnn_cell,
inputs=data,
dtype=tf.float32)
参数解释:
cell
: 通过RNNCell类创建的实例对象inputs
: 这个是RNN网络的输出. 如果time_major == False
(这是默认值), 则inputs的shape必须是:[batch_size, max_time, features]
, 但是如果time_major == True
,则inputs的shape必须是:[max_time, batch_size,features]
, 注意:这里的max_time就相当于前面的timesteps,但是这里为什么要用max_time呢?因为我们的序列有时候是长度不固定的,这里是指的最大序列长,后面还会讲到对于不定长序列得处理该怎么做,所以为了一般性,这里使用了max_time。而这里的features有时候也用depth来表示,总之是特征的意思。sequence_length
: 这是一个可选参数,专门用来处理变长序列的,后面再详细说明,这里先不说明。time_major
: 这个参数决定了inputs
andoutputs
的形状. 如果是True, 则输入inputs和输出outputs是[max_time, batch_size, depth]
. 如果是False, 则输入inputs和输出outputs是[batch_size, max_time, depth]
. 需要注意的是默认的False可能更加符合我们的习惯,但是使用True的话,运算效率更高一些,至于是什么原因,我在上面的一篇文章里面已经向说明了的,因为timesteps在前面对应着static_rnn,这个是按照RNN的运算过程来实现的,不需要转置等操作,如果是timesteps在中间的话,与dynamic_rnn是对应着的,这个是需要通过一些转置运算的,所以效率相对较低
返回值:
outputs
: The RNN output Tensor
.
If time_major == False (default), this will be a Tensor
shaped: [batch_size, max_time, cell.output_size]
.这是默认的情况
If time_major == True, this will be a Tensor
shaped: [max_time, batch_size, cell.output_size]
.
state
:同上所述。
五:补充,
这里收集到几张关于RNN比较好理解的动态图,分享给大家: