CNN模型训练与验证准确率提升过慢问题咨询(Olivetti数据集)
问题分析与解决方案
嘿,我来帮你梳理下这个问题!首先明确说:前500轮准确率几乎没提升绝对不正常,咱们从代码里找几个关键问题,一步步解决:
1. 最核心的问题:训练方式错误(全批量+无数据打乱)
你现在的训练逻辑是每次循环都把整个训练集一次性喂给模型,而且全程没有打乱数据。这种全批量梯度下降(Full-Batch GD)在小数据集上收敛极慢——梯度更新频率太低,且没有数据噪音帮助模型跳出局部最优。
另外,你定义了train_loader但完全没用到它,等于白做了分批处理的准备。
2. 优化器配置不合理
你用的是纯SGD,没有加momentum,学习率0.03在全批量场景下要么太大导致震荡,要么太小导致模型“不动”。SGD+Momentum能显著加速收敛,尤其是在损失平坦的区域。
3. 其他细节问题
- 没有切换训练/验证模式:训练时应该用
model.train(),验证时用model.eval()(虽然你的模型没有BatchNorm/Dropout,但养成好习惯很重要)。 - 验证时没关闭梯度计算:用
torch.no_grad()包裹验证代码,能大幅节省内存和计算时间。
修改后的代码示例
我把你的代码做了关键调整,你可以直接运行试试:
# -*- coding: utf-8 -*- #Libraries import torch import torch.nn.functional as F from torch import nn import numpy as np import matplotlib.pyplot as plt from torch.utils.data import TensorDataset, DataLoader from sklearn.datasets import fetch_olivetti_faces from sklearn.model_selection import train_test_split # 用sklearn划分数据集更简洁 # Olivetti dataset download olivetti = fetch_olivetti_faces() X = olivetti.images Y = olivetti.target print("\nDownload Ok") # 分层划分数据集,保证每个类的训练/验证比例一致 X_train, X_val, Y_train, Y_val = train_test_split(X, Y, test_size=0.2, stratify=Y, random_state=42) # 调整形状并转成Tensor:(样本数, 通道数, 高, 宽) X_train = torch.Tensor(X_train).unsqueeze(1) # 增加通道维度,变成(320,1,64,64) Y_train = torch.LongTensor(Y_train) X_val = torch.Tensor(X_val).unsqueeze(1) Y_val = torch.LongTensor(Y_val) # 绑定数据与标签,用DataLoader分批处理 batch_size = 16 train_dataset = TensorDataset(X_train, Y_train) train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) # 训练时强制打乱 val_dataset = TensorDataset(X_val, Y_val) val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False) class CNNModule(nn.Module): def __init__(self): super(CNNModule, self).__init__() self.conv1 = nn.Conv2d(1, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.fc1 = nn.Linear(16 * 13 * 13, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 40) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = x.view(-1, 16 * 13 * 13) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x def make_train(model, train_loader, val_loader, n_epochs, gpu): criterion = nn.CrossEntropyLoss() # 改用SGD+Momentum,收敛速度会快很多;也可以试试Adam优化器(注释里有写) optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9) # optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 记录训练过程 train_losses = [] train_accs = [] val_losses = [] val_accs = [] # GPU配置 device = torch.device("cuda" if gpu and torch.cuda.is_available() else "cpu") model.to(device) print(f"Using {device}") for epoch in range(n_epochs): # 训练阶段 model.train() train_loss = 0.0 train_correct = 0 total_train = 0 for batch_x, batch_y in train_loader: batch_x, batch_y = batch_x.to(device), batch_y.to(device) optimizer.zero_grad() # 先清零梯度,避免累积 outputs = model(batch_x) loss = criterion(outputs, batch_y) loss.backward() optimizer.step() # 累计损失与准确率 train_loss += loss.item() * batch_x.size(0) _, predicted = torch.max(outputs.data, 1) total_train += batch_y.size(0) train_correct += (predicted == batch_y).sum().item() avg_train_loss = train_loss / total_train avg_train_acc = train_correct / total_train train_losses.append(avg_train_loss) train_accs.append(avg_train_acc) # 验证阶段 model.eval() val_loss = 0.0 val_correct = 0 total_val = 0 with torch.no_grad(): # 关闭梯度计算,节省资源 for batch_x, batch_y in val_loader: batch_x, batch_y = batch_x.to(device), batch_y.to(device) outputs = model(batch_x) loss = criterion(outputs, batch_y) val_loss += loss.item() * batch_x.size(0) _, predicted = torch.max(outputs.data, 1) total_val += batch_y.size(0) val_correct += (predicted == batch_y).sum().item() avg_val_loss = val_loss / total_val avg_val_acc = val_correct / total_val val_losses.append(avg_val_loss) val_accs.append(avg_val_acc) # 每5个epoch打印一次结果 if (epoch + 1) % 5 == 0: print(f"Epoch --> {epoch+1}") print(f"Train Loss : {avg_train_loss:.4f}, Train Accuracy : {avg_train_acc:.4f}") print(f"Validation Loss : {avg_val_loss:.4f}, Validation Accuracy : {avg_val_acc:.4f}\n") # 绘制训练曲线 plt.figure(figsize=(12,8)) plt.subplot(2,2,1) plt.plot(np.arange(n_epochs), train_losses, 'r-', label='Train Loss') plt.legend() plt.title('Train Loss') plt.subplot(2,2,2) plt.plot(np.arange(n_epochs), train_accs, 'b--', label='Train Accuracy') plt.legend() plt.title('Train Accuracy') plt.subplot(2,2,3) plt.plot(np.arange(n_epochs), val_losses, 'r-', label='Val Loss') plt.legend() plt.title('Validation Loss') plt.subplot(2,2,4) plt.plot(np.arange(n_epochs), val_accs, 'b--', label='Val Accuracy') plt.legend() plt.title('Validation Accuracy') plt.show() # 初始化模型并开始训练 gpu = True model = CNNModule() make_train(model, train_loader, val_loader, n_epochs=50, gpu=gpu)
关键修改点说明
- 分批训练+数据打乱:用
TensorDataset绑定数据和标签,DataLoader设置shuffle=True,每次用小批量数据训练,梯度更新更频繁,数据噪音也能帮助模型更快跳出局部最优。 - 优化器升级:添加
momentum=0.9让SGD“带惯性”,或者直接用Adam优化器(对学习率容忍度更高,收敛速度更快)。 - 规范训练流程:加入
model.train()和model.eval()切换模式,用torch.no_grad()关闭验证阶段的梯度计算,避免不必要的资源消耗。 - 合理划分数据集:用
sklearn的分层划分,保证每个类的训练/验证样本比例一致,避免数据分布不均影响模型收敛。
预期效果
修改后,你应该能看到训练准确率在前10个epoch左右就开始明显提升,不会再出现前几百轮几乎不动的情况。如果还是收敛慢,可以试试微调学习率(SGD建议0.005-0.01,Adam建议0.001-0.0001),或者给模型添加BatchNorm层稳定训练过程。
内容的提问来源于stack exchange,提问作者Serdar ASARKAYA
相关产品推荐
相关产品推荐

