手动遍历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的核心残差逻辑:
- 未保留输入
x作为identity张量 - 未将
conv2+bn2的输出与identity相加 - 最后未执行额外的ReLU激活
而调用bb2(x)时,会完整执行上述残差连接逻辑,因此两者输出结果必然存在差异。
内容的提问来源于stack exchange,提问作者Dr. Prof. Patrick
相关产品推荐
相关产品推荐

