ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ResNet残差网络解析:从梯度消失到恒等映射的深度学习设计

ResNet残差网络解析:从梯度消失到恒等映射的深度学习设计

说到ResNet,也就是2015年那篇横扫CV界的Deep Residual Learning for Image Recognition,几乎所有做深度学习的人都不会陌生。我第一次看这篇论文时,最大的感受不是那152层的数字有多吓人,而是它把一个极其朴素的问题——网络深了反而变差——用同样朴素的方式解决了。从那以后,不管是做人脸识别、目标检测,还是做图像分类和分割,我的backbone基本都是从这个结构演化来的。这是一份非常值得反复读的论文。

这篇内容适合刚开始入门深度学习、打算认真把CNN吃透的朋友,也适合有几年经验想复盘一下经典结构的从业者。我会把残差学习的思想、网络结构的设计细节、实际训练时的踩坑经验都过一遍,同时把论文里没有直接写、但实际动手时一定会遇到的操作经验补上。

1. 为什么网络越深,模型反而越不给力

1.1 退化现象的诡异之处

学习CNN时,大多数人的直觉是:只要把网络的层数堆上去,表达能力就会增强,准确率应该随之提升。这个直觉在浅层网络阶段是正确的,但是当网络加深到一定程度之后,问题就出现了。很多人会立刻想到梯度消失,也就是反向传播时梯度越乘越小。不过说实话,在Batch Normalization、合理的激活函数以及较好的权重初始化手段逐渐普及之后,梯度消失已经得到了明显缓解,但网络加深之后依然会在训练集上出现表现下降,这就是论文里说的退化现象(degradation problem)。

这个退化现象诡异在什么地方呢?它并不是过拟合——因为训练误差本身就居高不下。你可以把网络理解成一个巨大的函数容器,原本以为层数越多,容器能容纳的函数空间越大,结果反而连训练集都拟合不好。想象一下,如果你的作业题已经做过一遍了,第二天再让你做同样的题,你反而做不对了,这显然不是脑容量不够的问题,而是学习策略出了问题。

1.2 恒等映射带来的灵感

论文作者从退化现象中得出一个关键结论:深度模型很难直接通过堆叠非线性层来逼近恒等映射。假如一个较浅的网络已经能达到不错的效果,那么在它后面再接几层,理想情况下这几层至少应该做到不改变原有输出,也就是完成恒等映射。测试下来发现,直接让这些层去学恒等映射并不容易,这当然是受制于优化过程中的各种阻碍。

于是思路就变成了:既然直接让非线性层去拟合恒等映射很难,那就把恒等映射作为网络里的一条固定通路显式地保留下来,让非线性层只需要去学习输入和输出之间的残差。这个转变看似只是把代数式从F(x)+x换了个形式,但对优化的影响却是颠覆性的。

2. 残差学习到底在改变什么

2.1 从拟合答案到拟合差异

传统卷积层学习的是输入到输出的完整映射。假设某层输入是x,我们期待的理想输出是H(x),那么网络层需要学到的映射可以认为是H(x)本身。换成残差结构后,网络层学习的目标变成了F(x)=H(x)-x,最终的输出是F(x)+x。

为什么这个改变有效?一个直观的解释是:让一堆非线性层去学一个非零的映射,也就是F(x),远比让它们同时负责“完成映射”和“保持恒等”要容易。特别是在最优映射接近恒等映射的场景下,理想情况就是F(x)逼近零,那么网络的权重自然被推向一个小数值的方向。与直接学习一个非恒等映射相比,学习一个接近零的输出所需要的权重调整幅度要小得多,这实际上是在降低优化难度。

用生活一点的方式类比:你要从A地到B地,终点B离你出发的A只有10米,但给你的任务是沿着一条复杂路径“走”过去。如果必须从原点完整规划一条到B的路径,容错率很低;但如果你被允许直接站在A点,只需要感知到“往右挪10米”这个差值,就容易多了。残差结构做的事情,就是让每一层都站在快捷连接的肩膀上,只需要感知到局部差异。

2.2 前向和反向的密码

残差结构不只是改变了数学表达式,它的前向传播和反向传播都有了独特优势。前向传播时,数据流经F(x)+x这条路径,其中x这条快捷连接不会对输入做任何变换,信息可以直接穿透若干层传递到后续网络。反向传播时,梯度也通过这条快捷连接完整地回流到浅层,从而缓解了梯度在深层次间衰减的问题。

这里有一个值得注意的细节:快捷连接没有引入任何额外的参数,也没有增加计算复杂度。两个分支逐元素相加,仅在通道数和尺寸匹配时成立。正是这种“零成本”的恒等映射,才让数百层的网络训练成为可能。我们在实际搭建网络时,经常把ResNet的这条捷径理解为“神经网络的高速公路”,语义分割和检测任务里的特征融合也受到它的启发。

3. ResNet网络结构的拆解与实现细节

3.1 基础模块与瓶颈模块

论文中主要设计了两种残差模块。第一种是BasicBlock,用于ResNet-18和ResNet-34这类较浅的网络,由两个3×3卷积组成,两个卷积的通道数一致。结构上就是:输入x,经过第一个3×3卷积、BatchNorm和ReLU,再经过第二个3×3卷积和BatchNorm,最后与输入x逐元素相加,再过一个ReLU。

第二种是Bottleneck,用于ResNet-50及以上的深层网络。它由三个卷积层组成:1×1卷积负责降维,3×3卷积负责空间特征提取,然后跟着另一个1×1卷积负责升维。以ResNet-50中的一个常见Bottleneck为例,输入通道是256,先经过1×1卷积降到64,再经过3×3卷积保持64通道,最后由1×1卷积升回256通道。

这个设计的核心动机是控制计算量和参数量。如果直接堆叠3×3卷积去增加通道数,计算量会急剧膨胀。引入1×1卷积以后,虽然层数多了,但整体计算量反而更可控,表达能力却没有下降。这个思路后来也被很多轻量化网络借鉴。

3.2 四种经典深度配置

论文给出了四种经典配置,我把它们的核心结构用表格整理一下。基本规律是:从conv2_x到conv5_x,每个阶段的起始卷积都负责下采样(步长为2),特征图尺寸依次减半,通道数依次翻倍。

网络名称模块类型各阶段模块数量 (conv2_x/conv3_x/conv4_x/conv5_x)输出尺寸(输入224×224时)参数量
ResNet-18BasicBlock2/2/2/27×7,通道512约1100万
ResNet-34BasicBlock3/4/6/37×7,通道512约2180万
ResNet-50Bottleneck3/4/6/37×7,通道2048约2550万
ResNet-101Bottleneck3/4/23/37×7,通道2048约4450万
ResNet-152Bottleneck3/8/36/37×7,通道2048约6020万

数据来源参考论文设定。可以看到,层数加深主要集中在中后段的conv4_x阶段,因为这个阶段特征图尺寸已经缩小,叠加模块对计算量的影响相对可控,而带来的感受野扩大和语义抽象能力提升非常明显。

3.3 下采样时捷径怎么处理

残差模块要求输入输出尺寸和通道数一致才能相加。但有些模块的输出尺寸会减半、通道数会翻倍,这时候捷径就不能直接恒等相加了。论文里提供了两种方案:一种是捷径上使用1×1卷积来调整通道数和尺寸,步长和主分支保持一致;另一种是在输入不足时对特征图做零填充补齐维度。前者更常用,因为引入的额外参数很少,却让信息变换更灵活。

很多初学者在复现时容易忽略的一点是:并不是每个模块都要做下采样,只有每个阶段开头的模块才需要把尺寸降下来。如果你把每一个残差模块的步长都设成2,特征图会缩得特别快,模型根本训不起来。我当时复现时在这里吃了不少亏,最后对比源码才发现问题出在每个阶段的下采样位置。

3.4 激活函数的位置也有讲究

论文原版把ReLU放在相加之后,也就是F(x)+x之后再经过ReLU。这个设计在当时看起来自然,后来被后续的残差变体深入研究过。ResNetV2论文指出,如果激活函数放在加法之前,即“预激活”结构,前向和反向传播的路径会更加通畅,因为恒等映射分支不会被任何非线性变换阻塞。原版结构虽然在很多任务上表现不错,但通过这种微小的结构调整,ResNetV2在深层网络上获得了更稳定的训练效果和更低的训练误差。

实际操作中,如果用的是PyTorch或类似框架直接调用预训练模型,默认用的是原版结构。如果是你自己按论文手动搭建,我建议先按原版来,毕竟能对上预训练权重,等整个流程跑通之后再尝试预激活的改动,观察在你自己任务上的表现。

4. 训练ResNet时绕不开的实操细节

4.1 优化器与学习率策略

论文里使用SGD,动量设为0.9,权重衰减设为0.0001,初始学习率0.1,批大小是256。在大规模数据集上,每30个epoch把学习率除以10,整个训练约60到90个epoch能收敛到一个不错的效果。这个配置至今仍是训练ResNet最稳妥的基线。

不过换成Adam或AdamW之后,训练曲线往往会更快地降下来,但最终精度不一定比得上正确调参的SGD。我的使用感受是:如果你只做小规模任务或迁移学习微调,AdamW的体验更好,用不着频繁调整学习率;如果你要训练大规模数据集,或者努力刷新精度记录,那么SGD配合阶梯式学习率衰减依然值得优先尝试。这里没有绝对的标准,关键是理解每个优化器背后的收敛特性。

4.2 数据增强与正则化

ResNet时代的标配数据增强包括随机裁剪和水平翻转,以及后续实践中常用的颜色抖动、随机擦除等。如果涉及小数据集或类别不均衡,数据增强是防止严重过拟合的最直接手段。在正则化方面,除了权重衰减之外,Dropout在ResNet里不是必须的,因为网络里大量使用了BatchNorm,本身就带有一定的正则化效果。

我在实际项目里遇到过这种情况:某个任务用了预训练ResNet-50,在冻结前几个阶段只训练后面几层时,准确率提高得很快,但验证集的表现反而越来越差。加入MixUp或CutMix这些数据混合增强手段之后,验证集表现明显回升。这说明数据增强不仅对从零训练有用,对迁移学习场景同样有效。

4.3 BatchNorm的批次大小误区

BatchNorm的统计量依赖每个batch内的均值和方差,如果batch size设置得太小,统计量会非常不稳定。我的建议是:在显存允许的情况下至少把batch size保持在32以上。如果只能使用很小的batch size,可以考虑冻结部分BatchNorm层的统计量,或者改用GroupNorm这类不依赖batch统计量的归一化方式,但这属于工程上的取舍,不能简单认为换了归一化效果就一定会更好。

在训练过程中,BatchNorm层里的均值方差滑动更新参数默认不会随优化器更新。有的框架实现中,如果你不小心把BatchNorm里的running_mean和running_var也放进了优化器参数组,整个训练过程可能会出现莫名其妙的震荡。排查这类问题的时候,查看模型状态字典里是否有那些不需要梯度的张量被意外标记为需要梯度,是一个很实用的技巧。

5. 论文里的实验验证与消融分析

5.1 深度提升带来的准确率增益

论文里最直观的实验就是对比不同深度ResNet在ImageNet上的表现。ResNet-18、ResNet-34、ResNet-50、ResNet-101、ResNet-152误差依次降低。这个趋势说明在残差结构下,深度的增加仍然能有效带来表达能力的提升。对比同一时期的VGG网络,ResNet在更深的层数下反而计算量更小,准确率更高。这也是ResNet能够迅速取代VGG成为基础backbone的原因之一。

5.2 各种捷径设计的对比

论文作者还专门做过关于“捷径形式”的消融实验:一种是把捷径设计成恒等连接,另一种是在捷径上加入缩放因子或门控机制。实验结果表明恒等连接的效果最好,而且训练更稳定。缩放因子或门控机制的出发点可能是希望网络能自适应地决定恒等映射的权重,但在实际优化中反而增加了障碍。这个结论提醒我们,在结构设计上不要盲目追求复杂的连接方式,简单且通畅的信息通路往往是更优选择。

5.3 CIFAR数据集上的100层稳定性

论文在CIFAR-10这样的相对小规模数据集上训练了超过100层的ResNet,同样观察到了稳定的收敛和良好精度。这非常有说服力,因为在小型数据集上训练超深网络,过拟合风险非常大,而残差结构配合正则化手段不仅没有过拟合,还展示出了更低的训练误差。那时候我做对比实验,把同样的模型从CIFAR迁移到稍微复杂一些的自定义数据集上,稳定性依然不错,说明ResNet在中小规模数据上同样具备很强的可塑性。

6. 常见问题与排查技巧实录

6.1 Loss一开始就不下降

遇到Loss完全不动的情况,先检查输入数据是否归一化,标签是否错位,网络最后输出层的尺寸是否和类别数匹配。排除这些低级错误之后,再看看学习率是否设置得太大或太小。ResNet训练初始学习率一般不会超过0.1(在batch size为256时),如果batch size缩小到64以下,学习率最好相应调小。经验上,batch size减半,学习率也可以尝试减半,这个对应关系虽然不严谨,但在我实践过的很多任务里都是有效的。

6.2 训练震荡非常严重

训练Loss震荡通常和学习率偏大有关。在ResNet这类较深的网络上,尤其是从零训练时,过大的学习率会让深层参数更新过于剧烈。另一个容易被忽视的原因是BatchNorm层的momentum设置。默认momentum为0.1在某些任务里可能反应太快,我有时候会改成0.01或0.001,让滑动统计量更平稳一些。

还可以在训练初期使用一个较短的学习率预热(warmup)阶段。特别是从零训练较大的ResNet-101或ResNet-152时,如果前几个epoch直接以0.1学习率开始,很容易出现早期震荡。线性预热5个epoch,让学习率从0缓慢提升到目标值,能明显缓解这个问题。

6.3 验证集准确率上不去

如果训练集准确率接近100%,验证集却不高,那就是过拟合。除了加强数据增强和正则化,也可以看看是否数据集划分本身存在问题。如果训练集和验证集分布不完全一致,再好的网络也白搭。我在某个项目里就遇到过拍摄设备不同导致的光照和纹理分布差异,换了一套数据增强策略后,验证集表现立刻回升。深度迁移学习中,记住一个原则:不要在验证集上反向调参调得过多,否则验证集也就失去了参考意义。

6.4 显存溢出的应对

Bottleneck结构虽然控制计算量,但ResNet-50在常规输入尺寸下显存开销也不小。如果训练时显存不足,可以先考虑减小batch size,代价是BatchNorm稳定性变差。这时可以用梯度累积来模拟更大的batch size,或者降低输入分辨率后再使用更大的输入裁剪策略。还有一个常用操作是在推理阶段把BatchNorm层融合进卷积层,这样模型参数量不变但推理速度提升明显,比如有些部署框架会自动做这种优化。

7. ResNet之后:它影响的不只是图像分类

ResNet的意义远不止拿下当年图像分类比赛的冠军。目标检测领域的Faster R-CNN、SSD,语义分割领域的DeepLab、PSPNet,以及人脸识别和姿态估计的很多经典方法,早期版本都把ResNet作为backbone。一直到后来的自注意力模型和Vision Transformer出现,ResNet在卷积网络家族里的地位依然稳固。很多Two-Stage检测框架里,ResNet的某个阶段特征图还会被单独拉出来作为FPN的输入,这是因为ResNet不同阶段的特征图在语义和空间分辨率上有很规律的层次性。

从ResNet衍生出的改进方向也非常多:WideResNet用宽度换深度,ResNeXt引入分组卷积,ResNetV2调整激活位置,SE-ResNet加入通道注意力,这些改进很多都是基于“如何让恒等映射更干净”或“如何让残差分支更有效”来展开的。可以说,ResNet把深度学习的结构设计从“层数堆积”引导到了“通路设计”的思路上来。

对我个人来说,读ResNet最大的收获是领悟到:很多时候模型效果不佳,问题不在于容量不够,而在于优化的出入口被堵住了。就像一条信息高速公路,把捷径修通,让梯度通畅,比无限拓宽路面更能解决问题。后来我看很多新的网络结构,都会本能地在脑海里问一句:它的信息通路是否通畅?它的残差连接用在哪个位置?这个思考习惯,很大程度上就是ResNet留给我的。

我自己在实际使用中还有一个体会:无论现在的多模态模型多么复杂,很多底层视觉特征提取依然离不开ResNet式的骨架设计。轻量化的MobileNet里能看见残差思想的影子,Transformer里也经常插入残差连接。换句话说,ResNet的“残差”已经不是某一种网络模块,而是一种通用的建模思想。把这个思想吃透,再去看那些五花八门的新架构,你会觉得它们之间并没有那么大的隔阂,无非是把信息通路和组织形态换了一种方式罢了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进