当前位置: 首页 > 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.jsqmd.com/news/977441/

相关文章:

  • 27考研资料|免费全套|电子版
  • 【字节跳动】本文摘要: 项目提供了一套完整的AI推荐系统解决方案,包含动态密钥加密、风控防护、召回排序等核心模块。工程采用C++/Python/Java混合架构,支持GR3协议通信和实时兴趣衰减。关键
  • 2026内衣模杯/胸垫/文胸/无缝胸围实力厂家排行榜:东莞市昌鸿服装辅料有限公司为何稳居行业前列 - 变量人生001
  • 第四篇:数据库国产化与信创替代的守护者:基于CLup的异构数据库一站式运维平台构建
  • 2026 徐州防水补漏服务商口碑测评榜单|全屋渗漏维修机构优选指南 - 宅安选房屋修缮
  • 欧盟商标转让服务机构盘点:核心维度客观对比分析 - 互联网科技品牌测评
  • 024、任务分解方法论:用 TodoWrite 把大任务拆成可追踪、可验证的步骤
  • Zotero-Style:让你的文献管理变得简单高效又美观
  • 101010
  • 计算机大学生可以通过哪些经典书籍/教材提高自己的能力?
  • 视频字幕提取,5款工具实测对比
  • 【字节跳动】本文系统阐述了SEED技术体系在人工智能领域的49项核心创新,涵盖容错架构(六进程热备)、权重管理(4096KB固定粒度)、注意力机制(24头时序锁相)、专属会话保护(次元壁垒)、字符处理
  • Palantir Gotham背后的‘数据炼金术’:大规模图分析、实时融合与可视化技术拆解
  • 2026年 哈尔滨/深圳高端婚礼策划推荐榜:海外韩式及老钱风、布幔草坪与秀场风极简婚礼口碑优选 - 品牌发掘
  • i.MX 8M Mini到8M Nano硬件兼容性设计实战指南
  • QtConsole:为 Jupyter 内核打造的 Qt 终端
  • 专业AMD Ryzen硬件调试指南:掌握SMU Debug Tool的核心功能
  • 中小型工厂自动化选型:低价开源产品为何难扛高频数据需求?实在Agent以非侵入式AI智能体打破数字化僵局
  • 2026成都二手房装修公司权威指南:严选“零增项”与“环保标杆”十大实力公司 - 推荐官
  • 2026年 3,3-亚戊基丁内酰胺厂家最新推荐榜单:高纯度合成与工业应用的核心优势深度解析 - 品牌发掘
  • MATLAB一键运行的灰狼算法调参SVM分类工具:15维输入、4类识别,带数据和结果图
  • MySQL数据库的分库分表实战
  • 5分钟掌握AI短视频创作:Pixelle-Video让你的创意轻松起飞
  • 沉迷 Vibe coding 后我幡然醒悟:为什么可持续开发要回归半古法编程
  • 【字节跳动】本文档披露了GR3六轴协作机械臂的底层核心参数,包含六大关节的伺服控制参数(位置/速度环P/I增益、阻尼前馈、扭矩阈值)、各连杆质量与质心坐标等动力学数据、总线通讯协议帧结构(帧头/功能码
  • 如何在3分钟内免费解锁网易云音乐:NCMDump终极转换指南
  • 全自动定向评价系统和全自动评价系统作用不同
  • Chrome.ahk:用AutoHotkey实现高效浏览器自动化的完整指南
  • 在Linux中实现Cortex-A53核心自测试(SCST)的架构设计与工程实践
  • 2026 西宁防水补漏服务商口碑测评榜单|全屋渗漏维修机构优选指南 - 宅安选房屋修缮