💡 问题引入:模型好坏只取决于网络结构吗?

在进行图像分类任务时,很多同学一上来就死磕模型结构,比如研究 ResNet 到底有几层、卷积核怎么卷。但在真实工程中,模型能不能训练好,往往不只取决于网络结构,更取决于数据增强、迁移学习、学习率控制和最佳模型保存等训练策略。

本文将以花卉分类任务(Flower-102)为例,带你走通一套基于 PyTorch 的通用训练流程。

1. 数据工程:从原始像素到张量

数据预处理阶段有两个关键点:数据增强(增加多样性)和标准化(加速收敛)。

1.1 数据增强(Data Augmentation)

针对小样本任务,通过 transforms 模拟不同拍摄形态:

  • ToTensor():将像素从 [0, 255] 压缩至 [0, 1] 的归一化过程。

  • Normalize():按照 ImageNet 的均值和标准差执行标准化(Standardization),使数据分布符合正态分布。

from torchvision import transforms, datasets
from torchvision.models import resnet18, ResNet18_Weights

# 训练集:让模型看到“同一类目标的各种变体”
train_transforms = transforms.Compose([
    transforms.Resize(128),
    transforms.RandomRotation(45),      # 模拟多角度拍摄
    transforms.RandomResizedCrop(96),   # 模拟遮挡与距离远近
    transforms.RandomHorizontalFlip(),  # 镜像增强
    transforms.ToTensor(),              # 归一化:[0, 255] -> [0, 1]
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 标准化
])

2. 迁移学习:分阶段权重控制

直接训练一个几十层的深层网络很难收敛。我们采取“先学后教”的策略。

2.1 阶段一:特征提取层冻结

我们保留 ResNet-18 的“大脑”(卷积特征提取层),只训练它的“嘴巴”(全连接分类层)。

import torch.nn as nn

# 使用新版 torchvision 写法代替 pretrained=True
model = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)

# 冻结特征提取层:requires_grad = False
for param in model.parameters():
    param.requires_grad = False

# 重构分类层
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, 102) 

2.2 阶段二:全量解冻与微调(Fine-tuning)

当分类层初步稳定后,解冻全量参数,并使用更小的学习率(如 1e-4)进行全局打磨。


3. 训练核心机制:保存“最强瞬间”

模型训练并不是 Epoch 越多越好。为了应对过拟合,我们必须在验证集上执行“实时监控”。

3.1 学习率调节器与权重备份

import copy
from torch.optim import lr_scheduler

# 每 7 个 Epoch 学习率衰减一次(高尔夫进洞前的减速过程)
exp_lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)

best_acc = 0.0
best_model_wts = copy.deepcopy(model.state_dict()) # 备份最佳模型权重

# 在训练循环中执行判断
if phase == 'val' and epoch_acc > best_acc:
    best_acc = epoch_acc
    best_model_wts = copy.deepcopy(model.state_dict())

4. 可视化测试:将 Tensor 还原为图像

模型识别完后输出的是标准化的张量,展示前必须还原。

import numpy as np
import matplotlib.pyplot as plt

def imshow(inp, title=None):
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    # 逆运算还原图像:X = X' * std + mean
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)

完整训练流程回顾(总结)

为了方便大家理解,我们将上述散乱的代码逻辑串联成一个完整的伪代码模板:

# 1. 准备阶段
load_pretrain_model() -> freeze_layers() -> define_new_fc()

# 2. 训练主循环
for epoch in range(num_epochs):
    # 训练阶段
    train_one_epoch() # 前向传播 -> 计算 Loss -> 反向传播 -> 更新权重
    
    # 验证阶段
    validate_model()  # 计算 Val Acc
    
    # 策略执行
    if current_acc > best_acc:
        save_best_model_weights() # 打捞当前最强权重
    
    scheduler.step() # 学习率按需衰减

# 3. 结束阶段
load_best_weights() -> test_and_visualize()

💡 本文与前文(架构解析篇)的核心差异

维度前期文章:ResNet 内部架构解析本次总结:模型训练与微调策略
关注点卷积层内部的数学、残差块原理预处理、调参、保存、微调的工程闭环
代码重点BasicBlock 类、1x1 卷积维度对齐weights 参数、requires_grad、StepLR
数据规模28x28 医学小图(验证网络逻辑)复杂背景真实花卉分类(工程落地)
最终目标解释模型为什么“深而不退化”掌握如何练出一款“高准确率模型”

结语:深度学习不只是关于网络层数的堆叠,更多的是关于如何通过精细的策略来引导数据流。希望这套分类任务的训练模板能让你在实际项目中事半功倍!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐