与前馈网络类似,循环神经网络,特别是更复杂的LSTM和GRU,可能出现过拟合。当模型过度拟合训练数据,包括其中的噪声和特定模式,而无法应用于新的、未见过的数据时,就会发生过拟合。这通常表现为在训练集上准确率高,但在验证集或测试集上显著降低。考虑到序列数据有时可能有限,并且RNN的参数随时间步演变,它们特别容易记住训练序列,而不是学习一般的时间模式。正则化方法旨在通过对模型或其训练过程添加限制来对抗过拟合,促使其学习更简单、更具普适性的模式。神经网络(包括RNN)中最常用且有效的一种正则化方法是 Dropout。理解DropoutDropout背后的主要思想出人意料地简单。在每次训练迭代中,Dropout随机将层中一部分神经元(或单元)的输出设为零。要丢弃的神经元比例由 dropout率 控制,这是一个通常设在0.1到0.5之间的超参数。通过随机“丢弃”单元,网络无法过度依赖任何单个神经元或一小群神经元共同适应以学习特定特征。这迫使网络学习冗余表示,并将学习分布到更多单元上,从而使学到的特征更可靠,对单个神经元的特定权重不那么敏感。可以将其视为同时训练许多不同的稀疏网络并对它们的预测进行平均。在RNN中应用Dropout:难题虽然标准Dropout对前馈层效果良好,但在RNN的循环连接中直接应用它会带来问题。请记住,在时间步 $t$ 的隐藏状态 $h_t$ 是根据之前的隐藏状态 $h_{t-1}$ 和当前输入 $x_t$ 计算的。如果你将标准Dropout应用于循环连接(即从 $h_{t-1}$ 到 $h_t$ 的连接),那么在每个时间步, $h_{t-1}$ 中可能都会有 不同 的单元被丢弃。这种有效循环连接的持续变化会严重干扰网络传播信息和在长序列上保持记忆的能力。这就像你试图进行一场对话,但你的短期记忆中随机的词汇在每一步都被不断抹去。这可能会阻止RNN、LSTM或GRU学习有意义的时间依赖关系。循环Dropout:正确应用Dropout为了有效地将Dropout应用于循环连接,而不妨碍时间动态的学习,通常使用一种被称为 循环Dropout(具体来说,是一种 变分Dropout 形式)的方法。主要思想是在给定训练序列内的所有时间步中,对循环连接使用 相同的Dropout掩码。这意味着如果一个特定的循环单元连接在时间步 $t$ 被丢弃(设为零),那么在该序列的 整个 前向和反向传播过程中,它在时间步 $t+1, t+2, \dots$ 也被丢弃。对于下一个训练序列或批次,会生成一个新的Dropout掩码,并一致地应用于其所有时间步。digraph G { rankdir=LR; node [shape=box, style=rounded, fontname="helvetica"]; edge [fontname="helvetica"]; subgraph cluster_standard { label = "循环连接上的标准Dropout"; style=dashed; color="#adb5bd"; bgcolor="#f8f9fa"; sn0 [label="h(t-1)", shape=ellipse]; sn1 [label="h(t)", shape=ellipse]; sn2 [label="h(t+1)", shape=ellipse]; sx0 [label="x(t)", shape=cds]; sx1 [label="x(t+1)", shape=cds]; sx2 [label="x(t+2)", shape=cds]; sn0 -> sn1 [label=" W_h * mask_t", color="#fa5252", style=dashed, fontcolor="#f03e3e"]; sn1 -> sn2 [label=" W_h * mask_{t+1}", color="#fa5252", style=dashed, fontcolor="#f03e3e"]; sx0 -> sn0 [label=" W_x"]; sx1 -> sn1 [label=" W_x"]; sx2 -> sn2 [label=" W_x"]; {rank=same; sn0; sx0;} {rank=same; sn1; sx1;} {rank=same; sn2; sx2;} label_std [label="问题:每一步掩码不同\n破坏信息流。", shape=plaintext, fontcolor="#c92a2a"]; } subgraph cluster_variational { label = "循环(变分)Dropout"; style=dashed; color="#adb5bd"; bgcolor="#f8f9fa"; vn0 [label="h(t-1)", shape=ellipse]; vn1 [label="h(t)", shape=ellipse]; vn2 [label="h(t+1)", shape=ellipse]; vx0 [label="x(t)", shape=cds]; vx1 [label="x(t+1)", shape=cds]; vx2 [label="x(t+2)", shape=cds]; vn0 -> vn1 [label=" W_h * mask_seq", color="#4263eb", style=dotted, fontcolor="#4263eb"]; vn1 -> vn2 [label=" W_h * mask_seq", color="#4263eb", style=dotted, fontcolor="#4263eb"]; vx0 -> vn0 [label=" W_x"]; vx1 -> vn1 [label=" W_x"]; vx2 -> vn2 [label=" W_x"]; {rank=same; vn0; vx0;} {rank=same; vn1; vx1;} {rank=same; vn2; vx2;} label_var [label="解决方案:序列中所有步骤使用相同掩码\n保持信息流。", shape=plaintext, fontcolor="#3b5bdb"]; } }比较了应用于循环连接的标准Dropout(有问题)和循环(变分)Dropout(优选)之间的差异。循环Dropout在给定序列的时间步中应用一致的掩码。这种一致性使得网络能够适当学习时间依赖性,同时仍能受益于Dropout的正则化效果。标准Dropout仍可应用于 非循环 连接,例如从输入 $x_t$ 到隐藏状态 $h_t$ 的连接,或从隐藏状态 $h_t$ 到输出层的连接,而不会引起同样的问题。框架中的实现TensorFlow(Keras API)和PyTorch等深度学习框架提供了在LSTM和GRU层中实现标准Dropout和循环Dropout的方便方式。TensorFlow/Keras: LSTM 和 GRU 层通常有两个单独的参数:dropout:指定输入连接(从 $x_t$ 到 $h_t$)的Dropout率。recurrent_dropout:指定循环连接(从 $h_{t-1}$ 到 $h_t$)的Dropout率,实现上述变分Dropout方法。# TensorFlow/Keras使用示例 import tensorflow as tf # 对输入应用20%的Dropout,对循环连接应用30%的循环Dropout lstm_layer = tf.keras.layers.LSTM( units=64, dropout=0.2, recurrent_dropout=0.3, return_sequences=True ) # 在模型中: model = tf.keras.Sequential([ tf.keras.layers.Embedding(input_dim=10000, output_dim=16, mask_zero=True), tf.keras.layers.LSTM(units=64, dropout=0.2, recurrent_dropout=0.3), tf.keras.layers.Dense(1, activation='sigmoid') ]) # Dropout在model.fit()期间自动应用,在model.predict()期间禁用PyTorch: PyTorch中的 LSTM 和 GRU 模块使用单个 dropout 参数。如果 num_layers 大于1,则此参数会将标准Dropout应用于每个层(除了最后一层)的输出。它 不会 像Keras的 recurrent_dropout 那样自动将变分Dropout应用于每个层内的循环连接。实现真正的变分Dropout通常需要自定义实现或第三方库,尽管在堆叠的RNN层之间使用标准Dropout是常见做法。# PyTorch使用示例(如果层堆叠,则在层间应用Dropout) import torch import torch.nn as nn # 如果num_layers > 1,Dropout应用于层间 lstm_layer = nn.LSTM( input_size=16, hidden_size=64, num_layers=2, # 设置 > 1 以在层间启用Dropout dropout=0.3, # 堆叠LSTM层之间的Dropout率 batch_first=True ) # model.train() 启用Dropout # model.eval() 禁用Dropout重要提示: Dropout应仅在 训练 阶段激活。在评估或预测(推理)期间,应使用完整的网络(不丢弃单元)。当你调用训练函数(如Keras中的 model.fit() 或PyTorch中的 model.train())与评估/预测函数(Keras中的 model.evaluate()、model.predict() 或PyTorch中的 model.eval())时,深度学习框架会自动处理此切换。选择Dropout率最佳的Dropout率(包括标准和循环Dropout,如果适用)是需要调整的超参数。常用值范围为0.1到0.5。从小值(例如0.1或0.2)开始,如果过拟合持续存在,则增加。监测验证集上的性能。如果验证性能开始下降而训练性能持续提高,则可能发生过拟合,此时增加Dropout(或应用其他正则化方法)可能有所帮助。如果训练和验证性能都很差或过早停滞,则Dropout率可能过高,阻碍了模型的学习能力(欠拟合)。其他正则化方法虽然Dropout在RNN中非常普遍,但也可以使用其他方法,有时会结合使用:权重正则化(L1/L2): 根据网络权重的量级向损失函数添加惩罚项(L1促使稀疏性,L2促使权重变小)。在Keras中,这些可以通过RNN层中的 kernel_regularizer、recurrent_regularizer 和 bias_regularizer 参数应用。早停: 在训练期间监测验证损失或指标,并在验证性能停止提高或开始下降时停止训练过程。这可以阻止模型在后续周期中继续过拟合训练数据。这种方法经常与其他正则化方法一起使用。在训练LSTM和GRU时,应用适当的正则化,特别是循环Dropout,是防止过拟合并提高其对新序列数据泛化能力的标准做法。调整Dropout率是本章前面讨论的超参数优化过程中的一个重要部分。