将Dropout和Early Stopping正则化技术集成到典型的PyTorch训练流程中,是对抗过拟合并提升模型泛化能力的实用方法。这涉及在馈网络训练过程中应用这些方法。设想我们已为一个分类任务训练了一个神经网络,也许与第5章中的MNIST数字分类器相似。在训练了多个周期后,我们可能会看到训练损失持续降低,但验证损失却开始上升。这种分歧是过拟合的一个典型表现:模型对训练数据(包括其中的噪声)学习得过于细致,并失去了对未见数据的泛化能力。Dropout和Early Stopping是应对此问题的有效手段。场景设置:一个基本网络与训练循环首先,我们使用PyTorch的nn.Module定义一个简单的全连接神经网络。该网络将作为我们添加正则化之前的基准。import torch import torch.nn as nn import torch.optim as optim # 假设数据加载器train_loader和val_loader已就绪 # 假设input_size、hidden_size、output_size已定义 class SimpleNet(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(SimpleNet, self).__init__() self.layer_1 = nn.Linear(input_size, hidden_size) self.relu = nn.ReLU() self.layer_2 = nn.Linear(hidden_size, output_size) # Softmax通常包含在损失函数中(nn.CrossEntropyLoss) def forward(self, x): x = self.layer_1(x) x = self.relu(x) x = self.layer_2(x) return x # 初始化基准模型 model_baseline = SimpleNet(input_size, hidden_size, output_size) criterion = nn.CrossEntropyLoss() # 示例损失函数 optimizer_baseline = optim.Adam(model_baseline.parameters(), lr=0.001) # 示例优化器我们的标准训练循环会遍历周期,执行前向和反向传播,并计算损失。我们还需要在每个周期内进行验证步骤,以监督未见数据上的表现。# 训练循环的基本结构(为简洁起见省略了细节) # def train_epoch(model, loader, criterion, optimizer): # model.train() # 将模型设置为训练模式 # # ... 训练步骤 ... # return average_training_loss # def validate_epoch(model, loader, criterion): # model.eval() # 将模型设置为评估模式 # # ... 验证步骤 ... # return average_validation_loss, average_validation_accuracy # num_epochs = 20 # for epoch in range(num_epochs): # train_loss = train_epoch(model_baseline, train_loader, criterion, optimizer_baseline) # val_loss, val_acc = validate_epoch(model_baseline, val_loader, criterion) # print(f"Epoch {epoch+1}/{num_epochs}, Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}") 运行此基准模型可能会出现前面描述的过拟合模式。实现Dropout为了加入Dropout,我们在模型定义中引入nn.Dropout层。常用做法是将其置于激活函数之后,尤其是在全连接层中。p参数指定元素被置零的概率。p的典型值在0.2到0.5之间。让我们修改SimpleNet以包含Dropout:class NetWithDropout(nn.Module): def __init__(self, input_size, hidden_size, output_size, dropout_prob=0.5): super(NetWithDropout, self).__init__() self.layer_1 = nn.Linear(input_size, hidden_size) self.relu = nn.ReLU() self.dropout = nn.Dropout(p=dropout_prob) # 添加了Dropout层 self.layer_2 = nn.Linear(hidden_size, output_size) def forward(self, x): x = self.layer_1(x) x = self.relu(x) x = self.dropout(x) # 在激活后应用dropout x = self.layer_2(x) return x # 初始化带Dropout的模型 model_dropout = NetWithDropout(input_size, hidden_size, output_size, dropout_prob=0.5) optimizer_dropout = optim.Adam(model_dropout.parameters(), lr=0.001)在训练阶段前使用model.train(),在验证或测试阶段前使用model.eval()是很要紧的。nn.Dropout在这些模式下的行为不同:model.train():Dropout处于活跃状态,随机将神经元置零。model.eval():Dropout处于非活跃状态。Dropout层之前的输出会按等于dropout概率p的因子缩小,以考虑评估时更多神经元活跃的事实。这种缩放确保了训练和评估之间,激活的预期总和大致保持一致。当调用model.eval()时,PyTorch会自动处理此缩放。然后,你将使用相同的训练循环结构来训练model_dropout。你应该会看到与基准模型相比,训练损失和验证损失之间的差距更小,这表明过拟合程度降低了。实现Early StoppingEarly Stopping监督验证表现(例如,验证损失),当此指标在连续指定数量的周期内停止提升时,便会停止训练,这个数量被称为“耐心”(patience)。这阻止模型继续训练进入过拟合状态。我们可以在训练循环中手动实现Early Stopping。我们需要变量来追踪迄今为止达到的最佳验证损失,以及自上次提升以来的周期数。我们还需要一个保存最佳模型状态的机制。import copy # 包含Early Stopping的训练循环 num_epochs = 50 # 允许更多周期,因为early stopping可能会更早停止训练 patience = 5 # 停止前等待提升的周期数 best_val_loss = float('inf') epochs_no_improve = 0 best_model_state = None # 初始化模型(可以是带Dropout或基准模型) model_early_stop = NetWithDropout(input_size, hidden_size, output_size, dropout_prob=0.5) # 带Dropout的示例 optimizer_early_stop = optim.Adam(model_early_stop.parameters(), lr=0.001) print("Starting training with Early Stopping...") for epoch in range(num_epochs): # 训练一个周期 model_early_stop.train() # 设置为训练模式 # 假设train_loader、criterion、optimizer_early_stop已定义 # ... (你一个周期的训练循环逻辑) ... avg_train_loss = calculate_average_train_loss() # 占位符 # 验证模型 model_early_stop.eval() # 设置为评估模式 current_val_loss = 0.0 current_val_acc = 0.0 with torch.no_grad(): # 验证时禁用梯度计算 # 假设val_loader已定义 # ... (你一个周期的验证循环逻辑) ... avg_val_loss = calculate_average_val_loss() # 占位符 avg_val_acc = calculate_average_val_accuracy() # 占位符 print(f"Epoch {epoch+1}/{num_epochs}, Train Loss: {avg_train_loss:.4f}, Val Loss: {avg_val_loss:.4f}, Val Acc: {avg_val_acc:.4f}") # Early Stopping检查 if avg_val_loss < best_val_loss: best_val_loss = avg_val_loss epochs_no_improve = 0 # 保存最佳模型状态 best_model_state = copy.deepcopy(model_early_stop.state_dict()) print(f"Validation loss improved to {best_val_loss:.4f}。保存模型状态。") else: epochs_no_improve += 1 print(f"Validation loss did not improve for {epochs_no_improve} epoch(s)。") if epochs_no_improve >= patience: print(f"Early stopping triggered after {epoch + 1} epochs。") # 停止前加载最佳模型状态 if best_model_state: model_early_stop.load_state_dict(best_model_state) print("已加载训练期间找到的最佳模型状态。") break # 退出训练循环 # 循环结束后,model_early_stop包含验证损失最佳周期时的权重 print("训练完成。")在这个修改后的循环中,我们在每个周期后检查验证损失。如果它有所提升,我们就重置计数器并保存模型的state dictionary。如果它没有提升,我们就增加计数器。一旦计数器达到patience限制,训练就会停止,我们会加载我们之前保存的最佳模型状态。可视化影响比较基准模型、带有Dropout的模型,以及带有Dropout并结合Early Stopping的模型的验证损失曲线,通常能清楚地显示其益处。{"layout": {"title": "Dropout和Early Stopping对验证损失的影响", "xaxis": {"title": "周期"}, "yaxis": {"title": "验证损失"}, "legend": {"title": "模型"}}, "data": [{"x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], "y": [0.60, 0.50, 0.42, 0.35, 0.30, 0.26, 0.23, 0.21, 0.22, 0.24, 0.27, 0.30, 0.33, 0.36, 0.39, 0.42, 0.45, 0.48, 0.51, 0.54], "mode": "lines", "name": "基准(无正则化)", "line": {"color": "#ff6b6b"}}, {"x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], "y": [0.62, 0.53, 0.45, 0.39, 0.34, 0.30, 0.27, 0.25, 0.23, 0.21, 0.20, 0.19, 0.195, 0.20, 0.21, 0.22, 0.23, 0.24, 0.25, 0.26], "mode": "lines", "name": "带Dropout", "line": {"color": "#228be6"}}, {"x": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "y": [0.62, 0.53, 0.45, 0.39, 0.34, 0.30, 0.27, 0.25, 0.23, 0.21, 0.20, 0.19, 0.195, 0.20, 0.21], "mode": "lines+markers", "name": "Dropout + Early Stopping", "line": {"color": "#12b886"}}]}各周期验证损失曲线的比较。基准模型在第8个周期后损失增加,显示出明显的过拟合。Dropout有助于保持较低的验证损失。Early Stopping在第15个周期左右停止训练,此时(带dropout的)验证损失不再有明显提升,这阻止了进一步不必要的训练,并可能选取一个更接近最佳验证表现点(第12个周期)的模型。该图表显示了基准模型的验证损失如何开始增加,表明过拟合。带有Dropout的模型显示出泛化能力的提升,更长时间地保持较低的验证损失。在Dropout之上应用Early Stopping,阻止了在验证表现趋于平稳或恶化后继续不必要的训练,节省了计算资源,并通过在验证损失最低点附近停止,可能获得一个在未见数据上表现更好的模型。通过运用Dropout和Early Stopping,你将获得实用的工具来构建更可靠的深度学习模型,使其能从训练数据更好地泛化到新的、未见示例。请记住监督训练和验证指标,以理解这些方法如何影响你模型的学习过程。