JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

让深度网络真正能训练的那些技巧

按理说,多堆几层应该更强——可多年来事实并非如此,直到三个不起眼的工程技巧改变了一切。我们来认识随机失活、归一化和残差连接,并切实看清每一个为什么能同时帮助深度模型「训得动」又「学得稳」。

为什么「更深」一度不再奏效

到现在为止,你已经见过这些积木:层层叠起的隐藏层在学一套分层的特征,处理图像的卷积网络,处理序列的循环网络。下一步看起来很显然——再多堆几层。纸面上,更深的网络能表达更浅网络能表达的一切,还能更多。那么,为什么在 2010 年前后,许多团队发现一味加层往往让结果*更糟*而不是更好?

其实有两个不同的问题被搅在了一起。第一个是优化问题:在反向传播时,梯度逐层往回流,可能一路缩向零——这就是梯度消失——于是靠前的层几乎学不到东西。第二个是泛化问题:一个动辄上百万参数的庞大模型,可能干脆把训练集背下来,从而过拟合,而不是学到真正的规律。本篇的三个技巧,各自瞄准其中一个或两个问题。

随机失活:训练的是一支合唱团,不是一位独唱

[[dropout|随机失活]]简单得几乎让人意外。在每一步训练里,你随机关掉一部分神经元——比如一半——把它们这一步的输出置为零。下一步,又是另一批随机的神经元熄灭。网络永远没法指望某个特定神经元总在场,因为它随时可能缺席。

它为什么帮的是*泛化*?因为它阻止神经元结成脆弱、过度专门化的小团伙——人们称之为「共适应」:某个特征只有在另外三个特定邻居一起发火时才管用。被迫应对随机缺席后,每个神经元都得多少独立地学到点真正有用的东西。一个很美的理解角度是:随机失活其实在偷偷训练一个巨大的「瘦身网络」集成,它们共享权重,并在测试时被平均起来。靠冗余换来的稳健。

一个常把新手绊倒的细节:随机失活*只*在训练时开启。到推理时,你用的是每个神经元都在场的完整网络。为了让数学保持一致,激活值会被缩放,使两种模式下的「期望总信号量」对得上。忘了这个缩放,模型一上线行为就变了样。

归一化:让信号保持「正常体温」

信号一层层往上传,它的尺度可能漂移——数值要么膨胀得巨大,要么塌缩得极小——而这种漂移会让损失曲面变得极难行走。[[batch-normalization|批归一化]]这样解决:在每一层,把激活值重新居中、重新缩放,使其大致均值为零、方差为一。关键在于,它是在当前这一小批量样本上计算这个均值和方差的,再让网络学习两个参数对结果做缩放和平移,以免丢掉表达能力。

它换来什么?主要是*优化*上的好处:损失曲面变得更平滑,于是你能用更大的学习率,训练收敛快得多。它还带有轻微的正则副作用,因为按批计算的统计量会注入一点噪声。2015 年那篇原始论文把功劳归于减少了「内部协变量偏移」——但后续研究表明,损失曲面更平滑才是更好的解释。这也提醒我们:一个技巧可以效果惊人,哪怕它最初对「为什么有效」的说法后来被证明并不完整。

批归一化有个真实的弱点:它依赖于批。当批很小,或者在循环网络里同一层要在变长序列上反复运行时,那些按批统计量会变得嘈杂、不可靠。[[layer-normalization|层归一化]]绕开了这点:它不是跨批、而是*在单个样本内部*跨特征去归一化。它根本不需要任何批统计量——这正是它成为支撑当今语言系统的Transformer模型内部默认选择的原因。

残差连接:给梯度修一条捷径

最大胆的修法恰恰也是最简单的。[[residual-connection|残差连接]](2015 年 ResNet 的核心)把一层的*输入*直接加到它的*输出*上:与其要一个模块算出完整答案 H(x),不如只让它算出那点*变化量* F(x),再把 x 加回来。这个模块学的是一个残差——对已经传到的信号做一点修正——而不是从头重造整个信号。

# a plain block:        y = F(x)
# a residual block:     y = F(x) + x
#
# in backprop, the gradient through 'y' splits:
#   dL/dx = dL/dy * (dF/dx + 1)
#                              ^ the +1 is the shortcut:
#                                gradient flows even if dF/dx -> 0
那个「+ x」意味着一部分梯度原封不动地抵达更早的层——正是这个「+1」击败了梯度消失。

看看代码片段里的数学:因为加了 x,往回流的梯度多出一个干净的「+1」项。哪怕这个模块自身的导数缩向零,那个 +1 也能让一股健康的信号继续流向下面的层。这就直接击败了梯度消失,也正因如此,残差让研究者得以训练几百层深的网络——这种深度在过去根本无法训练。

还有第二个、更温和的好处。如果某个模块没什么有用的东西可加,它最容易学到的就是输出接近零,于是 y ≈ x——一个恒等映射。所以残差网络实际上能*自己挑选深度*,只用它需要的那些层,悄悄跳过其余的。额外的深度不再是负担,而变成模型可取可舍的一种选项。

它们如何协同(以及它们修不了什么)

在一个现代深度网络里,这些技巧不是对手,它们是叠在一起用的。一个典型模块会先对输入做归一化,跑一个带非线性ReLU激活的变换,加一点随机失活,再把整块用残差连接包起来。每个技巧治不同的病——残差管梯度流动,归一化管损失曲面更平滑,随机失活管过拟合——所以合在一起,就能训练出十年前根本没指望的模型。

  1. 随机失活——训练时随机丢弃神经元;靠强制冗余对抗过拟合。推理时关闭。
  2. 归一化——把激活值重新居中、缩放;让损失曲面更平滑,使训练更快更稳。批归一化跨批计算,层归一化在单个样本内计算。
  3. 残差连接——把输入加回到输出上;给梯度一条捷径,让极深的网络仍然训得动。

现在说点诚实的话,因为这个领域充斥着夸大其词。这些技巧没有一个能增添知识,也没有一个能让模型「理解」任何东西——它们只是让优化得以成功的管道工程。它们救不了一个有缺陷的数据集、一个错误的目标,或一个对着错误问题发力的模型;垃圾进,依旧垃圾出。而且「我们现在训得动了」并不等于「它学到了对的东西」——一个 200 层的残差网络照样会过拟合,照样会抓住虚假的捷径,照样会在训练分布之外无法泛化

尽管如此,它们的历史影响怎么说都不算夸张。这三个毫不起眼的点子——丢掉一些、归一化一些、加一条捷径——基本上就是把「深度」从一个梦想变成日常工具的关键。带着它们继续走:当你在下一阶遇到 Transformer 时,会发现每一个模块外面都裹着残差连接和层归一化。你在这里学到的管道从未消失——它只是被放大了。