
文献精读第六篇轮到ResNet了。这篇论文我想单独拿出来写不只是因为它拿了当年的ImageNet冠军而是因为这几年我落地过分类、检测、分割项目翻来覆去用的backbone几乎都绕不开它。框架换来换去但残差连接这个概念真的像地基一样铺在几乎所有现代卷积网络下面。如果你也经常看论文你会发现ResNet的影响已经远远超过它自己那几层卷积它改写了深度网络的组装方式。这篇笔记我不会把论文从头到尾翻译一遍而是基于我对这篇文献的理解和复现经验讲清楚三件事第一ResNet到底解决了一个什么问题而且是其他方法解决不了的问题第二残差块内部的细节为什么长成这样每个设计背后都有什么考虑第三你真正动手去训练一个ResNet时会遇到哪些论文里没写但实打实存在的坑。适合谁看呢如果你是刚读完基础CNN结构、准备认真精读经典论文的入门者这篇能帮你把主线理顺如果你已经跑过不少模型但总觉得ResNet的代码和论文对不上这篇也能帮你踩掉几个常见的坑。我尽量讲得直白复杂的部分用计算和类比拆开说。1. 这篇文献到底解决了什么问题1.1 深度网络遇到的两个坎过拟合与退化在ResNet之前大家普遍认为网络越深表达能力越强所以研究趋势就是疯狂往深了堆。但实际问题远没有这么简单。模型能力上去之后第一个坎是过拟合这个大家认识一致训练集上表现很好测试集上拉胯。所以早年的办法是加正则、加大数据集、加Dropout。但第二个坎当时很多人没意识到就是退化问题。举个最简单的现象你用一个20层的神经网络和一个56层的神经网络做同一个分类任务训练迭代足够的轮数20层的训练误差可能更低56层的训练误差反而更高。这不是过拟合造成的因为在训练集上就已经输了。这就很反直觉了层数多了理论上表达能力只会更强至少应该不比浅层差。如果层数多而效果差那说明网络在训练过程中出了问题特征没能被有效传递。文献里把这个叫退化现象。注意它不是梯度消失那么简单因为在某些阶段梯度并没有消失但很深层的网络仍然很难被优化。1.2 残差学习的核心思路——从“直接学映射”到“学残差”作者团队换了一个视角来思考这个问题。他们说与其让卷积层直接拟合一个目标映射H(x)不如把目标拆成两个部分一个是短接过来的恒等映射x另一个是需要学习的残差F(x)。最终输出是F(x)加x。这样做的直接好处是如果某个模块已经足够好理想的F(x)应该趋近于0这时候网络就可以退化成恒等映射至少不会比浅层网络差。我用生活里的例子类比一下。你想把一个箱子推上台阶整段路径很难一步到位。但残差学习的意思是你已经站在了某一级台阶上下一步只需要关心怎么再抬高一点点。这个“一点点”就是残差。它把学习目标从高难度的完整映射拆解成了相对容易的小步逼近。这个思路在数学上就是y F(x, {W_i}) x其中F代表残差模块中间的几个卷积操作x是输入y是最终输出。这个结构一旦成立网络堆多深都不会越堆越差因为至少存在一个理论上的最优解剩余部分全部学成0。这是整篇文献最核心的洞察后续所有网络设计比如DenseNet、ResNeXt、甚至Transformer里的残差连接底层逻辑都是从这里长出来的。2. 残差块设计里的那些细节2.1 两类模块BasicBlock 与 Bottleneck论文里其实给了两种基础模块这也是很多人看代码时容易忽略的。第一种叫BasicBlock就是两个3x3卷积串联每个卷积后面跟着Batch NormalizationBN和ReLU最后输入输出直接相加。你去看一个18层或34层的ResNet用的就是这种。第二种叫Bottleneck专门给50层以上的网络准备的。它由三个卷积组成先是1x1卷积把通道数降下来然后是一个3x3卷积真正做空间特征提取最后再用1x1卷积把通道数升回去。这样设计的理由是控制计算量。以ResNet-50为例如果直接使用两个3x3的BasicBlock堆到152层计算量会严重膨胀训练和推理都很难落地。Bottleneck通过降低中间通道数把一个模块的乘法计算量压缩到原来的四分之一左右。注意Bottleneck里两个1x1卷积的通道不是对称的。第一个1x1把通道从256压到64第二个1x1再恢复回来。这样把计算集中在了中间的3x3卷积上同时也保留了足够的跨通道信息融合能力。2.2 恒等映射的三种连接方式残差块要做加法就必须保证F(x)和x的形状一致。三种方式分别对应不同的网络配置。第一种最简单输入和输出形状完全一致直接相加论文里叫identity shortcut。第二种如果输出尺寸缩小了比如通过步长为2的卷积下采样那x也需要跟着改变形状。论文给出的方案是用1x1卷积且步长也为2把x变换到目标形状。第三种是通道数不一致的时候你还可以选择补零但作者明确指出补零的性能不如1x1卷积。我复现的时候这两种情况在实际代码里会写成两个分支。短接分支如果为空加法就是原位对位相加如果不为空这个分支就是一个独立的1x1卷积它也有自己的权重。很多人第一次手写ResNet时经常在这里犯迷糊把这个1x1卷积漏掉然后发现维度对不上报错都不知道错在哪。补充一点identity branch是可以被权重衰减砸到的。实际工程里有人将shortcut单独拿出来不放进主路径的正则里得到的效果略有不同但我个人测试下来只要BN和初始化正确常规做法就已经很稳了。2.3 为什么不加Batch Normalization反而训练不动在我刚上手时对BN在残差块里的位置理解得特别浅。总觉得BN加在哪都行。直到自己手搭了一个没有BN的残差块简单数据集上还能跑一到ImageNet和复杂任务损失就像卡死一样。这时候我才真正体会到论文里反复强调内部协变量偏移这个概念的意义。在残差块里BN的作用是稳定激活值的分布保证残差F(x)的数值范围足够小且稳定。恒等分支x本来就已经有一个合理的分布如果残差分支的分布一直乱跳加法结果就会不稳定反向传播时梯度的尺度也很难控制。BN本质上是给残差学习提供了一个数值上的稳定平台这也是论文作者团队在原代码的每个卷积后面都接BN的原因。所以我可以很负责任地讲复现ResNet结构时那个两个卷积夹一个BN的顺序尽量不要自己发明。你先按原论文和官方代码的顺序跑通再逐渐尝试调整否则很难定位问题到底出在结构还是出在训练配置上。3. 网络架构与层数配置3.1 从18层到152层的配置规律ResNet系列不是一个单独的网络而是一组按层数划分的家族。论文里给出了从18到152层的多个配置。虽然层数不同但它们遵循一个统一的规律网络被分成四个阶段每个阶段内部的残差块数量递增每个阶段开始时空间尺寸减半、通道数翻倍。这样设计的意图是控制计算量。深层网络不会在所有阶段有相同分辨率而是逐步把空间信息压缩成高语义特征。举个例子ResNet-50后面的层数分布是3、4、6、3。这三个数字加起来等于16再乘以Bottleneck的3层再加上最前面的一层初始卷积和最后一层分类层总共50层。同理ResNet-101是3、4、23、3152是3、8、36、3。我在实际工程中更常把ResNet当成一个模板而不是固定结构。很多框架允许你定义每个阶段的残差块数量比如检测网络用地比较多的ResNet-50带FPN就经常只保留前三个阶段的特征拿来做多尺度预测。所以理解这个块数配置背后的逻辑比背数字有用得多。3.2 不同ResNet怎么选、在哪用层数选择不仅关乎精度更关乎显存和推理时间。我的经验是如果数据集规模中等比如一万到十万张图ResNet-34或ResNet-50就够用了。盲目上ResNet-152在数据量不足的情况下收益很小还会增加训练时间和显存占用。如果业务里对延迟非常敏感比如线上实时推理我一般会降到ResNet-18或ResNet-34再通过蒸馏等手段补偿精度。在检测和分割任务里ResNet-50和ResNet-101是最常见的backbone。它们提取出的特征层次分明浅层适合定位深层适合分类。很多项目直接拿ImageNet预训练的ResNet来初始化然后在新任务上微调收敛速度快很多。只有在数据量特别大且任务特别复杂时我才会考虑ResNet-152甚至比它更深的网络。3.3 计算量对比和感受野的变化从计算量看不同深度的ResNet差异非常大。ResNet-18的FLOPs大约在18亿左右ResNet-50是38亿ResNet-152则到了113亿。如果只用CPU推理这个差距是灾难性的。但感受野的变化也同样重要。随着层数增多每个输出位置能看到的输入区域变大模型对全局信息的感知能力增强。在细粒度分类任务里大感受野往往能帮助模型抓住整体的结构关系但也会丢失一部分局部细节。所以你会看到一些项目在浅层引入额外的小感受野分支来同时兼顾局部和全局。理解感受野的增量对搭网络很有帮助。残差块中两个3x3卷积叠加等效于一个5x5卷积的感受野但参数量更少。Bottleneck中的1x1、3x3、1x1组合感受野等于一个3x3再加上额外的跨通道映射。所以不要只看网络层数要看你实际需要多大的感受野再来决定在哪个阶段加深。4. 恒等映射为什么能缓解梯度消失——前向传播的视角4.1 前向传播中的恒等连接这一节我们要把数学搞明白。一个残差块输出可以写成x_{l1} x_l F(x_l, W_l)如果网络一共有L个这样的残差块从第l层到第L层的特征可以通过连续展开得到x_L x_l sum(F(x_i, W_i))这个展开式说明了一个很直观的事实深层特征x_L不仅包含经过一系列残差变换后的信息还包含最原始的输入x_l。只要恒等路径畅通信息就能无损直通。这一设计意味着深层特征的全局信息不会完全依赖逐层映射即使中间的某几个模块学习得不太好浅层信息依然能搭上直通车。这也是为什么在ResNet里信息流动非常顺畅。对比一下普通网络每一层的输出完全依赖前一层的非线性变换一旦中间某个节点数值坍缩后面的层都会跟着遭殃。4.2 反向传播中的梯度直达反向传播时损失C对x_l求导可以用链式法则展开∂C/∂x_l ∂C/∂x_L * (1 ∂(sum(F))/∂x_l)括号里这个1非常关键。即使残差分支的整体导数很小甚至趋近于0梯度仍然能够通过这个恒等项传回到浅层不会彻底消失。这就是ResNet能支撑超深层网络的数学原因。我用一个更直观的方式来解释梯度在网络里传播时相当于一条高速公路每一个残差块都开了一个收费站的便道让车可以直接穿过去。即使便道落满了积雪残差分支梯度很小主路依然畅通。对比一下没有残差连接的网络梯度必须穿过每一层卷积和非线性的乘积。如果这些因子的最大值小于1多层相乘之后梯度就会指数级变小如果大于1梯度又会爆炸。ResNet通过这个加法结构硬生生把这条乘法链变成了叠加链梯度缩放问题就大大缓解了。4.3 为什么“恒等”很重要你可能想问短接分支为什么一定要是恒等映射而不是另一个可学习的层论文里专门提到如果shortcut换成别的变换比如乘以一个可学习的标量可能会损害梯度传播。因为任何非恒等变换都会对自己求导产生缩放因子。如果该因子小于1多层残差堆积后梯度仍可能衰减如果大于1又面临梯度爆炸风险。恒等映射的导数为1是最安全的选择既不增也不减。这一点后来被很多变体实验验证。比如有的工作尝试在恒等路径上加可学习的缩放参数往往效果并不比标准版本好多少却引入了额外的调参成本。我个人建议在没有充分理由的情况下shortcut保持恒等别乱改。5. 复现过程中的踩坑与经验5.1 训练配置与优化器调节细节直接复现ResNet的过程中最容易翻车的不是网络结构而是训练配置。原论文里使用了一批图片大小224x224随机裁剪和水平翻转做增强Momentum设为0.9权重衰减设为0.0001初始学习率0.1批次大小256一共迭代60万步。我自己复现时遇到最常见的现象是第一代网络结构就是按论文搭的但测试集精度始终比论文低好几个点。排查来排查去问题多数出在优化器上。现在的框架默认优化器实现细节不完全一样尤其是动量缓冲的初始化和BN的momentum参数。如果你用PyTorch注意SGD里的nesterov是否开启权重衰减是否真的加在卷积和BN的所有参数上。很多复现精度上不去就是因为BN层的参数也被施加了不合理的权重衰减。5.2 几个容易踩的坑以下坑都是我实际踩过或在帮别人排查时见过的逐条列出来第一个维度匹配时1x1卷积的步长。下采样阶段shortcut的1x1卷积必须步长2否则空间尺寸对不上。这是新手最常见的报错点。第二个BN默认初始化。BN层的gamma初始化为1beta初始化为0。如果某个任务里你修改了初始化会导致早期训练特别不稳定。第三个学习率太大或太小。ResNet在ImageNet这类大任务上初始学习率0.1很合理但如果你用了更大的批次最好按比例换算。迁移到小数据集时0.1就没有参考价值了0.01或0.001更稳妥。第四个多卡同步BN。跨卡训练时BN的均值和方差如果不做同步处理大批次下的统计量会出现偏移。小batch单卡训练时要注意本地统计和全局统计的切换。第五个预训练权重的通道顺序。从某个仓库下载的权重如果网络代码定义的通道顺序不一致加载时可能静默出错精度直接崩掉。加载权重后最好先用单batch做前向比对。5.3 从论文到实际业务的项目化落地如果你只是做实验室复现跑通训练、看准确率就够了。但如果要落地到实际业务你需要多考虑几个层面。第一模型压缩和剪枝。ResNet-50的权重约25MB边缘设备往往扛不住需要蒸馏或结构化剪枝。第二推理速度优化。把BN折叠到前一层的卷积里可以省去推理时的额外计算。这是工程上常用且安全的操作。第三不同框架间的转换。保存成ONNX再转到边缘推理框架往往会在BN和Resize这类算子上遇到兼容性问题。我的建议是把ResNet当成一个标准组件转换前后都做一次全模型输出对比确保误差可控。6. 实验结果该怎么读、怎么和别的网络比6.1 ImageNet上的对比论文的实验部分最核心的对比是34层ResNet和34层Plain网络。这两个网络层数相同唯一区别是有无残差连接。训练条件保持一致的情况下34层ResNet的top-5错误率是5.71%而34层Plain网络是8.60%。这个对比特别干净因为它控制变量控制得非常好证明了提升不是来自更深的层数而是来自残差连接本身。同时18层ResNet和18层Plain网络都对18层的精度差异不算特别大。这一点也非常关键说明在浅层时网络能力可能还没遇到瓶颈残差的优势展现得不够充分。只有当网络加深到一定程度退化问题出现时残差连接的价值才真正凸显出来。6.2 CIFAR-10上的深层实验论文作者还在CIFAR-10上做了极限实验将网络加到了110层甚至尝试了1202层。在110层时错误率可以做到6.61%左右1202层的网络也依然能收敛但是精度略有下降。这个结果的意义不在于那一点点错误率而在于证明了残差连接可以支撑起1000层以上的网络训练。这对于当时的认知来说冲击力非常大。我自己在CIFAR-10上复现过110层的ResNet训练时间比34层明显长很多但收益已经很小。小数据集上深层的优势并不明显因为模型容量早饱和了反而是正则化手段的收益更大。6.3 网络结构对比表的读法论文里那张对比各网络在ImageNet上的错误率表我建议你逐行细读。你会发现ImageNet当时主流网络VGG-19的错误率约8.20%参数量也大。ResNet-34的参数量小一些但错误率已经降到5.71%左右ResNet-50则可以直接到4.75%附近ResNet-152能到3.57%左右。读完这张表基本就能理解为什么后来大家的backbone都换成了ResNet家族。读这张表的另一个技巧是不要只看精度还要看计算量和模型大小。有些论文在对比时故意忽略这些因素只报最高的精度但实际工程部署时效率和精度需要同时权衡。7. 对后续研究的影响和一些个人体会7.1 残差思想如何渗透到整个深度学习现在去看各个领域的深度学习模型几乎都有残差连接的身影。DenseNet把残差思想进一步发展成了密集连接WideResNet通过加宽残差块结构来提升性能ResNeXt则把残差里的卷积换成了分组卷积。甚至Transformer里的每个子层都有Add操作本质上也是一种残差设计。残差思想的影响范围已经超越了图像识别进入了自然语言处理、图神经网络等领域。我在不同任务里反复观察到同一个模式只要网络变深残差连接就成了刚需。不管网络结构本身是卷积还是全连接还是注意力只要没有明确的跨层连接深层训练就会变得极不稳定。这个观察让我在很多时候设计一个新的网络模型时都会先问自己各层之间有没有合适的残差通路这是不是避免退化最廉价的手段7.2 一些个人体会和后续可以扩展的方向最后分享一点我自己的看法。ResNet的成功让我明白一个深刻的道理在深度学习里设计一个让优化更容易的网络结构有时比单纯提升模型表达能力更管用。表达能力强但没有良好的优化通道深度带来的红利会被训练困难抵消甚至变成负收益。目前我在实际项目中还会经常使用ResNet作为对比基线。每当我拿到一个新任务不知道哪种结构更合适时我都会先跑一遍ResNet-50这个结果基本上可以作为后续所有实验的基准线。因为ResNet的训练已经非常成熟代码、权重、技巧一应俱全它能很快帮我建立对数据规模和任务难度的基本判断。后续如果你想继续扩展可以沿着几个方向一是结合最新的训练技巧比如不同的优化器、学习率调度、数据增强再看ResNet的上限能到多少二是把ResNet与Transformer做混合结构比如浅层用卷积或专门的特征提取模块深层用注意力机制这种方式在很多任务上效果极佳三是研究更高效的残差变体在保持精度的同时把计算量压下来。这些方向都建立在读懂ResNet基础细节之上并不难。