Transformers 中 ResNet 实现全解析:从 ResNet v1.5 配置到图像分类与 Backbone 特征提取
本文围绕 Hugging Face Transformers 的 ResNet 官方模型文档(docs/source/en/model_doc/resnet.md)展开,系统讲解 ResNet 残差网络的设计思想、Transformers 对 "ResNet v1.5" 变体的具体实现、ResNetConfig 的全部关键配置项,以及如何用 ResNetForImageClassification 做图像分类、用 ResNetBackbone 提取多尺度特征图。读完本文,你将能够独立构建/加载 ResNet 模型、按 ResNet-18/34/50/101/152 规格定制配置,并理解其 Stem、Basic/Bottleneck 残差层与 Stage 的组织方式。
1. 背景:ResNet 与 Transformers 采用的 "v1.5" 变体
ResNet 出自论文 Deep Residual Learning for Image Recognition(Kaiming He 等人,2015)。其核心贡献是残差连接(residual connection):将每一层重新表述为"以层输入为参考学习残差函数",而非学习无参考函数,从而使得数百甚至上千层的深层网络可被有效训练,并凭借极大的表示深度在 2015 年 ILSVRC & COCO 竞赛中拿下 ImageNet 分类、检测、定位以及 COCO 检测与分割的冠军。
Transformers 中的实现与原始论文有一个刻意的小差异(文档原文强调):下采样(stride=2)被放在 bottleneck 的 3x3 卷积上,而不是第一个 1x1 卷积上。这一变体由 Nvidia 提出并推广,业界通称为 "ResNet v1.5"。这一差异在源码中由 downsample_in_bottleneck 开关控制(见下文 modeling_resnet.py 中 ResNetBottleNeckLayer 的实现)。
论文摘要(原文收录于文档)指出:在 ImageNet 上评估了深度至 152 层(比 VGG 深 8 倍但复杂度更低)的残差网络,集成模型在 ImageNet 测试集上取得 3.57% 错误率;在 CIFAR-10 上还验证了 100 层与 1000 层网络的可行性。
该模型由社区贡献者 Francesco 于 2022-03-14 提交至 Transformers,官方预训练检查点命名为
microsoft/resnet-50、microsoft/resnet-18等(文档中ResNetConfig的 autodoc 即以microsoft/resnet-50为示例检查点)。
2. 模块组成:从 Conv 原子到完整模型
阅读 src/transformers/models/resnet/ 目录可以看到实现分为三部分:
- configuration_resnet.py:
ResNetConfig配置类; - modeling_resnet.py:
ResNetModel、ResNetForImageClassification、ResNetBackbone; - convert_resnet_to_pytorch.py:从 timm 转换官方权重的脚本,其中定义了各规格 ResNet 的完整参数表。
modeling_resnet.py 中的构建层级自底向上为:
ResNetConvLayer(modeling_resnet.py#L39-L69):Conv2d + BatchNorm2d + 激活函数三件套,默认relu,padding = kernel_size // 2;ResNetEmbeddings(Stem,#L72-L93):单块"激进卷积"——7x7、stride=2卷积将num_channels映射到embedding_size,随后3x3、stride=2的MaxPool2d进一步下采样;并会校验输入通道数与config.num_channels一致;ResNetShortCut(#L96-L110):1x1卷积 + BatchNorm 的跳跃连接,用于在通道数或步长变化时把残差投影到正确尺寸(通道数与步长均不变时退化为nn.Identity);ResNetBasicLayer(#L113-L136):两个3x3卷积组成的经典残差块,供 ResNet-18/34 使用;ResNetBottleNeckLayer(#L139-L178):1x1 降维 -> 3x3 -> 1x1 升维的瓶颈块,reduction=4。注意其中两个 stride 分支:
ResNetConvLayer(in_channels, reduces_channels, kernel_size=1,
stride=stride if downsample_in_bottleneck else 1),
ResNetConvLayer(reduces_channels, reduces_channels,
stride=stride if not downsample_in_bottleneck else 1),
即 downsample_in_bottleneck=True 时下采样前移到第一个 1x1 卷积(经典 v1.0),默认 False 时留在 3x3 卷积上(v1.5,Transformers 的默认行为,与文档描述一致);
6. ResNetStage(#L181-L216):按 config.layer_type 选用 Bottleneck 或 Basic 层,首层带步长、后续层等通道堆叠 depth 层;
7. ResNetEncoder(#L219-L257):依次串起所有 stage。第一个 stage 的步长由 downsample_in_first_stage 决定(stride=2 或 1),其余 stage 固定 stride=2。若 output_hidden_states=True,会记录每个 stage 前后的中间特征(共 num_stages + 1 个张量)。
ResNetModel 在 embedder 与 encoder 之外再加一个 AdaptiveAvgPool2d((1, 1)) 池化器,输出 last_hidden_state(最终特征图)、pooler_output(全局平均池化结果)与可选的 hidden_states。
3. ResNetConfig:完整配置参数说明
ResNetConfig 继承 BackboneConfigMixin 与 PreTrainedConfig,model_type = "resnet"。结合 configuration_resnet.py#L52-L75 的源码,完整参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
num_channels |
3 |
输入图像通道数,需与 Stem 卷积的输入一致 |
embedding_size |
64 |
Stem 卷积输出通道数(stage1 的输入通道) |
hidden_sizes |
(256, 512, 1024, 2048) |
各 stage 输出通道数,即 ResNet-50 及以上规格 |
depths |
(3, 4, 6, 3) |
各 stage 中残差块的层数 |
layer_type |
"bottleneck" |
"basic"(ResNet-18/34)或 "bottleneck"(ResNet-50 及以上),非法取值会由 validate_layer_type 抛错 |
hidden_act |
"relu" |
残差块激活函数 |
downsample_in_first_stage |
False |
True 时第一个 stage 也用 stride=2 下采样(经典配置) |
downsample_in_bottleneck |
False |
True 时瓶颈块在第一个 1x1 卷积上下采样(v1.0),否则在 3x3 卷积上(v1.5) |
num_labels / id2label / label2id |
— | 分类头相关,由 PreTrainedConfig 提供 |
在 __post_init__ 中,配置会自动生成 stage_names = ["stem", "stage1", "stage2", "stage3", "stage4"],并通过 set_output_features_output_indices 解析 out_indices / out_features(Backbone 输出哪些特征图)。
文档给出的最小示例(可直接运行,随机权重):
from transformers import ResNetConfig, ResNetModel
# 初始化 resnet-50 风格的配置(默认值即为 resnet-50 规格)
configuration = ResNetConfig()
# 从该配置初始化模型(随机权重)
model = ResNetModel(configuration)
# 访问模型配置
configuration = model.config
规格对照表:convert_resnet_to_pytorch.py#L128-L147 中定义了官方转换脚本使用的六种规格,可作为自定义配置的权威参照:
| 规格 | depths |
hidden_sizes |
layer_type |
|---|---|---|---|
| resnet18 | [2, 2, 2, 2] |
[64, 128, 256, 512] |
basic |
| resnet26 | [2, 2, 2, 2] |
[256, 512, 1024, 2048] |
bottleneck |
| resnet34 | [3, 4, 6, 3] |
[64, 128, 256, 512] |
basic |
| resnet50 | [3, 4, 6, 3] |
[256, 512, 1024, 2048] |
bottleneck |
| resnet101 | [3, 4, 23, 3] |
[256, 512, 1024, 2048] |
bottleneck |
| resnet152 | [3, 8, 36, 3] |
[256, 512, 1024, 2048] |
bottleneck |
可见 basic 规格(18/34)通道数减半(最高 512),bottleneck 规格(26 及以上)统一为 256/512/1024/2048,深度由 depths 区分。此外该脚本还从 huggingface/label-files 数据集拉取 ImageNet-1k 的 id2label(1000 类),说明官方检查点的分类头均对应 1000 类 ImageNet。
4. ResNetForImageClassification:图像分类用法
文档 Resources 一节明确:ResNetForImageClassification 被 image-classification 管线支持,配套示例脚本为 examples/pytorch/image-classification/run_image_classification.py,任务指南见 docs/source/en/tasks/image_classification.md。
模型本体在 modeling_resnet.py#L340-L384:内部实例化一个 ResNetModel,分类头为 nn.Flatten() + nn.Linear(hidden_sizes[-1], num_labels)(num_labels=0 时退化为 Identity,可当作特征提取器使用)。forward 的签名为:
def forward(
self,
pixel_values: torch.FloatTensor | None = None,
labels: torch.LongTensor | None = None,
output_hidden_states: bool | None = None,
return_dict: bool | None = None,
**kwargs,
) -> ImageClassifierOutputWithNoAttention:
pixel_values:图像张量;labels:形状(batch_size,),取值范围[0, num_labels-1]。提供后按 Cross-Entropy 计算loss(num_labels > 1时);- 返回
ImageClassifierOutputWithNoAttention,含loss、logits、hidden_states。
推理端的最小闭环(检查点 microsoft/resnet-50,与测试用例 tests/models/resnet/test_modeling_resnet.py#L268-L291 一致):
import torch
from transformers import ResNetForImageClassification, AutoImageProcessor
model = ResNetForImageClassification.from_pretrained("microsoft/resnet-50")
processor = AutoImageProcessor.from_pretrained("microsoft/resnet-50")
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
# ImageNet-1k 检查点的 logits 形状为 (1, 1000)
print(outputs.logits.shape)
集成测试中,对固定测试图像的 logits 前三个值做了切片比对([-11.1069, -9.7877, -8.3777],CPU 与 CUDA 一致,容差 2e-4),可作为复现环境时的数值校验手段。
权重转换:如需自行转换 timm 权重,可用 convert_resnet_to_pytorch.py。其命令行参数为:
python convert_resnet_to_pytorch.py \
--model_name resnet50 \
--pytorch_dump_folder_path ./output \
--push_to_hub False
脚本内部用 Tracker 记录前向传播中实际参与计算的叶子模块(Conv2d/BatchNorm2d 等),按顺序把 timm 模型参数拷入 Transformers 模型,并在推送前断言 torch.allclose(from_model(x), our_model(x).logits) 保证输出一致。
5. ResNetBackbone:面向 DETR / MaskFormer 的多尺度特征提取
除分类头外,文档的 autodoc 体系还覆盖 ResNetBackbone(BackboneMixin 实现,用于 DETR、MaskFormer 等下游框架)。其 forward 的官方示例(见 modeling_resnet.py#L415-L440):
from transformers import AutoImageProcessor, AutoBackbone
processor = AutoImageProcessor.from_pretrained("microsoft/resnet-50")
model = AutoBackbone.from_pretrained(
"microsoft/resnet-50",
out_features=["stage1", "stage2", "stage3", "stage4"],
)
inputs = processor(image, return_tensors="pt")
outputs = model(**inputs)
feature_maps = outputs.feature_maps
list(feature_maps[-1].shape) # [1, 2048, 7, 7](224x224 输入时)
实现上,ResNetBackbone.forward 强制以 output_hidden_states=True 前向,拿到 encoder 中每个 stage 的特征,再按 out_features 在 stage_names 中匹配并挑选对应的特征图;out_features=None 时退化为只输出最后一层。测试用例 test_modeling_resnet.py#L117-L144 同时验证了两种情形下的特征图形状与 channels 属性(如 stage2 特征为 [B, hidden_sizes[1], 4, 4],out_features=None 时为 [B, hidden_sizes[-1], 1, 1])。
此外,从 Auto 映射可确认各入口均已注册(auto/modeling_auto.py):AutoModel -> ResNetModel、AutoModelForImageClassification -> ResNetForImageClassification、AutoBackbone -> ResNetBackbone;图像处理器方面,resnet 模型族复用 ConvNext 的 image processor(auto/image_processing_auto.py#L141),这解释了为什么 microsoft/resnet-50 直接搭配 AutoImageProcessor 即可工作。
6. 权重初始化与其他实现细节
- 初始化:
ResNetPreTrainedModel._init_weights(modeling_resnet.py#L268-L287)对Conv2d使用 Kaiming 正态(fan_out+ relu)、对Linear使用 Kaiming 均匀并给 bias 按1/sqrt(fan_in)界内均匀采样、对 BatchNorm 置weight=1, bias/mean=0, var=1,与 PyTorch 官方reset_parameters语义对齐; - 无注意力:模型声明
has_attentions = False,测试中相应跳过了inputs_embeds、embedding 增删、FFN chunking 等文本模型用例(test_modeling_resnet.py#L190-L242); - 管线映射:
image-feature-extraction->ResNetModel,image-classification->ResNetForImageClassification(test_modeling_resnet.py#L169-L173); - 空间下采样总倍数:从测试断言
image_size // 32可知,默认配置下(downsample_in_first_stage=False)Stem 下采样 4 倍 + 3 个后续 stage 各 2 倍,最终特征图边长为输入的 1/32;若开启downsample_in_first_stage,则首 stage 额外下采样 1 倍(对应经典 v1.0 布局)。
7. 小结
Transformers 的 ResNet 文档与实现构成了一条完整的链路:以 ResNet v1.5(stride 位于 3x3 卷积)为默认架构,用 depths + hidden_sizes + layer_type 三个参数覆盖 ResNet-18 到 ResNet-152 全部规格;ResNetModel 提供带全局池化的基础输出,ResNetForImageClassification 加上 1000 类 ImageNet 分类头并被 image-classification 训练脚本与管线支持,ResNetBackbone 则通过 out_features 暴露 stage1~stage4 多尺度特征图供检测/分割框架复用。测试套件 tests/models/resnet/test_modeling_resnet.py 覆盖了形状校验、hidden states 数量(stage 数 + 1)与 microsoft/resnet-50 的 logits 数值比对,可直接用于验证本地环境的正确性。
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 StartedRust0626
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00