ViT微调实战:pytorch-image-models中让准确率起飞的4组超参
【免费下载链接】pytorch-image-modelsThe largest collection of PyTorch image encoders / backbones. Including train, eval, inference, export scripts, and pretrained weights -- ResNet, ResNeXT, EfficientNet, NFNet, Vision Transformer (ViT), MobileNetV4, MobileNet-V3 & V2, RegNet, DPN, CSPNet, Swin Transformer, MaxViT, CoAtNet, ConvNeXt, and more项目地址: https://gitcode.com/GitHub_Trending/py/pytorch-image-models
pytorch-image-models(timm)是目前规模最大的 PyTorch 图像骨干库之一,ViT 全系列模型、训练脚本与预训练权重齐全。用它在自己的数据集上微调 Vision Transformer 时,准确率上的差距往往不在模型本身,而在学习率调度、随机深度、分层学习率衰减和 EMA 这四组超参上。下面按"最小可用流程 → 关键参数 → 避坑清单"的顺序逐一拆解。
为什么 ViT 比 CNN 更难微调
ViT 的两个特点决定了它微调时的"娇气":
- 对学习率敏感。ViT 的注意力层参数与输入几乎逐元素相关,大学习率下初期 loss 波动剧烈,不加 warmup 很容易在前几个 epoch 发散;
- 预训练知识密度高。
vit_base的 86M 参数全部来自预训练,用 CNN 级别的学习率(1e-3 量级)直接全参微调,会快速破坏底层学到的通用纹理特征。
一个常被忽略的细节:建议选用带.augreg_in21k_ft_in1k标签的权重(如vit_base_patch16_224.augreg_in21k_ft_in1k),它是 21k 图像预训练、再在 ImageNet-1k 上正则化微调过的版本,标签与权重定义都写在 timm/models/vision_transformer.py 的模型注册表里,迁移到下游任务时起点更高。
最小可运行流程:三行数据 + 两个工厂函数
timm 的数据管线由三个工厂函数组成,全部在 timm/data/ 下导出:
from timm.data import create_dataset, create_loader import timm dataset = create_dataset( name='', # 自定义数据集留空,直接读文件夹 root='path/to/data', # train/val 子目录结构 split='train', class_map='path/to/class_map.txt', # 可选,类别名映射 ) loader = create_loader( dataset, input_size=(3, 224, 224), batch_size=128, is_training=True, use_prefetcher=True, # GPU 上预取,避免数据加载拖慢训练 )模型与优化器:
model = timm.create_model( 'vit_base_patch16_224.augreg_in21k_ft_in1k', pretrained=True, num_classes=10, drop_rate=0.1, drop_path_rate=0.15, # 随机深度,微调必配,原因见下文 ) from timm.optim import create_optimizer_v2 optimizer = create_optimizer_v2( model, opt='adamw', lr=1e-4, weight_decay=0.05, )两个要点:
create_optimizer_v2默认filter_bias_and_bn=True(见 timm/optim/_optim_factory.py),bias 和归一化层参数自动被排除在权重衰减之外,不需要手动拆参数组;- AdamW 的 0.05 权重衰减是 ViT 类模型的常用区间,配合小学习率能稳定抑制过拟合。
如果直接用仓库自带的 train.py 跑,以上等价于--model vit_base_patch16_224.augreg_in21k_ft_in1k --opt adamw --wd 0.05 --lr 1e-4这样的命令行组合。
学习率调度:warmup、余弦退火与 min_lr 下限
ViT 微调的调度公式基本固定为warmup + cosine + min_lr 地板,在 timm/scheduler/scheduler_factory.py 中一次配齐:
from timm.scheduler import create_scheduler_v2 scheduler, num_epochs = create_scheduler_v2( optimizer, sched='cosine', num_epochs=30, warmup_epochs=5, warmup_lr=1e-6, min_lr=1e-6, step_on_epochs=True, # 微调按 epoch 步进即可 )每个参数为什么这样取:
- warmup_epochs 取总轮数的 10%–20%(30 轮配 5 轮)。ViT 前几千个 step 的梯度方差大,从 1e-6 线性爬升到峰值学习率,给 BN/注意力权重一个缓冲期,是防止前期发散的关键;
- min_lr 设为峰值学习率的约 1%(1e-6),而不是默认值 0。余弦退火如果衰减到 0,训练尾段等于"冻结",分类头和高层特征失去了最后精细调整的机会。留一个地板能让模型在后期持续收敛细节,这在数据量小的微调场景收益明显;
- 批量变化时学习率要同步缩放。train.py 内置了线性缩放规则
lr = lr_base × global_batch / 256,且对 AdamW 系优化器自动改用平方根缩放(对应 train.py 中--lr-base与--lr-base-scale的逻辑),batch 从 256 降到 64 时不要把学习率也直接除以 4。
正则化三板斧:随机深度、分层学习率衰减、标签平滑
drop_path_rate:微调时最被低估的开关
drop_path_rate即随机深度(stochastic depth)。timm 通过calculate_drop_path_rates(在 timm/models/vision_transformer.py 中调用)把它按层数线性递增分布到每个 Transformer block,越深的块丢弃概率越高。微调时建议 0.1–0.2:
- 预训练的 ViT 本身就是在 0.2 随机深度下训练的,微调时关掉它,等于改变了一个已被权重"适应"过的结构;
- 数据量小于 5 万张时,0.15 左右通常比 0.05 更稳,训练曲线波动会小很多。
注意drop_path_rate必须在create_model时显式传入,加载预训练权重不会自动带上这个设置。
layer_decay:让浅层学得慢、深层学得快
ViT 浅层(靠近输入)编码的是边缘、纹理等通用特征,深层编码的是语义判别特征。分层衰减让学习率从后往前逐层缩小:
optimizer = create_optimizer_v2( model, opt='adamw', lr=1e-4, weight_decay=0.05, layer_decay=0.75, # 每往前一层学习率 ×0.75 )layer_decay在 0.7–0.9 之间取值,效果是:分类头以 1e-4 学习,第一个块可能只有 1e-5 量级,从而在保留预训练底层特征的同时让顶层快速适配新任务。数据量越少的场景,这一项的收益越明显,建议作为第二个调优变量。
标签平滑
小数据集上模型容易对训练样本过度自信,标签平滑直接压住这种自信:
from timm.loss import LabelSmoothingCrossEntropy criterion = LabelSmoothingCrossEntropy(smoothing=0.1)0.1 是 ImageNet 系预训练的标准配置(实现在 timm/loss/cross_entropy.py),微调沿用即可;类别数很少(<20)时可尝试 0.05,避免平滑项对置信度的抑制过强。
数据增强:对齐预训练管线
推荐的增强组合与原 ViT 预训练管线(randaug-m9-inc1 + 双三次插值)保持一致,定义在 timm/data/transforms_factory.py:
from timm.data import create_transform transform = create_transform( input_size=(224, 224, 3), is_training=True, auto_augment='rand-m9-mstd0.5-inc1', # RandAugment,9 级算子,强度 0.5 方差 color_jitter=0.4, re_prob=0.25, # 随机擦除:擦掉一块区域逼模型不依赖局部纹理 re_mode='pixel', re_count=1, interpolation='bicubic', # 与预训练保持一致 )- 选
rand-m9-mstd0.5-inc1而不是更强的 m10:m9-inc1 是 ViT 预训练使用的强度档,微调阶段再叠加更强增强容易过正则,小数据集上验证指标反而下降; re_prob=0.25的随机擦除与 AutoAugment 互补,前者破坏空间局部性,后者改变颜色与几何分布,两者都保留;- 这些参数同样可以直接作为关键字传进
create_loader,不用单独构建 transform。
模型 EMA:用平滑权重做验证和导出
指数滑动平均(EMA)对 ViT 这类对权重噪声敏感的模型收益稳定,timm 的 timm/utils/model_ema.py 提供了ModelEmaV3:
from timm.utils import ModelEmaV3 model_ema = ModelEmaV3(model, decay=0.9998, device='cuda') # 训练循环中,每个 optimizer.step() 之后 model_ema.update(model)使用建议:
- 验证和最终保存都用 EMA 权重,而不是原模型。微调场景下 EMA 权重通常比瞬时权重更稳;
ModelEmaV3的衰减率是动态的:支持use_warmup让 decay 从较低值逐渐爬升到目标值(训练初期权重变化快,应跟得更紧),并可通过min_decay设置下限,不需要自己写随 epoch 变化的衰减逻辑;- 注意
update的调用频率与optimizer.step()一致,不要放在验证循环里。
实战避坑清单
- 梯度裁剪:train.py 默认开启
--clip-grad(默认 5.0)。ViT 在 warmup 阶段偶发梯度尖峰,保留裁剪能避免个别坏 batch 毁掉 BN 统计量; - 混合精度:
train.py加--amp开启,训练速度接近翻倍且 ViT 微调下精度损失可忽略;推理侧可用torch.compile或走 onnx_export.py 导出; - EMA 更新与调度步进的位置:scheduler 按 epoch 调(
step_on_epochs=True时每个 epoch 末调一次scheduler.step(epoch)),EMA 按 step 调,两者混在一个 batch 循环里时容易写错位置,对照 train.py 的train_one_epoch主循环最稳妥; - 不要拿 CNN 的直觉套 ViT:1e-3 起步、无 warmup、无 drop path,这三条任何一条命中,训练大概率在 2–3 个 epoch 内表现崩坏;
- 结果参考:仓库 results/ 下有各模型在 ImageNet 及鲁棒性测试集(imagenet-r、imagenet-a 等)上的精度表,
model_metadata-in1k.csv记录了参数量、FLOPs 等指标,选基线模型时先查这张表。
下一步
按这个顺序落地能最快拿到稳定提升:先用 30 epoch 跑通"AdamW 1e-4 + warmup 5 epoch + cosine(min_lr=1e-6) + drop_path 0.15"的基线,然后只改一个变量地依次尝试layer_decay=0.75、标签平滑 0.1、EMA,每个变量对比验证集 top-1。如果数据量再上一个量级(百万级),可以把 drop_path 提到 0.2、增强升到更强的 auto_augment 档位,并考虑更大 patch 数的变体模型。
【免费下载链接】pytorch-image-modelsThe largest collection of PyTorch image encoders / backbones. Including train, eval, inference, export scripts, and pretrained weights -- ResNet, ResNeXT, EfficientNet, NFNet, Vision Transformer (ViT), MobileNetV4, MobileNet-V3 & V2, RegNet, DPN, CSPNet, Swin Transformer, MaxViT, CoAtNet, ConvNeXt, and more项目地址: https://gitcode.com/GitHub_Trending/py/pytorch-image-models
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考