开始的话:
从基础做起,不断学习,坚持不懈,加油。
一位爱生活爱技术来自火星的程序汪
前面两节讲了两个基础的BasicRNNCell和BasicLSTMCell,今天就来看下怎么把这些简单的cell给堆叠起来,一起使用。
这就是我在本章要介绍的MultiRNNCell这个接口了。
话不多说,先上图。
简单以三个输入以及两层结构作为简要说明。
X
t
X_t
Xt 表示时间步的输入
Y
t
Y_t
Yt 表示时间步的输出
s
t
a
t
e
state
state 表示每一层的初始化state
n
e
w
s
t
a
t
e
new state
newstate 表示每一层最后一个cell输出的新状态
去理解一个网络架构的时候,看图还是相对要简单很多的。
第一层是以
X
X
X作为网络的输入
而第二层之后的层数则是以前面一层的输出作为输入,直到最后一层得到
Y
Y
Y
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是 c c c 和 h h h ,而 BasicRNNCell只有 c c c。
注意:为什么有的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,维度当然是可以不同的了。
所以要保证:每个 c e l l cell cell都是单独的 c e l l cell 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中分别都用了
d
r
o
p
o
u
t
dropout
dropout。在
r
n
n
rnn
rnn中的
d
r
o
p
o
u
t
dropout
dropout和
c
n
n
cnn
cnn中
d
r
o
p
o
u
t
dropout
dropout还是有区别的。
RECURRENT NEURAL NETWORK REGULARIZATION
r
n
n
rnn
rnn中的
d
r
o
p
o
u
t
dropout
dropout不会在一层的内部计算中做
d
r
o
p
o
u
t
dropout
dropout,而是在每层的输入或者输出的时候做
d
r
o
p
o
u
t
dropout
dropout。
如图:
和上图相比,输入
X
t
−
1
X_{t-1}
Xt−1是一个虚线,表示这个输入
d
r
o
p
o
u
t
dropout
dropout,设置为0,
而第一层的输出
X
t
+
1
X_{t+1}
Xt+1也是虚线,表示这个输出
d
r
o
p
o
u
t
dropout
dropout,设置为0,
最后一层的输出
Y
t
Y_{t}
Yt 也是虚线,表示这个输出
d
r
o
p
o
u
t
dropout
dropout,设置为0,
在
r
n
n
rnn
rnn的
d
r
o
p
o
u
t
dropout
dropout中,只在层与层之间做
d
r
o
p
o
u
t
dropout
dropout。在层内部是不做
d
r
o
p
o
u
t
dropout
dropout的。
这也就是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)
最后再来简单讲下 d r o p o u t dropout dropout。
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)
关键的是:
r
e
t
=
d
i
v
(
x
,
k
e
e
p
p
r
o
b
)
∗
b
i
n
a
r
y
T
e
n
s
o
r
ret = div(x, keepprob) * binaryTensor
ret=div(x,keepprob)∗binaryTensor
先给每个输入的值 除以
1
/
0.5
1 / 0.5
1/0.5 也就是乘以
2
2
2。然后再乘以
0
0
0 或者
1
1
1 的
b
i
n
a
r
y
binary
binary
t
e
n
s
o
r
tensor
tensor.
从demo中是可以看到这一点操作的。
这样做的原因,其实是由于:
假设
k
e
e
p
keep
keep_
p
r
o
b
=
0.5
prob=0.5
prob=0.5,理论上有一半的输出会被设置为
0
0
0,
结果近似的缩小了
2
2
2倍。
如果我们
r
e
s
c
a
l
e
rescale
rescale 每一个输出到原来的
2
2
2倍,那么
e
x
p
e
c
t
e
d
expected
expected
s
u
m
sum
sum的值就很近似了,不会出现太大的差别。
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.
我们都知道训练的时候会设置 d r o p o u t dropout dropout,而在预测的时候是不会设置 d r o p o u t dropout dropout的,所以我们在训练的时候对没有 d r o p o u t dropout dropout的值进行 r e s c a l e rescale rescale,这样就能保证在预测的时候结果不会差的很大了。
谢谢。