从零开始的模型训练(CIFAR10分类)
刚看完土堆的pytorch视频,做一个cifar10分类的模型,也算是作为系统学习pytorch的入门了吧~
本文的训练环境和超参数设置如下:
数据集:cifar10
设备:4060 8g
epoch:100
batch_size:256
优化器:SGD
学习率:余弦退火有序调整学习率
整体框架:
# 数据集
- datasets
- dataloader
# 模型 -> 仿照cifar10基础架构并融合vgg16搭建一个分类cifar10的网络结构
- model 单独写在model.py
# 训练
- optimizer
- loss crossentropy
- scheduler 学习率调整
# 数据分析/可视化
- tensorboard
- accuracy -> 测试集测试正确率
最初本来想仿照这个模型搭一个,但测试出来的效果并不好,所以后面采用resnet-18了
![[CIFAR10+VGG魔改.png]]
完整代码:
modelv3.py
import torch.nn as nn
import torch, torchvision
# 尝试一下对resnet-18 进行微调
class SmallResnet(nn.Module):
def __init__(self):
super(SmallResnet, self).__init__()
self.model = torchvision.models.resnet18(weights=None)
# 对应修改 resnet-18 # self.model.conv1 = nn.Conv2d(3,64, kernel_size=(7, 7), stride=(1, 1), padding=(3, 3), bias=False) # self.model.layer1[0].conv1 = nn.Conv2d(...)
self.model.conv1 = nn.Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
# 这里单独修改线性层 fc self.model.fc = nn.Sequential(nn.Linear(in_features=512, out_features=256, bias=True),
nn.ReLU(),
nn.Linear(in_features=256, out_features=10, bias=True))
# self.model.add_module("fc_add1", nn.Linear(in_features=256, out_features=10)) # 这样 fc_add1 会是 self.model 的单独模块,并不会自动接在 fc 后面运行,除非你改 forward 手动调用它:
def forward(self, x):
x = self.model.conv1(x)
# print("after con1 ->", x.shape)
x = self.model.bn1(x)
x = self.model.relu(x)
x = self.model.maxpool(x)
# print("after maxpool ->", x.shape)
x = self.model.layer1(x)
# print("after layer1 ->", x.shape)
x = self.model.layer2(x)
# print("after layer2 ->", x.shape)
x = self.model.layer3(x)
# print("after layer3 ->", x.shape)
x = self.model.layer4(x)
# print("after layer4 ->", x.shape)
x = self.model.avgpool(x)
# print("after avgpool ->", x.shape)
x = torch.flatten(x, 1)
# print("after flatten ->", x.shape)
x = self.model.fc(x)
# print("after fc ->", x.shape)
# print(self.model) return x
if __name__=="__main__":
model = SmallResnet()
input_ = torch.ones((64, 3, 32, 32))
res = model(input_)
print(res.shape)
train.py
# -*- coding: utf-8 -*-
# @Time : 2025/9/9 09:36
# @Author : RuiHcc
import torch.optim, torch
import torch.nn as nn
import torchvision.datasets
from torchvision import transforms
from torch.utils.data import DataLoader
from modelv3 import *
from torch.utils.tensorboard import SummaryWriter
# 数据增强transforms
train_transform = transforms.Compose([transforms.Resize(40),
transforms.RandomResizedCrop(32, scale=(0.64, 1.0), ratio=(1.0,1.0)),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.4914, 0.4822, 0.4465],
[0.2023, 0.1994, 0.2010])])
test_transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize([0.4914, 0.4822, 0.4465],
[0.2023, 0.1994, 0.2010])])
# ---- 数据集 ----train_dataset = torchvision.datasets.CIFAR10("../datasets", train=True,
transform=train_transform, download=True)
test_dataset = torchvision.datasets.CIFAR10("../datasets", train=False,
transform=test_transform, download=True)
train_dataloader = DataLoader(train_dataset, batch_size=256, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=256, shuffle=True)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# ---- 模型 ----# model = Mymodule()
model = SmallResnet()
model = model.to(device)
epochs = 100
learning_rate = 1e-1
# 加上了动量来避免训练陷入局部最优 一般为0.9
optimizer = torch.optim.SGD(params=model.parameters(), lr=learning_rate, momentum=0.9, weight_decay=1e-4)
# 学习率调度 -> 采用 CosineAnnealingLR(余弦退火调整学习率)
# scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.3)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
loss = nn.CrossEntropyLoss()
loss = loss.to(device)
train_steps = 0
test_steps = 0
# ---- tensorboard ----
writer = SummaryWriter("project_log6")
# 训练主函数:
if __name__=="__main__":
for epoch in range(epochs):
print("---- 当前为第{}轮训练 ----".format(epoch+1))
for data in train_dataloader:
imgs, targets = data
imgs = imgs.to(device)
targets = targets.to(device)
# break
output = model(imgs)
optimizer.zero_grad()
train_loss = loss(output, targets)
train_loss.backward() # 反向传播得到每一个参数节点的梯度
optimizer.step()
train_steps += 1
if train_steps%100 == 0:
print("训练次数为{}, 训练损失为{}".format(train_steps, train_loss.item()))
writer.add_scalar("train_loss", train_loss.item(), train_steps)
scheduler.step() # 更新一下学习率 每个epoch更新
print("当前学习率为:{}".format(scheduler.optimizer.param_groups[0]['lr']))
# 验证一下
total_test_loss = 0
correct_num = 0
with torch.no_grad():
for test_data in test_dataloader:
imgs_, targets_ = test_data
imgs_ = imgs_.to(device)
targets_ = targets_.to(device)
test_res = model(imgs_)
correct_num += (test_res.argmax(1)==targets_).sum() # 1:横向比较,==:True或False,sum:计算True或False个数
test_loss = loss(test_res, targets_) # tensor
total_test_loss += test_loss.item()
test_steps += 1
print("测试集上的损失为{}".format(total_test_loss))
print("准确率为{}".format(correct_num/len(test_dataset)))
# 准确率计算
accuracy = correct_num/len(test_dataset)
writer.add_scalar("test_loss", total_test_loss, test_steps)
writer.add_scalar("accuracy", accuracy, test_steps)
# 保存模型:
if accuracy>0.89:
torch.save(model, "model_{}.pth".format(epoch)) # 保存准确率高的模型
print("第{}轮的模型被保存".format(epoch))
# break # 终止训练
writer.close()
训练过程中的一些问题:
目前的问题是:1、在epoch=100的时候,图像分类的准确率达到最大值0.7;2、测试集上的损失在epoch=30时达到最低之后一直升高。且测试集损失在升高,但准确率却上升 --> 这通常意味着过拟合
1 如何提高准确率?
模型层面
- 更深的网络:现在用的是 3 层卷积 + 2 层全连接,相对浅。可以考虑:
- ResNet18(轻量且性能好)
- 或者在现有模型上增加卷积层数、BatchNorm、Dropout。当然resnet中也用到了batchnorm
- BatchNorm:在 Conv 层后加上
nn.BatchNorm2d,可以加速收敛并提升泛化。- 什么是BN?简单来说就是对一个batch(如64)里的不同样本在每个通道下的值/特征进行分布的统一化
- 下面写了一段代码来简单说明了一下 BN 的计算过程
![[batchnorm计算过程.png]]
class Mymodule(nn.Module):
def __init__(self):
super(Mymodule, self).__init__()
self.model = nn.BatchNorm2d(3)
def forward(self, x):
output = self.model(x)
return output
input_ = torch.tensor(list(range(1, 25)), dtype=torch.float32)
input_ = input_.reshape((2, 3, 2, 2))
print(input_)
model = Mymodule()
print(model(input_))
- Dropout:经典的防止过拟合方法。这里没有采用。
训练层面 - 学习率调度:余弦退火调整学习率
- 个人感觉
torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)的训练效果要好于torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.3) - 而且前者根据训练进度自动调整,而后者依赖于自己设置step,没有经验的话几乎无从下手
- 个人感觉
- 优化器:
- 这里用的随机梯度下降 SGD,会更快收敛。后续可以尝试下 Adam
- optimizer参数设置,后续调整了一些参数:参考博客:【深度学习】Pytorch实现CIFAR10图像分类任务测试集准确率达95%_cifar10准确率排名-CSDN博客
- Momentum
- 推荐值:0.9
- 适用于大多数深度学习任务
- 可以稍微调整(0.8 - 0.95)
- Weight Decay
- 推荐值:1e-4 到 5e-4
- 根据具体任务调整
- 过大会使模型欠拟合
- 过小无法有效正则化
- 采用 CosineAnnealingLR(余弦退火调整学习率)
- scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)- 优化器参数设置
- optimizer = torch.optim.SGD(params=model.parameters(), lr=learning_rate, momentum=0.9, weight_decay=1e-4) - 数据增强 -> transforms
- RandomCrop + Flip 几乎是 CIFAR-10 “标准增强”,非常有效
train_transform = transforms.Compose([transforms.Resize(40),
transforms.RandomResizedCrop(32, scale=(0.64, 1.0), ratio=(1.0,1.0)), # 随机缩放裁剪 32-裁剪后的大小 缩放比例0.64~1.0 ratio-长宽比不变
transforms.RandomHorizontalFlip(), # 随机水平翻转 p=0.5
transforms.ToTensor(),
transforms.Normalize([0.4914, 0.4822, 0.4465],
[0.2023, 0.1994, 0.2010])]) # mean and std
其中normalize中的mean和std是根据整个数据集计算得出的,一般常用的数据集的
CIFAR 10:
mean = [0.4914, 0.4822, 0.4465] std = [0.2023, 0.1994, 0.2010]
IMAGENET:
mean = [0.485, 0.456, 0.406] std = [0.229, 0.224, 0.225]
采用数据增强后的提升:差不多提升了10个点,但大致也就这样了。
![[使用transforms前后对比.png]]
最终效果:不到0.9准确率,但我感觉也差不多了,毕竟不是打比赛,哈哈哈
![[resnet_try2.png]]
reference:
小土堆视频,yyds!
PyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】_哔哩哔哩_bilibili
这个博主的笔记也让我收益良多,感谢感谢
【我是土堆 - PyTorch教程】学习随手记(已更新 | 已完结 | 10w字超详细版)_我是土堆csdn-CSDN博客
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)