雾山上的球:用直觉看懂梯度下降里的「梯度」

想象一下:你被扔到一座雾很大的山上,看不见路,只能靠脚底感觉哪边更陡。这就是机器学习里"梯度下降"想解决的那件事——我们看不见那条真正的下山之路,但能靠"脚下感觉到的最陡方向"一步一步挪。而你问的那个核心问题——"梯度的大小到底反映什么"——就是这座雾山里最值得弄清楚的一根尺子。

整篇专题不打算给你写任何公式,我们只靠山、脚底、小球这三个东西,把"梯度"这一个概念从里到外讲透。你已经凭直觉答对了两件事("梯度指向最陡上升方向"+"劲儿大=离谷底远"),下面要做的就是把它们扶正、掰开一条缝、再装上几个新维度。

梯度到底是什么:从「雾山脚底」到「最陡上升方向」

先把你已经答对的那件事摆到桌面上:梯度指向最陡上升的方向。这条直觉完全对。3Blue1Brown 的第二章用"输入空间 = 山脚的坐标图、损失曲面 = 立体的山形"这一对图,把这个直觉落成了图示3Blue1Brown。

看下面这张图,它把"地图"和"地形"两件事放在同一画面里:

图注:左侧是"输入空间"——你可以把它想成这座雾山的等高线地图,黄点就是你当前站的位置,八个等长的灰色箭头是从你脚下放射出去的八个候选方向。右侧是同一黄点对应的"损失曲面"——立体山形。这两半用一根黄色虚线投影连在一起,把"地图坐标"和"立体山形"接上。

注意那八个箭头是等长的——3Blue1Brown 这张图并不回答"哪个方向最陡",它只问你"哪个方向会让损失下降最快"。回答这个问题的是下一张图。

现在把方向写下来。梯度用一个英文的希腊字母 ∇ 配一个函数名(比如损失 C),写作 ∇C。它就是从你脚下出发、指向最陡上升那一侧的箭头——换句话说,绿箭头朝哪边,你就往哪边最难爬。那反过来,红箭头指向的就是最陡下降那一侧,也就是下山最快的方向3Blue1Brown。

图注:同样是输入空间 + 损失曲面两半并列。这一次图里直接给出了答案:黄点上有一支绿色箭头指向"梯度 ∇C",另一支红色箭头指向它的反方向。整张图就是"梯度=最陡上升、负梯度=最陡下降"这一件事的官方图示。

这就是"梯度"两个字最核心的意思:它是一个带方向的箭头——绿箭头指向上升最快,红箭头指向下降最快。在教程余下的所有篇幅里,"沿负梯度方向走一步"翻译成人话就是"沿红箭头方向走一步","看梯度大小"就是"看这支红箭头有多长"。至于怎么把梯度"真正算出来"这件事——也就是把整个神经网络里成千上万个权重的偏导数一次打包出来——那是反向传播算法干的事,Michael Nielsen 的《Neural Networks and Deep Learning》第二章里有专门的文字版解释:每次训练要看的就是神经网络里几十万个权重各自对应的偏导数,反向传播把它们一次性打包算出来,而不需要对每个权重都单独跑一遍前向计算Nielsen 2015。

到这里你可能想问:那"梯度大小"到底意味着什么?为什么雾山脚底感觉到的"劲儿"有时候大、有时候小?这就是下一章要掰开那条缝。

梯度的大小在说什么:基本对,又在哪儿骗你

你刚才推出的第二条直觉——"劲儿大=离谷底还远、劲儿小=快到了"——在绝大多数地方都对。3Blue1Brown 那段原话是这样说的:梯度向量的长度,反映的正是"那条最陡的坡到底有多陡"3Blue1Brown。把这句话翻译成山-脚底的语言:你脚底感觉到的"陡劲儿",就是你脚下这一处山坡的坡度。

为什么这能反过来反映"离谷底远近"?因为一座正常的山,越靠近山顶越陡、越靠近谷底越平。你刚出发时脚下很陡——劲儿大——说明还在高处;你越往下走,脚下越平——劲儿越小——说明快到了。这条直觉之所以对,是因为它默认了我们脚下这座山有一个清楚的谷底。

但有一条缝要掰开:在真实神经网络的损失地形里,"看起来平"这件事,并不总意味着"到了终点"。Pascanu、Dauphin、Ganguli、Bengio 四个人 2014 年那篇专门谈"鞍点问题"的论文把这件事讲得很清楚Pascanu et al. 2014。

他们论文里画了这么一组示意:

图注:四类临界点的几何形状。(a) 退化鞍——沿一个方向几乎是一段平台;(c) monkey saddle——一段看起来平的方向上还套着 min-max 结构;(d) 圆形"酒瓶底"——最小值不是单点而是整个圆环。在这些位置,梯度模长都接近零,但人并没有真正到"最深的谷底"。

看 (d) 那张"酒瓶底":整个圆环上的每一点都是真正的最低点,但论文里特意画了一条线让你看到,沿这条圆环走一圈,高度没有任何变化。这就是"看起来平,但还没到终点"的几何形态——你站在圆环上的某个位置,脚底感觉到的"劲儿"是零,但往圆环的"内侧"再挤一点点(或者往"外侧"挪一点点),你都能找到一个更低的位置。

论文还附了一张实证图,把这件事从几何示意落到训练数据上:

图注:左图是一张散点图——横轴上 index 越大代表这点越像鞍点、纵轴是训练误差。点的灰度反映该位置梯度范数的大小。从左上到右下,训练误差单调下降——也就是说,训练误差高的临界点,几乎都是梯度很小的鞍点。右图展示三个不同临界点的曲率结构差异。

这张图我们不展开它的横纵坐标含义,只让你看到一件事:真正让训练停下来的,往往不是"小山谷",而是这种"看起来平、其实还有路"的鞍点。

所以现在把你第二条直觉扶正、改一改:梯度大小基本反映"那一处山坡有多陡",而这又大致反映"是不是快到谷底"——但在鞍点和平原附近,"平"≠"到终点",反而可能正是训练卡住的地方。后面我们还会看到,"梯度大小"还受第二件事影响——你想让小球每一步迈多远。

顺着最陡的方向走一步:步长太大太小会怎样

前两章我们其实只用了梯度这一个维度:方向 = 沿负梯度方向走一步(红箭头),大小 = 这一处坡有多陡。但光有方向还不够——你还得决定每一步迈多远。这就是"步长"(又叫学习率,learning rate)这个新维度。

3Blue1Brown 那张最有名的"梯度下降动作分解图"正好把这三个东西摆在一起3Blue1Brown:

图注:3D 损失地形上有一个黄点(当前位置),一支向上的黄色锥形箭头指向梯度方向,一条蓝色虚线从高处蜿蜒降到局部低点。右上角嵌了一个黑色文字框,三步走:"Compute ∇C / Small step in −∇C direction / Repeat."——其中"Small step"那一行正是这一章要展开的钩子:步长选得太小,挪不动;选得太大,会冲过谷底来回弹。

方向对,但步子没选对,两种典型后果:太大——3Blue1Brown 原文写"overshooting and oscillating a lot around that minimum",你想象成小球冲向谷底,但因为惯性过大冲过谷底,然后又被对面山坡的负梯度推回来——来回来回振荡,永远没站稳。太小——每一次挪动只挪了一小步,几万步下来还在半山腰原地打转,根本挪不到谷底。看下面这张并排示意:

图注:梯度方向对、但步长选错:overshoot 来回振荡 vs 步子太小原地打转

左面板:起点沿负梯度方向直冲谷底,但冲过去一点、又被反向推回一点、来回振荡。右面板:起点沿负梯度方向挪了一点点,但步子太小、挪了几万次还没到谷底。两种失败里,方向都没错——问题出在"每一步迈多远"这一维。

这一条特别重要,因为它直接回应"为什么有些训练明明方向对、可就是停不下来"这种直觉困惑。3Blue1Brown 同一段还给了第二个直觉:如果你让"步长跟着坡度走"——坡陡时迈大些、坡缓时自动迈小——那就既不会 overshoot,也不会挪不动。这正是后面要讲的"自适应调步长"的伏笔3Blue1Brown。

你可能会问:方向对、步长也选了一个相对合理的值——那"每一步看几个样本"这件事又会有什么差别?这就是下一章的"三种走法"。

三种走法:每一步用多少样本看梯度

到现在为止,我们的小球都是站在山的某一个点上,靠"脚底感觉到的最陡方向"走一步。但"脚底感觉"这件事的精度,是由你这一脚踩下去之前看了多少地形决定的——是看了整座山的全貌才迈步,还是只看了脚下一小块、甚至只看了一眼脚底一寸?这一维就是"批量大小"(batch size),它和"走法"是同一件事的两种说法。

Sebastian Ruder 在他 2016 年那篇被广泛引用的综述里,把这一件事的三种走法讲得很清楚Ruder 2016。

全批量(BGD):每一步之前,先把整座山的全貌看一遍——也就是用所有训练数据算一次梯度。然后迈一步。优点是这一脚的方向和大小都算得最准;缺点是看一遍整座山的代价太大,神经网络训练动辄几十万张图、每一步都看一遍根本跑不动。

随机(SGD):每一步之前,只看脚下的一寸——也就是只用一个样本算梯度。然后迈一步。优点是快、看一眼就迈步;缺点是噪声太大,每一步的方向都和真实方向偏差很大,结果就是你眼看着小球在谷底附近来回打转、损失曲线像心电图一样剧烈震荡。

小批量(Mini-Batch):折中——每一步之前看一小撮样本(典型的批量大小是 50–256)。这是今天训练神经网络的默认走法;文献里说的"SGD"很多时候其实就是指 Mini-Batch。

为了让你看见"不同走法的脾气",看 Ruder 综述里那张很有名的对比图:

图注:两幅并列的损失地形等高线俯视图,共享同一组同心椭圆等高线。左面板是"不带动量的 SGD"轨迹——沿陡坡方向锯齿状来回振荡,前进缓慢。右面板是"带动量的 SGD"轨迹——曲线沿谷底平滑收敛,振荡明显减少。来源:Genevieve B. Orr via Ruder 2016, arXiv:1609.04747。

左图那条"锯齿状来回振荡"的轨迹,就是不带任何减振机制的纯 SGD 在窄谷里的样子——你选对了方向,但每一步的方向都偏、往一偏好几步结果就一直在谷的两边墙之间撞来撞去。右图加了动量之后轨迹变光滑——这件事我们下一章展开。

把这件事再往前推一步:在同一张损失地形上,6 种不同的走法各自怎么走?Ruder 综述里 Figure 4 把这件事画得很清楚:

图注:左右两幅子图共享同一组 6 色图例——SGD、Momentum、NAG、AdaGrad、AdaDelta、RMSProp,每条彩色轨迹从同一组起点出发走向终点。左面板是 Beale 函数的等高线俯视图,6 条轨迹都从右上往中心收敛。右面板是 3D 鞍形曲面,红色和品红色(SGD 和 NAG)只走了很短一段就被困住,蓝色、黄色、黑色(AdaGrad、AdaDelta、RMSProp)则沿下坡方向走得远得多。来源:Alec Radford via Ruder 2016, arXiv:1609.04747。注意这张图不含 Adam——Adam 是后来才被加进这种对照里的。

这张图最值得看的是右面板的鞍形曲面:SGD 系(红色和品红)因为方向只看一步的局部梯度,遇到这种地形就走不动;AdaGrad、AdaDelta、RMSProp 因为给每个方向分量单独调过步子,下坡方向果断得多。这正是下一章"会自己调步长的走法"要解决的事。

为什么"批量大小"这件事值得单独讲?因为它不只是"快一点/慢一点"——它直接决定了小球下山的轨迹形状。批量大,轨迹稳但慢;批量小,轨迹抖但快;今天深度学习默认的折中点(Mini-Batch)就是踩在这两者中间。

到这里你心里可能又冒出一个问题:方向对、步长也合适、批量也选好了——那能不能让算法自己学会"什么时候迈大步、什么时候迈小步",不用我手动调?这就是从动量到 Adam 的故事。

会自己调步长的走法:从动量到 Adam

前一章那张右图已经剧透了第一种"自调"——动量(Momentum)。它的直觉很简单:把过去几步的梯度攒起来用,相当于让小球有了"惯性"。在窄谷里走时,前一步往上撞了一下墙,下一步的方向就会记着那个"撞墙"的方向——你不需要每一步都重新算"哪个方向最陡",而是用"过去几步的方向加权平均"作为下一步方向。Distill 上 Gabriel Goh 2017 年的那篇文章把这个直觉说得很到位Goh 2017。

看下面这张 sidebar,它不声称是哪一种具体方法的轨迹,只用一种 generic 的"迭代收敛"视觉让你看见"轨迹逐步向中心塌缩"这件事:

图注:两行五列共十张子图的快照。每张子图都有从外围螺旋状逐步向中心塌缩的曲线,颜色从黄到橙再到红。整张图表达的是"迭代收敛"——一次比一次更靠近中心——而不主张任何一张子图属于 momentum 或 GD。来源:Distill.pub /2017/momentum/ article thumbnail。

动量解决的问题是"小球在窄谷里两边撞墙"。但还有一类问题动量解决不了:在一个方向上,梯度一直很大;在另一个方向上,梯度一直很小。如果步长是固定的,那么梯度小的那个方向就挪得超慢、梯度大的那个方向又可能 overshoot——这就需要"每个方向按自己的历史幅度自己调步长"。这正是 Kingma 和 Ba 2014 年提出的 Adam 的核心Diederik P. Kingma, Jimmy Lei Ba 2014。看 Adam 论文里的官方算法锚点:

图注:Adam 论文 Algorithm 1 的官方框图——Require 块、初始化、while 循环里的一阶矩 m_t 和二阶矩 v_t 更新、偏置修正、参数更新,以及论文给出的一组默认设置。你不需要读懂每一步——这张图的意义在于让你看到算法在内部维护两个滑动平均。

这张框图你不需要读懂每一步。它的意义在于:你能看到算法在内部维护两个滑动平均——一个是"过去几步的梯度方向",另一个是"过去几步的梯度幅度"。每一步的方向按前一个走,步子按后一个自己调。直觉翻译:梯度方向我按"过去几步的方向加权平均"走(这一段和动量一致),但每一步的步子我按"过去几步这个方向的平均幅度"自己调——一直很小的方向就迈大步、一直很大的方向就迈小步。这就是 Adam 在做的事。

为了让这件事在视觉上落地,再看 Adam 论文里的实验图(右面板是 IMDB 文本分类的稀疏梯度场景):

图注:两幅训练成本曲线。左图 MNIST 逻辑回归、右图 IMDB 词袋特征逻辑回归(稀疏梯度场景)。两图都把 Adam 与 AdaGrad/SGD-Nesterov/RMSProp 做了对比。右图 IMDB 这种"特征很稀疏、有的方向梯度一直接近零"的任务上,Adam 和 AdaGrad 的收敛曲线非常接近——这就是 Adam 把"梯度方向"和"梯度幅度"分别处理带来的好处。

这张图是训练成本随迭代次数下降的曲线,不是在损失地形上的轨迹——它证明的是 Adam 在稀疏梯度场景下能跑得跟 AdaGrad 一样好,再加上它对非平稳目标的内置适应能力,就成了今天深度学习的默认优化器之一。

到这里你已经看到了两种"自调步长"的方法:动量是把"过去的方向"攒起来;Adam 是再把"过去的大小"也攒起来,让每一步的步子按方向分量自己决定。这两个能力组合在一起,是今天你打开任何一个深度学习训练库的默认配置里都看得到的东西。

但你最初问的那个问题——"梯度大小到底反映什么"——到现在还没彻底收掉。下一章我们把这件事讲到结尾。

回到你的问题:梯度大小到底反映什么

把前五章串起来,你最初的直觉"梯度大小=离谷底远近",可以收成一个有边界的判断了:在平坦的损失地形上基本对,在崎岖的损失地形上会骗你;并且越靠近今天真实神经网络的训练场景,"梯度大小"这件事就越不能孤立地看。

我们先给你看一张真实的损失地形长什么样。Li、Xu、Taylor、Studer、Goldstein 2018 年那篇把"损失地形可视化"做成了系统方法的工作,用一种叫 filter normalization 的规范化手段,把真实神经网络在不同架构下的损失曲面切成了 3D 图Li et al. 2018。

图注:一张 3D 损失曲面渲染图——ResNet-56 把 skip connection 拆掉之后的损失函数。曲面崎岖、有多个尖锐的局部极小点,等高线不闭合,谷底不明显。这就是"梯度大小会骗你"的真实地形:表面很多位置梯度都接近零,但它们不是谷底。

这张图就是"梯度大小=离谷底远近"这条直觉失效的真实场景。地形破碎成多个尖锐小坑,每个小坑底梯度都接近零、看起来都像"快到了",但很多位置其实根本不是真正的最低点。站在这种地形上,"脚底感觉到的劲儿小"完全不能等同于"我快到了"。

作为对照,看下面这张同一架构但加了 skip connection 之后的版本:

图注:与上一张同一论文同一网络,但这一张是 ResNet-56 加了 skip connection 之后的损失曲面。曲面几乎是单调下降的碗,等高线接近同心椭圆,谷底一目了然,没有任何尖锐的伪极小点。这就是"梯度大小基本靠谱"的真实地形。

这两张放在一起,就是一条完整的视觉论证:梯度大小的直觉,在平缓盆地里成立、在破碎山地里失效。

再看一张更崎岖的:

图注:ResNet-110 把 skip connection 拆掉之后的 3D 损失曲面。比上一张 ResNet-56 的版本更崎岖:曲面有更多尖锐的局部极小点,山脊破碎,几乎看不出有清晰的谷底。地形越极端,"梯度大小反映远近"这件事就越失效。

但这张图不是要说"深度没用"——它说的是"深度 × 无 skip connection 的组合"导致的问题。同一论文里给出来的对应版本(ResNet-110 加 skip connection)的地形就跟 ResNet-56 加 skip 一样是个平缓的盆。所以这件事更准确的总结是:梯度的可靠性,绑在地形的形状上,地形的形状绑在架构上。

把这条线最后收一收:

  • 梯度方向:基本可信——除非你在鞍点正中央,否则它都指向"最陡下降"那一侧。
  • 梯度大小:默认情形下反映"那一处坡有多陡",大致对应"是不是快到谷底"。但在鞍点、平原、深度无 skip 的崎岖地形上会骗你。
  • 步长:是独立于方向的第二维。太小挪不动、太大会 overshoot 来回弹。
  • 批量大小:决定每一步看几个样本,看得越多方向越准、越少越抖。
  • 动量:把过去几步的方向攒起来用,让小球穿过窄谷时不弹来撞去。
  • Adam:再把"过去几步的方向分量大小"也攒起来,让每一步的步子按方向分量自己调。

今天你打开任何一个深度学习训练库(PyTorch、TensorFlow、JAX),默认的优化器就是 Adam 家族里的一员。它在动量之上再让"梯度大小不再唯一决定步长"——这就是你当初那个问题"梯度大小到底反映什么"的完整回答:梯度大小反映那一处坡有多陡,但不要让它单独决定你的步子。

参考文献

本专题引用的核心资料如下,建议深入学习时阅读原文:

  1. Gradient descent, how neural networks learn — Deep Learning Chapter 2 (3Blue1Brown, Grant Sanderson, text adapted by Josh Pullen) — https://www.3blue1brown.com/lessons/gradient-descent/
  2. Adam: A Method for Stochastic Optimization (Diederik P. Kingma, Jimmy Lei Ba) — arXiv:1412.6980
  3. Identifying and attacking the saddle point problem in high-dimensional non-convex optimization (Razvan Pascanu, Yann N. Dauphin, Surya Ganguli, Yoshua Bengio) — arXiv:1405.4604
  4. An overview of gradient descent optimization algorithms (Sebastian Ruder) — arXiv:1609.04747
  5. How the backpropagation algorithm works — Neural Networks and Deep Learning, Chapter 2 (Michael A. Nielsen) — https://neuralnetworksanddeeplearning.com/chap2.html
  6. Why Momentum Really Works (Gabriel Goh, Distill.pub) — https://distill.pub/2017/momentum/
  7. Visualizing the Loss Landscape of Neural Nets (Hao Li, Zheng Xu, Gavin Taylor, Christoph Studer, Tom Goldstein) — arXiv:1712.09913