You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

机器学习中各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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 04:55:20