发布于2026-07-09 阅读(0)
扫一扫,手机访问
大家在使用PyTorch预训练模型做特征提取时,最常见的问题就是:直接用 model.features 结果报错了。我来梳理一下,这背后到底是怎么回事,以及正确做法是什么。
一句话总结:PyTorch新版的预训练模型,根本没有统一的 features 属性。旧版VGG或某些第三方封装里常见这个写法,但已经不再适用了。ResNet需要手动从conv1到layer4逐一拼接;VGG16虽然有features属性,但要切片还得靠列表索引;EfficientNet_b0虽然有features,但内部是MBConv嵌套结构,切片时需要格外小心,并且最后一定要记得加上eval()。

model.features 会报错原因很简单:像 resnet50、vgg16、efficientnet_b0 这些官方预训练模型,并没有统一的 features 属性。这个写法在 torchvision 0.12 以前的旧版VGG或某些第三方封装里确实存在,但新版中已经被移除了。直接访问,系统会直接给你一个 AttributeError: 'ResNet' object has no attribute 'features'。
所以,最可靠的方式,是先把模型结构搞清楚,定位到特征提取的终点(通常是最后一个全局池化层之前),然后用 nn.Sequential 精准地截取子模块。
resnet50 的主干从 model.layer1 到 model.layer4,后面是 model.a vgpool 和 model.fc。特征层应该截到 layer4 的输出为止。vgg16 的卷积部分在 model.features(确实有这个属性!),但它包含所有卷积、激活和池化。你很可能只想截取到某一层,比如第29层(倒数第二个卷积)。efficientnet_b0 也没有统一的 features 属性。虽然它的主干也叫 model.features(这里确实有!),并且是 nn.Sequential,可以直接索引,但内部模块命名不一致(比如包含 Conv2d、SiLU、MBConv),操作起来需要多留个心眼。nn.Sequential 切 resnet50:别漏掉 model.conv1 和 model.bn1很多人只切 layer1 到 layer4,但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.features 是 nn.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), ...)
几个常见的需求场景:
Conv2d + ReLU):用 nn.Sequential(*modules[:29])MaxPool2d:用 nn.Sequential(*modules[:-1])(去掉末尾的池化层)modules[28] 是 ReLU,modules[29] 是 Conv2d。这个顺序千万不能反,否则激活会在卷积之前,语义就完全错了。efficientnet_b0 时小心 MBConv 和 nn.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的行为会异常,导致特征很不稳定。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8