ResNet-34 残差块 PyTorch 实现:Identity 与 Convolution Block 的 3 处关键差异
ResNet-34 残差块 PyTorch 实现Identity 与 Convolution Block 的 3 处关键差异深度残差网络ResNet作为计算机视觉领域的里程碑式架构其核心创新在于残差块的设计。本文将聚焦 ResNet-34 中两种基础残差块Identity Block 和 Convolution Block的 PyTorch 实现差异通过代码级对比揭示通道匹配、维度变换和计算效率三个关键设计要点。1. 残差块基础结构与实现框架残差块的核心思想是通过跨层连接skip connection解决深度网络中的梯度消失问题。在 PyTorch 中我们可以构建一个基础残差块类作为两种变体的父类import torch import torch.nn as nn class BasicBlock(nn.Module): expansion 1 # 通道扩展系数 def __init__(self, in_channels, out_channels, stride1): super().__init__() self.conv1 nn.Conv2d(in_channels, out_channels, kernel_size3, stridestride, padding1, biasFalse) self.bn1 nn.BatchNorm2d(out_channels) self.conv2 nn.Conv2d(out_channels, out_channels, kernel_size3, stride1, padding1, biasFalse) self.bn2 nn.BatchNorm2d(out_channels) self.relu nn.ReLU(inplaceTrue) self.shortcut nn.Sequential() # 默认identity mapping这个基础类定义了两种残差块共有的组件两个 3×3 卷积层conv1 bn1 relu → conv2 bn2ReLU 激活函数inplace 操作节省内存shortcut 路径初始化为恒等映射2. Identity Block 的纯净数据通路Identity Block 适用于输入输出维度完全匹配的场景其特点是无参数 shortcutclass IdentityBlock(BasicBlock): def __init__(self, in_channels, out_channels, stride1): super().__init__(in_channels, out_channels, stride) assert in_channels out_channels # 通道数必须相同 def forward(self, x): residual x out self.conv1(x) out self.bn1(out) out self.relu(out) out self.conv2(out) out self.bn2(out) out self.shortcut(residual) # 直接相加 return self.relu(out)关键特性对比特性Identity BlockConvolution BlockShortcut 参数无1×1 卷积输入输出维度必须相同可不同计算复杂度较低较高适用场景同一stage内部stage间过渡3. Convolution Block 的维度适配机制当需要改变特征图尺寸或通道数时必须使用 Convolution Block。其核心差异在于 shortcut 路径的 1×1 卷积class ConvolutionBlock(BasicBlock): def __init__(self, in_channels, out_channels, stride2): super().__init__(in_channels, out_channels, stride) # 维度不匹配时添加1x1卷积 if stride ! 1 or in_channels ! out_channels: self.shortcut nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size1, stridestride, biasFalse), nn.BatchNorm2d(out_channels) ) def forward(self, x): residual self.shortcut(x) # 维度转换 out self.conv1(x) out self.bn1(out) out self.relu(out) out self.conv2(out) out self.bn2(out) out residual return self.relu(out)1×1 卷积的三个关键作用通道数调整匹配主路径输出维度下采样通过 stride2 实现特征图尺寸减半参数效率相比 3×3 卷积更节省计算量4. 残差块的实战部署策略在实际 ResNet-34 构建中两种块的组合遵循特定模式def make_layer(block, in_channels, out_channels, blocks, stride1): layers [] # 第一个block处理维度变化 layers.append(block(in_channels, out_channels, stride)) # 后续block保持维度不变 for _ in range(1, blocks): layers.append(block(out_channels, out_channels)) return nn.Sequential(*layers) class ResNet34(nn.Module): def __init__(self, num_classes1000): super().__init__() self.conv1 nn.Conv2d(3, 64, kernel_size7, stride2, padding3) self.bn1 nn.BatchNorm2d(64) self.relu nn.ReLU(inplaceTrue) self.maxpool nn.MaxPool2d(kernel_size3, stride2, padding1) # Stage配置(输出通道, block数量, stride) stage_config [(64, 3, 1), # stage1 (128, 4, 2), # stage2 (256, 6, 2), # stage3 (512, 3, 2)] # stage4 self.in_channels 64 self.stages nn.ModuleList() for channels, num_blocks, stride in stage_config: stage make_layer(ConvolutionBlock if stride1 else IdentityBlock, self.in_channels, channels, num_blocks, stride) self.stages.append(stage) self.in_channels channels self.avgpool nn.AdaptiveAvgPool2d((1, 1)) self.fc nn.Linear(512, num_classes)典型部署规律每个 stage 的第一个 block 使用 Convolution Block 进行下采样同一 stage 内部使用 Identity Block 保持维度通道数变化遵循 64→128→256→512 的翻倍规律5. 梯度传播特性对比实验通过自定义反向钩子可以验证两种块的梯度差异def add_grad_hook(model): for name, layer in model.named_modules(): if isinstance(layer, nn.Conv2d): def hook_fn(grad): print(f{name}梯度范数: {grad.norm().item():.4f}) return grad layer.register_full_backward_hook(hook_fn) # 测试Identity Block identity_model IdentityBlock(64, 64) add_grad_hook(identity_model) test_input torch.randn(1, 64, 56, 56) output identity_model(test_input) output.mean().backward() # 测试Convolution Block conv_model ConvolutionBlock(64, 128, stride2) add_grad_hook(conv_model) test_input torch.randn(1, 64, 56, 56) output conv_model(test_input) output.mean().backward()实验结果通常显示Identity Block 的梯度在各层分布更均匀Convolution Block 的 shortcut 路径梯度幅度更大两种结构都能有效缓解梯度消失问题