PaddleClas 模型微调(Finetune)实战指南:从数据准备到配置修改与多卡训练
PaddleClas 模型微调(Finetune)实战指南:从数据准备到配置修改与多卡训练
本指南面向已在 Linux 上完成 PaddleClas 环境配置并跑通过快速开始(进阶版)的开发者,系统讲解如何使用 ImageNet 预训练模型在自己的分类数据集上完成微调(finetune):包括 txt 格式数据的组织方式、配置文件逐项修改要点、多卡训练命令,以及分辨率、数据增强、评价指标、精度提升等高频问题的处理方案。读完本文,你将能够把任意单标签图像分类数据集接入 PaddleClas,并基于预训练模型快速收敛、缓解过拟合。
PaddleClas 提供了丰富的 ImageNet 预训练模型,使用这些预训练权重作为初始化训练自己的任务,不仅可以快速收敛,还能在一定程度上避免模型在小数据集上的过拟合。微调流程可以概括为三条主线:组织数据(txt 清单)→ 修改配置(类别数 / 预训练 / 学习率 / 数据路径)→ 启动训练。下面以 ResNet50_vd 为例逐一展开,该模型的完整训练配置位于 ppcls/configs/ImageNet/ResNet/ResNet50_vd.yaml。
1. 数据准备:目录结构与 txt 清单格式
PaddleClas 通过 txt 文件读取数据,因此需要将分类数据单独存放在一个文件夹中,其中主要包含以下四部分内容:
├── train
├── train_list.txt
├── val
├── val_list.txt
其中 train 和 val 分别存放训练数据和验证数据;train_list.txt 和 val_list.txt 分别记录训练/验证数据的路径和标签。清单文件每一行采用空格分隔图像路径与标注,格式如下:
# 下面是 train_list.txt 中的格式样例
train/n01440764/n01440764_10026.JPEG 0
...
# 下面是 val_list.txt 中的格式样例
val/ILSVRC2012_val_00000001.JPEG 65
...
其中 train/n01440764/n01440764_10026.JPEG 表示该数据的路径(相对于 image_root),0 表示该数据属于第 0 类。注意:train_list.txt 和 val_list.txt 需要根据你的实际数据自行生成,PaddleClas 不会自动生成。
1.1 清单文件如何被解析:ImageNetDataset 源码解读
在配置文件中,数据读取由 DataLoader.Train.dataset.name: ImageNetDataset 指定,其实现位于 ppcls/data/dataloader/imagenet_dataset.py,核心逻辑如下:
- 构造参数
image_root(图像根目录)与cls_label_path(清单文件路径)由CommonDataset基类保存(见 ppcls/data/dataloader/common_dataset.py); _load_anno会逐行读取清单:以默认分隔符(空格)切分line,将line[0]拼接到image_root下得到完整图像路径,将line[1]解析为np.int64标签;- 解析时会对每个路径做
os.path.exists断言,路径或清单文件不存在会直接报错,这有助于在训练前快速定位数据路径问题; - 若设置
relabel: True,则会先收集清单中的全部标签并重新映射为从 0 开始的连续编号,适合原始标签不是从 0 开始或存在空缺的场景(例如仅使用全部 1000 类中的部分类别微调)。
以 ImageNet1k 为例,官方数据下载解压后按如下结构摆放即可直接使用(该结构也见图像分类任务数据集说明):
PaddleClas/dataset/ILSVRC2012/
├── train/
│ ├── n01440764/...
│ └── ...
├── val/
│ ├── ILSVRC2012_val_00000001.JPEG
│ └── ...
├── train_list.txt
└── val_list.txt
2. 模型训练
2.1 更改配置文件
在 ppcls/configs/ImageNet/ 目录下定义了所有模型在 ImageNet 数据上的训练配置,可以直接修改对应配置完成自己的训练任务。下面以 ResNet50_vd 为例(配置文件位于 ppcls/configs/ImageNet/ResNet/ResNet50_vd.yaml),详述微调时通常需要修改的四处配置。
2.1.1 修改类别数
类别数量默认是 1000(ImageNet-1k 的类别数),需要修改为实际任务的类别数量:
Arch:
name: ResNet50_vd
class_num: your_class_num
Arch.class_num 决定了分类头(全连接层)的输出维度。从模型定义源码 ppcls/arch/backbone/legendary_models/resnet.py 可以看到,class_num 会作为 kwargs 传入 ResNet 并最终影响 fc 层的输出节点数,因此该值必须与清单中的标签取值范围一致(或借助 relabel 自动重映射)。
2.1.2 加载预训练模型
增加预训练模型可以让 Loss 更快收敛,修改方式如下:
Arch:
name: ResNet50_vd
class_num: your_class_num
pretrained: True
pretrained 支持两种取值:
True:加载 PaddleClas 官方发布的 ImageNet 预训练权重(自动从配置内置的MODEL_URLS下载);- 字符串路径:加载本地权重,例如
pretrained: "./pretrained/best_model",此时将直接以该路径作为预训练模型加载。
其底层逻辑见 ppcls/arch/backbone/legendary_models/resnet.py 中的 _load_pretrained:pretrained is True 时调用 load_dygraph_pretrain(model, model_url, use_ssld=use_ssld);pretrained 为字符串时调用 load_dygraph_pretrain(model, pretrained);其余类型会抛出运行时错误。此外,若希望在 pretrained=True 时加载 SSLD 蒸馏得到的更强预训练权重,只需在 Arch 下追加 use_ssld: True(详见下文 FAQ-Q4)。
2.1.3 修改学习率
当训练任务的数据量远小于 ImageNet 时,学习率需要减小若干倍。具体减小的幅度需要调试,可以先从减小 10 倍开始:
Optimizer:
name: Momentum
momentum: 0.9
lr:
name: Cosine
learning_rate: your_learning_rate
在 ResNet50_vd.yaml 中默认配置为 learning_rate: 0.1(配合 regularizer.name: 'L2'、coeff: 0.00007)。学习率调度器 Cosine 的实现位于 ppcls/optimizer/learning_rate.py,其按公式 lr = 0.05 * (cos(epoch * π / epochs) + 1) 做余弦退火,并可配置 eta_min(最小学习率,默认 0.0)、warmup_epoch、warmup_start_lr 等参数;Momentum 优化器与各类调度器的装配逻辑见 ppcls/optimizer/init.py。关于学习率策略的深入讨论(piecewise_decay 与 cosine_decay 对比、warmup、batch_size 与学习率线性缩放关系等),可参考图像分类训练调参策略。
2.1.4 修改数据路径
数据路径需要改为第 1 节中准备好的数据路径:
DataLoader:
Train:
dataset:
name: ImageNetDataset
image_root: your_image_root
cls_label_path: your_train_cls_label_path
Eval:
dataset:
name: ImageNetDataset
image_root: your_image_root
cls_label_path: your_eval_cls_label_path
以原配置为例,image_root: ./dataset/ILSVRC2012/、cls_label_path: ./dataset/ILSVRC2012/train_list.txt(Eval 对应 val_list.txt)。训练阶段默认的预处理流水线为 DecodeImage → RandCropImage(size=224) → RandFlipImage → NormalizeImage,并可选 MixupOperator(alpha: 0.2);验证阶段为 DecodeImage → ResizeImage(resize_short=256) → CropImage(size=224) → NormalizeImage;DistributedBatchSampler 的 batch_size 默认为 64,loader.num_workers 默认为 4。这些字段在微调时通常保持不变,只需替换数据路径即可。
2.2 模型训练
修改完配置文件后即可开始训练,此处默认使用 4 卡训练,命令如下:
export CUDA_VISIBLE_DEVICES=0,1,2,3
python3 -m paddle.distributed.launch \
--gpus="0,1,2,3" \
tools/train.py \
-c ppcls/configs/ImageNet/ResNet/ResNet50_vd.yaml
训练入口 tools/train.py 的执行链路为:config.parse_args() 解析命令行参数 → config.get_config(args.config, overrides=args.override) 读取并(可选)覆盖 YAML 配置 → 构造 Engine(config, mode="train") 并调用 engine.train()。每个 epoch 的训练循环(前向、Loss 计算、反向、优化器更新、学习率步进、EMA 更新与日志打印)实现在 ppcls/engine/train/train.py。
2.2.1 单卡与多卡切换时的学习率调整
如果你只有单卡,可将 CUDA_VISIBLE_DEVICES 与 --gpus 同时指定为同一张卡:
export CUDA_VISIBLE_DEVICES=0
python3 -m paddle.distributed.launch \
--gpus="0" \
tools/train.py \
-c ppcls/configs/ImageNet/ResNet/ResNet50_vd.yaml \
-o Optimizer.lr.learning_rate=0.01
由于初始学习率与 batch_size 需要保持线性关系,从 4 卡切换到 1 卡训练时,总 batch_size 缩减为原来的 1/4,学习率也应缩减为原来的 1/4(例如从 0.04 调到 0.01)。该经验在快速开始(进阶版)的 CIFAR-100 迁移学习示例中同样被采用。
2.2.2 用 -o 参数覆盖配置,避免改动文件
ppcls/utils/config.py 提供了命令行覆盖机制:-o(--override)参数可追加任意配置键值对,例如上面的 -o Optimizer.lr.learning_rate=0.01、-o Global.output_dir="output_CIFAR"、-o Arch.pretrained=True。其原理是 override_config 将 key0.key1.key2=value 字符串按 . 拆分为键路径,递归定位并替换目标值;若键不存在则会打印 "A new field detected!" 并自动创建该字段。这让你在微调时可以复用现成配置文件、通过命令行临时覆盖路径/学习率/预训练开关等参数,不必为每次实验复制一份 YAML。
FAQ
Q1:如何更改输入分辨率?
以将 224 分辨率修改为 320 为例。如果只做训练和评测,只需修改以下三个字段:
DataLoader.Train.dataset.transform_ops.1.RandCropImage.size:从224修改为320;DataLoader.Eval.dataset.transform_ops.1.ResizeImage.resize_short:从256修改为366(保持约 1.14 的短边缩放比例);DataLoader.Eval.dataset.transform_ops.2.CropImage.size:从224修改为320。
如果后续还涉及模型导出或 infer,需要同时修改以下字段(与导出配置的联动逻辑见 ppcls/utils/config.py 中的 dump_infer_config,它会将 DataLoader.Eval 或 Infer 的预处理转换写入推理配置):
Global.image_shape:从[3, 224, 224]修改为[3, 320, 320](用于静态图模式与模型导出);Infer.transforms.1.ResizeImage.resize_short:从256修改为366;Infer.transforms.2.CropImage.size:从224修改为320。
经验:更大的分辨率通常带来更高的精度,但训练与推理耗时、显存占用也会相应上升;修改时需同步调整
Global.image_shape,避免导出后的 inference 模型输入尺寸与训练/评测阶段不一致。
Q2:如何更改数据增强?
数据增强部分的详细说明参见数据增强文档。PaddleClas 除 ImageNet 标准增强(RandCropImage、RandFlipImage、NormalizeImage)外,还内置了多种增强算子,可归为三类:
- 裁剪后变换类:AutoAugment、RandAugment;
- 通道重排后裁剪类:Cutout、RandomErasing、HideAndSeek、GridMask;
- Batch 级混合类:Mixup、Cutmix。
以 ResNet50_vd.yaml 为例,训练阶段的 batch_transform_ops 中默认启用了 MixupOperator(alpha: 0.2),它属于对 batch 内图像与标签同时做线性混合的增广方式,在 ResNet50 300 epoch 的 ImageNet 实验中可使 Top-1 从 77.31% 提升到 78.28% 量级。更多增强算子的用法、参数与对应配置文件可直接查阅该文档及 ppcls/configs/ImageNet/DataAugment/ 下的配置文件。
Q3:如何更改评价指标?
PaddleClas 的分类指标目前支持 Top-k 准确率。如果需要更改 k 值,可修改配置文件中 Metric.Eval 下的 TopkAcc.topk,例如从 [1, 5] 修改为 [1, 3],即评测指标从 Top-1、Top-5 改为 Top-1、Top-3:
Metric:
Train:
Eval:
- TopkAcc:
topk: [1, 3]
指标计算与更新逻辑位于 ppcls/metric/metrics.py(TopkAcc 实现)及 ppcls/engine/train/utils.py(update_metric 在训练循环中每 batch 更新指标并用于日志输出)。
Q4:如何进一步提升模型的精度?
- 更换更强的模型:如果不考虑推理速度,可以更换在 ImageNet 上精度更高的模型,或使用更大的输入分辨率;如果在意推理速度,建议使用经 PaddleClas 优化的 PP 系列模型,如 PP-LCNet、PP-HGNet 等;
- 使用 SSLD 蒸馏预训练权重:PaddleClas 提供 20+ 个 SSLD(Simple Semi-supervised Label Distillation)预训练权重,在很多任务中都能带来 1 个百分点以上的提升。使用方式只需在
Arch字段下增加use_ssld: True:
Arch:
name: ResNet50_vd
class_num: your_class_num
pretrained: True
use_ssld: True
从源码看,use_ssld 会透传到各 backbone 工厂函数(如 ppcls/arch/backbone/legendary_models/resnet.py 的 ResNet50_vd)并最终影响 _load_pretrained 中预训练 URL 的选择。关于 SSLD 的原理(无需真实标签、基于 ImageNet22k 扩充数据、soft label 与 JS 散度蒸馏等)与使用方式,可参考 SSLD 知识蒸馏实战;
3. 调整训练轮数:实际任务中训练轮数对结果影响较大,初期调试可以先使用较小的轮数(如 20)快速验证流程与配置,再逐步增大(Global.epochs 默认为 200,注意余弦退火学习率在轮数较少时收敛效果有限,具体讨论见图像分类训练调参策略);
4. 多尝试几组学习率:模型在微调过程中对学习率较为敏感,建议在参考值(如 ImageNet 默认 0.1 的 1/10)附近多尝试几组,并注意学习率与 batch_size 保持线性关系(切换 GPU 数量后同步缩放学习率)。
此外,若任务数据集较小、存在过拟合倾向,还可结合 图像分类训练调参策略 中的建议进一步调整:适当增大 L2 正则系数(Optimizer.regularizer.coeff)、启用 label smoothing(Loss.Train.CELoss.epsilon,ImageNet 实验中常设为 0.1,大模型上通常有稳定提升)、使用 warmup 策略等;数据规模有限时也可参考 Q2 引入数据增强来提升泛化能力。
相关文档导航
- 图像分类任务数据集说明:数据集格式与常见分类数据集介绍
- 快速开始(进阶版):基于 CIFAR-100 的零基础训练、迁移学习、数据增广与知识蒸馏全流程
- 图像分类训练调参策略:学习率、batch_size、weight_decay、label_smoothing 的调参经验
- 数据增强文档:8 种数据增强方式详解与配置
- SSLD 知识蒸馏实战:SSLD 预训练模型与蒸馏方案
- 单标签分类训练 与 分布式训练:训练流程与多机多卡细节