pytorch官方torchvision直到2.3版仍无内置vision transformer类,需自行搭建或使用timm;timm支持500+视觉模型、灵活配置与预训练权重加载,且正确处理cls token、位置编码、droppath及finetune流程。

PyTorch官方不提供vision_transformer类,得自己搭或用timm
PyTorch核心库(torch 和 torchvision)直到 2.3 版本仍**没有内置 Vision Transformer 模型类**。你查 torchvision.models 会发现只有 ResNet、ViT 等名字——但注意:torchvision.models.vit_b_16 这类是 2022 年后才加入的轻量封装,底层仍是手动拼的模块,且只覆盖有限预训练权重(如 ImageNet-1k),不支持自定义 patch size 或 hybrid backbone。
更常见也更灵活的做法是用 timm(PyPI 包名 timm):
– 它统一管理了 ViT、DeiT、MAE、FlexiViT 等 500+ 视觉模型
– 支持一行加载预训练权重、自动适配输入尺寸、导出 ONNX 等
– 不依赖 torchvision 的硬编码结构,参数可调性高
安装与最简加载示例:
pip install timm
# 加载一个 ViT-Base/16,自动下载预训练权重
import timm
model = timm.create_model('vit_base_patch16_224', pretrained=True)
patch embedding层不能直接用nn.Conv2d,必须对齐cls token和pos embedding
ViT 的核心是把图像切成 patch 后线性投影。新手常误用 nn.Conv2d(in_channels=3, out_channels=768, kernel_size=16, stride=16) ——这看似等价,但会漏掉两个关键点:
– 缺少 [CLS] token:ViT 输入序列开头必须加一个可学习向量,用于最终分类
– 位置编码(pos embedding)维度必须严格匹配总 token 数(patch 数 + 1)
正确做法(以 timm 为例,其 vit_base_patch16_224 内部逻辑):
- 输入
224×224×3→ 切成(224//16)² = 196个 patch → 加上[CLS]后共197个 token - pos embedding 是一个
nn.Parameter,shape 为(197, 768),不可动态缩放 - 若你改输入为
384×384,patch 数变成(384//16)² = 576,此时原 pos embedding 尺寸不匹配,会报错size mismatch for pos_embed
解决方法:
– 用 timm 的 interpolate_pos_encoding=True(仅部分模型支持)
– 或手动双线性插值重采样 pos embedding:F.interpolate(pos_embed.unsqueeze(0), size=(new_n_tokens, dim), mode='bilinear')
forward时别忘了处理attention mask和training模式下的dropout
ViT 的 MultiheadAttention 层默认不接受 attn_mask(不像 NLP 中常用),但如果你做下游任务(如目标检测中的特征图 ViT 编码),可能需要掩码无效区域。此时不能直接传 attn_mask 给 nn.MultiheadAttention,因为 ViT 的 attention 是在 patch token 序列上做的,而标准 PyTorch MultiheadAttention 要求 mask shape 为 (N×num_heads, L, S),容易维数错乱。
更实际的问题是训练/推理模式切换:
– ViT 主干中大量使用 DropPath(stochastic depth)和 Dropout
– 若你冻结 backbone 做迁移学习,但没设 model.eval(),DropPath 仍会随机丢弃路径,导致输出不稳定
– timm 中对应模块叫 timm.models.layers.DropPath,它**只在 training=True 时生效**,这点和 nn.Dropout 一致,但容易被忽略
建议写法:
model = timm.create_model('vit_base_patch16_224', pretrained=True)
model.eval() # 确保 DropPath / Dropout 关闭
with torch.no_grad():
out = model(img_tensor) # img_tensor: [1, 3, 224, 224]
finetune时class token输出不是最终logits,要过head再归一化
很多人取 model.forward_features(x)[:, 0](即 cls token)就直接接 nn.Linear 分类,结果准确率远低于预期。问题在于:
– timm 的 ViT 默认 head 是 nn.Sequential(nn.LayerNorm(dim), nn.Linear(dim, num_classes))
– 如果你替换 head,必须保留 LayerNorm;否则 cls token 的分布偏移会让 Linear 层难以收敛
– 更隐蔽的坑:ViT 的预训练 head 权重(如 ImageNet 的 1000 类)和你新任务类别数不同时,model.reset_classifier(num_classes=10) 才会重置 head,而不是简单 model.head = nn.Linear(...)
正确 finetune 流程要点:
- 用
model.reset_classifier(num_classes=N)替换 head(自动初始化) - 冻结前几层(如
for p in model.blocks[:8].parameters(): p.requires_grad = False) - 确保
model.head的weight和bias在优化器参数列表中 - 如果用混合精度训练(
amp),确认LayerNorm的eps不太小(1e-6安全,1e-12可能溢出)
ViT 对 head 初始化和归一化敏感,cls token 不是“拿来就能用”的特征向量,它高度依赖后续 norm 和 linear 的协同校准。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











