机器学习中各Epoch误差的正常表现及PyTorch模型问题排查
PyTorch新手模型问题解答:误差趋势与代码修正
一、关于训练误差的核心问题
- 单个样本的误差没有“必须下降”的要求:单个样本的误差受样本自身特性、模型当前状态影响极大,出现波动甚至上下起伏都是正常现象,完全不能代表模型的整体训练效果。
- 训练集的平均误差才应该呈现逐渐下降的趋势:随着模型不断学习,整体预测能力提升,平均误差会先快速下降,后期逐渐趋于平稳。你改用平均误差后,才能正确判断训练是否正常。
二、代码中的关键问题与修正建议
1. 数据集处理问题
- 缺少验证集划分:当前代码只使用训练集,无法评估模型泛化能力,建议划分验证集,训练时同步监控验证集误差。
- DataLoader默认
batch_size=1:单样本训练(纯SGD)噪声大、收敛慢,建议设置合理的batch_size(如32、64)。 - 标准化复用问题:后续添加验证/测试集时,必须用训练集拟合的
StandardScaler做转换,避免数据泄露。
2. 模型与损失函数不匹配
- 任务是二分类(pima-indians-diabetes为二元分类任务),输出层用
sigmoid激活后,应搭配BCELoss(二元交叉熵损失),而非MSELoss。MSELoss更适合回归任务,对分类任务的优化效率远低于交叉熵损失。 - 隐藏层用
sigmoid激活易引发梯度消失,建议替换为ReLU激活函数,提升训练稳定性。
3. 训练循环的计算错误
- 原代码平均误差计算逻辑错误:
ll = loss.item()会每次覆盖数值,最终仅得到每个Epoch最后一个样本的误差。正确做法是累加所有样本的loss,再除以总样本数。 optimizer.zero_grad()位置不规范:应放在每个batch计算loss之前,避免梯度累积异常。
三、修正后的完整代码
修正后的dataset.py
import pandas as pd import torch from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split class DataSet: divide_rate = 0.8 file = './pima-indians-diabetes.csv' def __init__(self, is_train=True): dataframe = pd.read_csv(self.file) features = dataframe.loc[:, dataframe.columns != 'label'] labels = dataframe['label'] # 划分训练集和验证集 train_features, val_features, train_labels, val_labels = train_test_split( features, labels, train_size=self.divide_rate, random_state=42) sc = StandardScaler() train_features = sc.fit_transform(train_features) # 验证集用训练集的scaler转换 val_features = sc.transform(val_features) if is_train: self.features = torch.tensor(train_features, dtype=torch.float32) self.labels = torch.tensor(train_labels.values, dtype=torch.float32) else: self.features = torch.tensor(val_features, dtype=torch.float32) self.labels = torch.tensor(val_labels.values, dtype=torch.float32) def __getitem__(self, index): return self.features[index], self.labels[index] def __len__(self): return len(self.features)
修正后的主文件代码
import torch import torch.nn as nn import matplotlib.pyplot as plt import seaborn as sns from dataset import DataSet from torch.utils.data import DataLoader class NeuralNetwork(nn.Module): def __init__(self, input_dim=8, hidden_dim=4, output_dim=1): super().__init__() self.layers = nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), # 替换sigmoid为ReLU nn.Linear(hidden_dim, output_dim), nn.Sigmoid() ) def forward(self, x): return self.layers(x) # 初始化数据集和DataLoader train_dataset = DataSet(is_train=True) val_dataset = DataSet(is_train=False) train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) val_loader = DataLoader(val_dataset, batch_size=32) # 模型、损失、优化器 model = NeuralNetwork() loss_fn = nn.BCELoss() # 替换MSELoss为BCELoss optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # 调整学习率 sns.set(style="whitegrid") Epochs = 50 train_losses = [] val_losses = [] for epoch in range(Epochs): # 训练模式 model.train() total_train_loss = 0.0 for features, labels in train_loader: optimizer.zero_grad() # 放在batch开头 preds = model(features) loss = loss_fn(preds, labels.unsqueeze(1)) loss.backward() optimizer.step() total_train_loss += loss.item() * features.size(0) # 累加总loss # 计算训练平均误差 avg_train_loss = total_train_loss / len(train_dataset) train_losses.append(avg_train_loss) # 验证模式 model.eval() total_val_loss = 0.0 with torch.no_grad(): for features, labels in val_loader: preds = model(features) loss = loss_fn(preds, labels.unsqueeze(1)) total_val_loss += loss.item() * features.size(0) avg_val_loss = total_val_loss / len(val_dataset) val_losses.append(avg_val_loss) print(f"Epoch {epoch+1}: Train Loss = {avg_train_loss:.4f}, Val Loss = {avg_val_loss:.4f}") # 绘制误差曲线 plt.figure(figsize=(10,6)) sns.lineplot(x=range(Epochs), y=train_losses, color='green', label='Train Loss') sns.lineplot(x=range(Epochs), y=val_losses, color='red', label='Val Loss') plt.ylabel('Error') plt.xlabel('Epoch') plt.legend() plt.show()
四、额外建议
- 调整优化器:SGD可尝试从0.01开始调优,也可以改用Adam优化器,通常比SGD收敛更快。
- 监控过拟合:若训练集误差持续下降但验证集误差上升,说明出现过拟合,可添加Dropout层或减小模型规模。
- 多维度评估:除损失值外,可计算准确率、Precision、Recall等分类指标,更全面评估模型性能。
内容的提问来源于stack exchange,提问作者M a m a D
相关产品推荐
相关产品推荐

