Transformers 中的 PVTv2 全解析:金字塔视觉 Transformer V2 的架构原理、源码实现与实战使用
PVTv2(Pyramid Vision Transformer V2)是由 PVT 作者团队提出的改进型层级式视觉 Transformer,也是 🤗 Transformers 中以 pvt_v2 模型类型提供、可直接用于图像分类与作为通用骨干网络的多尺度视觉编码器。本篇指南以官方模型文档为核心,结合本仓库中的配置与实现源码,系统讲解 PVTv2 的三大设计改进(重叠 Patch 嵌入、卷积前馈网络、线性复杂度注意力)、PvtV2Config 的全部配置参数,以及如何用 AutoModelForImageClassification 做图像分类、用 AutoBackbone 将其接入 Deformable DETR 等目标检测架构。读完你将能够在 Transformers 框架内完成 PVTv2 的加载、推理、backbone 替换与自定义配置,并理解其每层计算背后对应的源码逻辑。
PVTv2 是什么:层级式 Transformer 与三大改进
PVTv2 由 Wenhai Wang、Enze Xie 等人在论文 PVT v2: Improved Baselines with Pyramid Vision Transformer(arXiv 2106.13797)中提出,作为原始 PVT v1 的改进基线。该模型于 2024-03-13 被贡献进入本仓库(模型文档 docs/source/en/model_doc/pvt_v2.md),ImageNet 预训练权重以 B0–B5 多种规格托管于 Hub,常用模型标识形如 OpenGVLab/pvt_v2_b0。
与列式结构(columnal)的 ViT 不同,PVTv2 属于层级式 Transformer(hierarchical transformer):它在各编码器阶段之间不断对特征图做下采样,从而直接输出 1/4、1/8、1/16、1/32 等多尺度特征图。多尺度特征图保留了细粒度空间细节,非常利于密集预测任务,因此 PVTv2 编码器在学术界通常也被称为 Mix Transformer(MiT)。
PVTv2 相比 PVT v1 引入了三项关键设计,论文将其总结为:
- 线性复杂度注意力层(linear complexity attention layer);
- 重叠 Patch 嵌入(overlapping patch embedding);
- 卷积前馈网络(convolutional feed-forward network)。
借助这三项改进,PVTv2 将 PVT v1 的整体计算复杂度降为线性量级,并在分类、检测、分割等基础视觉任务上取得显著提升。原文摘要明确指出:PVT v2 在这些任务上可以达到与 Swin Transformer 相当甚至更好的表现。
需要强调的架构特色是:PVTv2 完全不使用位置编码(position embedding)。其位置信息完全依赖零填充(zero-padding)与重叠 Patch 嵌入中的卷积隐式编码,这不仅简化了结构,还让模型可以在任意输入分辨率下推理,无需像 ViT 那样对位置编码做插值。
PVTv2 的编码器已经在许多公开工作中作为骨干网络被验证:Segformer(语义分割)、GLPN(单目深度估计)、Panoptic Segformer(全景分割)等都通过 PVTv2 backbone 取得了优异分数,且论文与相关工作中相同规模的 PVTv2 配置普遍优于同等规模 ResNet backbone。
三大核心设计逐层拆解
PVTv2 的完整 PyTorch 实现在 modeling_pvt_v2.py(共约 580 行),下面逐一看清每个设计在源码中的对应实现。
1. 重叠 Patch 嵌入:生成多尺度 token 并为特征注入位置信息
PvtV2OverlapPatchEmbeddings(modeling_pvt_v2.py)在每个编码器阶段之前执行"图像 → Patch token"的转换。它用一个 2D 卷积完成切块:
self.proj = nn.Conv2d(
num_channels,
hidden_size,
kernel_size=patch_size, # 例如 stage1 使用 7
stride=stride, # 例如 stage1 使用 4
padding=(patch_size[0] // 2, patch_size[1] // 2),
)
self.layer_norm = nn.LayerNorm(hidden_size, eps=config.layer_norm_eps)
关键点在于 kernel 尺寸大于 stride(重叠卷积),相邻 patch 之间存在重叠区域(默认配置 kernel [7, 3, 3, 3]、stride [4, 2, 2, 2])。卷积核在空间上滑过图像,感受野重叠且输出 token 保持了空间相邻关系,这一机制本身就是一种隐式的位置注入方式。下采样倍率由 stride 累计决定:四个阶段依次输出输入分辨率的 1/4、1/8、1/16、1/32 特征图(4 → 4×2 → 4×2×2 → 4×2×2×2)。
此外 PvtV2DepthWiseConv(modeling_pvt_v2.py)在 FFN 内部以深度可分离卷积 + 零填充进一步注入位置信息:
self.dwconv = nn.Conv2d(dim, dim, 3, 1, 1, bias=True, groups=dim) # kernel=3, padding=1(零填充)
由于 groups == 输入通道数,每个通道使用一个卷积核,参数量与计算开销都很低——因为该层存在的核心目的只是位置编码,而非特征变换。
2. 卷积前馈网络(ConvFFN)
PVTv2 把经典 Transformer 的 MLP 升级为带深度卷积的 PvtV2ConvFeedForwardNetwork(modeling_pvt_v2.py),在"升维-非线性-降维"的标准结构中插入 DW 卷积:
hidden_states = self.dense1(hidden_states) # Linear: in -> hidden
hidden_states = self.relu(hidden_states) # linear_attention=True 时为 ReLU
hidden_states = self.dwconv(hidden_states, height, width) # 3x3 深度卷积
hidden_states = self.intermediate_act_fn(hidden_states) # 默认 GELU
hidden_states = self.dropout(hidden_states)
hidden_states = self.dense2(hidden_states) # Linear: hidden -> out
从源码可见一个值得注意的实现细节:当 linear_attention=True 时,FFN 会在第一个 Linear 后额外应用一次 ReLU(modeling_pvt_v2.py),对应论文中线性注意力变体对 MLP 的强化。
3. Spatial Reduction Attention(SRA)与 Linear SRA
自注意力计算量是图像任务的主要瓶颈。PVTv2 沿用了 PVT 提出的 SRA:在计算注意力之前,先用带步长的 2D 卷积把 key/value 的隐藏状态在空间上压缩,再让 query 与压缩后的 key/value 做注意力。这一方法把复杂度从 O(n²) 降到 O(n²/R),其中 R 即空间缩减比例 sr_ratio,它同时充当 2D 卷积的 kernel size 与 stride。源码实现位于 PvtV2SelfAttention(modeling_pvt_v2.py):
if self.linear_attention:
self.pool = nn.AdaptiveAvgPool2d(7)
self.spatial_reduction = nn.Conv2d(self.hidden_size, self.hidden_size, kernel_size=1, stride=1)
self.layer_norm = nn.LayerNorm(self.hidden_size, eps=config.layer_norm_eps)
self.act = nn.GELU()
elif spatial_reduction_ratio > 1:
self.spatial_reduction = nn.Conv2d(
self.hidden_size, self.hidden_size,
kernel_size=spatial_reduction_ratio, stride=spatial_reduction_ratio,
)
self.layer_norm = nn.LayerNorm(self.hidden_size, eps=config.layer_norm_eps)
两条分支的区别非常直观:
- SRA(默认):当
sr_ratio > 1时用kernel_size == stride == sr_ratio的卷积将特征图按比例缩小。默认四阶段sr_ratios = (8, 4, 2, 1),即前三个阶段的 key/value 分别被压缩到原来的 1/8、1/4、1/2,最后阶段因分辨率已经很小而不再缩减。 - Linear SRA:由 PVTv2 新增的线性复杂度选项。它使用
nn.AdaptiveAvgPool2d(7)把隐藏状态平均池化到固定的 7×7 尺寸,再经过 1×1 卷积、LayerNorm 与 GELU。由于池化目标尺寸固定,注意力的计算量只与通道数相关、与输入图像分辨率解耦,因此复杂度关于图像大小是线性的——代价是平均池化本身比步长卷积更有信息损失(文档明确标注其 inherently more lossy)。
启用方式是在 PvtV2Config 中设置 linear_attention=True(此时 sr_ratio 将被忽略)。论文中对 B2 尺寸提供了 B2-Linear 变体,其 ImageNet-1K 精度(82.1)甚至略高于同尺寸标准 B2(82.0),而参数量更少(22.6M vs 25.4M),可见"lossy"在工程上换来的是可观的性价比。
注意力剩余部分(Q/K/V 线性投影、缩放点积、Softmax、注意力 Dropout、输出投影)遵循标准 Transformer 实现,缩放系数为 1/sqrt(attention_head_size)。
编码器块的组装与随机深度
每个阶段由"重叠 Patch 嵌入 + 若干 Transformer 块 + 末尾 LayerNorm"构成,见 PvtV2EncoderLayer(modeling_pvt_v2.py)。其中每个 PvtV2BlockLayer(modeling_pvt_v2.py)遵循 pre-LN 残差结构:
hidden = LayerNorm_1 -> SRA/Linear SRA -> DropPath -> (+ 残差)
hidden = LayerNorm_2 -> ConvFFN -> DropPath -> (+ 残差)
各块的随机深度(stochastic depth)衰减率由代码统一生成后按深度索引分配:
drop_path_decays = torch.linspace(0, config.drop_path_rate, sum(config.depths), device="cpu").tolist()
即 dropout 率从 0 到 config.drop_path_rate 在整个网络所有块之间线性递增。四个 PvtV2EncoderLayer 再被 PvtV2Encoder(modeling_pvt_v2.py)串联,每个阶段结束后把 token 序列还原为 (batch, channels, height, width) 的四维特征图再送入下一阶段,从而自然形成多尺度金字塔。编码器默认以 BaseModelOutput 返回 last_hidden_state、hidden_states 与 attentions。
PvtV2Config:配置参数全表
PvtV2Config(定义于 configuration_pvt_v2.py,实现于 src/transformers/models/pvt_v2/configuration_pvt_v2.py)继承 BackboneConfigMixin 与 PreTrainedConfig,model_type = "pvt_v2",并被注册进 Auto 体系(auto_mappings.py 第 514 行将 "pvt_v2" 映射到 PvtV2Config)。下表汇总了全部核心字段的默认值与含义(默认值即 b0 规格的配置):
| 参数 | 默认值 | 说明 |
|---|---|---|
image_size |
224(int / list / tuple / dict) |
训练时输入分辨率;初始化时若为 int 会被自动转为 (224, 224) 元组(见 __post_init__) |
num_channels |
3 |
输入图像通道数(RGB) |
num_encoder_blocks |
4 |
Mix Transformer 编码器中的阶段(stage)数 |
depths |
(2, 2, 2, 2) |
每个阶段内 Transformer 块的数量 |
sr_ratios |
(8, 4, 2, 1) |
每个阶段的 SRA 空间缩减比例(同时也是 2D 卷积的 kernel/stride) |
hidden_sizes |
(32, 64, 160, 256) |
每个阶段的输出通道数(即特征图深度) |
patch_sizes |
(7, 3, 3, 3) |
每个阶段重叠 Patch 嵌入的卷积核尺寸 |
strides |
(4, 2, 2, 2) |
每个阶段 Patch 嵌入的下采样步长,决定金字塔倍率 |
num_attention_heads |
(1, 2, 5, 8) |
每个阶段中每层注意力的头数 |
mlp_ratios |
(8, 8, 4, 4) |
ConvFFN 隐藏层维度与输入维度的比值 |
hidden_act |
"gelu" |
FFN 激活函数 |
hidden_dropout_prob |
0.0 |
全连接层的 dropout 概率 |
attention_probs_dropout_prob |
0.0 |
注意力权重 dropout 概率 |
initializer_range |
0.02 |
截断正态初始化(Linear 权重)的标准差 |
drop_path_rate |
0.0 |
随机深度衰减率上限(按深度线性递增分配) |
layer_norm_eps |
1e-6 |
LayerNorm 的 epsilon |
qkv_bias |
True |
Q/K/V 投影是否使用偏置 |
linear_attention |
False |
是否启用 Linear SRA(置 True 后忽略 sr_ratio,改用固定 7×7 平均池化) |
out_indices / out_features |
由 stage 名推导 | backbone 输出哪些阶段的特征(见下方 Backbone 章节) |
__post_init__ 中还自动完成了两件事(configuration_pvt_v2.py):把标量 image_size 归一为二维元组;按 depths 长度生成 stage1…stage4 的 stage_names,并将用户传入的 out_indices/out_features 传给 set_output_features_output_indices 方法。也就是说,backbone 模式的输出阶段命名在配置层就已约定好。
需要注意 hidden_sizes 与 num_attention_heads 必须能被整除——源码在 PvtV2SelfAttention 初始化时显式校验:若 hidden_size 不是 num_attention_heads 的整数倍会抛出 ValueError。自定义各阶段维度时请保证满足整除关系。
快速上手:图像分类推理
文档给出的标准入门方式是用 Auto 类从 Hub 加载任意尺寸的 PVTv2 预训练权重(需联网下载 checkpoint,且环境需安装 torch、transformers 及 Pillow):
import requests
import torch
from PIL import Image
from transformers import AutoImageProcessor, AutoModelForImageClassification
model = AutoModelForImageClassification.from_pretrained("OpenGVLab/pvt_v2_b0", device_map="auto")
image_processor = AutoImageProcessor.from_pretrained("OpenGVLab/pvt_v2_b0")
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw)
processed = image_processor(image)
outputs = model(torch.tensor(processed["pixel_values"]))
底层调用链为:PvtV2Config 注册于 CONFIG_MAPPING,PvtV2Model/PvtV2ForImageClassification 注册于 MODEL_MAPPING(见 modeling_auto.py),图像预处理器则由 image_processing_auto.py 将 "pvt_v2" 映射到 PvtImageProcessor。因此全程无需显式 import 任何 pvt_v2 相关类。
分类头逻辑(PvtV2ForImageClassification,modeling_pvt_v2.py)为:取编码器输出的四维特征图 → 展平为 (batch, h*w, hidden) → 沿空间维做全局平均池化 → 送入线性分类头:
sequence_output = sequence_output.permute(0, 2, 3, 1).reshape(batch_size, -1, self.config.hidden_sizes[-1])
sequence_output = sequence_output.mean(dim=1)
logits = self.classifier(sequence_output)
若传入 labels([0, config.num_labels - 1] 的索引),则会基于 config.num_labels 自动计算交叉熵(多类)或 MSE(单类回归)损失并返回带 loss 的 ImageClassifierOutput。仓库测试 test_modeling_pvt_v2.py 对该路径做了完整校验,例如验证分类输出 logits 形状为 (batch, num_labels)。
PvtV2Model:纯粹的层级编码器
若只需要多尺度特征提取而不需要分类头,可直接使用 PvtV2Model(modeling_pvt_v2.py)。它本质上就是 PvtV2Encoder 的一个薄封装:
from transformers import PvtV2Model, PvtV2Config
configuration = PvtV2Config() # pvt_v2_b0 风格配置
model = PvtV2Model(configuration)
PvtV2Model 的前向参数与标准视觉编码器一致:pixel_values、output_attentions、output_hidden_states、return_dict。它的 base_model_prefix = "pvt_v2"、main_input_name = "pixel_values"、supports_gradient_checkpointing = True,说明它天然支持梯度检查点以节省显存。模型的 _init_weights 实现也值得注意(modeling_pvt_v2.py):Linear 层用标准差为 initializer_range 的截断正态初始化;Conv2d 层按 sqrt(2/fan_out) 的正态初始化(即 He 初始化思路)。
把 PVTv2 当 Backbone 用:AutoBackbone 与 Deformable DETR
PVTv2 的多尺度输出让它非常适合替换 DETR、MaskFormer 等架构中的传统 CNN backbone。PvtV2Backbone(modeling_pvt_v2.py)继承自 BackboneMixin 与 PvtV2Model,被注册在 backbone 映射中(modeling_auto.py),因此可以通过 AutoBackbone 使用。其 docstring 给出了典型示例(224×224 输入在 stage4 输出 [1, 256, 7, 7] 的特征图,正好对应 1/32 下采样与 b0 的 256 维末阶段通道数):
from transformers import AutoBackbone, AutoImageProcessor
processor = AutoImageProcessor.from_pretrained("OpenGVLab/pvt_v2_b0")
model = AutoBackbone.from_pretrained(
"OpenGVLab/pvt_v2_b0", out_features=["stage1", "stage2", "stage3", "stage4"]
)
inputs = processor(image, return_tensors="pt")
outputs = model(**inputs)
feature_maps = outputs.feature_maps # 四张多尺度特征图
backbone 的 forward 会强制编码器输出 hidden_states,然后按 stage_names(stage1–stage4)过滤出 out_features 指定的阶段特征图,最终以 BackboneOutput(feature_maps=...) 形式返回。实际推理中可用任意输出子集,例如只取 out_features=["stage3", "stage4"] 以节省显存。
要把它装进更大的检测模型(如 Deformable DETR),文档给出的模式是:用 backbone_config 在构建阶段替换原模型的 backbone。注意替换后的 backbone 是随机初始化权重,需要重新微调,不能直接零样本使用:
import requests
import torch
from PIL import Image
from transformers import AutoConfig, AutoImageProcessor, AutoModelForObjectDetection
model = AutoModelForObjectDetection.from_config(
config=AutoConfig.from_pretrained(
"SenseTime/deformable-detr",
backbone_config=AutoConfig.from_pretrained("OpenGVLab/pvt_v2_b5"),
),
)
image_processor = AutoImageProcessor.from_pretrained("SenseTime/deformable-detr")
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw)
processed = image_processor(image)
outputs = model(torch.tensor(processed["pixel_values"]))
这段代码展示了"模型级替换":外层读取 SenseTime/deformable-detr 的配置,backbone_config 则指向 OpenGVLab/pvt_v2_b5,最终 AutoModelForObjectDetection 依据合并后的配置构建一个以 PVTv2-B5 为骨干的检测器。
规模与精度:ImageNet-1K 预训练基线
PVTv2 提供 B0–B5 六种规格(另有 B2-Linear 变体),全部在 ImageNet-1K 上以 224×224 分辨率预训练。下表转载自官方文档(原始数据源自论文/原版代码库),可据此按算力与精度预算选型:
| 方法 | 输入尺寸 | Acc@1 | 参数量 (M) |
|---|---|---|---|
| PVT-V2-B0 | 224 | 70.5 | 3.7 |
| PVT-V2-B1 | 224 | 78.7 | 14.0 |
| PVT-V2-B2-Linear | 224 | 82.1 | 22.6 |
| PVT-V2-B2 | 224 | 82.0 | 25.4 |
| PVT-V2-B3 | 224 | 83.1 | 45.2 |
| PVT-V2-B4 | 224 | 83.6 | 62.6 |
| PVT-V2-B5 | 224 | 83.8 | 82.0 |
从 B0 的 3.7M 参数到 B5 的 82M 参数,跨度约 22 倍,配合"可任意分辨率推理、无需位置编码插值"的特性,PVTv2 特别适合做轻量级骨干或在不同输入尺度间灵活切换。仓库中的转换脚本 convert_pvt_v2_to_pytorch.py 提供了从原版 PVT 官方仓库权重到 Transformers 格式的转换逻辑(含 QKV 重组、权重键名映射与可选 ImageNet 精度校验),说明 Hub 上的预训练权重与论文基线保持同源一致。
结论与延伸阅读
PVTv2 通过在层级式视觉 Transformer 中融入 CNN 的归纳偏置(重叠卷积、深度卷积、零填充位置信息),同时保留自注意力的全局建模与动态响应能力,是"卷积与 Transformer 混合"设计思路的代表作。本文覆盖了其文档所定义的核心事实:三大设计改进、PvtV2Config 全参数字典、Auto 体系使用方式、backbone 替换流程与规模基线。想进一步深入,可以在本仓库内沿着以下路径阅读:
- 完整模型文档:docs/source/en/model_doc/pvt_v2.md;
- 配置与实现源码:configuration_pvt_v2.py、modeling_pvt_v2.py;
- 官方权重转换脚本:convert_pvt_v2_to_pytorch.py;
- 自动化测试(覆盖配置校验、前向形状、分类 logits、backbone 输出等):tests/models/pvt_v2/test_modeling_pvt_v2.py;
- Auto 映射注册:auto_mappings.py、modeling_auto.py、image_processing_auto.py。
结合源码中的随机深度分配、整除性校验、backbone 输出过滤等实现细节,你可以在完全理解底层计算逻辑的前提下,自由地在 Transformers 中微调、替换或二次开发 PVTv2。
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 StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
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