基于经典网络架构训练图像分类模型 代码学习

基于经典网络架构训练图像分类模型 代码学习

引用的库

import os
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import torch
from torch import nn
import torch.optim as optim
import torchvision
#pip install torchvision
from torchvision import transforms, models, datasets
#https://pytorch.org/docs/stable/torchvision/index.html
import imageio
import time
import warnings
warnings.filterwarnings("ignore")
import random
import sys
import copy
import json
from PIL import Image
  • os​:用于与操作系统交互,例如读取或写入文件、目录操作等。

  • sys​:提供对 Python 解释器相关的变量和函数的访问。例如可以用来退出程序或获取命令行参数。

  • random​:生成随机数或随机选择元素,常用于数据打乱、初始化等。

  • copy​:用于复制对象。例如深拷贝(copy.deepcopy()​)可以在不修改原始对象的情况下复制复杂结构。

  • json​:用于处理 JSON 数据格式。可以将数据转换为 JSON 字符串,或者从 JSON 字符串解析数据。

  • numpy as np​:提供高效的多维数组(ndarray​)以及各种数学运算支持,是机器学习和科学计算的基础库。

  • matplotlib.pyplot as plt​:用于绘制图表(如折线图、柱状图、热力图等)。

  • imageio​:用于读取和写入图像文件,支持多种格式(如 PNG、JPG 等)。

  • time​:用于获取当前时间、计时等。

  • torch​:PyTorch 是一个流行的深度学习框架,提供张量操作、自动求导、神经网络模块等功能。

  • torch.nn as nn​:包含构建神经网络所需的类和函数,如卷积层、全连接层、激活函数等。

  • torch.optim​:提供优化算法,如 SGD、Adam 等,用于训练模型时更新权重。

  • torchvision​:PyTorch 的视觉工具包,包含:常用数据集(如 CIFAR-10、ImageNet);预训练模型(如 ResNet、VGG);图像变换工具(如缩放、裁剪)。

  • torchvision.transforms​:提供图像预处理和增强的方法,如归一化、随机翻转、裁剪等。

  • torchvision.models​:提供预训练的深度学习模型(如 ResNet、AlexNet、VGG 等),可以直接使用或进行迁移学习。

  • torchvision.datasets​:提供常用视觉数据集的加载方法,简化数据读取过程。

  • PIL.Image​:PIL(Python Imaging Library)用于处理图像文件,支持多种图像格式和操作。


主要分类:

  • 数据处理os​, PIL.Image​, imageio​, torchvision.datasets​, transforms
  • 模型构建torch​, torch.nn​, torchvision.models
  • 训练和优化torch.optim​, time
  • 可视化matplotlib​, numpy
  • 辅助功能random​, copy​, json​, warnings

设置目录

data_dir = './flower_data/'
train_dir = data_dir + '/train'
valid_dir = data_dir + '/valid'

定义变量,+号主要用于字符串拼接。

valid_dir变量就是./flower_data//valid

制作数据源

data_transforms = {
    'train': 
        transforms.Compose([
        transforms.Resize([96, 96]),
        transforms.RandomRotation(45),#随机旋转,-45到45度之间随机选
        transforms.CenterCrop(64),#从中心开始裁剪
        transforms.RandomHorizontalFlip(p=0.5),#随机水平翻转 选择一个概率概率
        transforms.RandomVerticalFlip(p=0.5),#随机垂直翻转
        transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),#参数1为亮度,参数2为对比度,参数3为饱和度,参数4为色相
        transforms.RandomGrayscale(p=0.025),#概率转换成灰度率,3通道就是R=G=B
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])#均值,标准差
    ]),
    'valid': 
        transforms.Compose([
        transforms.Resize([64, 64]),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),

Python中的一些基本概念

1.字典
  • Python 中的一种数据结构,用 {}​ 表示。

  • 存储键值对(key-value pairs),例如:

    my_dict = {'name': 'Tom', 'age': 20}
    
  • 你可以通过键来访问对应的值:

    print(my_dict['name'])  # 输出: Tom
    
2. 列表(List)
  • 使用 []​ 包裹多个元素,如:

    my_list = [1, 2, 3]
    
  • 列表是有序的,可以通过索引访问元素。

3. 函数调用
  • transforms.RandomRotation(45)​ 是调用一个函数(或类)并传入参数。
  • transforms.Compose([...])​ 是将多个变换组合成一个流水线。

PyTorch 图像变换详解(transforms)

'train'​ 数据增强与预处理

这个集合用于训练数据,目的是增加数据多样性,提高模型泛化能力。

transforms.Compose([
    transforms.Resize([96, 96]),           # 缩放图像为 96x96
    transforms.RandomRotation(45),         # 随机旋转 -45~+45 度
    transforms.CenterCrop(64),             # 从中心裁剪出 64x64 大小
    transforms.RandomHorizontalFlip(p=0.5),# 以50%概率水平翻转
    transforms.RandomVerticalFlip(p=0.5),  # 以50%概率垂直翻转
    transforms.ColorJitter(...),           # 随机改变亮度、对比度等
    transforms.RandomGrayscale(p=0.025),   # 以2.5%概率转灰度图
    transforms.ToTensor(),                 # 转换为张量 (C x H x W),范围[0,1]
    transforms.Normalize(...)              # 标准化(均值、标准差)
])
🔁 顺序执行逻辑(非常重要!)

每张图像都会按照列表中的顺序依次经过这些变换。比如:

  1. 先缩放 →
  2. 再旋转 →
  3. 接着裁剪 →
  4. 然后可能随机翻转 →
  5. …直到最后标准化成模型可接受的数据格式。

'valid'​ 预处理(无增强)

验证集不需要做数据增强,所以变换更简单:

transforms.Compose([
    transforms.Resize([64, 64]),  # 缩放至 64x64
    transforms.ToTensor(),        # 转张量
    transforms.Normalize(...)     # 同样标准化
])

注意这里的尺寸是 64x64​,而训练集中间经历了 96x96 -> CenterCrop -> 64x64​。这样设计是为了保证训练和验证图像有相同的输入尺寸。

变换目的
Resize统一图像大小,便于批量处理
RandomRotation​, RandomFlip​, ColorJitter​, RandomGrayscale数据增强,防止过拟合
CenterCrop提取图像主体部分,减少背景干扰
ToTensor()将 PIL 图像转为 PyTorch 张量
Normalize归一化处理,加速模型收敛(使用 ImageNet 的均值/标准差)

batch_size = 128

image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'valid']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) for x in ['train', 'valid']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}
class_names = image_datasets['train'].classes

batch_size = 128
  • 定义每次训练或验证时输入模型的样本数量(即 batch size)。
  • 每次处理 128 张图像。
image_datasets = {
    x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x])
    for x in ['train', 'valid']
}
  • 使用 ImageFolder​ 加载数据集:

    • 自动从目录结构中读取类别;
    • 对每个图像应用之前定义的变换(如 resize、normalize 等);
    • x​ 是 'train'​ 和 'valid'​,表示训练和验证集;
  • 返回一个字典:{'train': Dataset对象, 'valid': Dataset对象}


dataloaders = {
    x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True)
    for x in ['train', 'valid']
}
  • 创建 DataLoader:

    • 将 Dataset 包装成可迭代的对象;
    • shuffle=True​ 表示在每个 epoch 开始前打乱训练数据,有助于提升泛化能力;
    • 每次返回一个 batch 的数据(图像 + 标签);
  • 同样返回一个字典:{'train': Dataloader, 'valid': Dataloader}


dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}
  • 获取训练集和验证集的样本数量;
  • 可用于计算训练进度、准确率比例等。

class_names = image_datasets['train'].classes
  • 获取所有类别名称;
  • ImageFolder​ 会自动根据文件夹名排序并映射为数字标签;
  • class_names​ 是一个列表,例如:['daisy', 'dandelion', 'roses', ...]

def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        for param in model.parameters():
            param.requires_grad = False

迁移学习

🔍 param.requires_grad = False​ 是什么意思?

在 PyTorch 中,每个模型参数(权重、偏置等)都有一个属性:requires_grad​,它决定了:

  • 是否需要计算该参数的梯度;
  • 是否会在反向传播中更新该参数。

默认情况下是 True​,即会计算梯度并更新。

当你设置为 False​ 后:

  • 不会计算梯度;
  • 不会在训练过程中更新;
  • 相当于“冻结”了这部分网络。
迁移学习做法

在迁移学习中,我们通常有两种做法:

✅ 方法 1:特征提取(Feature Extraction)
  • 使用预训练模型(如 ImageNet 上训练好的 ResNet);
  • 冻结前面所有的卷积层(不更新它们的参数);
  • 只训练最后几层(通常是全连接层)来适配新任务;
  • 适用于小数据集,节省时间和显存。
set_parameter_requires_grad(model, feature_extracting=True)
✅ 方法 2:微调(Fine-tuning)
  • 解冻所有层(或部分层);
  • 用较小的学习率重新训练整个模型;
  • 适用于大数据集,能更好地适应目标任务。
set_parameter_requires_grad(model, feature_extracting=False)

🛠️ 使用示例(配合预训练模型)
# 加载预训练的 ResNet18 模型
model = models.resnet18(pretrained=True)

# 设置冻结参数
set_parameter_requires_grad(model, feature_extracting=True)

# 替换最后一层全连接层
num_ftrs = model.fc.in_features
num_classes = len(class_names)  # 根据你的数据集类别数修改
model.fc = nn.Linear(num_ftrs, num_classes)

# 将模型移动到 GPU(如果可用)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
📊 查看哪些参数可训练(调试用)

如果你想看看当前模型中哪些参数是可以训练的,可以用下面这段代码:

for name, param in model.named_parameters():
    print(f"{name} requires_grad: {param.requires_grad}")

输出示例:

conv1.weight requires_grad: False
bn1.weight requires_grad: False
...
fc.weight requires_grad: True
fc.bias requires_grad: True

✅ 进阶用法:只冻结部分层

有时你想冻结前几层,但保留后面的层进行训练。例如:

for name, param in model.named_parameters():
    if 'layer4' not in name and 'fc' not in name:
        param.requires_grad = False

这样就只训练 layer4​ 和 fc​ 层。


🧠 总结

操作说明
requires_grad = False冻结参数,不参与训练
requires_grad = True参与训练
set_parameter_requires_grad(model, True)冻结整个模型参数
set_parameter_requires_grad(model, False)所有参数都可训练


关于params_to_update

🎯 场景设定

假设你使用的是 ResNet18 模型,并做了以下操作:

  1. 冻结前面所有卷积层(不更新它们);
  2. 只替换最后一层全连接层(fc​),并设置它为可训练;
  3. 然后通过遍历模型参数,收集所有 requires_grad == True​ 的参数到 params_to_update​ 列表中。

✅ 示例代码

import torch
import torch.nn as nn
import torchvision.models as models

# 1. 加载预训练 ResNet18 模型
model = models.resnet18(pretrained=True)

# 2. 冻结所有参数
for param in model.parameters():
    param.requires_grad = False

# 3. 替换最后的全连接层(输出类别数改为 10)
model.fc = nn.Linear(512, 10)
model.fc.requires_grad = True  # 设置为可训练

# 4. 收集所有需要更新的参数
params_to_update = []
for name, param in model.named_parameters():
    if param.requires_grad:
        params_to_update.append(param)
        print(f"参数名: {name} | 参数形状: {param.shape}")

📋 输出结果示例(模拟)

运行上面这段代码,你可能会看到类似如下输出:

参数名: fc.weight | 参数形状: torch.Size([10, 512])
参数名: fc.bias   | 参数形状: torch.Size([10])

🧠 解释这个输出

  • params_to_update​ 是一个 Python 列表,里面包含两个元素:

    • 第一个元素是 fc.weight​:全连接层的权重矩阵,形状是 [10, 512]​,表示输出 10 类,输入来自 512 个特征;
    • 第二个元素是 fc.bias​:偏置项,长度是 10​,对应每个类别的偏置。

这两个参数就是你希望在训练过程中被优化器更新的部分。


🔍 打印 params_to_update​ 列表内容(模拟)

如果你打印 params_to_update​,你会看到类似这样的内容(不是字符串,而是 tensor 对象):

[
    Parameter containing:
    tensor([[ 0.01, -0.02, ...], ..., requires_grad=True),  # fc.weight

    Parameter containing:
    tensor([0.1, -0.1, ...], requires_grad=True)            # fc.bias
]

它们都是 torch.nn.Parameter​ 类型,是 PyTorch 中专门用于自动求导和优化的张量类型。


✅ 传给优化器的样子

你可以这样使用它:

optimizer = torch.optim.Adam(params_to_update, lr=0.001)

这时优化器只会更新 fc.weight​ 和 fc.bias​,而不会动前面的所有卷积层。


🧩 总结一下

内容说明
params_to_update​ 是什么?一个包含所有“可训练参数”的列表
它里面的元素是什么?torch.nn.Parameter​ 类型,也就是模型中的权重和偏置
举例说明?包含 fc.weight​ 和 fc.bias​,即最后的全连接层参数
有什么用?告诉优化器哪些参数要更新,在迁移学习中非常有用

训练模型

def train_model(model, dataloaders, criterion, optimizer, num_epochs=25, filename='best.pt'):

参数说明:

参数名类型作用
modelPyTorch 模型要训练的神经网络模型(如 ResNet)
dataloaders字典 { 'train': DataLoader, 'valid': DataLoader }数据加载器
criterion损失函数nn.CrossEntropyLoss()
optimizer优化器optim.Adam()​ 或 optim.SGD()
num_epochs整数训练多少轮
filename字符串保存最佳模型的路径

🧠 初始化变量

since = time.time()
best_acc = 0
model.to(device)
  • since​: 开始计时,记录训练总时间;
  • best_acc​: 记录验证集上最好的准确率;
  • model.to(device)​: 把模型放到 GPU 或 CPU 上运行。

📊 训练过程中的指标记录

val_acc_history = []
train_acc_history = []
train_losses = []
valid_losses = []
LRs = [optimizer.param_groups[0]['lr']]
best_model_wts = copy.deepcopy(model.state_dict())

这些列表用于记录训练过程中的关键信息:

变量作用
val_acc_history验证集准确率历史
train_acc_history训练集准确率历史
train_losses训练损失历史
valid_losses验证损失历史
LRs学习率变化历史
best_model_wts最佳模型参数快照(用于最后恢复)

🔁 主训练循环:按 Epoch 运行

for epoch in range(num_epochs):
    print('Epoch {}/{}'.format(epoch, num_epochs - 1))
    print('-' * 10)
  • 控制训练的总轮数;
  • 每个 epoch 打印当前进度。

🔄 训练 + 验证阶段

for phase in ['train', 'valid']:
    if phase == 'train':
        model.train()
    else:
        model.eval()
  • 每个 epoch 中都会先训练一遍,再验证一遍;
  • model.train()​:启用 BatchNorm 和 Dropout;
  • model.eval()​:关闭这些层,用于评估。

🚀 数据遍历 + 前向传播 + 反向传播

for inputs, labels in dataloaders[phase]:
    inputs = inputs.to(device)
    labels = labels.to(device)

    optimizer.zero_grad()

    with torch.set_grad_enabled(phase == 'train'):
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        _, preds = torch.max(outputs, 1)

    if phase == 'train':
        loss.backward()
        optimizer.step()
  • inputs​ 和 labels​ 是一批图像和标签;
  • zero_grad()​:清空梯度;
  • with torch.set_grad_enabled(...)​:控制是否计算梯度;
  • loss.backward()​ 和 optimizer.step()​:只在训练阶段进行反向传播;
  • preds​:预测结果(最大概率对应的类别);

📈 统计本轮 Loss 和 Accuracy

running_loss += loss.item() * inputs.size(0)
running_corrects += torch.sum(preds == labels.data)

epoch_loss = running_loss / len(dataloaders[phase].dataset)
epoch_acc = running_corrects.double() / len(dataloaders[phase].dataset)
  • loss.item()​:取出标量值;
  • inputs.size(0)​:batch size;
  • torch.sum(preds == labels.data)​:统计正确预测的数量;
  • epoch_loss​ 和 epoch_acc​:本轮平均损失和准确率。

💾 保存最佳模型

if phase == 'valid' and epoch_acc > best_acc:
    best_acc = epoch_acc
    best_model_wts = copy.deepcopy(model.state_dict())
    state = {
        'state_dict': model.state_dict(),
        'best_acc': best_acc,
        'optimizer': optimizer.state_dict(),
    }
    torch.save(state, filename)
  • 如果验证准确率比之前高,就更新最佳模型;
  • 使用 copy.deepcopy()​ 防止引用被修改;
  • torch.save(state, filename)​:保存为 .pt​ 文件,包含模型权重、最优准确率、优化器状态。

📤 记录训练过程数据

if phase == 'valid':
    val_acc_history.append(epoch_acc)
    valid_losses.append(epoch_loss)
if phase == 'train':
    train_acc_history.append(epoch_acc)
    train_losses.append(epoch_loss)
  • 把每个 epoch 的训练和验证指标保存下来,方便后续可视化。

📉 学习率调度器(可选)

print('Optimizer learning rate : {:.7f}'.format(optimizer.param_groups[0]['lr']))
LRs.append(optimizer.param_groups[0]['lr'])
scheduler.step()  # 学习率衰减
  • 输出当前学习率;
  • 添加到 LRs​ 列表中;
  • scheduler.step()​:使用学习率调度器(如 StepLR​, ReduceLROnPlateau​)进行学习率衰减。

⏱️ 打印训练耗时

time_elapsed = time.time() - since
print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
print('Best val Acc: {:4f}'.format(best_acc))
  • 显示训练总共花了多少时间;
  • 输出验证集上的最好准确率。

🔄 返回最终模型和训练记录

model.load_state_dict(best_model_wts)
return model, val_acc_history, train_acc_history, valid_losses, train_losses, LRs
  • 加载最佳模型参数;
  • 返回模型对象和所有训练记录(可用于画图、分析等)。

🧪 示例调用方式

model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs = train_model(
    model=model_ft,
    dataloaders=dataloaders,
    criterion=criterion,
    optimizer=optimizer_ft,
    num_epochs=10,
    filename='best_model.pth'
)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值