1. padding
文本数据在处理的时候,由于各样本的长度并不一样,有的句子长有的句子短。抛开动态图、静态图模型的差异,由于需要进行矩阵运算,句长需要是等长的才可以,这就需要padding操作。padding一般是用最长的句子长度为最大长度,然后其他样本补0到最大长度,这样样本就是等长的了。
但是注意padding后的样本如果不作处理只用普通的循环神经网络来做的话其实是有影响的,因为即使输入的是0,做了embedding后也不是0,而且还有上一时刻隐藏层,所以输出不会是0。但是在实际使用中,padding的这种操作如果不做特殊处理,模型也是可以学到它是无用的padding。
下面介绍不同模型在处理padding上的不同操作
2. keras
keras中对变长rnn的使用应该是最简单的了,只需设置embedding的参数mask_zero为true就可以了,注意设置为true后,需要后面的所有层都能够支持mask,比如LSTM之类的层。这里详细说一下当用预训练的词向量的时候,需要在训练好的词向量权重前补一个标签0的向量,并把embedding层设置为trainable=false
word_count = 10 # 词表大小
embedding_dim = 3 # embedding 向量维度,为了便于演示,长度设置成3
input_seq = Input(shape=[None]) # 尽管这里是变长序列,但实际上输入还是一个固定大小的序列
embedding_weight = np.vstack([(np.ones(shape=(1, embedding_dim)) * (idx)) for idx in range(word_count + 1)]) # 这里创建了一个 (10 + 1) * 3的矩阵作为embedding的权值。为了方便展示效果,每一行的值都是这行的index。
embedding_layer = Embedding(word_count + 1, embedding_dim, weights=[embedding_weight], mask_zero=True, trainable=False) # 这里的重点是输入词表大小、mask_zero=True
output_seq = embedding_layer(input_seq)
embedding_model = Model(input=input_seq, output=output_seq)
3. TensorFlow
TensorFlow中对变长rnn的支持靠tf.dynamic_rnn
outputs, last_states = tf.nn.dynamic_rnn(
cell=cell,
dtype=tf.float64,
sequence_length=X_lengths,
inputs=X)
由于TensorFlow是静态图模型,普通的rnn在计算图搭建好后,所有batch中的sequence长度都要是一样的,而dynamic_rnn只需要每个batch内部的sequence长度一样就可以了。sequence_length是每一句话未padding的实际长度,当rnn计算到padding的地方就不再更新权重,而是直接输出和上一个时刻相同的hidden state。
4. pytorch
像pytorch这种动态图模型就比较方便了,可以像写python代码一样任意的用while和for循环,每一次运行都会从新建立计算图。比如我们可以将batch设为1,每一步都只输入一句话,运行的时候从新设置网络中的rnn的sequence长度,这是可以的。当然,batch为1的话模型运行起来会比较慢,下面介绍另一种方法。
下面的是pytorch官方api中的代码,主要是用函数torch.nn.utils.rnn.pack_padded_sequence()和pad_packed_sequence()来进行的。将原始数据padding后,和sequence_length一起传入pack中。注意句子需要按实际长度从长到短排序,sequence_length也要相应对齐。
import torch
import torch.nn as nn
from torch.autograd import Variable
from torch.nn import utils as nn_utils
batch_size = 2
max_length = 3
hidden_size = 2
n_layers =1
tensor_in = torch.FloatTensor([[1, 2, 3], [1, 0, 0]]).resize_(2,3,1)
tensor_in = Variable( tensor_in ) #[batch, seq, feature], [2, 3, 1]
seq_lengths = [3,1] # list of integers holding information about the batch size at each sequence step
# pack it
pack = nn_utils.rnn.pack_padded_sequence(tensor_in, seq_lengths, batch_first=True)
# initialize
rnn = nn.RNN(1, hidden_size, n_layers, batch_first=True)
h0 = Variable(torch.randn(n_layers, batch_size, hidden_size))
#forward
out, _ = rnn(pack, h0)
# unpack
unpacked = nn_utils.rnn.pad_packed_sequence(out)
print(unpacked)
上面pack的值如下,pack函数会生成一个PackedSequence,包含data和batch_size。注意上面pack函数设置的batch_first为true,输入的维度是(batch size, sequence_length, 1),也就是(2,3,1),我们将数据排列好后如下
[1,2,3]
[1,0,0]
data的数据形式是从第一列开始的一个一个的值,不包括0。batch_sizes是第一列有两个有效值,第二列有一个,第三列有一个。这样排列的原因是batch做矩阵运算的时候网络是先计算所有句子的第一位,然后第二位,第三位。https://zhuanlan.zhihu.com/p/34418001
PackedSequence(data=tensor([[ 1.],
[ 1.],
[ 2.],
[ 3.]]), batch_sizes=tensor([ 2, 1, 1]))
参考
http://friskit.me/2018/04/23/rnn-padding-and-masking-in-keras/
https://zhuanlan.zhihu.com/p/34418001 pytorch处理变长rnn