当前位置: 首页 > news >正文

ResNet 残差块 PyTorch 实现:Identity Block 与 Conv Block 的 3 点核心差异

ResNet 残差块 PyTorch 实现:Identity Block 与 Conv Block 的 3 点核心差异

深度残差网络(ResNet)自2015年提出以来,已成为计算机视觉领域的基石架构。其核心创新在于残差块(Residual Block)的设计,而其中Identity Block与Conv Block的差异往往让初学者感到困惑。本文将深入解析这两种残差块在PyTorch实现中的关键区别,并提供可直接集成到项目中的模块化代码。

1. 残差网络基础与两种块的设计初衷

残差网络的核心思想是通过引入"跳跃连接"(Skip Connection)解决深层网络的梯度消失问题。当网络深度增加时,传统CNN会出现性能退化现象,而ResNet通过让输入信号跨越若干层直接传递到后续层,使网络能够轻松学习恒等映射。

在标准ResNet架构中(如ResNet34),两种残差块交替出现:

  • Identity Block:当输入输出维度一致时使用,残差路径是简单的恒等映射
  • Conv Block:当需要改变特征图尺寸或通道数时使用,残差路径包含1×1卷积
import torch import torch.nn as nn class BasicBlock(nn.Module): """基础残差块模板""" expansion = 1 def __init__(self, in_channels, out_channels, stride=1): super().__init__() self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_channels) self.shortcut = nn.Sequential() if stride != 1 or in_channels != self.expansion * out_channels: self.shortcut = nn.Sequential( nn.Conv2d(in_channels, self.expansion * out_channels, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(self.expansion * out_channels) ) def forward(self, x): out = F.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) out += self.shortcut(x) return F.relu(out)

2. 维度处理:1×1卷积的关键作用

两种残差块最显著的区别在于如何处理维度变化。当特征图的空间尺寸(高/宽)或通道数需要调整时,必须使用Conv Block。

Conv Block的核心组件

  • 主路径:两个3×3卷积(第一个可能带stride=2)
  • 捷径路径:1×1卷积 + BatchNorm
  • 维度匹配规则:
    • 空间维度:通过stride=2的卷积减半
    • 通道维度:通过1×1卷积调整
def conv_block(in_channels, out_channels, stride=2): """Conv Block工厂函数""" return BasicBlock(in_channels, out_channels, stride=stride) # 输入输出维度对比示例 x = torch.randn(1, 64, 56, 56) # (batch, channels, height, width) block = conv_block(64, 128) print(f"输入形状: {x.shape} -> 输出形状: {block(x).shape}") # 输出: 输入形状: torch.Size([1, 64, 56, 56]) -> 输出形状: torch.Size([1, 128, 28, 28])

相比之下,Identity Block不改变输入输出维度:

def identity_block(in_channels): """Identity Block工厂函数""" return BasicBlock(in_channels, in_channels, stride=1) x = torch.randn(1, 256, 28, 28) block = identity_block(256) print(f"输入形状: {x.shape} -> 输出形状: {block(x).shape}") # 输出: 输入形状: torch.Size([1, 256, 28, 28]) -> 输出形状: torch.Size([1, 256, 28, 28])

3. 网络结构中的部署策略

在完整ResNet中,两种块的部署遵循特定模式:

  1. 每个stage开始时使用Conv Block进行下采样
  2. 后续堆叠多个Identity Block保持维度
  3. 典型ResNet34结构示例:
Stage输出尺寸块类型序列
156×56[Conv, Identity×2]
228×28[Conv, Identity×3]
314×14[Conv, Identity×5]
47×7[Conv, Identity×2]

完整实现示例

class ResNet(nn.Module): def __init__(self, block, num_blocks, num_classes=1000): super().__init__() self.in_channels = 64 self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm2d(64) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1) self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2) self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2) self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2) self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) self.fc = nn.Linear(512 * block.expansion, num_classes) def _make_layer(self, block, out_channels, num_blocks, stride): strides = [stride] + [1]*(num_blocks-1) layers = [] for stride in strides: layers.append(block(self.in_channels, out_channels, stride)) self.in_channels = out_channels * block.expansion return nn.Sequential(*layers) def forward(self, x): x = F.relu(self.bn1(self.conv1(x))) x = self.maxpool(x) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = self.avgpool(x) x = torch.flatten(x, 1) x = self.fc(x) return x def ResNet34(): return ResNet(BasicBlock, [3,4,6,3])

4. 梯度传播特性的对比分析

两种残差块在反向传播时表现出不同的梯度流动特性:

特性Identity BlockConv Block
梯度路径两条平行路径主路径+转换路径
梯度消失风险极低较低
参数更新效率高(直接相加)中等(需学习转换)
典型应用场景同一stage内的特征细化stage间的过渡

实验对比两者的梯度幅值:

# 梯度监测工具 def check_gradient(block, input_shape=(1,64,56,56)): x = torch.randn(*input_shape, requires_grad=True) y = block(x) y.mean().backward() return x.grad.abs().mean().item() identity = identity_block(64) conv = conv_block(64, 128) print(f"Identity Block平均梯度: {check_gradient(identity):.4f}") print(f"Conv Block平均梯度: {check_gradient(conv):.4f}")

在实际项目中,合理搭配两种残差块可以构建出高效的深度网络。例如在图像分割任务中,通常会在编码器部分使用Conv Block进行下采样,在解码器部分使用Identity Block保持分辨率。

http://www.jsqmd.com/news/1134652/

相关文章:

  • Linux 网卡中断绑定实战:3步隔离CPU与手动设置smp_affinity
  • Windows 11 26H1 离线安装 .NET Framework 3.5:DISM 命令与独立安装器 2 种方案对比
  • Windows 11/10 内置 Administrator 账户启用与关闭:2条命令与3个关键风险点
  • C盘告急?揪出AppData里的“空间刺客”
  • VirtualBox 共享文件夹权限配置:解决 vboxsf 组与 sudo 的 2 种权限方案实测
  • 【Windows】告别乱码:BAT批处理创建中文文件夹的编码实战指南
  • 5步轻松下载加密视频:res-downloader跨平台资源下载工具完全指南
  • Netdata 1.45 集群监控实战:3步配置Streaming,聚合5台服务器数据
  • Linux进程替换实战:execlp与fork组合实现3种并发任务调度模式
  • 下班1小时Day7 | Attention 学习
  • LP5812与PIC18F85K90实现RGB LED动态效果控制
  • CentOS7系统克隆实战:使用Mondo Rescue制作可启动ISO镜像
  • Linux 磁盘扩容实战:fdisk 无损替换分区与 3 步扩容文件系统
  • FileMeta 1.2 + Quicker 动作:为任意文件类型添加备注,3步完成批量管理
  • CentOS 7/8 KVM 实战部署:3步完成基础环境与首个虚拟机创建
  • Ubuntu桌面版安装进阶:从单系统到移动便携式系统的实战指南
  • 终极QMK Toolbox指南:免费开源机械键盘固件刷写神器
  • 银河麒麟V10 SP1 密码策略配置:PAM 模块实战与 8 个关键参数详解
  • Windows下利用WinSCP命令行与计划任务实现自动化文件同步
  • UltraISO 9.6.6.33 制作 Windows 11 启动盘:FAT32 转 NTFS 解决 4GB 文件写入难题
  • OpenAI Realtime API实时语音交互系统深度实践指南
  • 如何快速批量下载音乐歌词:LRCGET完整指南
  • 图像增强实战:从灰度变换到视觉优化
  • 基于Playwright与GPT-4o构建智能Web自动化AI Agent实战
  • CentOS 7 嵌套虚拟化实战:在KVM虚拟机中部署Xen 4.x的3步配置
  • 银河麒麟V10 0710/0711 双版本实测:3 种账户锁定方案与恢复指南
  • SPOOLing 技术实战:Linux 下 CUPS 打印队列的 3 个核心配置与性能调优
  • Linux Nvidia 多卡进程清理:fuser 与 kill 指令的 3 种组合与 2 个关键误区
  • Linux 磁盘扩容后处理:fdisk 与 parted 工具实战对比与5步操作指南
  • Ubuntu移动硬盘即插即用系统:从分区到引导修复的完整避坑指南