深度学习------专题《图像处理项目》终!

目录

[一、模型参数可视化:像 Keras 一样 "看透" 每一层](#一、模型参数可视化:像 Keras 一样 “看透” 每一层)

[二、优化器与训练循环:细节里藏着 "收敛密码"](#二、优化器与训练循环:细节里藏着 “收敛密码”)

[1. 优化器:选 SGD 还是 Adam?](#1. 优化器:选 SGD 还是 Adam?)

[2. 训练循环:"梯度清零" 是重中之重](#2. 训练循环:“梯度清零” 是重中之重)

三、训练观察:从损失曲线看模型学习状态

[四、结尾:调参是个 "慢功夫",但每一步都有收获](#四、结尾:调参是个 “慢功夫”,但每一步都有收获)


给 CNN "做体检"!PyTorch 模型参数可视化 + 训练调优实战

今天接着折腾 CIFAR-10 图像分类的项目,发现 "训完模型就丢一边" 可不行 ------ 得知道模型每一层长啥样、参数有多少,训练时损失怎么变化,这样才能针对性调优。过程中我自己写了个 "参数统计工具",还摸清了优化器和训练循环的门道,分享一下~

一、模型参数可视化:像 Keras 一样 "看透" 每一层

之前用 PyTorch 搭 CNN,只能靠print(net)看个大概,层数一多,"每一层输入输出是啥、有多少参数、哪些能训练" 根本理不清。今天学着写了个函数,能像 Keras 的model.summary()一样,把模型每一层的细节都列得明明白白。

核心思路是用 "钩子(hook)" 记录前向传播时的层信息,代码逻辑(加了自己的理解注释)大概是这样:

python 复制代码
import collections
import torch
import torch.nn as nn

def params_summary(input_size, model):
    def register_hook(module):
        def hook(module, input, output):
            # 提取层的类名(比如Conv2d、Linear)
            class_name = str(module.__class__).split('.')[-1].split("'")[0]
            # 给每层编个号,方便区分
            module_idx = len(summary)
            m_key = f'{class_name}-{module_idx+1}'
            
            summary[m_key] = collections.OrderedDict()
            # 记录输入形状(批量大小设为-1,代表"任意批量")
            summary[m_key]['input_shape'] = list(input[0].size())
            summary[m_key]['input_shape'][0] = -1
            # 记录输出形状
            summary[m_key]['output_shape'] = list(output.size())
            summary[m_key]['output_shape'][0] = -1
            
            params = 0
            # 统计权重参数(层如果有weight,就计算它的参数数量)
            if hasattr(module, 'weight') and hasattr(module.weight, 'size'):
                params = torch.prod(torch.LongTensor(list(module.weight.size())))
                # 标记该层参数是否可训练
                summary[m_key]['trainable'] = module.weight.requires_grad
            # 统计偏置参数(层如果有bias,就加上bias的参数数量)
            if hasattr(module, 'bias') and hasattr(module.bias, 'size'):
                params += torch.prod(torch.LongTensor(list(module.bias.size())))
            summary[m_key]['params'] = params

        # 只对"非容器类"的层(比如Conv2d、Linear,不是Sequential、ModuleList)注册钩子
        if not isinstance(module, nn.Sequential) and \
           not isinstance(module, nn.ModuleList) and \
           not (module == model):
            hooks.append(module.register_forward_hook(hook))

    # 生成一个随机张量,用于触发前向传播(方便记录每层形状)
    if isinstance(input_size, tuple):
        x = torch.rand(1, *input_size)
    else:
        x = torch.rand(1, *input_size[0])

    summary = collections.OrderedDict()
    hooks = []
    # 给模型的每个层都注册"钩子"
    model.apply(register_hook)
    # 执行一次前向传播,让钩子记录信息
    model(x)
    # 用完钩子后移除,避免影响后续训练
    for h in hooks:
        h.remove()

    return summary

用这个函数看我之前搭的 CNN,能清晰看到每一层的细节:

  • 卷积层Conv2d-1:输入是(3, 32, 32)(CIFAR-10 的彩色图),输出是(16, 28, 28),参数有3×16×5×5 + 16 = 1216个;
  • 池化层MaxPool2d-2:不改变通道数,只把特征图尺寸缩到(16, 14, 14),没有可训练参数;
  • 最后全连接层Linear-5:输入是1296维(前面卷积、池化后展平的结果),输出是10类(对应 CIFAR-10 的 10 个类别),参数有1296×10 + 10 = 12970个。

这下对模型 "长啥样" 心里有底了,调参时也能更有针对性 ------ 比如知道哪层参数多,会不会容易过拟合。

二、优化器与训练循环:细节里藏着 "收敛密码"

训练模型的核心是 "优化器" 和 "训练循环",今天把这部分的细节扒得更透了。

1. 优化器:选 SGD 还是 Adam?

这次我用的是带动量的 SGD,代码如下:

python 复制代码
import torch.optim as optim

LR = 0.001
criterion = nn.CrossEntropyLoss()  # 分类任务常用交叉熵损失
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9)
  • lr=0.001:学习率是个 "玄学但关键" 的参数 ------ 太大,损失会 "上蹿下跳" 不收敛;太小,训练速度慢得像蜗牛;
  • momentum=0.9:动量的作用是让梯度更新更 "顺滑",避免在局部极小值附近来回震荡(想象成 "滚下坡", momentum 让下坡更流畅)。

之前我也试过把优化器换成 Adam(就是代码里注释掉的optimizer = optim.Adam(...)),发现 Adam 前期收敛更快,但后期 SGD 加动量的稳定性更好。所以像 CIFAR-10 这种小数据集,用 SGD + 动量,调好了学习率也很能打~

2. 训练循环:"梯度清零" 是重中之重

训练循环的模板代码大家都见过,但optimizer.zero_grad()这行极易被忽略,我就踩过坑:

python 复制代码
for epoch in range(10):  # 总共训练10轮
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # 取出数据,放到设备(CPU/GPU)上
        inputs, labels = data
        inputs, labels = inputs.to(device), labels.to(device)
        
        # 梯度清零!!!每次迭代都要清,否则梯度会累加
        optimizer.zero_grad()
        
        # 前向传播 → 计算损失 → 反向传播 → 更新参数
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        # 打印损失,观察训练趋势
        running_loss += loss.item()
        if i % 2000 == 1999:  # 每2000批打印一次平均损失
            print(f'[{epoch + 1}, {i + 1}] loss: {running_loss / 2000:.3f}')
            running_loss = 0.0

踩坑提醒 :有一次我忘了写optimizer.zero_grad(),结果损失值 "越训越大",模型直接发散了。原来 PyTorch 的梯度会自动累加,所以每次迭代必须手动清零!

三、训练观察:从损失曲线看模型学习状态

训完 10 轮,损失从最开始的2.100慢慢降到1.3左右,说明模型在 "学习",但离 "学好" 还有距离:

  • 损失下降的趋势是对的,但速度不算快(可能学习率还能调大一点,或者训练轮数再加多一些);
  • 如果损失降到一定程度后 "不动了",说明模型可能过拟合,或者学习率太小,卡在局部最优里出不来。

后续我打算试试这些优化方向:

  1. 把训练轮数加到 20 轮,看损失能不能继续下降;
  2. 用 TensorBoard 把损失曲线画出来,更直观地分析训练过程;
  3. 对模型结构做微调(比如加 BatchNorm 层,让训练更稳定)。

四、结尾:调参是个 "慢功夫",但每一步都有收获

今天最大的感受是:深度学习不是 "跑个代码等结果",而是要看懂模型、看懂训练过程。自己写参数可视化工具,能明白每一层的作用;盯着损失曲线调优化器和学习率,能摸清模型 "学习的节奏"。

接下来继续折腾,争取把 CIFAR-10 的准确率再提一提~如果有同样在调参的朋友,欢迎分享你们的 "稳准狠" 调参技巧~

相关推荐
铁手飞鹰2 小时前
从零复现论文:深度学习域适应1
linux·pytorch·python·深度学习·ubuntu·ai·迁移学习
Nautiluss3 小时前
WIN7下安装RTX3050 6GB显卡驱动
人工智能·驱动开发·opencv
wwww.bo3 小时前
深度学习(5)完整版
人工智能·深度学习
yourkin6664 小时前
什么是神经网络?
人工智能·深度学习·神经网络
嘀咕博客5 小时前
Frames:Runway推出的AI图像生成模型,提供前所未有的风格控制和视觉一致性
人工智能·ai工具
isNotNullX5 小时前
ETL详解:从核心流程到典型应用场景
大数据·数据仓库·人工智能·架构·etl
科技峰行者6 小时前
通义万相2.5系列模型发布,可生成音画同步视频
人工智能·阿里云·ai·大模型·agi
Vizio<6 小时前
《面向物理交互任务的触觉传感阵列仿真》2020AIM论文解读
论文阅读·人工智能·机器人·机器人触觉
尤超宇6 小时前
基于卷积神经网络的 CIFAR-10 图像分类实验报告
人工智能·分类·cnn