首页
/ 深入解析 Hugging Face Transformers 中的卷积视觉 Transformer(CvT):架构原理、配置参数与图像分类实战

深入解析 Hugging Face Transformers 中的卷积视觉 Transformer(CvT):架构原理、配置参数与图像分类实战

2026-09-09 14:21:16作者:魏献源Searcher

导读

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 两种设计的优势,实现性能与效率的双重提升。
  • 架构上做了两项关键改动
    1. 包含卷积 Token 嵌入的层级式 Transformer 结构;
    2. 使用**卷积投影(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 的关键使用提示:

  1. CvT 是"用卷积训练的普通 Vision Transformer"。在 ImageNet-1K 和 CIFAR-100 上微调后,其表现优于原始 ViT 模型(关于 ViT 可参考 docs/source/ja/model_doc/vit.md)。

  2. 迁移成本极低:针对自定义数据集的推理与微调演示 Notebook,只需做两处替换即可从 ViT 迁移到 CvT——把 ViTFeatureExtractor 替换为 AutoImageProcessor,把 ViTForImageClassification 替换为 CvtForImageClassification。也就是说,CvT 完全兼容 Transformers 的标准图像预处理与 Auto 模型加载体系。

  3. 检查点的三种类型(可用预训练权重分三类):

    • (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.pyCvtModelIntegrationTest 中,正是使用 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.pyCvtSelfAttention 类可以看到其实现细节:Q、K、V 分别经过 CvtSelfAttentionProjection(卷积投影 + 线性投影两级),其中卷积投影当 projection_method == "dw_bn" 时使用 CvtSelfAttentionConvProjection——即 groups=embed_dim 的深度可分离 Conv2dBatchNorm2d;随后再通过 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_statecls_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 wb (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 的组成(按前向顺序):

  1. 前置 LayerNormlayernorm_before)——CvT 在自注意力之前应用 LayerNorm;
  2. CvtAttentionCvtSelfAttention(卷积 QKV 投影 + 多头注意力 + Dropout)接 CvtSelfOutput(线性层 + Dropout),残差连接在 CvtLayer 中完成而非注意力内部;
  3. DropPathCvtDropPath,随机深度);
  4. 第一次残差连接hidden_state = attention_output + hidden_state
  5. 后置 LayerNormlayernorm_after)后进入 MLP:CvtIntermediate(线性层放大 mlp_ratio 倍 + GELU 激活)接 CvtOutput(线性层还原维度 + Dropout);
  6. 第二次残差连接:在 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

CvtForImageClassificationCvtModel 之上叠加了分类头,结构如下:

  • 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(含 losslogits、可选的 hidden_states)。

关于注意力输出需要特别说明:CvT 不输出注意力权重。测试类中显式标注了 has_attentions = False,并跳过 test_attention_outputs(原因注释为 "Cvt does not output attentions")。

Pipeline 与测试验证

支持的 Pipeline

CvT 在仓库测试中注册了两个 pipeline 映射(见 tests/models/cvt/test_modeling_cvt.pypipeline_model_mapping):

  • image-classificationCvtForImageClassification
  • image-feature-extractionCvtModel

这意味着 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,仓库提供了以下可直接参考的资源:

微调自定义数据集时,只需要像本文"快速上手"一节那样用 AutoImageProcessor 处理图像、用 CvtForImageClassification 替换掉 ViT 的分类头即可,其余训练流程(如 Trainer)与 Transformers 其他视觉模型完全一致。由于 CvT 不依赖位置编码,调整输入分辨率时无需处理位置编码插值问题,这在高分辨率场景下尤其省心。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.35 K
docsdocs
暂无描述
Markdown
899
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
924
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.84 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
533
599
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.03 K
525
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
394