刚看完土堆的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,没有经验的话几乎无从下手
  • 优化器
    - 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博客

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐