3.5 ResNet:残差跳线与超深网络


3.5 ResNet:残差跳线与超深网络

本节摘要:按直觉,网络越深表达力越强,实验却给出怪结果:五十六层的训练误差反而高于二十层——问题不是过拟合,是深网络"学不动"。ResNet 的解法只加了一条线:把输入原封不动接到输出上,让每层只学"相对输入的改动量"。这条残差跳线让上百层网络第一次训练得动,2015 年它把 ImageNet 前五错误率压到 3.57%。

越深反而越差的怪案

先把怪案摆正:研究者把二十层网络加深到五十六层,训练集误差不降反升。这不是过拟合——训练误差本身变差了,而是优化层面的失灵:梯度要逐层连乘着往回传,链路一长,传到浅层已面目全非;更隐蔽的是,哪怕最优解就是"这层什么都不做",普通层也要把一组非线性变换硬拧成恒等映射,对权重来说是道难题。深度带来的表达力红利,被优化难度全额没收。残差学习的破题角度是把难题改简单:既然"什么都不做"难学,那就把它设成默认——跳线直接把输入送过去,卷积层只需要学"要改什么"。

学习目标

阅读完本节,你应当能够:

  1. 写出残差块的前向式 y = F(x) + x,解释恒等捷径为何让"恒等映射"零成本;
  2. 实现带通道变化的基本残差块,说出跳线上 1×1 投影的作用;
  3. 复算瓶颈块相对两个全带宽 3×3 的省料倍数;
  4. 报出 ResNet-18/34/50 的参数量级与"深度翻倍、参数缓涨"的原因。

一、残差块:把"改动量"设为学习目标

普通层学的是完整映射 H(x);残差块把目标改写成 H(x) = F(x) + x,网络实际学的是 F(x) = H(x) − x 这个"残差"。妙处在于:若某层最优解就是恒等映射,卷积核只需把权重推向零——权重衰减本来就在干这件事,几乎零成本。反向传播时,x 项让梯度直接从输出跳回输入,链式法则里最深的那段连乘被短路,浅层照样收到足量梯度。

import torch import torch.nn as nn class BasicBlock(nn.Module): """ResNet-18/34 的基本块:两层 3×3,跳线在第二次 ReLU 前汇入""" def __init__(self, cin, cout, stride=1): super().__init__() self.conv1 = nn.Conv2d(cin, cout, 3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(cout) self.conv2 = nn.Conv2d(cout, cout, 3, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(cout) if stride != 1 or cin != cout: # 形状对不齐时,跳线加 1×1 投影 self.shortcut = nn.Sequential( nn.Conv2d(cin, cout, 1, stride=stride, bias=False), nn.BatchNorm2d(cout)) else: self.shortcut = nn.Sequential() def forward(self, x): out = torch.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out)) return torch.relu(out + self.shortcut(x)) # 残差汇合点 blk = BasicBlock(64, 128, stride=2) x = torch.randn(1, 64, 32, 32) print(tuple(blk(x).shape)) # (1, 128, 16, 16):尺寸减半通道翻倍

跳线不是永远白给:当块的输入输出形状不一致(这里步幅 2 让尺寸减半、通道 64 变 128),恒等捷径没法直接相加,于是加一个 1×1 卷积做投影对齐——这是跳线上唯一的新增参数。汇合点选在第二次 ReLU 之前,让加法发生在非线性之前,梯度可以沿加法节点无损分流。

二、瓶颈块与参数账

深度过百之后,连基本块都嫌贵,ResNet-50 起改用瓶颈块:1×1 把通道压缩,3×3 在窄通道上加工,1×1 再放宽。省料账一算便知:

# 512 通道环境里做一次深度加工,两种做法的参数量 pair_full = 2 * 512 * 512 * 3 * 3 # 两个全带宽 3×3 bottleneck = 512 * 128 + 128 * 128 * 3 * 3 + 128 * 512 # 压到 128 再加工 print("两个全带宽 3×3:", f"{pair_full:,}") # 4,718,592 print("瓶颈块三连:", f"{bottleneck:,}") # 278,528 print("省料倍数:", round(pair_full / bottleneck)) # 17

十七倍的差距,换来的是"深而不贵"。于是有了那组著名数字:ResNet-18 约 1,170 万参数,ResNet-34 约 2,180 万,ResNet-50 约 2,560 万,层数翻倍到 152 层也不过 6,000 万上下——深度涨、参数缓涨,瓶颈块与全局平均池化分类头各记一功。2015 年的 ImageNet 赛场,152 层的集成系统把前五错误率压到 3.57%,"网络能训多深"从此不再是敢不敢的问题。

巡检记录

  • 怪案定性:深层普通网络训练误差反升是优化失灵,不是过拟合——恒等映射难学是病根之一;
  • 跳线本质:把学习目标从"映射"改成"改动量",恒等退化为默认值,梯度沿捷径短路回传;
  • 两种块:基本块两层 3×3 服务 34 层以内,瓶颈块 1×1-3×3-1×1 服务百层,省料 17 倍;
  • 参数账:18/34/50/152 层对应约 1,170 万、2,180 万、2,560 万、6,000 万,深度与参数解绑。

💡 关键直觉:残差连接不只是训练技巧,它是"默认不折腾"的工程哲学——改动需要理由,保持现状零成本。这个思想后来长成了 Transformer 里的残差主干,你今天用的大模型底层依然流着这条线。

深度的问题至此解决,剩下的矛盾是算力与电量:把流水线塞进手机,得把卷积本身拆薄。下一节看轻量化一族的刀法。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U