首页
/ Transformers 中的 PVTv2 全解析:金字塔视觉 Transformer V2 的架构原理、源码实现与实战使用

Transformers 中的 PVTv2 全解析:金字塔视觉 Transformer V2 的架构原理、源码实现与实战使用

2026-09-07 09:22:40作者:裘旻烁

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 引入了三项关键设计,论文将其总结为:

  1. 线性复杂度注意力层(linear complexity attention layer);
  2. 重叠 Patch 嵌入(overlapping patch embedding);
  3. 卷积前馈网络(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 并为特征注入位置信息

PvtV2OverlapPatchEmbeddingsmodeling_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)。

此外 PvtV2DepthWiseConvmodeling_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 升级为带深度卷积的 PvtV2ConvFeedForwardNetworkmodeling_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。源码实现位于 PvtV2SelfAttentionmodeling_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"构成,见 PvtV2EncoderLayermodeling_pvt_v2.py)。其中每个 PvtV2BlockLayermodeling_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 再被 PvtV2Encodermodeling_pvt_v2.py)串联,每个阶段结束后把 token 序列还原为 (batch, channels, height, width) 的四维特征图再送入下一阶段,从而自然形成多尺度金字塔。编码器默认以 BaseModelOutput 返回 last_hidden_statehidden_statesattentions

PvtV2Config:配置参数全表

PvtV2Config(定义于 configuration_pvt_v2.py,实现于 src/transformers/models/pvt_v2/configuration_pvt_v2.py)继承 BackboneConfigMixinPreTrainedConfigmodel_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 长度生成 stage1stage4stage_names,并将用户传入的 out_indices/out_features 传给 set_output_features_output_indices 方法。也就是说,backbone 模式的输出阶段命名在配置层就已约定好。

需要注意 hidden_sizesnum_attention_heads 必须能被整除——源码在 PvtV2SelfAttention 初始化时显式校验:若 hidden_size 不是 num_attention_heads 的整数倍会抛出 ValueError。自定义各阶段维度时请保证满足整除关系。

快速上手:图像分类推理

文档给出的标准入门方式是用 Auto 类从 Hub 加载任意尺寸的 PVTv2 预训练权重(需联网下载 checkpoint,且环境需安装 torchtransformers 及 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_MAPPINGPvtV2Model/PvtV2ForImageClassification 注册于 MODEL_MAPPING(见 modeling_auto.py),图像预处理器则由 image_processing_auto.py"pvt_v2" 映射到 PvtImageProcessor。因此全程无需显式 import 任何 pvt_v2 相关类。

分类头逻辑(PvtV2ForImageClassificationmodeling_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(单类回归)损失并返回带 lossImageClassifierOutput。仓库测试 test_modeling_pvt_v2.py 对该路径做了完整校验,例如验证分类输出 logits 形状为 (batch, num_labels)

PvtV2Model:纯粹的层级编码器

若只需要多尺度特征提取而不需要分类头,可直接使用 PvtV2Modelmodeling_pvt_v2.py)。它本质上就是 PvtV2Encoder 的一个薄封装:

from transformers import PvtV2Model, PvtV2Config

configuration = PvtV2Config()          # pvt_v2_b0 风格配置
model = PvtV2Model(configuration)

PvtV2Model 的前向参数与标准视觉编码器一致:pixel_valuesoutput_attentionsoutput_hidden_statesreturn_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。PvtV2Backbonemodeling_pvt_v2.py)继承自 BackboneMixinPvtV2Model,被注册在 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_namesstage1stage4)过滤出 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 替换流程与规模基线。想进一步深入,可以在本仓库内沿着以下路径阅读:

结合源码中的随机深度分配、整除性校验、backbone 输出过滤等实现细节,你可以在完全理解底层计算逻辑的前提下,自由地在 Transformers 中微调、替换或二次开发 PVTv2。

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

项目优选

收起
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
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391