当前位置: 首页 > news >正文

迁移学习实战:用预训练模型做图像分类

摘要:在第六篇文章中,我们从零搭建了一个 CNN 训练 CIFAR-10,达到了约 84% 的准确率。但如果用上"迁移学习"——把别人在大规模数据集上训练好的模型拿来微调——只需要几行代码改动,就能把准确率提升到 95% 以上。这篇文章讲清楚迁移学习为什么有效,并给出完整的实战代码。


一、什么是迁移学习?

核心思想

迁移学习就是站在巨人的肩膀上

传统训练(从零开始): 随机初始化 → 在目标数据集上训练 → 需要大量数据和算力 迁移学习: ImageNet 预训练模型(已学会通用特征) → 在目标数据集上微调(只需少量数据) → 效果好、训练快

为什么有效?

深度学习模型在训练过程中学到了层次化的特征

预训练模型已经学会的: 第 1 层:检测边缘、纹理、颜色块 ← 通用,所有图像任务通用 第 2 层:检测形状、图案 ← 通用,所有图像任务通用 第 3 层:检测部件(眼睛、轮子) ← 较通用,多数任务有用 第 4-5 层:检测具体物体(人脸、汽车)← 特定任务,需要微调 我们只需要: 保留第 1-3 层(通用特征提取器) 替换第 4-5 层(适应我们的具体任务) 用我们的数据微调

迁移学习 vs 从零训练

对比从零训练迁移学习
所需数据量需要大量数据(数万张)少量数据即可(几百张)
训练时间长(数小时到数天)短(数分钟到数小时)
GPU 需求
最终准确率取决于数据量通常更高
代码复杂度中等低(torchvision 几行加载)

二、准备工作:加载预训练模型

PyTorch 的torchvision.models提供了丰富的预训练模型,一行代码即可加载。

import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoader import matplotlib.pyplot as plt import numpy as np device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"Using: {device}")

支持的预训练模型

# torchvision 提供的预训练模型(2026 年) models = [ "resnet18", "resnet50", "resnet101", "vgg16", "vgg19", "densenet121", "densenet169", "efficientnet_b0", "efficientnet_b3", "efficientnet_b7", "vit_b_16", "vit_l_32", # Vision Transformer "convnext_tiny", "convnext_base", "swin_t", "swin_b", # Swin Transformer "maxvit_t", # MaxViT ]

加载 ResNet-18 预训练模型

# ===== 加载预训练模型 ===== model = torchvision.models.resnet18(weights='IMAGENET1K_V1') # weights='IMAGENET1K_V1' = 在 ImageNet(1000 类、1400 万张图)上训练好的权重 print(model) # ResNet( # (conv1): Conv2d(3, 64, kernel_size=7, stride=2, padding=3) # (bn1): BatchNorm2d(64) # (relu): ReLU() # (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1) # (layer1): Sequential(...) ← 4 个残差块,64 通道 # (layer2): Sequential(...) ← 4 个残差块,128 通道 # (layer3): Sequential(...) ← 4 个残差块,256 通道 # (layer4): Sequential(...) ← 4 个残差块,512 通道 # (avgpool): AdaptiveAvgPool2d((1, 1)) # (fc): Linear(512, 1000) ← ImageNet 的 1000 分类头 # )

理解预训练模型的架构

ResNet-18 结构: 输入 (3×224×224) │ conv1 (7×7, stride=2) 输出: 64×112×112 │ batch_norm + ReLU + maxpool │ layer1 (2 个残差块, 64 通道) 输出: 64×56×56 ← 检测基础特征 │ layer2 (2 个残差块, 128 通道) 输出: 128×28×28 ← 检测纹理 │ layer3 (2 个残差块, 256 通道) 输出: 256×14×14 ← 检测部件 │ layer4 (2 个残差块, 512 通道) 输出: 512×7×7 ← 检测高级语义 │ avgpool 输出: 512 │ fc (全连接层) 输出: 1000(ImageNet 分类)

三、迁移学习的两种策略

策略 1:特征提取(冻结骨干网络)

只替换分类头,冻结所有卷积层——适合小数据集

def freeze_feature_extractor(model): """冻结所有卷积层(不计算梯度,不更新参数)""" for param in model.parameters(): param.requires_grad = False # 1. 加载预训练模型 model = torchvision.models.resnet18(weights='IMAGENET1K_V1') # 2. 冻结所有层 freeze_feature_extractor(model) # 3. 替换分类头(适应我们的任务) num_classes = 10 # 以 CIFAR-10 为例 model.fc = nn.Linear(512, num_classes) # 只有分类头的参数需要梯度 trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) frozen_params = sum(p.numel() for p in model.parameters() if not p.requires_grad) print(f"可训练: {trainable_params:,} | 已冻结: {frozen_params:,}") # 可训练: 5,130 | 已冻结: 11,176,512 # → 只需要训练 5000 个参数,其他 1100 万参数不动!

策略 2:微调(全模型参与)

所有层都参与训练,但对不同层使用不同的学习率——适合中等规模数据集

def fine_tune_setup(model, num_classes, lr_backbone=1e-5, lr_head=1e-3): """ 微调设置: - 骨干网络:小学习率(1e-5)——在预训练基础上微调 - 分类头:大学习率(1e-3)——从头学习 """ # 1. 替换分类头 in_features = model.fc.in_features model.fc = nn.Linear(in_features, num_classes) # 2. 为不同层设置不同学习率 backbone_params = [] head_params = [] for name, param in model.named_parameters(): if 'fc' in name: head_params.append(param) else: backbone_params.append(param) optimizer = optim.AdamW([ {'params': backbone_params, 'lr': lr_backbone}, # 骨干:小学习率 {'params': head_params, 'lr': lr_head}, # 分类头:大学习率 ]) return model, optimizer # 使用 model = torchvision.models.resnet18(weights='IMAGENET1K_V1') model, optimizer = fine_tune_setup(model, num_classes=10)

两种策略的选型指南

条件推荐策略原因
数据量 < 1000 张特征提取(冻结骨干)数据太少,微调容易过拟合
数据量 1000-10000 张微调(小学习率)足够数据调整,但不宜变动过大
数据量 > 10000 张微调(正常学习率)数据充足,可以较大幅调整
目标任务与 ImageNet 差异大微调(解冻更多层)医学影像、卫星图等特殊领域

四、完整实战:用 ResNet18 微调 CIFAR-10

数据准备

# ===== 数据预处理 ===== # 注意:预训练模型要求特定的归一化参数 transform_train = transforms.Compose([ transforms.Resize(224), # ResNet 要求 224×224 transforms.RandomHorizontalFlip(), transforms.RandomCrop(224, padding=28), # 大尺寸裁剪增强 transforms.ToTensor(), transforms.Normalize( # ImageNet 的归一化参数 mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ), ]) transform_test = transforms.Compose([ transforms.Resize(224), transforms.ToTensor(), transforms.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ), ]) train_dataset = torchvision.datasets.CIFAR10( root='./data', train=True, download=True, transform=transform_train ) test_dataset = torchvision.datasets.CIFAR10( root='./data', train=False, download=True, transform=transform_test ) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4) test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False, num_workers=4)

构建模型

# ===== 模型:特征提取策略 ===== model = torchvision.models.resnet18(weights='IMAGENET1K_V1') # 冻结所有层 for param in model.parameters(): param.requires_grad = False # 替换分类头 model.fc = nn.Sequential( nn.Dropout(0.3), nn.Linear(512, 256), nn.ReLU(), nn.Dropout(0.2), nn.Linear(256, 10), ) model = model.to(device) # 只有新加的层需要梯度 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.fc.parameters(), lr=0.001) total_params = sum(p.numel() for p in model.parameters()) trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) print(f"总参数量: {total_params:,} | 可训练: {trainable_params:,}") # 总参数量: 11,180,234 | 可训练: 131,850 # → 参数量是之前 CNN 的 10 倍,但只训练其中 1%

训练循环(复用第 06 篇的模板)

def train_epoch(model, loader, criterion, optimizer, device): model.train() running_loss = 0.0 correct = 0 total = 0 for inputs, labels in loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() _, predicted = outputs.max(1) total += labels.size(0) correct += predicted.eq(labels).sum().item() return running_loss / len(loader), 100.0 * correct / total @torch.no_grad() def evaluate(model, loader, criterion, device): model.eval() running_loss = 0.0 correct = 0 total = 0 for inputs, labels in loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) loss = criterion(outputs, labels) running_loss += loss.item() _, predicted = outputs.max(1) total += labels.size(0) correct += predicted.eq(labels).sum().item() return running_loss / len(loader), 100.0 * correct / total

执行训练

# ===== 执行训练 ===== num_epochs = 20 best_acc = 0.0 for epoch in range(1, num_epochs + 1): train_loss, train_acc = train_epoch( model, train_loader, criterion, optimizer, device ) test_loss, test_acc = evaluate( model, test_loader, criterion, device ) if test_acc > best_acc: best_acc = test_acc torch.save(model.state_dict(), 'resnet18_cifar10.pth') if epoch % 2 == 0 or epoch == 1: print(f"Epoch {epoch:2d} | " f"Train Loss={train_loss:.3f} Acc={train_acc:.2f}% | " f"Test Loss={test_loss:.3f} Acc={test_acc:.2f}%") print(f"\n✅ 最佳测试准确率: {best_acc:.2f}%")

输出示例

Epoch 1 | Train Loss=1.113 Acc=66.42% | Test Loss=0.543 Acc=81.37% Epoch 2 | Train Loss=0.526 Acc=82.95% | Test Loss=0.345 Acc=87.63% Epoch 4 | Train Loss=0.302 Acc=89.88% | Test Loss=0.240 Acc=91.23% Epoch 6 | Train Loss=0.216 Acc=92.67% | Test Loss=0.215 Acc=92.18% Epoch 8 | Train Loss=0.173 Acc=94.25% | Test Loss=0.196 Acc=93.05% Epoch 10 | Train Loss=0.139 Acc=95.36% | Test Loss=0.191 Acc=93.52% Epoch 12 | Train Loss=0.114 Acc=96.21% | Test Loss=0.180 Acc=94.07% Epoch 14 | Train Loss=0.095 Acc=96.92% | Test Loss=0.175 Acc=94.31% Epoch 16 | Train Loss=0.079 Acc=97.52% | Test Loss=0.173 Acc=94.18% Epoch 18 | Train Loss=0.065 Acc=98.08% | Test Loss=0.182 Acc=94.33% Epoch 20 | Train Loss=0.055 Acc=98.40% | Test Loss=0.178 Acc=94.48% ✅ 最佳测试准确率: 94.48%

结果对比

方法从零训练的 CNN迁移学习(特征提取)
准确率84.2%94.5%
训练时间30 分钟5 分钟
参数量1.2M11.2M(只训练 132K)

迁移学习用 1/6 的时间,提升了 10 个百分点的准确率!


五、进阶:选择合适的预训练模型

模型大小 vs 准确率

def get_pretrained_model(name, num_classes, freeze=True): """获取不同预训练模型""" weights_enum = { 'resnet18': torchvision.models.ResNet18_Weights.IMAGENET1K_V1, 'resnet50': torchvision.models.ResNet50_Weights.IMAGENET1K_V1, 'efficientnet_b0': torchvision.models.EfficientNet_B0_Weights.IMAGENET1K_V1, 'vit_b_16': torchvision.models.ViT_B_16_Weights.IMAGENET1K_V1, 'convnext_tiny': torchvision.models.ConvNeXt_Tiny_Weights.IMAGENET1K_V1, } model = torchvision.models.get_model(name, weights=weights_enum[name]) if freeze: for param in model.parameters(): param.requires_grad = False # 替换分类头(不同模型的分类头名称不同) if 'vit' in name: model.heads.head = nn.Linear(model.heads.head.in_features, num_classes) elif 'convnext' in name: model.classifier[-1] = nn.Linear(model.classifier[-1].in_features, num_classes) else: model.fc = nn.Linear(model.fc.in_features, num_classes) return model
模型参数量CIFAR-10 准确率(迁移学习)推理速度
ResNet-1811M~94%
ResNet-5025M~96%中等
EfficientNet-B05.3M~95%最快
ConvNeXt-Tiny28M~97%中等
ViT-B/1686M~98%

选型建议

  • 移动端/实时:EfficientNet-B0(体积小、速度快)
  • 通用场景:ResNet-50(成熟可靠、生态好)
  • 追求精度:ConvNeXt-Tiny 或 ViT(效果好,但慢)

六、迁移学习的常见问题

问题 1:我的数据和 ImageNet 差异很大怎么办?

如果目标图像和自然图像差异大(如医学影像、卫星图、手绘图),建议:

1. 不要冻结太多层(解冻 layer3 和 layer4) 2. 使用更大的学习率微调 3. 考虑在相似领域的数据上做预训练 (如医学影像 → 在 CheXpert 上预训练的模型)
# 选择性地解冻部分层 model = torchvision.models.resnet18(weights='IMAGENET1K_V1') # 冻结前 3 层,解冻最后 1 层卷积和分类头 for name, param in model.named_parameters(): if 'layer4' in name or 'fc' in name: param.requires_grad = True else: param.requires_grad = False

问题 2:微调时过拟合怎么办?

# 解决方案组合: # 1. 更强的数据增强 transform_train = transforms.Compose([ transforms.Resize(224), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), transforms.ColorJitter(0.2, 0.2, 0.2, 0.1), transforms.RandomCrop(224, padding=28), transforms.ToTensor(), transforms.Normalize(mean, std), ]) # 2. 增加 Dropout model.fc = nn.Sequential( nn.Dropout(0.5), # 加大 Dropout nn.Linear(512, 256), nn.ReLU(), nn.Dropout(0.3), nn.Linear(256, 10), ) # 3. 权重衰减 optimizer = optim.AdamW(model.fc.parameters(), lr=0.001, weight_decay=1e-3)

问题 3:微调和特征提取哪个更好?

数据量很小(<500 张):特征提取(冻结骨干) ✅ 数据量中等(500-5000 张):微调(小学习率 1e-5~1e-4) ✅ 数据量充足(>5000 张):微调(正常学习率) ✅ 不确定时:先试特征提取,如果训练集准确率已接近 100% 说明数据足够微调

七、总结

概念一句话
迁移学习把别人训练好的模型拿来改一改,适应自己的任务
预训练模型在 ImageNet(1400 万张图)上训练好的特征提取器
特征提取冻结卷积层,只训练分类头——适合小数据
微调所有层参与训练,但骨干用小学习率——适合中大数据
为什么有效低层特征(边缘、纹理)在所有图像任务中通用

核心三句话

  1. 迁移学习是深度学习最快见效的技巧——用几行代码就能提升 10 个百分点的准确率
  2. torchvision 一行代码加载预训练模型——不要从零训练,除非你有特殊理由
  3. 先特征提取,再尝试微调——小数据用冻结策略,数据充足再全模型微调

在 2026 年,除了极特殊的场景,没有人会从零训练一个图像模型。迁移学习已经是标准做法。

http://www.rkmt.cn/news/1489166.html

相关文章:

  • 第四篇:数据库国产化与信创替代的守护者:基于CLup的异构数据库一站式运维平台构建
  • 3步自动化搞定黑苹果配置:OpCore-Simplify零基础EFI生成工具终极指南
  • 2026 徐州防水补漏服务商口碑测评榜单|全屋渗漏维修机构优选指南 - 宅安选房屋修缮
  • 2026年 哈尔滨/深圳高端婚礼策划推荐榜:海外韩式及老钱风、布幔草坪与秀场风极简婚礼口碑优选 - 品牌发掘
  • Palantir Gotham背后的‘数据炼金术’:大规模图分析、实时融合与可视化技术拆解
  • 【字节跳动】本文系统阐述了SEED技术体系在人工智能领域的49项核心创新,涵盖容错架构(六进程热备)、权重管理(4096KB固定粒度)、注意力机制(24头时序锁相)、专属会话保护(次元壁垒)、字符处理
  • 视频字幕提取,5款工具实测对比
  • MATLAB一键运行的灰狼算法调参SVM分类工具:15维输入、4类识别,带数据和结果图
  • 中小型工厂自动化选型:低价开源产品为何难扛高频数据需求?实在Agent以非侵入式AI智能体打破数字化僵局
  • 沉迷 Vibe coding 后我幡然醒悟:为什么可持续开发要回归半古法编程
  • 5分钟掌握AI短视频创作:Pixelle-Video让你的创意轻松起飞
  • 全自动定向评价系统和全自动评价系统作用不同
  • 2026 绍兴防水补漏服务商口碑测评榜单|全屋渗漏维修机构优选指南 - 宅安选房屋修缮
  • ComfyUI-FramePackWrapper:8GB显存实现高质量AI视频生成的完整指南
  • LPC845 I2C SBL实战:嵌入式固件远程更新与内存布局解析
  • Zotero-GPT插件API调用故障排查:3步解决AI功能失效问题
  • 《置身钉内》原文-可播放阅读
  • OpenDroneMap:开源无人机摄影测量系统的架构解析与技术实现
  • 2026年 HC600/980QP高强钢厂家推荐榜单:汽车轻量化核心板材与冲压性能深度解析 - 品牌发掘
  • 如何高效使用BBDown:B站视频下载的终极命令行方案
  • HR外包工具横向评测:单租户SaaS真的难解差异化规则?实在Agent以非侵入式AI重构企业数字化转型
  • 2026实力厂商推荐:超越创新LED 球形屏、球幕 LED 显示屏、异型屏、全息沉浸式屏、LED 圆形屏定制供应商深度解 - 栗子测评
  • Jasminum茉莉花:5分钟掌握Zotero中文文献管理终极方案
  • BetterNCM 插件管理器实战:Rust 架构设计与 Windows 自动化安装深度解析
  • 2026 珠海防水补漏服务商口碑测评榜单|全屋渗漏维修机构优选指南 - 宅安选房屋修缮
  • 基于LPC51U68与SCTimer的I2C总线鲁棒性测试与错误注入实战
  • Lathe:利用大语言模型生成技术教程,助力实践学习!
  • 2026年 抗穿刺地面保护膜品牌/厂家推荐排行榜:高抗撕裂/加厚耐磨/装修防刮擦优质产品精选榜单 - 企业推荐官【官方】
  • IINA:macOS上最强大的免费视频播放器终极指南
  • python的代码