正如我们所讨论的,仅仅衡量神经网络在训练数据上的表现并不能说明全部情况。一个模型可能在其训练样本上达到非常低的误差,但当面对新的、未见过的数据时却表现糟糕。这种差异突出了学习与记忆之间的区别。我们需要一种方法来评估模型在开发过程中实际表现如何,而无需接触最终测试数据。这正是验证集的作用。训练期间评估泛化能力核心思路很简单:在训练开始前,您会留出部分数据集,这部分数据在训练过程中模型绝不会接触到(这意味着,模型的梯度将不会基于这些数据计算)。这部分预留的数据就是您的验证集。其余部分则用于模型训练(即训练集)。通常,您可以这样划分数据:训练集: 用于计算损失,并通过反向传播和梯度下降更新网络的权重和偏差。模型直接从这些数据中学习。(例如,数据量的60-80%)验证集: 在训练期间(例如,每个周期后)定期使用,以评估模型在未训练数据上的表现(损失、准确率等)。此评估有助于监测泛化能力,并就训练过程本身做出决策。(例如,数据量的10-20%)测试集: 在所有训练和模型选择完成后只使用一次,以获得模型在真正未见过数据上表现的最终、无偏估计。(例如,数据量的10-20%)区分验证集和测试集很重要。可以将验证集看作您的彩排或模拟考试。您用它来微调方法(调整超参数,决定何时停止训练)。测试集是期末考试,只进行一次,以了解您真实的学习效果。在开发过程中反复使用测试集,就好像提前研究期末考试题目一样;您的最终分数将无法准确反映您的知识水平。验证集如何指导训练验证集在开发过程中主要有两个用途:监测过拟合: 通过跟踪每个周期后训练集和验证集上的性能指标(如损失或准确率),您可以观察模型泛化能力如何。最初,训练损失和验证损失通常都会下降。如果模型开始过拟合,训练损失将继续下降(因为模型记住了训练数据),但验证损失将趋于平稳或开始上升。这种背离是一个清晰的迹象,表明模型正在丧失其泛化能力。{"layout": {"title": "训练损失 vs. 验证损失", "xaxis": {"title": "周期数"}, "yaxis": {"title": "损失"}, "legend": {"traceorder": "normal"}, "template": "plotly_white", "autosize": true, "height": 350}, "data": [{"x": [1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50], "y": [1.5, 1.1, 0.8, 0.6, 0.45, 0.35, 0.3, 0.26, 0.23, 0.21, 0.19], "mode": "lines+markers", "name": "训练损失", "line": {"color": "#228be6"}}, {"x": [1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50], "y": [1.6, 1.2, 0.9, 0.75, 0.65, 0.6, 0.58, 0.59, 0.61, 0.64, 0.68], "mode": "lines+markers", "name": "验证损失", "line": {"color": "#fd7e14"}}]}训练损失持续下降,而验证损失在大约第30个周期开始上升,这表明过拟合开始发生。指导超参数调整和模型选择: 如何选择最佳学习率?您的网络应有多少层或神经元?哪种正则化技术效果最好?验证集提供了回答这些问题所需的客观衡量标准。您可以使用不同的超参数设置或架构训练模型,并比较它们在验证集上的表现。通常,在对测试集进行最终评估之前,会选择在验证集上表现最佳的配置作为首选模型。实际操作在训练循环中加入验证检查相对简单。在每个周期后(或有时更频繁),您会使用验证数据进行一次前向传播,计算损失和任何其他相关指标(如准确率),但非常重要的是,您不会基于这些验证数据执行反向传播或更新权重。以下是一个类似 Python 的伪代码片段,说明了此过程:# 假设数据集已划分为 train_data, validation_data # 假设 model, optimizer, loss_function 已定义 for epoch in range(num_epochs): # --- 训练阶段 --- model.train() # 将模型设置为训练模式(与 Dropout 等层相关) total_train_loss = 0 for batch in train_data: inputs, targets = batch optimizer.zero_grad() # 重置梯度 outputs = model(inputs) # 前向传播 loss = loss_function(outputs, targets) # 计算损失 loss.backward() # 反向传播 optimizer.step() # 更新权重 total_train_loss += loss.item() avg_train_loss = total_train_loss / len(train_data) print(f"Epoch {epoch+1}: Training Loss = {avg_train_loss}") # --- 验证阶段 --- model.eval() # 将模型设置为评估模式 total_val_loss = 0 with torch.no_grad(): # 禁用验证的梯度计算 for batch in validation_data: inputs, targets = batch outputs = model(inputs) # 仅前向传播 loss = loss_function(outputs, targets) total_val_loss += loss.item() # 如果需要,计算准确率等其他指标 avg_val_loss = total_val_loss / len(validation_data) print(f"Epoch {epoch+1}: Validation Loss = {avg_val_loss}") # --- 检查点 / 早停逻辑(使用 avg_val_loss)--- # (更多内容请参阅早停部分) # 如果验证损失有所改善,则保存模型等。 # --- 最终评估(在训练循环之后)--- # 根据验证表现加载最佳模型 # 在 test_data 上进行评估(该数据之前从未被使用)因此,验证集是构建高效机器学习模型的迭代过程中不可或缺的一部分。它提供了指导训练、防止过拟合以及选择最有可能在新、未见过数据上表现良好的模型配置所需的反馈。没有它,您将如同盲人摸象,不确定模型在训练数据上的改进是否真的能转化为有意义的泛化能力。