说到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的“残差”已经不是某一种网络模块而是一种通用的建模思想。把这个思想吃透再去看那些五花八门的新架构你会觉得它们之间并没有那么大的隔阂无非是把信息通路和组织形态换了一种方式罢了。
阅读完成 · 觉得有帮助?