原文链接:https://blog.csdn.net/zwqjoy/article/details/95050794
Padding
文本数据在处理的时候,由于各样本的长度并不一样,有的句子长有的句子短。抛开动态图、静态图模型的差异,由于需要进行矩阵运算,句长需要是等长的才可以,这就需要padding
操作。padding
一般是用最长的句子长度为最大长度,然后其他样本补0到最大长度,这样样本就是等长的了。
但是注意padding
后的样本如果不作处理只用普通的循环神经网络来做的话其实是有影响的,因为即使输入的是0,做了embedding
后也不是0,而且还有上一时刻隐藏层,所以输出不会是0。但是在实际使用中,padding的这种操作如果不做特殊处理,模型也是可以学到它是无用的padding
。
但是这会有一个问题,什么问题呢?比如上图,句子“Yes
”只有一个单词,但是padding
了5的pad
符号,这样会导致LSTM
对它的表示通过了非常多无用的字符,这样得到的句子表示就会有误差,更直观的如下图:
结论:直接填充0,在数据运算上没有问题,但是从序列的整个含义来说,这是不合理的,所以一般情况下不能这么做。
RNN的Mask
在使用RNN based model
处理序列的应用中,如果使用并行运算batch sample
,我们几乎一定会遇到变长序列的问题。
通常解决变长的方法主要是将过长的序列截断,将过短序列用0补齐到一个固定长度(例如max_length)。
最后由n个sample组成的dataset能形成一个shape == (n, max_length)
的矩阵。然后可以将这个矩阵传递到后续的模型中使用。
然而我们可以很明显,如果用0或者其他整数补齐,势必会影响到模型自身(莫名其妙被输入很多个0,显然是有问题的)。有什么方法能够做到“能够使用一个二维矩阵作为输入数据集,从而达到并行化的同时,还能让RNN
模型自行决定真正输入其中的序列的长度。
Mask
主要用于解决RNN
中输入有多种长度的问题。要输入RNN
中的是尺寸固定的张量,即 批尺寸(batch size) * 序列长度(sequence length) * 嵌入大小(embedding size) 。因为RNN
在计算状态向量时不仅考虑当前,也考虑前一次的状态向量,如果为了维持真实长度,采用补0的方式,在进行状态向量计算的时候也会包含进用0补上的位置,而且这种方式无法进行彻底的屏蔽。由于喂到模型中是固定大小的tensor
( batch size * sequence length * embedding size
), RNN需要用mask
来maintain
序列真实长度,从而在计算loss
的时候去除掉padding
的部分。
相比于补0,Mask
会得到不同的状态向量。对于每一个用0初始化的的样本,我们建立一个Mask
,并使其长度与数据集中最长的序列相同。然后样本中所有有数值的地方,我们用1把Mask
中对应的位置填充起来。
举个例子,数据集中最长的序列长度为10,所以所有的Mask
将以如下的方式初始化:
mask = [ 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]
同时,我们还有一个如下的样本:
a = [ 2., 0. ,5. ,6. ]
现在我们用1将Mask
中所有有数值的地方填充起来,因而得到以下的Mask
:
mask_a = [ 1., 1., 1., 1., 0., 0., 0., 0., 0., 0.]
在这之后,我们将样本与mask
输入RNN
中,RNN将会把所有没有值的的地方加上0,所以a变成了:
a_hood = [ 2., 0. ,5. ,6., 0., 0., 0., 0., 0., 0.]
但是如果我们任由RNN
用这种补0的方式,RNN
会认为所有的序列长度都为10,并且在计算时用上所有的补上的0。
mask_a = [ 1., 1., 1., 1., 0., 0., 0., 0., 0., 0.]
而此时mask_a
的作用就是让RNN
跳过所有Mask
为0的输入,复制cell
中前一次的隐藏状态;对于Mask
为1的输入RNN
将按常规处理。
CNN
对于CNN
来说,首先它的输入已经是固定尺寸,不需要Mask
,其次就算用上Mask
,结果和补0一样,所以采用padding
补0这种方便的方法,而CNN
是卷积操作,补0的位置对卷积结果没有影响,即补0和Mask
两种方式的结果是一样的,因此大家为了省事起见,就普遍在CNN
使用补0的方法了。CNN
的的输入本身就是fixed-size
,所以不需要mask
。