前言:我们前面将的各种循环神经网络的实现都是固定的时间步长的,即timesteps的长度是固定的,但是在实际问题中,这个往往是不固定的,为什么呢?因为文本数据在处理的时候,由于各样本的长度并不一样,有的句子长有的句子短 这就导致在timesteps上面是维度不固定的,这种数据该如何处理呢?这就是本文要说的重点了。
目录
一、大胆尝试,直接填充0行不行?
二、tensorflow对于变长序列的处理
三、keras对于变长序列的处理
3.1 使用Embedding层来实现Word2vector的转化
3.2 使用embedding层来实现变长序列输入
3.2.1 当关键字参数mask_zero=False(即默认值)
3.2.2 当关键字参数mask_zero=True
3.3 使用Masking层来处理变长序列
3.4 关于Masking层和SimpleRNN层的理解
3.4.1 Masking层(其实就是覆盖层)
3.4.2 SimpleRNN层
3.5 keras中Embedding层和Masking层实现变长序列的输入的异同点
一、大胆尝试,直接填充0行不行?
比如我有下面的数据X
train_X = np.array([
[[0, 1, 2], [9, 8, 7],[3,6,8]],
[[3, 4, 5]],
[[6, 7, 8], [6, 5, 4],[1,7,4]],
[[9, 0, 1], [3, 7, 4]],
])
'''
样本数据为(samples,timesteps,features)的形式,其中samples=4,features=3,
但是很明显timesteps是不一样的,它们分别是3,1,3,2
'''
首先确定的是直接将这个train_X放入到RNN里面去,肯定是不行的,因为数据的维度是不同意的,肯定会报错,那我现在简单地处理一下,我都已经最长的timestep作为参考,将不足的全部补充为零,则得到如下样本:
train_X = np.array([
[[0, 1, 2], [9, 8, 7],[3,6,8]],
[[3, 4, 5], [0, 0, 0],[0,0,0]],
[[6, 7, 8], [6, 5, 4],[1,7,4]],
[[9, 0, 1], [3, 7, 4],[0,0,0]],
])
'''
#样本数据为(samples,timesteps,features)的形式,其中samples=4,timesteps=3,features=3,其中第二个、第四个样本是只有一个时间步长和二个时间步长的,这里自动补零
'''
现在我们搭建一个基本的RNN运算图,代码如下:
import tensorflow as tf
import numpy as np
import pprint
# tensorflow处理变长时间序列的处理方式,首先每一个循环的cell里面有5个神经元
X=tf.placeholder(tf.float32,shape=[None,3,3])
basic_cell=tf.nn.rnn_cell.BasicRNNCell(5)
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32)
train_X = np.array([
[[0, 1, 2], [9, 8, 7],[3,6,8]],
[[3, 4, 5], [0, 0, 0],[0,0,0]],
[[6, 7, 8], [6, 5, 4],[1,7,4]],
[[9, 0, 1], [3, 7, 4],[0,0,0]]
])
#样本数据为(samples,timesteps,features)的形式,其中samples=4,timesteps=3,features=3,其中第二个、第四个样本是只有一个时间步长的,这里自动补零
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))
运算结果如下:
array([[[ 0.19004992, -0.5324124 , -0.23640487, -0.78909075,-0.89586043],
[ 0.9995407 , -0.8933996 , 0.14939463, -0.49506482,-1. ],
[ 0.97084224, -0.9788352 , -0.39607894, -0.9992078 ,-0.99999964]],
[[ 0.9344785 , -0.8359945 , -0.2250551 , -0.9137756 ,-0.99995327],
[ 0.08450726, -0.1975519 , 0.05329951, -0.33512276,-0.3200547 ],
[-0.02103201, 0.02070498, 0.00529744, -0.11833917,-0.07321271]],
[[ 0.99663454, -0.94903105, -0.21364392, -0.96614444,-1. ],
[ 0.9939659 , -0.6614185 , 0.29727104, -0.24063313,-0.99999666],
[ 0.99651307, -0.9850747 , -0.7920838 , -0.98904556,-0.99953353]],
[[ 0.6827501 , 0.96307445, 0.9809377 , 0.99099195,-0.99995494],
[ 0.99678046, -0.97459066, 0.24722673, -0.98984957,-0.9999654 ],
[ 0.05819325, 0.12137431, 0.21952726, -0.54427665,-0.4205857 ]]],
dtype=float32)
array([[ 0.97084224, -0.9788352 , -0.39607894, -0.9992078 , -0.99999964],
[-0.02103201, 0.02070498, 0.00529744, -0.11833917, -0.07321271],
[ 0.99651307, -0.9850747 , -0.7920838 , -0.98904556, -0.99953353],
[ 0.05819325, 0.12137431, 0.21952726, -0.54427665, -0.4205857 ]],
dtype=float32)
==============================================
(4, 3, 5)
(4, 5)
从结果上来看这当然是没有问题的,也得到了我们想要的数据维度,但是这是不行的,为什么?
如果就这么简单,不足的地方就补0,那就实在是太简单了,更重要的是,在自然语言处理的时候,我补的0其实也是有特殊的含义的,这就相当于我篡改了一句话的原始意思,这当然是不好的。因为我填充的0也是会参与运算的。
结论:直接填充0,在数据运算上没有问题,但是从序列的整个含义来说,这是不合理的,所以一般情况下不能这么做。
下面介绍不同模型在处理padding上的不同操作,那到底该怎么做呢?这里以tensorflow和keras框架为例加以说明。
二、tensorflow对于变长序列的处理
看过我上一篇文章《【个人整理】tensorflow关于循环神经网络(RNN)的输出与状态的“维度”分析》的应该还有影响,后面在解释dynamic_rnn的参数的时候有一个 sequence_length 的参数,它就是专门用来处理变长序列的,具体怎么做,看代码:
import tensorflow as tf
import numpy as np
import pprint
#样本数据为(samples,timesteps,features)的形式,其中samples=4,features=3,timesteps不固定,第二个样本只有一个步长,第四个样本只有2个步长
train_X = np.array([
[[0, 1, 2], [9, 8, 7],[3,6,8]],
[[3, 4, 5]],
[[6, 7, 8], [6, 5, 4],[1,7,4]],
[[9, 0, 1], [3, 7, 4]]
])
#样本数据为(samples,timesteps,features)的形式,其中samples=4,timesteps=3,features=3,其中第二个、第四个样本所缺的样本自动补零
train_X = np.array([
[[0, 1, 2], [9, 8, 7],[3,6,8]],
[[3, 4, 5], [0, 0, 0],[0,0,0]],
[[6, 7, 8], [6, 5, 4],[1,7,4]],
[[9, 0, 1], [3, 7, 4],[0,0,0]]
])
# tensorflow处理变长时间序列的处理方式,首先每一个循环的cell里面有5个神经元
basic_cell=tf.nn.rnn_cell.BasicRNNCell(5)
#创建一个容纳训练数据的容器placeholder
X=tf.placeholder(tf.float32,shape=[None,3,3])
# 构建一个向量,这个向量专门用来存储每一个样本中的timesteps的数目,这个是核心所在
seq_length = tf.placeholder(tf.int32, [None])
#在使用dynamic_rnn的时候,传递关键字参数 sequence_length
outputs, states = tf.nn.dynamic_rnn(basic_cell, X, dtype=tf.float32,sequence_length=seq_length)
#实际上就是没一个样本的步长数所组成的一个数组
seq_length_batch = np.array([3, 1, 3, 2])
#在我们运行RNN的时候,需要将输入X和样本长度seq_length都传输进去,如下:
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
outputs_val, states_val = sess.run(
[outputs, states], feed_dict={X:train_X,seq_length:seq_length_batch})
pprint.pprint(outputs_val)
pprint.pprint(states_val)
print('==============================================')
print(np.shape(outputs_val))
print(np.shape(states_val))
测试得到的结果为:
array([[[ 0.19467512, -0.79010636, -0.57950014, 0.6630075 ,-0.391196 ],
[ 0.9999744 , -1. , 0.99997497, 0.12559196,-0.9973965 ],
[ 0.9755946 , -0.9999939 , 0.8523646 , 0.9613497 ,-0.9103804 ]],
[[ 0.9631605 , -0.99987656, 0.6419198 , 0.85546035,-0.8805427 ],
[ 0. , 0. , 0. , 0. ,0. ],
[ 0. , 0. , 0. , 0. ,0. ]],
[[ 0.9989558 , -0.99999994, 0.9749926 , 0.94184864,-0.9817268 ],
[ 0.9955585 , -0.9999952 , 0.99994856, 0.25942597,-0.9250224 ],
[ 0.79531014, -0.9992963 , 0.99106103, -0.82377946,0.9658859 ]],
[[ 0.9995462 , -0.99997026, 0.9998705 , 0.7101562 ,-0.9996873 ],
[ 0.95413935, -0.99994653, 0.99955887, -0.7478514 ,0.759941 ],
[ 0. , 0. , 0. , 0. ,0. ]]], dtype=float32)
array([[ 0.9755946 , -0.9999939 , 0.8523646 , 0.9613497 , -0.9103804 ],
[ 0.9631605 , -0.99987656, 0.6419198 , 0.85546035, -0.8805427 ],
[ 0.79531014, -0.9992963 , 0.99106103, -0.82377946, 0.9658859 ],
[ 0.95413935, -0.99994653, 0.99955887, -0.7478514 , 0.759941 ]],
dtype=float32)
==============================================
(4, 3, 5)
(4, 5)
原因分析:通过比较上面两个的测试结果,发现一个问题,第二次的结果中,第二组样本、第四组样本,分别出现了两个全为0的,一个全为0的,这是为什么呢?
这个地方我看见很多地方都解释错误了,就拿第二组样本来说,因为第二组样本来说,因为第二个时刻和第三个时刻的样本都是0,所以对应的输出也是0,很多人说这是因为,传入的X既然都是0,那运算出来不就是0嘛,这种解释是错误的,因为那这不就是上面的测试一中的结果吗?那一中为什么不是0,而且如果是这些0参与了运算,输出的结果也不会是0,为什么,因为RNN在每一个cell里面的运算为:
即使我输入的Xt=1这一个时刻的全部是0,由于前面的状态St=0不是全部为0的,所以Yt=1,Yt=2都是不会为零的,事实上这就是测试一中的结果的由来。
而这个地方之所以第二组样本的后面两个时间点的输出全部为0,第四组样本的第3个时刻输出为零,是因为这一个为0的输入根本就没有参与运算,通过参数
seq_length_batch = np.array([3, 1, 3, 2])
指定了第一组样本计算3个timestep,第二组样本计算1个timestep,第三组样本计算3个timestep,第四组样本计算2个timestep。那个全为0的样本根本就没有参与运算,只不过是一个“空壳子”,为了保持输入数据的维度完整性而存在的。
而每一个样本最后的输出状态也不再是固定的第3个时间步的输出了,它会默认对应最后一个参与了运算的时间步的输出,在这里,第一组样本对应的state为第3个timestep的输出、第二组样本对应的state为第1个timestep的输出、第三组样本对应的state为第3个timestep的输出、第四组样本对应的state为第2个timestep的输出,可以参照下面的不同颜色。
array([[[ 0.19467512, -0.79010636, -0.57950014, 0.6630075 ,-0.391196 ],
[ 0.9999744 , -1. , 0.99997497, 0.12559196,-0.9973965 ],
[ 0.9755946 , -0.9999939 , 0.8523646 , 0.9613497 ,-0.9103804 ]],
[[ 0.9631605 , -0.99987656, 0.6419198 , 0.85546035,-0.8805427 ],
[ 0. , 0. , 0. , 0. ,0. ],
[ 0. , 0. , 0. , 0. ,0. ]],
[[ 0.9989558 , -0.99999994, 0.9749926 , 0.94184864,-0.9817268 ],
[ 0.9955585 , -0.9999952 , 0.99994856, 0.25942597,-0.9250224 ],
[ 0.79531014, -0.9992963 , 0.99106103, -0.82377946,0.9658859 ]],
[[ 0.9995462 , -0.99997026, 0.9998705 , 0.7101562 ,-0.9996873 ],
[ 0.95413935, -0.99994653, 0.99955887, -0.7478514 ,0.759941 ],
[ 0. , 0. , 0. , 0. ,0. ]]], dtype=float32)
array([[ 0.9755946 , -0.9999939 , 0.8523646 , 0.9613497 , -0.9103804 ],
[ 0.9631605 , -0.99987656, 0.6419198 , 0.85546035, -0.8805427 ],
[ 0.79531014, -0.9992963 , 0.99106103, -0.82377946, 0.9658859 ],
[ 0.95413935, -0.99994653, 0.99955887, -0.7478514 , 0.759941 ]],
dtype=float32)
结论总结:tensorflow中对变长序列的输入是通过在dynamic_rnn()函数中的sequence_length参数来指定的,这个参数是一个一维数组,每一个数组的元素为所对应的那一组样本的timesteps数目,而填充的01根本就没有参与运算,只不过是一个“空壳子”,为了保持输入数据的维度完整性而存在的
还有一个需要注意的,就是那完整的第一组、第三组样本,他们的输出好像也不一样啊,这是因为初始状态和初始全职是随机设置的,这里只进行一次运算,没有进行迭代,自然不一样了。
三、keras对于变长序列的处理
keras相较于tensorflow而言,封装更加高层。使用keras有几种方式可以实现变长序列的输入
3.1 使用Embedding层来实现Word2vector的转化
先看一下Embedding层有什么作用,直接看代码:
import keras as ks
import numpy as np
'''
#这是原始的输入数据,一共四组样本(四个句子),没组样本的时间跨度为3,即timesteps=3,每一个数字表示一个单词
#现在我想把每一个数字(即单词)转化成一个三维向量
#即
4->[#,#,#]
10->[#,#,#]
5->[#,#,#]
2->[#,#,#]
.
..依次下去
'''
input_array=np.array([[4,10,5],[2,1,6],[3,7,9],[2,5,3]])
model = ks.models.Sequential()
model.add(ks.layers.Embedding(100, 3, input_length=3))
model.compile('rmsprop', 'mse')
output_array = model.predict(input_array)
print(output_array)
print('==========================================')
print(np.shape(output_array))
运行结果如下:
[[[ 0.00993429 0.04398254 -0.0125849 ]
[-0.02356382 -0.02504394 -0.01354498]
[-0.02061195 0.01945392 -0.02339504]]
[[ 0.03188958 0.02244325 0.04611005]
[-0.02672956 0.02242425 0.0270121 ]
[-0.03706253 0.03849537 0.02586788]]
[[ 0.01334128 0.0285162 -0.04092228]
[ 0.01520776 0.04370985 -0.00255948]
[-0.03107758 -0.03339813 -0.02454655]]
[[ 0.03188958 0.02244325 0.04611005]
[-0.02061195 0.01945392 -0.02339504]
[ 0.01334128 0.0285162 -0.04092228]]]
==========================================
(4, 3, 3)
现在我的输入样本数据变成了和前面的例子中一样的(samples,timesteps,features)这个形式了,其中
samples=4,timesteps=3,features=3
下面来看一下Embedding层的参数定义:
keras.layers.Embedding(input_dim, output_dim, embeddings_initializer='uniform', embeddings_regularizer=None, activity_regularizer=None, embeddings_constraint=None, mask_zero=False, input_length=None)
- input_dim: int > 0。词汇表大小, 即,最大整数 index + 1。这个是整个词汇表的最大数量,本例中写的500,是随便写的,一般这个数据需要经过分词之后通过统计词的数量求得
- output_dim: int >= 0。词向量的维度。即每一个单词表示成多少维向量,这里是3
-
embeddings_initializer:
embeddings
矩阵的初始化方法 (详见 initializers)。 -
embeddings_regularizer:
embeddings
matrix 的正则化方法 (详见 regularizer)。 -
embeddings_constraint:
embeddings
matrix 的约束函数 (详见 constraints)。 -
mask_zero: 是否把 0 看作为一个应该被遮蔽的特殊的 "padding" 值。 这对于可变长的 循环神经网络层 十分有用。 如果设定为
True
,那么接下来的所有层都必须支持 masking,否则就会抛出异常。 如果 mask_zero 为True
,作为结果,索引 0 就不能被用于词汇表中 (input_dim 应该与 vocabulary + 1 大小相同)。 -
input_length: 输入序列的长度,当它是固定的时。 如果你需要连接
Flatten
和Dense
层,则这个参数是必须的 (没有它,dense 层的输出尺寸就无法计算)。这其实就是所谓的timesteps,只不过换了一个说法,这里取值为3
注意:这里关键的参数是mask_zero,后面再详说。
3.2 使用embedding层来实现变长序列输入
上面的例子中,由于原始输入就没有缺少,每一个的长度都是固定的3,只是为了看一下Embedding层的作用是什么,现在假设有一些不定长的数据,我同样需要做的就是填充0,代码如下:
import keras as ks
import numpy as np
'''
#这是原始的输入数据,一共四组样本(四个句子),没组样本的时间跨度不一样,
第一组为3
第二组为1
第三组为3
第四组为2
'''
input_array=np.array([[4,10,5],[2],[3,7,9],[2,5]])
'''
#使用序列与处理函数pad_sequences()填充0,
这里的maxlen=3就是指timesteps最大为3,不足的全部补充0,
post表示在后面填充,pre表示在前面填充,默认为pre
'''
X=ks.preprocessing.sequence.pad_sequences(input_array,maxlen=3,padding='post')
print(X)
print('==========================================')
model = ks.models.Sequential()
model.add(ks.layers.Embedding(100, 3, input_length=3))
model.compile('rmsprop', 'mse')
output_array = model.predict(X)
print(output_array)
print('==========================================')
print(np.shape(output_array))
运行结果为:
[[ 4 10 5]
[ 2 0 0]
[ 3 7 9]
[ 2 5 0]]
==========================================
[[[-0.04528923 -0.00923926 0.01403354]
[ 0.01515898 0.01483223 -0.00947972]
[-0.01472244 -0.02926698 0.03155271]]
[[ 0.02277514 0.02044438 0.03303898]
[-0.0392134 -0.00636433 -0.02115151]
[-0.0392134 -0.00636433 -0.02115151]]
[[ 0.00736283 0.04576111 0.01646307]
[-0.00783598 0.0376038 -0.03824564]
[-0.0125035 -0.04934802 0.00373475]]
[[ 0.02277514 0.02044438 0.03303898]
[-0.01472244 -0.02926698 0.03155271]
[-0.0392134 -0.00636433 -0.02115151]]]
==========================================
(4, 3, 3)
从上面我们发现,虽然数据维度上现在已经没有问题了,但是,通过Embedding后,单词0所对应的向量并不是全为0呢?
事实上,这就是这个地方的一个误区之一,此处在使用Embedding层的时候并没有传递关键字参数mask_zero=True,很多人觉得如果是传入了关键字参数mask_zero=True,那么0所对应的单词转化成向量之后应该全部是0,这是不正确的,这个地方不管有没有传入关键字参数mask_zero=True,输出的结果都是一样的,单词0都不会全部为0向量。
既然关键字参数mask_zero=True有没有都一样,那还需要他有什么用?这其实是后面的使用要用到的地方,因为Embedding层只能放在网络的第一层,用来对数据进行处理,当后面要跟循环层的时候,关键字参数mask_zero=True就发挥出作用了。
3.2.1 当关键字参数mask_zero=False(即默认值)
此时的代码如下:
import keras as ks
import numpy as np
'''
#这是原始的输入数据,一共四组样本(四个句子),没组样本的时间跨度为3,即timesteps=3,每一个数字表示一个单词
#现在我想把每一个数字(即单词)转化成一个三维向量
#即
4->[#,#,#]
10->[#,#,#]
5->[#,#,#]
2->[#,#,#]
.
..依次下去
'''
input_array=np.array([[4,10,5],[2],[3,7,9],[2,5]])
X=ks.preprocessing.sequence.pad_sequences(input_array,maxlen=3,padding='post')
print(X)
model = ks.models.Sequential()
model.add(ks.layers.Embedding(100, 3, input_length=3,mask_zero=False))
rnn_layer=ks.layers.SimpleRNN(5,return_sequences=True)
model.add(rnn_layer)
model.compile('rmsprop', 'mse')
output_array = model.predict(X)
print(output_array)
运行结果为:
[[[-0.01507465 -0.0192386 -0.03286014 0.0430268 -0.01492362]
[ 0.04894346 -0.03795945 -0.03045793 -0.06069282 0.00017872]
[-0.04956069 0.03926758 -0.02741077 -0.02429961 -0.00730546]]
[[-0.01881071 0.00454673 -0.01398094 0.02748031 -0.02079656]
[ 0.0318772 -0.03267315 -0.01361655 -0.02769214 0.00790467]
[-0.02251629 0.03369562 -0.00906445 -0.04744482 0.00141174]]
[[ 0.00473799 -0.0216711 -0.04201381 -0.01117982 0.0191639 ]
[-0.00850029 0.01261848 -0.06416406 0.02927677 -0.03776023]
[ 0.03353037 -0.04458039 -0.05099481 -0.01612281 -0.02791761]]
[[-0.01881071 0.00454673 -0.01398094 0.02748031 -0.02079656]
[ 0.03428226 -0.0450107 -0.00934676 -0.00833553 0.00501254]
[-0.01707621 0.02320341 -0.00025231 -0.06497627 -0.00428138]]]
从上面可以看出,第二组样本、第四组样本的补充的单词0,也是参与了循环层的运算了的,这改变了输入的句子的意思。
3.2.2 当关键字参数mask_zero=True
代码如下,只需要修改上面代码的一个地方,将false改为True即可:
model.add(ks.layers.Embedding(100, 3, input_length=3,mask_zero=True))
运行结果如下:
[[[-0.00566185 -0.01395454 0.03897382 -0.00447031 -0.01689496]
[ 0.01163974 -0.007847 0.00704868 0.0319964 -0.01156033]
[ 0.02103921 0.03141655 0.01596024 0.00670511 -0.05503707]]
[[ 0.015795 -0.02212714 -0.00166886 -0.00120822 0.01417502]
[ 0.015795 -0.02212714 -0.00166886 -0.00120822 0.01417502]
[ 0.015795 -0.02212714 -0.00166886 -0.00120822 0.01417502]]
[[ 0.04398683 -0.02056707 0.012842 -0.00317691 -0.02015743]
[-0.05454749 0.03934489 -0.02353742 0.03340311 -0.0235149 ]
[ 0.02906755 0.07557297 0.0048439 -0.00078752 -0.00623714]]
[[ 0.015795 -0.02212714 -0.00166886 -0.00120822 0.01417502]
[ 0.01466416 -0.00909013 0.00990194 -0.01179877 -0.05580193]
[ 0.01466416 -0.00909013 0.00990194 -0.01179877 -0.05580193]]]
从上面我们发现,第二组样本、第四组样本他们的单词0,并没有参与运算,输出就是前一个时间步的输出。
总结:Embedding的关键字参数mask_zero=True不会改变Word2vector的结果,即不是讲所有补充的0全部变为0向量,这个很重要,关键字参数mask_zero=True的作用是决定了后面的“循环层”是否会将补充的0单词参与运算,如果设置为True,就是“覆盖掉0”的意思,自然就不参与运算,如果设置为False,就是不覆盖0,0也会参与运算的意思。
补充:既然Word2vector之后的结果根本就不是0向量,那后面循环层在运算的时候怎么会知道哪个是补充的单词0,哪个不是补充的单词0?
事实上,在对每一个单词进行Word2vector的时候,会存在一个单词与向量之间的映射关系在里面,比如
3->[#,#,#]
4->[#,#,#]
5->[#,#,#]
0->[#,#,#]
所以即使向量并不是0向量,但是哪个与单词0对应的向量依然是清楚地,所以只要有这样一个映射关系,循环层在遇到与0相对应的向量时,就“覆盖掉不参与运算”,这就是其中的原理了。后面要介绍的Masking层实际上也是要达到同样的效果,二者异曲同工。
3.3 使用Masking层来处理变长序列
直接看代码:
import keras as ks
import numpy as np
#样本数据为(samples,timesteps,features)的形式,其中samples=4,features=3,timesteps不固定,第二个样本只有一个步长,第四个样本只有2个步长
train_X = np.array([
[[0, 1, 2], [9, 8, 7],[3,6,8]],
[[3, 4, 5]],
[[6, 7, 8], [6, 5, 4],[1,7,4]],
[[9, 0, 1], [3, 7, 4]]
])
#样本数据为(samples,timesteps,features)的形式,其中samples=4,timesteps=3,features=3,其中第二个、第四个样本所缺的样本自动补零
train_X = np.array([
[[0, 1, 2], [9, 8, 7],[3,6,8]],
[[3, 4, 5], [0, 0, 0],[0,0,0]],
[[6, 7, 8], [6, 5, 4],[1,7,4]],
[[9, 0, 1], [3, 7, 4],[0,0,0]]
])
model = ks.models.Sequential()
#添加一个Masking层,这个层的input_shape=(timesteps,features)
model.add(ks.layers.Masking(mask_value=0,input_shape=(3,3)))
#添加一个普通RNN层,注意这里的return_sequence参数,后面会说
rnn_layer=ks.layers.SimpleRNN(5,return_sequences=True)
model.add(rnn_layer)
model.compile('rmsprop', 'mse')
output_array = model.predict(train_X)
print(output_array)
print('==========================================')
运行结果为:
[[[ 0.38400522 -0.07637138 0.80482227 0.5201789 0.8758846 ]
[ 0.9732032 -0.5962235 0.5779228 -0.6468719 0.99999994]
[ 0.9232968 0.3515737 0.9787954 0.7505297 0.9999945 ]]
[[ 0.8733732 -0.33613908 0.962015 0.17185715 0.9998116 ]
[ 0.8733732 -0.33613908 0.962015 0.17185715 0.9998116 ]
[ 0.8733732 -0.33613908 0.962015 0.17185715 0.9998116 ]]
[[ 0.9796784 -0.5531762 0.993092 -0.22548307 0.9999997 ]
[ 0.69188297 0.36132666 -0.59311306 -0.58000374 0.9999302 ]
[ 0.92864347 -0.89851534 0.40440124 -0.99098665 0.99703205]]
[[ 0.6512652 0.79500616 -0.69901264 0.5302738 0.9928446 ]
[ 0.9593913 -0.94486046 0.5666493 -0.9513761 0.9993295 ]
[ 0.9593913 -0.94486046 0.5666493 -0.9513761 0.9993295 ]]]
从上面的结果可以看到,输出的形状依然是(samples,timesteps,num_units)的格式,即(4,3,5),但是前面在使用tensorflow的时候,上面结果中的“蓝色标记”和“红色标记”应该是0才对啊,这里怎么不是0,仔细观察可以发现,原来它们其实也没有经过运算,他跟前面的那个经过运算的是重复的输出,即重复输出最后一个经过运算的时间步的结果,所以依然是正确的,只不过tensorflow中将没经过运算的时间步全部填充0,而keras将没经过运算的时间步全部填充前面重复的输出结果而已。
那现在来看一看状态:将上面的程序,修改一句话即可,将:
#添加一个普通RNN层,注意这里的return_sequence参数,后面会说
rnn_layer=ks.layers.SimpleRNN(5,return_sequences=True)
修改为(后面会介绍为什么):
#添加一个普通RNN层,注意这里的return_sequence参数,后面会说
rnn_layer=ks.layers.SimpleRNN(5,return_sequences=False)
结果为:
[[-0.13516335 0.99992615 -0.98370534 0.959703 -0.9999998 ]
[-0.82386047 0.99106854 -0.9709585 0.96902865 -0.9998157 ]
[-0.9918523 0.99992615 0.06573126 0.8771769 -0.99996233]
[-0.99787956 0.9999137 -0.10109197 0.95941854 -0.99998754]]
可以看出结果依然为(samples,num_units),此处即(4,5),本来应该和上面的对应的最后一个时间步的输出是对应一样的,但是由于这是重新运行的,参数初始化不一样,所以不一样而已。
3.4 关于Masking层和SimpleRNN层的理解
3.4.1 Masking层(其实就是覆盖层)
定义如下:
ks.layers.Masking(mask_value=0,input_shape=(timesteps,features))
使用覆盖值覆盖序列,以跳过时间步。
对于输入张量的每一个时间步(张量的第一个维度), 如果所有时间步中输入张量的值与 mask_value
相等, 那么这个时间步将在所有下游层被覆盖 (跳过) (只要它们支持覆盖)。
如果任何下游层不支持覆盖但仍然收到此类输入覆盖信息,会引发异常。
其实就是,因为mask_value=0,所以在每一个全部特征features都是0的那一个timesteps都会被覆盖掉,即像前面tensorflow中介绍的那样,不会对它进行任何计算,即所谓的覆盖掉了。
需要注意的是,Masking层默认是覆盖0,当然还可以屏蔽其它的值,比如我要覆盖1,则mask_value=1即可。
3.4.2 SimpleRNN层
定义如下:
keras.layers.SimpleRNN(units, activation='tanh', use_bias=True, kernel_initializer='glorot_uniform', recurrent_initializer='orthogonal', bias_initializer='zeros', kernel_regularizer=None, recurrent_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, recurrent_constraint=None, bias_constraint=None, dropout=0.0, recurrent_dropout=0.0, return_sequences=False, return_state=False, go_backwards=False, stateful=False, unroll=False)
它有一个很重要的参数,即
return_sequence:
它是布尔值。是返回输出序列中的最后一个输出,还是全部序列。什么意思呢?
- 如果
return_sequences=True
:返回 3D 张量, 尺寸为(batch_size, timesteps, num_units)
。即所谓的输出值; - 如果
return_sequences=False
:返回 2D 张量,返回尺寸为(batch_size, num_units)
。即所谓的状态值。
这里和前面tensorflow的实现是一致的。
3.5 keras中Embedding层和Masking层实现变长序列的输入的异同点
(1)不同点
Embedding层和Masking层都有覆盖过滤的功能,但与Masking层不同的是,Embedding它只能过滤0,不能指定其他字符,并且因为是embedding层,它会将序列映射到一个固定维度的空间中。但是Masking层可以通过mask_value关键字参数覆盖过滤掉其它的单词,因此,如果诉求仅仅是让keras中LSTM能够处理边长序列,使用Masking层会比使用Embedding层更加适合。
Embedding层的功能:Word2vector+mask覆盖
Masking层的功能:专门的mask覆盖
(2)相同点
Embedding层和Masking层都看可以实现填充的单词0的覆盖过滤,在使用的时候,后面所添加的循环层一定要支持mask操作(覆盖过滤操作)才行,否则没有办法compile,会报错。一般keras中不使用SimpleRNN()、LSTM()或GRU(),它们都是支持mask操作的,但是更快的CuDNNLSTM()和CuDNNGRU(),这两者是不支持mask的,如果Embedding()的参数mash_zero设为True,那model.compile()时就会报错。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:【个人整理】循环神经网络RNN对于变长序列的处理方法 - Python技术站