You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

手动遍历ResNet层与调用forward结果不一致的原因排查

手动遍历ResNet BasicBlock层与调用forward输出不一致的原因

我在提取预训练ResNet中间层结果时,发现手动遍历特定Block的层会导致模型准确率下降,进一步排查发现,手动遍历层得到的中间结果和调用Block内置forward函数的结果不一致。

相关代码与模型结构

模型拆分代码

net = resnet18()
before = torch.nn.Sequential(*list(net.children())[:7])
middle = list(net.children())[7]

middle的结构

middle是包含两个BasicBlock的Sequential结构,具体如下:

Sequential(
  (0): BasicBlock(
    (conv1): Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (downsample): Sequential(
      (0): Conv2d(256, 512, kernel_size=(1, 1), stride=(2, 2), bias=False)
      (1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    )
  )
  (1): BasicBlock(
    (conv1): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (relu): ReLU(inplace=True)
    (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)
    (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  )
)

测试代码

x = self.before(x)
bb1 = list(self.middle.children())[0]
x = bb1(x)
bb2 = list(self.middle.children())[1]

print(bb2(x))
for i, layer in enumerate(bb2.children()):
    x = layer(x)
    if i == 0:
        z = copy.copy(x)

print(x)

输出结果对比

  • 第一次打印(调用bb2(x)):
tensor([[[[1.1271e-01, 1.1205e-01],
          [1.6054e-01, 1.4965e-01]],...)
  • 第二次打印(手动遍历bb2的层):
tensor([[[[ 0.0533,  0.0498],
          [ 0.0607,  0.0574]],...)

原因分析

核心问题在于ResNet的BasicBlock并非简单按顺序执行子层,而是包含残差连接的自定义forward逻辑。

PyTorch官方ResNet中BasicBlock的标准forward实现如下:

class BasicBlock(nn.Module):
    def forward(self, x):
        identity = x

        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        if self.downsample is not None:
            identity = self.downsample(x)

        out += identity
        out = self.relu(out)

        return out

你手动遍历bb2.children()时,仅按顺序执行了conv1→bn1→relu→conv2→bn2,但完全忽略了BasicBlock的核心残差逻辑:

  1. 未保留输入x作为identity张量
  2. 未将conv2+bn2的输出与identity相加
  3. 最后未执行额外的ReLU激活

而调用bb2(x)时,会完整执行上述残差连接逻辑,因此两者输出结果必然存在差异。

内容的提问来源于stack exchange,提问作者Dr. Prof. Patrick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 07:35:39