开始的话:
从基础做起,不断学习,坚持不懈,加油。
一位爱生活爱技术来自火星的程序汪
前面两节讲了两个基础的BasicRNNCell和BasicLSTMCell,今天就来看下怎么把这些简单的cell给堆叠起来,一起使用。
这就是我在本章要介绍的MultiRNNCell这个接口了。
话不多说,先上图。
简单以三个输入以及两层结构作为简要说明。
表示时间步的输入
表示时间步的输出
表示每一层的初始化state
表示每一层最后一个cell输出的新状态
去理解一个网络架构的时候,看图还是相对要简单很多的。
第一层是以
作为网络的输入
而第二层之后的层数则是以前面一层的输出作为输入,直到最后一层得到
show me the code
一个简单的multiRNNCell的demo
def multi_rnn_demo():
m_cell = tf.nn.rnn_cell.MultiRNNCell([tf.nn.rnn_cell.BasicRNNCell(num_units=3 * i) for i in range(1, 5)])
wrapper = tf.nn.rnn_cell.DropoutWrapper(m_cell, input_keep_prob=0.5, output_keep_prob=0.8)
a = tf.random_normal([2, 4])
outs, states = wrapper(
inputs=a,
state=m_cell.zero_state(2, dtype=tf.float32)
)
一个更常见的demo,也是我们前面几节常用到的代码结构。
上面的demo,可以理解为sequence_length=1时候的代码
下面的demo,就和我们平常见到的差不多了,3-D的输入。
def multi_rnn_demo_02():
m_cell = tf.nn.rnn_cell.MultiRNNCell([tf.nn.rnn_cell.BasicRNNCell(num_units=3 * i) for i in range(1, 5)])
wrapper = tf.nn.rnn_cell.DropoutWrapper(m_cell, input_keep_prob=0.5, output_keep_prob=0.8)
a = tf.random_normal([2, 3, 4])
state = m_cell.zero_state(2, tf.float32)
output, state = tf.nn.dynamic_rnn(wrapper, a, initial_state=state, time_major=False)
核心计算,这个demo里面还是BasicRNNCell,当然BasicLSTMCell也是一样的,唯一差别就是输出LSTM是 和 ,而 BasicRNNCell只有 。
注意:为什么有的blog中说:embedding_size 要和 hidden_size要是同样的维度呢?
这是由于他们在定义每一个cell的时候定义错了。(代码实践的时候也发现了这个问题,哈哈)
下面就进行详细说明:
cell = tf.nn.rnn_cell.BasicRNNCell(num_units=6)
m_cell = tf.nn.rnn_cell.MultiRNNCell([cell] * 3)
# 这样定义的话是错误的,相当于是用一个cell,这样在第一层的时候,输入是[2,4](这时候的kernel_=[10,6]),输出是[2,6]
# gate_inputs = math_ops.matmul(array_ops.concat([inputs, state], 1), self._kernel)
# 到第二层的时候输入是:[2,6],如果是用一个cell的话,这时候的kernel_=[10,6]
# 而inputs和state的shape都为[2,6],这样做matmul就有问题了。
# 所以在定义多层cells的时候一定要注意了每一个cell都应该是不同的cell。
# 这也就是为什么一些blog说embedding_size和hidden_size要是一样的原因了。
# 只要定义对了cell,维度当然是可以不同的了。
所以要保证:每个 都是单独的 ,这样就不会出现这样的问题。
再讲一下outputs和states。
outputs的结果:shape=[2,3,12],12表示的是最后一层cell的hidden_unit_size
tf.Tensor(
[[[ 0.29871088 0.08804269 -0.01215147 0.01254939 -0.14880858
0.03153174 0.4693875 0.00485815 -0.08916988 -0.02274401
0.10895786 0.01575602]
[-0.00102792 -0. -0. 0.17538904 0.09420253
0.01793489 0.3424931 -0.18222454 -0. -0.
0.52473444 -0.26131606]
[ 0. -0.22403269 -0.4520385 0.2888178 -0.1598881
0.15004131 0.09338585 -0.636156 0.06017611 -0.28230968
0.6120767 -0.11296887]]
[[-0.00742886 0.0023214 -0.00750306 0.00525704 -0.00685527
0.00791772 0. -0.00799915 0.00368653 -0.00367894
0.00070026 0.00924461]
[ 0.2665488 0.05599632 -0. 0.01829319 -0.12248072
0.03517892 0.409784 0. -0.08614541 -0.01222569
0.10290711 0. ]
[-0.20192349 -0.01105446 0.04047677 0. 0.23573665
0.11497773 -0.05647155 -0.0019188 0.01215919 -0.44336665
0. 0.30004367]]], shape=(2, 3, 12), dtype=float32)
new_states:有几层cell,则有几个state输出,和图中是一样的。每一层的shape就是该层的hidden_unit_size
<tf.Tensor: id=324, shape=(2, 3), dtype=float32, numpy=
array([[ 0.04508227, -0.23633523, 0.30065763],
[ 0.7612733 , -0.52450776, -0.6661888 ]], dtype=float32)>,
<tf.Tensor: id=331, shape=(2, 6), dtype=float32, numpy=
array([[ 0.54584306, -0.17747684, -0.44227242, 0.2842951 , 0.43393528,-0.63828385],
[-0.5828087 , -0.5281809 , 0.06895413, -0.74510646, -0.53972644,
0.15232728]], dtype=float32)>,
<tf.Tensor: id=338, shape=(2, 9), dtype=float32, numpy=
array([[ 0.5603744 , -0.04951737, -0.26185265, 0.08209028, 0.672668 ,
0.18829748, 0.27329427, 0.4323986 , 0.09414196],
[ 0.03460297, -0.20349553, 0.47373882, 0.09012283, 0.02843269,
-0.6374881 , -0.10102204, -0.16552992, 0.35916832]],dtype=float32)>,
<tf.Tensor: id=345, shape=(2, 12), dtype=float32, numpy=
array([[ 0.0955615 , -0.17922615, -0.3616308 , 0.23105423, -0.12791048,
0.12003306, 0.07470868, -0.50892484, 0.04814089, -0.22584775,
0.48966137, -0.0903751 ],
[-0.1615388 , -0.00884357, 0.03238142, 0.1927497 , 0.18858932,
0.09198219, -0.04517724, -0.00153504, 0.00972735, -0.35469332,
0.25491163, 0.24003494]], dtype=float32)>)
认真的看的话,会发现这两个demo中分别都用了
。在
中的
和
中
还是有区别的。
RECURRENT NEURAL NETWORK REGULARIZATION
中的
不会在一层的内部计算中做
,而是在每层的输入或者输出的时候做
。
如图:
和上图相比,输入
是一个虚线,表示这个输入
,设置为0,
而第一层的输出
也是虚线,表示这个输出
,设置为0,
最后一层的输出
也是虚线,表示这个输出
,设置为0,
在
的
中,只在层与层之间做
。在层内部是不做
的。
这也就是DropoutWrapper中的input_keep_prob和output_keep_prob设置的区别,只在层与层之间,也就是输入和输出做keep_prob。
tf.nn.rnn_cell.DropoutWrapper(m_cell, input_keep_prob=0.5, output_keep_prob=0.8)
最后再来简单讲下 。
def dropout_test():
"""
a
tf.Tensor(
[[ 0.563435 -1.9721379 1.5006789 -2.2403116 ]
[-0.39942354 -0.14369683 2.0294921 -1.0674685 ]], shape=(2, 4), dtype=float32)
b
tf.Tensor(
[[ 0. -0. 0. -0. ]
[-0.7988471 -0. 4.0589843 -0. ]], shape=(2, 4), dtype=float32)
"""
a = tf.random_normal(shape=[2, 4])
# ret = math_ops.div(x, keep_prob) * binary_tensor,会看到输出的值是原来的1/0.5 倍,深度学习中正是这样解决,训练和预测过程的dropout
# 并不是说每一个的输入中有一半的输入会为0
b = tf.nn.dropout(x=a, keep_prob=0.5)
print(a)
print(b)
关键的是:
先给每个输入的值 除以
也就是乘以
。然后再乘以
或者
的
.
从demo中是可以看到这一点操作的。
这样做的原因,其实是由于:
假设
_
,理论上有一半的输出会被设置为
,
结果近似的缩小了
倍。
如果我们
每一个输出到原来的
倍,那么
的值就很近似了,不会出现太大的差别。
With probability `keep_prob`, outputs the input element scaled up by
`1 / keep_prob`, otherwise outputs `0`. The scaling is so that the expected
sum is unchanged.
我们都知道训练的时候会设置 ,而在预测的时候是不会设置 的,所以我们在训练的时候对没有 的值进行 ,这样就能保证在预测的时候结果不会差的很大了。
谢谢。