深入解析 Hugging Face Transformers 中的卷积视觉 Transformer(CvT):架构原理、配置参数与图像分类实战
导读
CvT(Convolutional Vision Transformer,卷积视觉 Transformer)是在 🤗 Transformers 中完整实现的一种视觉骨干网络,它把卷积神经网络(CNN)的局部建模能力与 Vision Transformer(ViT)的全局自注意力优势融合到同一架构中。本文以 docs/source/ja/model_doc/cvt.md 为骨架,结合仓库内 CvT 的配置类、模型实现与测试用例,系统讲解 CvT 的架构设计、CvtConfig 全部配置参数、CvtModel 与 CvtForImageClassification 的源码级工作原理,并给出可直接运行的图像分类推理与微调方案。读完本文,你将能够理解 CvT 的内部实现细节,并独立完成基于 microsoft/cvt-13 等检查点的图像分类任务。
CvT 模型概述(Overview)
CvT 模型由 Haping Wu、Bin Xiao、Noel Codella、Mengchen Liu、Xiyang Dai、Lu Yuan、Lei Zhang 在论文《CvT: Introduction Convolutions to Vision Transformers》中提出。它的核心思想是:在 ViT 中引入卷积操作,从而同时吸收两类架构的优点,提升视觉 Transformer 的性能与效率。
论文摘要的核心要点如下:
- CvT 是用于改进 ViT 的全新架构,通过在 ViT 中引入卷积来同时发挥 CNN 与 Transformer 两种设计的优势,实现性能与效率的双重提升。
- 架构上做了两项关键改动:
- 包含卷积 Token 嵌入的层级式 Transformer 结构;
- 使用**卷积投影(Convolutional Projection)**的卷积 Transformer Block。
- 这些改动为 Transformer 引入了 CNN 的理想特性(局部感受野、平移等变性等),同时保留了 Transformer 的优势(动态注意力、全局上下文、更好的泛化能力)。
- 实验表明,与 ImageNet-1k 上的其他视觉 Transformer 以及 ResNet 相比,CvT 以更少的参数和更低的 FLOPs 达到了当时的最先进水平;在更大数据集(如 ImageNet-22k)上预训练后微调到下游任务,性能提升依然保持。基于 ImageNet-22k 预训练的 CvT-W24 在 ImageNet-1k 验证集上取得了 87.7% 的 Top-1 准确率。
- 研究还发现:位置编码这一在既有视觉 Transformer 中至关重要的组件,在 CvT 中可以安全移除,从而简化了高分辨率视觉任务的设计。
在 🤗 Transformers 仓库中,CvT 的模型实现位于 src/transformers/models/cvt/modeling_cvt.py,配置实现位于 src/transformers/models/cvt/configuration_cvt.py,并提供从微软原始 PyTorch 检查点转换脚本 src/transformers/models/cvt/convert_cvt_original_pytorch_checkpoint_to_pytorch.py。
使用要点(Usage tips)
官方文档给出了三条针对 CvT 的关键使用提示:
-
CvT 是"用卷积训练的普通 Vision Transformer"。在 ImageNet-1K 和 CIFAR-100 上微调后,其表现优于原始 ViT 模型(关于 ViT 可参考 docs/source/ja/model_doc/vit.md)。
-
迁移成本极低:针对自定义数据集的推理与微调演示 Notebook,只需做两处替换即可从 ViT 迁移到 CvT——把
ViTFeatureExtractor替换为AutoImageProcessor,把ViTForImageClassification替换为CvtForImageClassification。也就是说,CvT 完全兼容 Transformers 的标准图像预处理与 Auto 模型加载体系。 -
检查点的三种类型(可用预训练权重分三类):
- (1) 仅在 ImageNet-22k(约 1400 万张图像、2.2 万类)上预训练;
- (2) 在 ImageNet-22k 上预训练后又在 ImageNet-1k 上微调;
- (3) 在 ImageNet-1k(即 ILSVRC 2012,约 130 万张图像、1000 类)上微调。
以
microsoft/cvt-13为例,它是 CvT 系列最常用的基础检查点,仓库测试代码中也直接使用该模型名进行慢速集成测试验证(见 tests/models/cvt/test_modeling_cvt.py)。
快速上手:图像分类推理
CvT 面向 image-classification 流水线任务,既可以通过高层 Pipeline 一行代码完成推理,也可以使用底层 AutoModel 系列 API 精确控制前向过程。以下两段代码演示了两种等价的用法。
方式一:使用 Pipeline
from transformers import pipeline
pipeline = pipeline(
task="image-classification",
model="microsoft/cvt-13",
device=0 # 指定 GPU 设备
)
pipeline("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg")
方式二:使用 AutoImageProcessor + AutoModelForImageClassification
import requests
import torch
from PIL import Image
from transformers import AutoImageProcessor, AutoModelForImageClassification
# 加载图像处理器与模型(device_map="auto" 自动分配设备)
image_processor = AutoImageProcessor.from_pretrained("microsoft/cvt-13")
model = AutoModelForImageClassification.from_pretrained(
"microsoft/cvt-13",
device_map="auto"
)
url = "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg"
image = Image.open(requests.get(url, stream=True).raw)
inputs = image_processor(image, return_tensors="pt").to(model.device)
with torch.no_grad():
logits = model(**inputs).logits
predicted_class_id = logits.argmax(dim=-1).item()
class_labels = model.config.id2label
predicted_class_label = class_labels[predicted_class_id]
print(f"The predicted class label is: {predicted_class_label}")
这段代码完整对应仓库测试中的集成测试流程:在 tests/models/cvt/test_modeling_cvt.py 的 CvtModelIntegrationTest 中,正是使用 AutoImageProcessor.from_pretrained("microsoft/cvt-13") 与 CvtForImageClassification.from_pretrained("microsoft/cvt-13") 加载模型,并对 microsoft/cvt-13 的输出 logits 前三个值(约 [0.9282, 0.9025, -0.3145])做了数值校验。也就是说,上面两段示例代码的正确性由仓库内的慢速集成测试直接背书。
配置类:CvtConfig 参数全解析
CvtConfig 继承自 PreTrainedConfig(见 src/transformers/models/cvt/configuration_cvt.py),model_type 为 "cvt"。CvT 是一个三级阶段(stage)层级结构,因此绝大多数参数都是长度为 3 的列表/元组,分别对应 Stage 1、Stage 2、Stage 3。默认值即 microsoft/cvt-13 对应的配置。
核心结构参数
| 参数 | 默认值 | 说明 |
|---|---|---|
num_channels |
3 |
输入图像的通道数(RGB 为 3) |
patch_sizes |
(7, 3, 3) |
每个阶段卷积 Token 嵌入的卷积核大小 |
patch_stride |
(4, 2, 2) |
每个阶段 Patch 嵌入的步长 |
patch_padding |
(2, 1, 1) |
每个阶段 Patch 嵌入的填充大小 |
embed_dim |
(64, 192, 384) |
每个阶段的嵌入维度(隐藏层宽度) |
num_heads |
(1, 3, 6) |
每个阶段自注意力的头数 |
depth |
(1, 2, 10) |
每个阶段中 Transformer Block 的层数 |
mlp_ratio |
(4.0, 4.0, 4.0) |
MLP 隐藏层相对嵌入维度的放大倍数 |
以 microsoft/cvt-13 为例:Stage 1 用 7×7 卷积核、步长 4 将输入下采样并映射到 64 维,只有 1 层 Block、1 个头;Stage 2 提升到 192 维、3 个头、2 层;Stage 3 提升到 384 维、6 个头、10 层。这种由浅到深、维度逐级升高的设计与 CNN 骨干网络(如 ResNet)的 stage 设计一脉相承,这也是 CvT"层级式"的含义。
注意力相关参数
| 参数 | 默认值 | 说明 |
|---|---|---|
qkv_projection_method |
("dw_bn", "dw_bn", "dw_bn") |
Q/K/V 的投影方式。默认 "dw_bn" 表示深度可分离卷积 + BatchNorm;若想用线性投影则设为 "avg" |
kernel_qkv |
(3, 3, 3) |
注意力层中 Q/K/V 卷积投影的核大小 |
padding_kv |
(1, 1, 1) |
K 与 V 卷积投影的填充 |
stride_kv |
(2, 2, 2) |
K 与 V 卷积投影的步长(大于 1 时会对 K/V 做空间下采样,降低注意力计算量) |
padding_q |
(1, 1, 1) |
Q 卷积投影的填充 |
stride_q |
(1, 1, 1) |
Q 卷积投影的步长 |
qkv_bias |
(True, True, True) |
Q/K/V 线性投影是否带偏置 |
attention_drop_rate |
(0.0, 0.0, 0.0) |
注意力概率矩阵的 Dropout 比例 |
从源码 src/transformers/models/cvt/modeling_cvt.py 的 CvtSelfAttention 类可以看到其实现细节:Q、K、V 分别经过 CvtSelfAttentionProjection(卷积投影 + 线性投影两级),其中卷积投影当 projection_method == "dw_bn" 时使用 CvtSelfAttentionConvProjection——即 groups=embed_dim 的深度可分离 Conv2d 加 BatchNorm2d;随后再通过 nn.Linear 线性投影并按 embed_dim**-0.5 缩放,用 einsum 完成注意力打分与加权求和。stride_kv > 1 意味着 K/V 的空间分辨率低于 Q,这正是 CvT 在注意力内部"压缩 K/V"来降低计算开销的机制。
正则化与 Token 参数
| 参数 | 默认值 | 说明 |
|---|---|---|
drop_rate |
(0.0, 0.0, 0.0) |
Patch 嵌入输出的 Dropout 比例 |
drop_path_rate |
(0.0, 0.0, 0.1) |
随机深度(Stochastic Depth)比例,仅最后一个阶段非零 |
cls_token |
(False, False, True) |
每个阶段输出是否拼接分类 Token,仅最后一个阶段使用 |
initializer_range |
0.02 |
截断正态初始化的标准差 |
layer_norm_eps |
1e-12 |
LayerNorm 的 epsilon |
cls_token 的默认值 (False, False, True) 是 CvT 的一个鲜明设计:前两个阶段不携带分类 Token,只有最后一个阶段才引入。这也与论文"位置编码可移除"的结论互为印证——层级卷积嵌入已经提供了足够的空间位置信息。
配置类的标准用法
>>> from transformers import CvtConfig, CvtModel
>>> # 初始化一个 msft/cvt 风格的配置
>>> configuration = CvtConfig()
>>> # 用随机权重初始化模型
>>> model = CvtModel(configuration)
>>> # 访问模型配置
>>> configuration = model.config
模型实现:CvtModel 的源码级剖析
CvtModel 是 CvT 的基础模型,输出 BaseModelOutputWithCLSToken(见 src/transformers/models/cvt/modeling_cvt.py),其中包含 last_hidden_state、cls_token_value(最后一层的分类 Token)以及可选的 hidden_states。其整体结构为:CvtModel → CvtEncoder → 3 个 CvtStage → 每个 Stage 由 CvtEmbeddings + 若干 CvtLayer 组成。
CvtConvEmbeddings:卷积 Token 嵌入
class CvtConvEmbeddings(nn.Module):
def __init__(self, patch_size, num_channels, embed_dim, stride, padding):
super().__init__()
patch_size = patch_size if isinstance(patch_size, collections.abc.Iterable) else (patch_size, patch_size)
self.patch_size = patch_size
self.projection = nn.Conv2d(num_channels, embed_dim, kernel_size=patch_size, stride=stride, padding=padding)
self.normalization = nn.LayerNorm(embed_dim)
它是标准的 Conv2d + LayerNorm 组合:卷积层按 patch_stride 对图像下采样并映射到 embed_dim,然后对每个空间位置的特征做 LayerNorm,再把张量在 b c h w 与 b (h w) c 之间重排(rearrange)。每个阶段输入前,第一个阶段接收原始图像(通道数 num_channels),后续阶段则接收上一阶段的特征图(通道数 embed_dim[stage-1])——这一点在 CvtStage.__init__ 中通过 num_channels=config.num_channels if stage == 0 else config.embed_dim[stage - 1] 体现。
CvtLayer:带卷积注意力的 Transformer Block
CvtLayer 的组成(按前向顺序):
- 前置 LayerNorm(
layernorm_before)——CvT 在自注意力之前应用 LayerNorm; - CvtAttention:
CvtSelfAttention(卷积 QKV 投影 + 多头注意力 + Dropout)接CvtSelfOutput(线性层 + Dropout),残差连接在CvtLayer中完成而非注意力内部; - DropPath(
CvtDropPath,随机深度); - 第一次残差连接:
hidden_state = attention_output + hidden_state; - 后置 LayerNorm(
layernorm_after)后进入 MLP:CvtIntermediate(线性层放大mlp_ratio倍 + GELU 激活)接CvtOutput(线性层还原维度 + Dropout); - 第二次残差连接:在
CvtOutput.forward内通过hidden_state + input_tensor完成。
注意 CvtDropPath 类是从 Swin 模型的 DropPath 实现复制而来(源码注释标明了 Copied from transformers.models.swin...),drop_prob 为 0 或非训练状态时是恒等映射。
CvtStage 与 DropPath 调度
每个 CvtStage 内部用 torch.linspace(0, config.drop_path_rate[stage], config.depth[stage]) 为每一层生成线性递增的 DropPath 概率,这也解释了为什么默认配置中只有第三阶段的 drop_path_rate 非零(0.1)。若 cls_token[stage] 为 True,则在序列开头拼接可学习的分类 Token,前向结束时会再将其拆分出来单独返回(对应 BaseModelOutputWithCLSToken.cls_token_value)。
CvtModel 前向接口
CvtModel.forward 的签名如下:
def forward(
self,
pixel_values: torch.Tensor | None = None,
output_hidden_states: bool | None = None,
return_dict: bool | None = None,
**kwargs,
) -> tuple | BaseModelOutputWithCLSToken:
pixel_values:形状为(batch_size, num_channels, height, width)的图像张量,必填,缺省会抛出ValueError("You have to specify pixel_values");output_hidden_states:为 True 时返回每个阶段输出的隐藏状态元组;return_dict:为 False 时返回元组,为 True(默认)时返回BaseModelOutputWithCLSToken。
由于 base_model_prefix = "cvt" 且 main_input_name = "pixel_values",CvT 可以无缝接入 AutoModel 等自动加载体系。测试类 CvtModelTest(见 tests/models/cvt/test_modeling_cvt.py)专门验证了隐藏状态输出的数量等于阶段数 len(depth),并校验第一阶段的隐藏状态形状为 (batch, embed_dim[0], image_size // 4, image_size // 4)——即 Stage 1 的 4 倍空间下采样。
图像分类头:CvtForImageClassification
CvtForImageClassification 在 CvtModel 之上叠加了分类头,结构如下:
cvt = CvtModel(config, add_pooling_layer=False):骨干网络;layernorm = nn.LayerNorm(config.embed_dim[-1]):对最后一阶段输出做归一化;classifier = nn.Linear(config.embed_dim[-1], config.num_labels)(num_labels > 0时,否则为nn.Identity)。
分类头的池化逻辑与 cls_token 配置强相关(源码中体现为分支判断):
- 当
config.cls_token[-1]为 True(默认配置如此):直接取BaseModelOutputWithCLSToken.cls_token_value(最后一个阶段的分类 Token)做 LayerNorm,再对序列维取均值; - 当
cls_token[-1]为 False:把最后一阶段特征图重排为序列形式后做 LayerNorm 并取均值; - 最后把池化向量送入
classifier得到 logits。
损失函数的自动选择同样在 forward 中实现:传入 labels(形状 (batch_size,))时,根据 problem_type 自动选择——num_labels == 1 时用均方误差(回归),num_labels > 1 且标签为整型时用交叉熵(单标签分类),否则用 BCEWithLogits(多标签分类)。输出类型为 ImageClassifierOutputWithNoAttention(含 loss、logits、可选的 hidden_states)。
关于注意力输出需要特别说明:CvT 不输出注意力权重。测试类中显式标注了 has_attentions = False,并跳过 test_attention_outputs(原因注释为 "Cvt does not output attentions")。
Pipeline 与测试验证
支持的 Pipeline
CvT 在仓库测试中注册了两个 pipeline 映射(见 tests/models/cvt/test_modeling_cvt.py 的 pipeline_model_mapping):
image-classification→CvtForImageClassificationimage-feature-extraction→CvtModel
这意味着 CvT 不仅可用于图像分类,还可以作为通用视觉特征提取骨干,输出阶段级特征。
测试如何验证实现正确性
CvtModelTester 用一个缩小版配置(embed_dim=[16, 32, 48]、num_heads=[1, 2, 3]、depth=[1, 2, 10]、image_size=64)验证:
- 特征图尺寸递推:逐阶段按公式
height = floor(((height + 2 * patch_padding[i] - patch_sizes[i]) / patch_stride[i]) + 1)计算下采样后的尺寸,并断言last_hidden_state形状为(batch, embed_dim[-1], height, width); - 分类输出:断言
logits.shape == (batch_size, num_labels); - 推理正确性:
CvtModelIntegrationTest使用真实图像与microsoft/cvt-13检查点,断言 logits 形状为(1, 1000)(ImageNet-1k 的 1000 类),并对前三个 logits 值做assert_close数值校验。
完整上手路径与扩展资源
围绕 CvT,仓库提供了以下可直接参考的资源:
- 推理与微调示例脚本:examples/pytorch/image-classification/ 目录中的图像分类示例脚本,支持在自定义数据集上微调 CvT;
- 任务指南:完整的图像分类流程(数据集、预处理、训练、评估)参见 docs/source/en/tasks/image_classification.md;
- ViT 对照阅读:CvT 与原始 ViT 的差异对照可阅读 docs/source/ja/model_doc/vit.md;
- 权重转换脚本:若需要复现从微软原始 PyTorch 实现转换权重,可参考 src/transformers/models/cvt/convert_cvt_original_pytorch_checkpoint_to_pytorch.py。
微调自定义数据集时,只需要像本文"快速上手"一节那样用 AutoImageProcessor 处理图像、用 CvtForImageClassification 替换掉 ViT 的分类头即可,其余训练流程(如 Trainer)与 Transformers 其他视觉模型完全一致。由于 CvT 不依赖位置编码,调整输入分辨率时无需处理位置编码插值问题,这在高分辨率场景下尤其省心。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00