变长序列咋使用mini-batch SGD训练

最近在研究 Pytorch 下的 RNN,CookBook 上都是单个样本的训练过程,没有 mini-batch 的方案,就自己研究了一下。
补全方案有一种最简单的方案,将所有的样本,都用 0 补全到固定长度。比如,每个 minibatch 中有三个句子,全部做长度为 15 的补全:
tensor(, , ])在计算 cost 时,再跳过将补全位置的值。对于翻译任务,attention 的 softmax 计算也要跳过这些位置。这种方案实现简单,但是为了和训练过程保持一致,预测解码也需要做补全。对于线上实时系统来说,对所有句子做补全,会大大降低处理速度。设想一下,将 “How are you ?” 这种短句做补全到 100,要多做25倍的迭代。
Mask 方案Pytorch 有 torch.nn.utils.rnn 工具包,来支持 RNN 的卷积。分三个步骤来做。
1. padding
第一步还是 padding 补全,但需要保留每个输入序列的长度信息作为 mask。按照 batch 中最长的序列的长度,作为补全长度。和第一种方案相区别,每个 mini-batch 的补全长度是不一样的。
PAD_VALUE = https://www.zhihu.com/api/v4/questions/264501322/0# batch_size = 3 的原始输入.src_ids = , , ]# 计算每个batch的原始长度.lengths = # 对原始序列进行补全.src_pad = list(itertools.zip_longest(*src_ids, fillvalue = PAD_VALUE))src_ten = torch.LongTensor(src_pad)sen_ten:
tensor(, , ])2. embedding
由于PAD_VALUE 没有对应的embedding值,pytorch 支持将对应位置的embedding输出,设置为 0。注意 num_beddings 需要包含 PAD_VALUE。
import torch.nn as nnemb_layer = nn.Embedding(num_embeddings = 3, embedding_dim = 5, padding_idx = PAD_VALUE)3. 在 lstm 之前
对 embedding 层的输出做 pack,保证 lstm 不会处理补全的时序位置。需要使用第一步计算的长度信息。这一步是 pad -\u0026gt; pack。
embedded = emb_layer(src_ten)packed = nn.utils.rnn.pack_padded_sequence(embedded, lengths, batch_first = True)4. 在 lstm 之后
对 lstm 的输出,恢复成带补全的矩阵形式,这一步是 pack -\u0026gt; pad。contiguous() 返回一个内存连续的有原数据的tensor。
padded, _ = .nn.utils.rnn.pad_packed_sequence(packed, batch_first = True)padded = padded.contiguous()在后续的非时序卷积的层,直接使用 padded 作为输入。
5. decoder
以上都是对源端数据做的处理。在目标端,除了上面的部分外,还需要在最初计算 mask 值。在 backword 过程中,计算 loss 时要跳过补全的位置。
trg_ten = src_ten # 只是示例, 表示目标端数据的tensor.mask = (trg_ten != PAD_VALUE).float()mask:
tensor(, , ])在解码时,循环次数为 trg_ten.size(),即补全后的长度。在时序位置 i,假设有对应的损失函数值 loss,使用 masked_select() 来获得非补全的序列。
criterion = nn.NLLLoss()loss = criterion(decoder_output.masked_select(mask), trg_ten, .masked_select(mask))需要额外注意的部分,就是以上几个。全部的代码还没有写完,后续再更新。

■网友
序列问题,用RNN类的神经元,训练样本是context,即当前点+前后两个点的Skip-gram,序列问题的神经元会处理这样的context,以进行记忆、遗忘、传递操作


推荐阅读