通过向神经网络添加 Dropout 层,可以直接应用这一正则化技术。使用像 PyTorch 这样的框架将 Dropout 集成到模型中是直接的。如何添加 nn.Dropout 层将得到演示,并讨论其对训练过程的影响。假设我们有一个用于分类任务的简单多层感知器 (MLP)。一个没有 Dropout 的可能架构可能如下所示:import torch import torch.nn as nn class SimpleMLP(nn.Module): def __init__(self, input_size, hidden_size1, hidden_size2, output_size): super(SimpleMLP, self).__init__() self.layer_1 = nn.Linear(input_size, hidden_size1) self.relu_1 = nn.ReLU() self.layer_2 = nn.Linear(hidden_size1, hidden_size2) self.relu_2 = nn.ReLU() self.output_layer = nn.Linear(hidden_size2, output_size) def forward(self, x): x = self.layer_1(x) x = self.relu_1(x) x = self.layer_2(x) x = self.relu_2(x) x = self.output_layer(x) return x # 示例实例化 # model_no_dropout = SimpleMLP(input_size=784, hidden_size1=256, hidden_size2=128, output_size=10) # print(model_no_dropout)这是一个标准的前馈网络。如果此模型容易在我们的数据集上过拟合,我们可以引入 Dropout 层。添加 Dropout 层PyTorch 中的 nn.Dropout 模块实现了 Dropout 技术。它接受 Dropout 概率 p 作为参数,即训练期间任何给定神经元输出被设置为零的概率。一种常见做法是将 Dropout 层放置在隐藏层的激活函数之后。以下是我们如何修改 SimpleMLP 以包含 Dropout 的方法:import torch import torch.nn as nn class MLPWithDropout(nn.Module): def __init__(self, input_size, hidden_size1, hidden_size2, output_size, dropout_prob=0.5): super(MLPWithDropout, self).__init__() self.layer_1 = nn.Linear(input_size, hidden_size1) self.relu_1 = nn.ReLU() # 在第一个隐藏层激活后应用 Dropout self.dropout_1 = nn.Dropout(p=dropout_prob) self.layer_2 = nn.Linear(hidden_size1, hidden_size2) self.relu_2 = nn.ReLU() # 在第二个隐藏层激活后应用 Dropout self.dropout_2 = nn.Dropout(p=dropout_prob) self.output_layer = nn.Linear(hidden_size2, output_size) def forward(self, x): x = self.layer_1(x) x = self.relu_1(x) x = self.dropout_1(x) # 应用 dropout x = self.layer_2(x) x = self.relu_2(x) x = self.dropout_2(x) # 应用 dropout x = self.output_layer(x) return x # 使用默认 Dropout 概率 0.5 的示例实例化 model_with_dropout = MLPWithDropout(input_size=784, hidden_size1=256, hidden_size2=128, output_size=10) print(model_with_dropout) # 或者指定不同的概率 # model_with_dropout_p25 = MLPWithDropout(input_size=784, hidden_size1=256, hidden_size2=128, output_size=10, dropout_prob=0.25) # print(model_with_dropout_p25) 在此修改后的版本中:我们向构造函数添加了一个 dropout_prob 参数,默认值为 0.5,这是一个常见的起始值。我们使用指定的概率实例化了 nn.Dropout 层(self.dropout_1、self.dropout_2)。在 forward 方法中,我们在隐藏层的 ReLU 激活之后立即应用这些 Dropout 层。请注意,Dropout 通常不应用于输出层。训练模式与评估模式使用 Dropout(以及批归一化等其他层)的一个重要方面是训练和评估阶段之间的区别。训练期间: 我们希望 Dropout 活跃,随机将神经元输出置零以防止共适应。评估/推断期间: 我们希望使用完整的网络能力。Dropout 应该停用,通常,Dropout 前一层的输出会按 $(1-p)$ 的因子进行缩放,以弥补训练期间有更多神经元活跃的事实(当使用 model.eval() 时,PyTorch 的 nn.Dropout 会自动处理此缩放,从而实现了“反向 Dropout”技术)。PyTorch 模型具有处理此行为的模式。您必须在它们之间显式切换:model.train():将模型设置为训练模式。Dropout 层处于活跃状态。model.eval():将模型设置为评估模式。Dropout 层处于非活跃状态,激活值会相应地进行缩放。以下是此在典型训练循环中的样子:# 假设模型、train_loader、val_loader、optimizer、criterion 已定义 num_epochs = 10 for epoch in range(num_epochs): # --- 训练阶段 --- model_with_dropout.train() # 将模型设置为训练模式 train_loss = 0.0 for data, target in train_loader: # 遍历训练批次 optimizer.zero_grad() output = model_with_dropout(data) loss = criterion(output, target) loss.backward() optimizer.step() train_loss += loss.item() * data.size(0) train_loss /= len(train_loader.dataset) print(f"Epoch {epoch+1} Training Loss: {train_loss:.4f}") # --- 验证阶段 --- model_with_dropout.eval() # 将模型设置为评估模式 val_loss = 0.0 with torch.no_grad(): # 禁用验证的梯度计算 for data, target in val_loader: # 遍历验证批次 output = model_with_dropout(data) loss = criterion(output, target) val_loss += loss.item() * data.size(0) val_loss /= len(val_loader.dataset) print(f"Epoch {epoch+1} Validation Loss: {val_loss:.4f}") # --- 在测试集上进行最终评估 --- # model_with_dropout.eval() # 确保模型处于评估模式 # with torch.no_grad(): # # 执行测试...在验证或测试期间忘记切换到 model.eval() 是一个常见错误。这会导致随机预测(由于 Dropout 活跃)和不正确的性能测量,因为输出没有正确缩放。可视化 Dropout 的效果Dropout 通常有助于弥合训练性能和验证/测试性能之间的差距,表明过拟合减少。虽然使用 Dropout 时训练损失可能略高或收敛较慢(因为网络在每次迭代中都会有效改变),但与没有 Dropout 且过拟合的模型相比,验证损失通常应该更低、更稳定。{ "layout": { "title": "Dropout 对学习曲线的影响", "xaxis": { "title": "训练轮次" }, "yaxis": { "title": "损失", "range": [0, 1.5] }, "legend": { "title": "图例" }, "autosize": true }, "data": [ { "x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "y": [1.2, 0.8, 0.6, 0.45, 0.35, 0.3, 0.25, 0.22, 0.2, 0.18], "mode": "lines+markers", "name": "训练损失(无 Dropout)", "line": { "color": "#74c0fc" } }, { "x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "y": [1.1, 0.9, 0.8, 0.7, 0.65, 0.6, 0.58, 0.56, 0.55, 0.54], "mode": "lines+markers", "name": "验证损失(无 Dropout)", "line": { "color": "#ff8787" } }, { "x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "y": [1.3, 0.95, 0.75, 0.6, 0.5, 0.45, 0.42, 0.4, 0.39, 0.38], "mode": "lines+markers", "name": "训练损失(Dropout p=0.5)", "line": { "color": "#1c7ed6" } }, { "x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], "y": [1.15, 0.92, 0.82, 0.75, 0.7, 0.68, 0.67, 0.66, 0.65, 0.64], "mode": "lines+markers", "name": "验证损失(Dropout p=0.5)", "line": { "color": "#f03e3e" } } ] }比较了有无 Dropout 的模型的训练和验证损失曲线。请注意,没有 Dropout 的模型验证损失开始增加(表明过拟合),而有 Dropout 的模型验证损失保持更低和更稳定,尽管训练损失略高。进一步实验本实践演示了基本实现。您现在可以尝试以下方面:Dropout 率 (p): 尝试不同的值(例如,0.2、0.3、0.5)。更高的值提供更强的正则化效果,但如果设置过高,可能会减慢收敛速度或导致欠拟合。位置: 虽然在全连接层激活后放置 Dropout 是常见的,但也可以尝试将其放置在激活之前或仅在特定层中。组合: 将 Dropout 与 L2 权重衰减等其他正则化技术结合使用,观察它们的联合效果。添加 Dropout 是您应对过拟合的有力工具。请记住正确使用 model.train() 和 model.eval(),以确保它在不同阶段表现出预期行为。