序列处理技巧
对变长序列进行批处理时,为什么要做 Padding?Padding 如何影响 RNN 的计算?¶

为什么要 Padding
神经网络通常要求输入张量具有规则的形状。在一个批次内,不同样本的序列长度往往不同,为了将它们组合成一个规整的张量(如形状 [batch_size, max_len, feature_dim]),必须将所有序列填充到同一长度。常见的做法是在序列末尾添加特殊的标记(如 0)作为填充值,使每个样本达到批次内最长序列的长度。
Padding 对 RNN 计算的影响
-
无效计算:RNN 会逐时间步计算,即便是填充的位置也会参与矩阵乘法和激活,产生无效的计算输出和梯度。这些填充位置的输出没有实际意义,纯粹浪费计算资源。
-
状态污染:如果不进行掩码处理,填充值(通常是零)会被当作正常输入,经过循环层影响隐藏状态,可能导致模型学习到错误的依赖关系。尤其是当填充位于序列开头时(例如语言模型中的前缀填充),这种影响更严重。
-
梯度问题:填充位置产生的梯度会被累积,如果不屏蔽,可能使模型向无意义的方向更新。
-
内存浪费:大量填充值会占用显存,降低有效计算比例。
缓解方法
-
使用 packing/masking 机制跳过填充位置的计算(见下问)。
-
在损失函数中使用掩码忽略填充位置的贡献。
-
对于 RNN 类网络,尽量减少 padding 占比,例如通过按长度排序、bucket 采样等。
PyTorch 中的 pack_padded_sequence 和 pad_packed_sequence 是如何提高效率的?¶

pack_padded_sequence
该函数将一个已填充的序列张量和一个记录各序列长度的列表作为输入,返回一个 PackedSequence 对象。它本质上将非填充元素按时间步紧凑排列,并记录每个时间步实际有效的 batch 大小(batch_sizes)。例如,batch 内有 3 个长度分别为 5,3,2 的序列,pack 后的数据按时间步组织:第一步有 3 个样本,第二步有 3 个,第三步有 2 个(最短的已结束),第四、五步只有 1 个样本。这样 RNN 在每一步只计算实际存在的样本,跳过了填充位置。
效率提升
-
避免无效计算:RNN(尤其是 cuDNN 实现的 LSTM/GRU)可以直接接收
PackedSequence,在每一步只对有效序列进行运算,完全跳过填充。从而节省了乘法、加法和激活的计算量。 -
梯度传播优化:反向传播时也仅在有效的路径上流动,减少无用梯度的计算。
-
内存节省:不需要存储填充位置的中间激活值,降低显存占用。
pad_packed_sequence
它是反向操作,将 PackedSequence 恢复为填充后的规整张量,便于后续的全连接层或损失计算。通常会返回一个和输入同样形状的张量,但非有效位置的值可以是任意(通常用 0 填充),需结合掩码使用。
示例用法
import torch.nn.utils.rnn as rnn_utils
# x: [max_len, batch, feature]
# lengths: [batch]
packed = rnn_utils.pack_padded_sequence(x, lengths, enforce_sorted=True)
output, hidden = lstm(packed)
output_padded, _ = rnn_utils.pad_packed_sequence(output)
注意:需按序列长度降序排列以保证正确性。
Masking 在序列损失计算中如何忽略 Padding 的位置?¶
在计算损失(如交叉熵)时,须确保填充位置的损失不计入总损失,否则模型会被迫预测无意义的填充符,影响性能。常用方法:
-
使用
ignore_index参数:在 PyTorch 的nn.CrossEntropyLoss中设置ignore_index=PAD_token_id,计算损失时自动忽略目标标签为该值的所有位置。这是最简单且高效的方式。 -
自定义掩码:生成一个与目标序列同形的布尔掩码,标记非填充位置(
mask = targets != PAD_token_id)。将损失张量乘以掩码再求和,最后除以有效位置总数:

- 加权平均:有些实现对每个样本单独计算平均损失(对有效长度取平均),然后再在 batch 维度取平均,这样可以平衡长短序列对梯度的贡献。
无论哪种方法,关键是确保填充位置的预测不会产生任何梯度更新信号。
如何处理序列中的缺失值?填充、插值还是用 Mask?¶
处理时间序列中的缺失值需根据缺失模式、任务需求和数据特性选择。
-
填充(Imputation)
-
前向填充/后向填充:用前一个有效值填充缺失位置,假设数据平稳连续。
-
均值/中位数/众数:用整个序列或滑动窗口内的统计量填充,简单但可能扭曲分布。
-
模型预测填充:使用插值模型(如线性回归、KNN、矩阵分解)预测缺失值,更精确但计算代价高。
-
特殊值标记:用一个超出正常范围的值(如 -999)填充,并配合掩码告知模型哪些是缺失值,让模型自己学习如何处理。
-
插值
适用于缺失比例不大且序列平滑的情况。常见有线性插值、样条插值、时间插值等。插值能保持序列的连续性,但可能引入假设。
- 使用 Mask(掩码)
在模型中显式建模缺失信息。可通过额外的二值掩码通道输入网络,指示哪些时间步是缺失的,让 RNN/Transformer 自己学习忽略或推断。比如 GRU-D 或 Transformer 中加入缺失指示向量。这种方式不扭曲原始数据分布,但要求模型能够处理。
选择建议
-
少量随机缺失:前向填充或线性插值即可。
-
长时间缺失或连续缺失:使用掩码+模型推断更合适。
-
高比例缺失:需谨慎,可能需要专门的缺失值处理模块(如生成模型)。
为什么在序列模型训练中常进行梯度裁剪?¶
RNN 及其变体在长序列上容易发生梯度爆炸。由于反向传播经过许多时间步,梯度范数可能指数级增长,导致参数更新过大、损失变为 NaN 或模型发散。梯度裁剪是一种简单有效的稳定训练技巧。
原理
设定一个阈值(如 max_norm=5)。每当梯度的全局 L2 范数(所有参数梯度的平方和再开方)超过该阈值时,将梯度按比例缩放到阈值:

这限制了单步参数更新的最大步长,避免因异常批次或长序列导致的剧烈更新。
在序列模型中的必要性
-
RNN/LSTM 中循环权重矩阵的连乘极易产生大梯度,尤其是序列长、目标损失大时。
-
使用注意力机制的模型也可能因注意力分数过大导致梯度尖峰。
-
梯度裁剪使训练过程对学习率更鲁棒,允许使用更大的学习率加速收敛,同时防止崩溃。
实践:PyTorch 中 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm) 放在 loss.backward() 和 optimizer.step() 之间。
在时间序列预测中,如何进行窗口化滑动构建样本?需要注意什么?¶
窗口化滑动方法
给定一个长序列,使用一个固定长度的滑动窗口将其切成多个“输入-目标”样本对:
-
输入窗口:长度为 LL 的历史观测值(如过去 24 小时的温度)。
-
目标窗口(预测窗口):紧跟输入窗口之后的一段长度为 HH 的未来值(如未来 6 小时温度)。 滑动步长(stride)通常为 1 个时间步,以尽可能多地生成样本。
操作步骤

注意事项
-
数据泄露:确保测试集或验证集的样本完全在时间上晚于训练集,不能有重叠,否则未来信息会泄露到训练中。
-
平稳性:对于非平稳序列,可通过差分、去趋势等预处理使窗口内特征更稳定。
-
批处理:若存在多个独立时间序列,可分别窗口化后混合。注意不同序列的尺度应一致或归一化。
-
多步预测:目标窗口可以是多个值,可采用直接多输出、递归多步预测或 seq2seq 结构。
-
边界处理:序列开头无法构建完整窗口时可以丢弃或特殊处理。
-
时间特征:通常将绝对时间(小时、星期等)作为额外特征输入,需在窗口化时一并提取。
示例:若序列长度为 1000,输入长度 100,输出长度 10,步长 1,则可生成约 891 个样本。
什么是时序数据的平稳性?对模型有什么影响?如何差分处理?¶
平稳性
时间序列的统计特性(均值、方差、自协方差)不随时间变化,称为强平稳;均值和方差恒定且自协方差仅依赖于时间间隔称为弱平稳(大多关注弱平稳)。许多经典模型(ARIMA)假设平稳性。平稳序列更易于建模,因为历史规律能稳定推广到未来。
对模型的影响
-
深度学习模型(RNN、LSTM)理论上能拟合非平稳趋势,但训练会更难,尤其当趋势和季节性幅度很大时,模型需要分配部分容量去学习随时间变化的基线。
-
非平稳可能导致梯度尺度随时间波动,训练不稳定。
-
模型可能学到伪相关,泛化差。
-
通常消除趋势和周期性后,序列的动态范围变小,学习更容易。
差分处理
最常用的平稳化方法是差分:计算相邻观测值的差。

其他方法
-
对数变换稳定方差。
-
移动平均平滑。
-
使用分解方法(STL)去除趋势和季节分量,对残差建模。
在神经网络中,也可不显式差分,而使用归一化层(如 Instance Norm、Layer Norm)逐序列自适应处理,或采用 time2vec 等嵌入时间信息。但差分预处理仍常见于多变量、长序列预测中。
当序列长度差异巨大时,如何设计批采样策略(如按长度排序、Bucket 采样)?¶
目标:减少因填充导致的浪费,提高 GPU 利用率和训练效率。
按长度排序
将整个训练集的样本按照序列长度排序,然后按固定的 batch size 顺序切分。这样每个 batch 内样本长度相近,填充量大大减少。但这样破坏了随机性,可能导致训练震荡或收敛到局部极值。通常用于静态数据集。
Bucket 采样(分桶采样)
-
设定若干长度区间(如 [0,50], [51,100], [101,200]…)。
-
将每个样本根据其长度分配到对应的桶中。
-
训练时,随机选择一个桶(或轮转),再从该桶内随机抽取样本组成 batch。这样 batch 内的长度差异被限制在一定范围内,同时保留了整体随机性。
-
可对每个桶的 batch size 单独设置(长度大的桶 batch size 小些以控制显存)。
动态批处理
在一次迭代中,动态地从剩余未训练样本中抽取若干样本,使得它们的总长度(或总 token 数)不超过预设上限。这在 NLP 预训练中常见,例如“按token数累积”,当累积 token 数达到阈值即作为一个 batch。这种方法最大化每步的计算量,对极长序列尤其有效。
其他技巧
-
尽量让数据加载器在每个 epoch 重新按长度采样,以保证不同 epoch 的顺序变化。
-
使用
collate_fn进行 padding 和 packing。 -
对于分布式训练,需注意不同 GPU 上的 batch 长度分布。
如何利用 RNN 进行序列分类?最后时刻的输出还是全部输出的池化?¶
RNN 用于序列分类时,输入是一个序列,输出是一个类别标签。常见表征方式:
- 最后时刻输出

- 全部输出池化
对 RNN 所有时间步的输出做聚合操作:
-
平均池化:取所有 htht 的平均值,能捕获全局信息。
-
最大池化:取每个维度的最大值,强调最关键特征(如文本分类中关键词)。
-
注意力池化:用可学习的注意力权重对所有输出加权求和,模型自适应选择重要时间步,效果通常最好。 池化后再送入全连接层。这种方法能更好地利用序列各处的信息。
选择依据
-
当序列较短或任务依赖于整段语义(如情感分析),池化或注意力更好。
-
最后时刻输出适用于在线场景或强调尾部信息任务(如流式分类)。
-
实践中,注意力池化是高性能方案,但会额外引入参数。
额外:还可以结合使用,如将最后隐态和池化后的向量拼接。
在 NLP 中,文本序列的起始和结束标记有什么作用?如何设计?¶
起始标记(SOS/BOS,Start of Sequence) 和 结束标记(EOS,End of Sequence) 是 Seq2Seq 等生成模型的关键特殊符号。
作用
-
解码启动:在推理时,解码器的第一个输入必须是 SOS,它告诉模型开始生成。
-
序列终止:模型在生成 EOS 时表示序列结束。这允许模型自主决定输出长度。
-
训练统一:训练时,目标序列被封装为
[SOS] y_1 y_2 ... y_T [EOS],解码器在接收[SOS]时预测 y1y1,依此类推。这样每个时间步都有明确的学习目标。 -
长度控制:防止模型无限生成。束搜索中,一旦某序列生成 EOS,即视为完整序列。
-
开头和结尾信息:模型可以通过 SOS 和 EOS 的嵌入学习到序列开始和结束的语境,对某些语言生成有帮助。
设计
-
使用与词表不同的专用 token ID,如
SOS_ID = 0,EOS_ID = 1,普通词从 2 开始。 -
在预处理时自动添加。对于编码器输入,通常不加 EOS(仅输出端加)。
-
某些 Transformer 中,编码器端也添加 EOS 作为句子结束的指示,利于注意力理解。
如何处理多变量时间序列输入?如何将不同类型的特征喂入 RNN?¶
多变量时间序列包含多个不同类型的变量(如温度、湿度、风速、星期几、节假日标志等)。需根据特征性质分别处理。
数值连续变量
通常进行标准化(Z-score)或 min-max 归一化,使各变量处于相近尺度,有利于网络训练。可以直接拼接成一个多维向量,作为 RNN 每个时间步的输入。
分类变量/离散特征

已知未来信息(如天气预报中的未来已知天气数据)
如果某些特征在预测范围内已知(如星期几),可在解码器端输入时提供;若在整个序列中都已知道,可直接与历史观测拼接。但需注意避免在训练时泄露真实未来目标值。
缺失值处理
如前所述,填充并增加二值掩码作为额外输入通道,指示该时间步各特征是否缺失,帮助模型学习缺失模式。
融合方式

注意:尽量保持输入特征的尺度一致,尤其对于没有门控的 vanilla RNN,爆炸/消失更敏感。LSTM 对此稍鲁棒,但仍建议标准化。
12. 训练 Seq2Seq 模型时,输入序列和输出序列是否需要反转?为什么以前这么做?¶
曾经的做法
在早期的神经机器翻译(Sutskever et al., 2014)中,将输入句子反转(reverse)后再喂给编码器,即输入 ABC 变成 CBA,而目标句子保持正常顺序。这样做的目的是缩短源句首部与目标句首部之间的“时间步距离”,使模型更容易学会开头的对齐。因为梯度从解码器早期时间步传到编码器早期时间步的路径变短了。
为什么不再必要
-
注意力机制的引入:注意力直接建立了任意解码步到任意编码步的快捷连接,彻底解决了长距离梯度问题,输入顺序不再关键。
-
实验证明:对有注意力的 Seq2Seq,反转带来的收益微乎其微,甚至有时有害(打乱自然语序)。
-
现代化方案:使用双向编码器,无论正序反序,编码器都能捕获完整上下文。
因此,当前 Seq2Seq 模型(如 Transformer)输入保持原始顺序,不再反转。