商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > 如何在Python中提取PyTorch预训练模型的特征层_利用nn.Sequential进行模型切片

如何在Python中提取PyTorch预训练模型的特征层_利用nn.Sequential进行模型切片

  发布于2026-07-09 阅读(0)

扫一扫,手机访问

大家在使用PyTorch预训练模型做特征提取时,最常见的问题就是:直接用 model.features 结果报错了。我来梳理一下,这背后到底是怎么回事,以及正确做法是什么。

一句话总结:PyTorch新版的预训练模型,根本没有统一的 features 属性。旧版VGG或某些第三方封装里常见这个写法,但已经不再适用了。ResNet需要手动从conv1layer4逐一拼接;VGG16虽然有features属性,但要切片还得靠列表索引;EfficientNet_b0虽然有features,但内部是MBConv嵌套结构,切片时需要格外小心,并且最后一定要记得加上eval()

如何在Python中提取PyTorch预训练模型的特征层_利用nn.Sequential进行模型切片

为什么直接用 model.features 会报错

原因很简单:像 resnet50vgg16efficientnet_b0 这些官方预训练模型,并没有统一的 features 属性。这个写法在 torchvision 0.12 以前的旧版VGG或某些第三方封装里确实存在,但新版中已经被移除了。直接访问,系统会直接给你一个 AttributeError: 'ResNet' object has no attribute 'features'

所以,最可靠的方式,是先把模型结构搞清楚,定位到特征提取的终点(通常是最后一个全局池化层之前),然后用 nn.Sequential 精准地截取子模块。

  • resnet50 的主干从 model.layer1model.layer4,后面是 model.a vgpoolmodel.fc。特征层应该截到 layer4 的输出为止。
  • vgg16 的卷积部分在 model.features(确实有这个属性!),但它包含所有卷积、激活和池化。你很可能只想截取到某一层,比如第29层(倒数第二个卷积)。
  • efficientnet_b0 也没有统一的 features 属性。虽然它的主干也叫 model.features(这里确实有!),并且是 nn.Sequential,可以直接索引,但内部模块命名不一致(比如包含 Conv2dSiLUMBConv),操作起来需要多留个心眼。

nn.Sequentialresnet50:别漏掉 model.conv1model.bn1

很多人只切 layer1layer4,但ResNet的第一层是独立的 conv1 + bn1 + relu + maxpool,它们不属于任何 layerX,却是特征提取流程的关键部分。漏掉它们,会导致输入维度不匹配,甚至特征信息直接丢失。

正确的做法是把前四块全部打包在一起:

import torch.nn as nn
from torchvision.models import resnet50

model = resnet50(pretrained=True)
model.eval()

# 提取完整特征主干(不含 a vgpool 和 fc)
feature_extractor = nn.Sequential(
    model.conv1,
    model.bn1,
    model.relu,
    model.maxpool,
    model.layer1,
    model.layer2,
    model.layer3,
    model.layer4
)

注意,model.layer4 的输出是 [B, 2048, H, W]。如果你后续需要一个固定长度的向量,还得自己加 nn.AdaptiveA vgPool2d(1) 或者调用 F.adaptive_a vg_pool2d

vgg16 做精细切片:用 list(model.features) 查索引

vgg16.featuresnn.Sequential,但它内部是按顺序堆叠起来的模块列表,没有明确的层级名。想取“前20层”该怎么办?答案是把它转成Python列表,看清楚结构:

model = vgg16(pretrained=True)
modules = list(model.features)
print(len(modules))  # 输出 31
print(modules[0])    # Conv2d(3, 64, kernel_size=(3, 3), ...)
print(modules[29])   # Conv2d(512, 512, kernel_size=(3, 3), ...)

几个常见的需求场景:

  • 取backbone到最后一个卷积(不含最后两个 Conv2d + ReLU):用 nn.Sequential(*modules[:29])
  • 想保留空间分辨率(比如做检测或分割),避免最后的 MaxPool2d:用 nn.Sequential(*modules[:-1])(去掉末尾的池化层)
  • 注意,modules[28]ReLUmodules[29]Conv2d。这个顺序千万不能反,否则激活会在卷积之前,语义就完全错了。

efficientnet_b0 时小心 MBConvnn.Sequential 嵌套

efficientnet_b0.features 看起来是一个扁平的 Sequential,但实际内部第1到第7层是 MBConv 模块,而每个 MBConv 自身又是一个 nn.Sequential。如果你直接索引 model.features[5],拿到的是整个 MBConv,无法进一步拆解。

如果你的需求只是截取到第5个 MBConv 的输出(也就是stage 3结束),那可以安全地这样切:

feature_extractor = nn.Sequential(*model.features[:6])  # 包含第0~5层(共6个模块)

但如果你想精确停在某个 MBConv 内部(比如只取它的depthwise卷积之后),那 nn.Sequential 切片就做不到了。这时必须重写该模块,或者使用 register_forward_hook

还有一点要注意:efficientnet_b0 默认输入尺寸是224×224,它的stem(前两层)对小尺寸图像比较敏感。切片之后,如果输入变小(比如112×112),一定要确认前几层的stride是否会导致输出尺寸为0(比如 torch.Size([1, 32, 0, 0]))。

最后,提醒一个容易忽略的细节:所有切片后的模型,默认仍然处于 training=True 模式。即使原模型已经调过 eval(),切片后也务必手动调用 feature_extractor.eval(),否则BatchNorm和Dropout的行为会异常,导致特征很不稳定。

本文转载于:https://www.php.cn/faq/2412551.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注