25-迁移学习入门:加载预训练模型并微调

概述

从零训练一个图像分类模型通常需要大量数据和计算资源。如果你的数据集只有几百到几千张图片,从随机初始化开始训练,很容易过拟合,也很难学到通用视觉特征。

迁移学习的思路是:

text 复制代码
先使用大数据集上训练好的模型
    |
保留通用特征提取能力
    |
替换最后分类层
    |
在自己的数据集上微调

预训练模型已经学到边缘、纹理、形状、局部模式等通用特征。你只需要让它适配自己的类别。

读完本文,你应该能用 Paddle 加载预训练模型,替换分类头,并完成冻结特征层和全量微调两种迁移学习流程。

迁移学习的两种常见方式

方式 做法 适合场景
特征提取 冻结 backbone,只训练分类头 数据很少、训练资源有限
微调 加载预训练权重,训练部分或全部参数 数据较多、想提升精度

可以这样选择:

text 复制代码
数据很少:先冻结 backbone
数据中等:先冻结训练分类头,再解冻全量微调
数据很多:直接全量微调

迁移学习不是固定公式,需要根据数据量、任务相似度、算力和验证集结果调整。

使用 paddle.vision.models 加载预训练模型

Paddle 提供了一些视觉模型接口。例如 ResNet:

python 复制代码
import paddle
import paddle.nn as nn
from paddle.vision.models import resnet18

model = resnet18(pretrained=True, num_classes=1000)

pretrained=True 表示加载官方预训练权重。num_classes=1000 对应 ImageNet 预训练分类头。

如果你的任务有 5 类,需要替换最后分类层。

不同模型最后分类层名字可能不同,使用前应打印结构:

python 复制代码
print(model)

resnet18,常见分类层是 model.fc

替换分类头

假设自定义任务有 5 类:

python 复制代码
num_classes = 5
in_features = model.fc.weight.shape[0]
model.fc = nn.Linear(in_features, num_classes)

有些版本或模型中,weight.shape 的维度含义可能需要确认。更稳的方法是先打印:

python 复制代码
print(model.fc)
print(model.fc.weight.shape)

如果 model.fc.weight.shape[in_features, out_features],则输入维度是第 0 维。

替换后检查:

python 复制代码
x = paddle.randn([2, 3, 224, 224])
logits = model(x)
print(logits.shape)

期望:

text 复制代码
[2, 5]

冻结 backbone:只训练分类头

冻结除分类头外的参数:

python 复制代码
for name, param in model.named_parameters():
    param.stop_gradient = True

for name, param in model.fc.named_parameters():
    param.stop_gradient = False

检查可训练参数:

python 复制代码
for name, param in model.named_parameters():
    if not param.stop_gradient:
        print("trainable:", name, param.shape)

创建优化器时,只传需要训练的参数:

python 复制代码
trainable_params = [p for p in model.parameters() if not p.stop_gradient]

optimizer = paddle.optimizer.Adam(
    learning_rate=0.001,
    parameters=trainable_params,
)

这种方式训练快、过拟合风险低,适合作为迁移学习 baseline。

全量微调:让所有参数参与训练

解冻全部参数:

python 复制代码
for param in model.parameters():
    param.stop_gradient = False

优化器:

python 复制代码
optimizer = paddle.optimizer.Adam(
    learning_rate=1e-4,
    parameters=model.parameters(),
    weight_decay=1e-4,
)

全量微调通常使用更小学习率,例如:

text 复制代码
1e-4
3e-5
1e-5

原因是预训练权重已经比较好,太大学习率可能破坏已有特征。

分阶段训练:先头部,后全量

推荐流程:

text 复制代码
阶段 1:冻结 backbone,只训练分类头
阶段 2:解冻全部或部分层,小学习率微调

示例:

python 复制代码
for name, param in model.named_parameters():
    param.stop_gradient = True

for param in model.fc.parameters():
    param.stop_gradient = False

optimizer = paddle.optimizer.Adam(learning_rate=0.001, parameters=model.fc.parameters())

训练若干 epoch 后:

python 复制代码
for param in model.parameters():
    param.stop_gradient = False

optimizer = paddle.optimizer.Adam(
    learning_rate=1e-4,
    parameters=model.parameters(),
    weight_decay=1e-4,
)

这种策略通常比直接全量大步训练更稳。

数据预处理:预训练模型更依赖一致输入

使用 ImageNet 预训练模型时,常见输入:

text 复制代码
[N, 3, 224, 224]

Transform:

python 复制代码
from paddle.vision import transforms

train_transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.RandomHorizontalFlip(prob=0.5),
    transforms.ToTensor(),
    transforms.Normalize(
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225],
    ),
])

val_transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225],
    ),
])

如果预处理和预训练模型习惯差异很大,迁移效果可能下降。

完整示例:ResNet18 迁移学习骨架

python 复制代码
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
from paddle.vision.models import resnet18


def build_model(num_classes, freeze_backbone=True):
    model = resnet18(pretrained=True, num_classes=1000)
    in_features = model.fc.weight.shape[0]
    model.fc = nn.Linear(in_features, num_classes)

    if freeze_backbone:
        for param in model.parameters():
            param.stop_gradient = True
        for param in model.fc.parameters():
            param.stop_gradient = False

    return model


def accuracy(logits, labels):
    pred = paddle.argmax(logits, axis=1)
    return paddle.mean((pred == labels).astype("float32"))


def train_one_epoch(model, loader, optimizer):
    model.train()
    total_loss = 0.0
    total_acc = 0.0
    count = 0

    for images, labels in loader:
        logits = model(images)
        loss = F.cross_entropy(logits, labels)
        acc = accuracy(logits, labels)

        loss.backward()
        optimizer.step()
        optimizer.clear_grad()

        total_loss += float(loss.numpy())
        total_acc += float(acc.numpy())
        count += 1

    return total_loss / count, total_acc / count

训练分类头:

python 复制代码
model = build_model(num_classes=5, freeze_backbone=True)
trainable_params = [p for p in model.parameters() if not p.stop_gradient]

optimizer = paddle.optimizer.Adam(
    learning_rate=0.001,
    parameters=trainable_params,
)

全量微调:

python 复制代码
for param in model.parameters():
    param.stop_gradient = False

optimizer = paddle.optimizer.Adam(
    learning_rate=1e-4,
    parameters=model.parameters(),
    weight_decay=1e-4,
)

PaddleClas 中的迁移学习

如果使用 PaddleClas,迁移学习通常更简单:

yaml 复制代码
Arch:
  name: MobileNetV3_large_x1_0
  class_num: 5
  pretrained: True

或者通过命令行覆盖:

bash 复制代码
python tools/train.py \
    -c config.yaml \
    -o Arch.class_num=5 \
    -o Arch.pretrained=True

如果已有训练权重:

bash 复制代码
python tools/train.py \
    -c config.yaml \
    -o Global.pretrained_model=./pretrained/best_model

迁移学习时常同时调整:

  • Arch.class_num
  • Arch.pretrained
  • Global.pretrained_model
  • Optimizer.lr.learning_rate
  • 数据集路径
  • transform 策略

常见错误

错误一:忘记替换分类头

ImageNet 预训练模型通常输出 1000 类。你的数据集如果是 5 类,输出必须改成 5。

错误二:学习率太大

微调预训练模型时,学习率通常要比从头训练小。

错误三:冻结后优化器仍传全部参数

虽然 stop_gradient=True 会阻断梯度,但更清晰的做法是只把可训练参数传给优化器。

错误四:预处理不匹配

预训练模型通常更依赖标准化输入。检查 Resize、ToTensor、Normalize 是否合理。

错误五:类别映射不稳定

训练、验证、推理必须使用同一类别 ID 映射。

迁移学习调参建议

推荐顺序:

  1. 使用预训练模型。
  2. 替换分类头。
  3. 冻结 backbone,只训练分类头。
  4. 验证集稳定后,解冻全量小学习率微调。
  5. 加入 weight_decay
  6. 根据验证集保存最佳模型。
  7. 推理前确认类别映射。

如果数据很少,直接全量微调可能过拟合;如果数据较多,只训练分类头可能欠拟合。

建议练习:从分类头微调开始

  1. 加载 resnet18(pretrained=True)
  2. 打印模型结构,找到分类头。
  3. 替换分类头为自己的类别数。
  4. 冻结 backbone,只训练分类头。
  5. 解冻全部参数,用小学习率微调。
  6. 对比冻结和全量微调的验证准确率。
  7. 用 PaddleClas 配置完成同样迁移学习流程。

总结

这一篇讲了迁移学习基础:

  • 预训练模型能提供通用视觉特征。
  • 小数据集优先考虑迁移学习。
  • 常见方式包括冻结特征提取器和全量微调。
  • 替换分类头是必需步骤。
  • 微调通常使用较小学习率。
  • 预处理和类别映射必须稳定一致。
  • PaddleClas 可以用配置快速完成迁移学习。

如果只能记住一句话,那就是:

迁移学习不是重新训练一个模型,而是在已有通用视觉能力上,让模型适配你的新类别。

相关推荐
深圳雨林凯AI1 小时前
印花提取的批量工程:底色分离、品牌字剔除与线条锐利度的技术拆解
图像处理·人工智能
2601_962284501 小时前
安卓APP UI自动化测试:Python + UiAutomator2 + pytest + pytest-html
python·pytest·uiautomator2·ui自动化测试·安卓app
万物智能信息科技1 小时前
LVDS屏幕输出桌面—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
人工智能·华为·开源·harmonyos·鸿蒙
高升说1 小时前
手眼 3D 相机选型:先定工作距离,再谈精度,最后定安装
人工智能·数码相机·3d
张彦峰ZYF1 小时前
从会话工具到常驻执行系统:重新理解 Prime Agent 的 RLM、Continual Harness 与长程 Agent 工程化
人工智能·ai agent·harness·openhands·智能体评测·prime agent·swe-agent
音视频牛哥1 小时前
从视频接入到灵活转推:SmartMediaKit 跨平台 RTSP 转RTMP 技术解析
人工智能·音视频·rtsp转rtmp·跨平台音视频sdk·边缘视频网关·多路视频转发·多路rtsp流转rtmp推送
海宇服务1 小时前
零信任架构实战:基于海宇天远入职背调报告构建自动化微服务准入网关
人工智能·微服务·架构·自动化
2601_962078031 小时前
构建RESTful APIs:使用Python和Flask
python·flask·web开发·api设计·构建restfulapis
外域速览1 小时前
亚马逊600亿美元押注高通AI芯片
人工智能