三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

方向导数和梯度

方向导数和梯度

为什么要了解方向导数和梯度?

学习神经网络的梯度下降时,我们经常会看到一句话:

沿着负梯度方向更新参数,损失函数下降得最快。

这句话很简单,理解起来还是需要有一些前置知识的:为什么梯度是一个向量?为什么它指向上升最快的方向?为什么前面还要学方向导数?

我会从这几个方面进行阐述:

导数 → 偏导数 → 方向导数 → 梯度 → 梯度下降


1. 从普通导数开始:站在曲线上看坡度

先看最熟悉的一元函数:

\[y=f(x) \]

在某个位置 \(x_0\),导数 \(f'(x_0)\) 描述了函数在这里的瞬时变化率,也就是曲线在这一点的切线斜率:

\[f'(x_0)=\lim_{\Delta x\to 0}\frac{f(x_0+\Delta x)-f(x_0)}{\Delta x} \]

可以把它理解成:我在 x 轴上向右走一点,函数值会变化多快?

  • \(f'(x_0)>0\):向右走时函数值增大,也就是上坡。
  • \(f'(x_0)<0\):向右走时函数值减小,也就是下坡。
  • \(f'(x_0)=0\):当前位置暂时是平的,但不一定就是最低点。

例如:

\[f(x)=x^2,\qquad f'(x)=2x \]

在 x=2 这一刻,x 往前走一小步 Δx(很小很小),y 大约往前走 4 × Δx 这么多。

因为 \(x=2\) 时,导数是 4。用极限表示如下:

\(f'(2) = \lim_{\Delta x \to 0} \frac{f(2 + \Delta x) - f(2)}{\Delta x}\)

上边是 \(f(x)\) 在 x=2 这个时刻x增大 \(\Delta x\)\(f(x)\) 增大多少,然后用这个增大值除以 \(\Delta x\) 就可以算出这个倍数,即为导数值。

在 2 附近,x 每增加一个很小的单位,函数值大约增加 4 倍的这个小量。


2. 到了多元函数,麻烦出现了:可以往很多方向走

现在看一个二元函数:

\[z=f(x,y) \]

这时,x 和 y 一起决定函数值 z。图像不再是一条曲线,而更像是一张有高有低的地形图。

假设你站在曲面上的某一点,接下来可以:

  • 沿 x 轴走;
  • 沿 y 轴走;
  • 往右上方斜着走;
  • 往左下方走;
  • 或者朝平面上的任意方向走。

问题来了:不同方向的坡度可能完全不同。

所以,多元函数不能再只用一个普通导数概括所有方向的变化情况。


3. 偏导数:先只看两个特殊方向

偏导数的做法很直接:一次只改变一个变量,其他变量暂时不动。

对 x 求偏导

保持 y 不变,只沿 x 轴方向走:

\[\frac{\partial f}{\partial x} \]

它表示函数沿 x 轴正方向的变化率。

对 y 求偏导

保持 x 不变,只沿 y 轴方向走:

\[\frac{\partial f}{\partial y} \]

它表示函数沿 y 轴正方向的变化率。

例如:

\[f(x,y)=x^2+2y^2 \]

分别求偏导:

\[\frac{\partial f}{\partial x}=2x \]

\[\frac{\partial f}{\partial y}=4y \]

在点 \((1,1)\)

\[\frac{\partial f}{\partial x}=2,\qquad \frac{\partial f}{\partial y}=4 \]

这表示:

  • 沿 x 轴正方向走,函数值的变化率是 2;
  • 沿 y 轴正方向走,函数值的变化率是 4。

从这里已经能看出,在这个点附近,函数对 y 的变化比对 x 的变化更加敏感。

但偏导数只检查了坐标轴方向。如果我想沿着右上方走呢?这就需要方向导数。


4. 方向导数:指定一个方向,再问这个方向有多陡

设我们准备沿向量 \(\mathbf v\) 的方向移动:

\[\mathbf v=(v_1,v_2) \]

计算方向导数时,通常先把它变成单位向量:

\[\mathbf u=\frac{\mathbf v}{\lVert\mathbf v\rVert} \]

然后,函数在点 \(\mathbf x\) 处沿单位方向 \(\mathbf u\) 的方向导数定义为:

\[D_{\mathbf u}f(\mathbf x) = \lim_{t\to0} \frac{f(\mathbf x+t\mathbf u)-f(\mathbf x)}{t} \]

这个极限可以这样理解:

  1. 从当前位置 \(\mathbf x\) 出发;
  2. 沿 \(\mathbf u\) 方向走一小步 \(t\)
  3. 用“函数值变化量 ÷ 实际移动距离”,得到这个方向的瞬时变化率。

因此,方向导数是一个数,不是一个方向

  • 方向导数大于 0:沿这个方向走会上坡;
  • 方向导数小于 0:沿这个方向走会下坡;
  • 方向导数等于 0:沿这个方向走,函数值在这一瞬间几乎不变;
  • 绝对值越大:这个方向越陡。

5. 梯度:把各坐标方向的偏导数组装成一个向量

对于二元函数 \(f(x,y)\),梯度定义为:

\[\nabla f(x,y) = \left( \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y} \right) \]

对于 n 元函数:

\[\nabla f(\mathbf x) = \left( \frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, \ldots, \frac{\partial f}{\partial x_n} \right) \]

注意:梯度不是一个导数值,而是一个向量。

它把函数在每个坐标轴方向上的变化情况收集到一起:

  • 每个分量的正负,表示沿对应坐标轴往正方向移动时,函数是增大还是减小;
  • 每个分量绝对值的大小,表示函数对这个变量有多敏感;
  • 所有分量合在一起,形成函数在当前位置的整体变化信息。

还是刚才的函数:

\[f(x,y)=x^2+2y^2 \]

它的梯度为:

\[\nabla f(x,y)=(2x,4y) \]

\((1,1)\) 处:

\[\nabla f(1,1)=(2,4) \]

导数描述函数在某个点附近的瞬时变化率。对于二元函数,梯度把函数沿 x 轴和 y 轴正方向的瞬时变化率组合成一个向量。

对于函数

\(f(x,y)=x^2+2y^2\)

它的梯度为:

\(∇f(x,y)=(2x,4y)\)

在 (1,1) 处:

\(∇f(1,1)=(2,4)\)

其中:

  • 第一个分量 2 表示:在 (1,1) 附近,只沿 x 轴正方向移动一个很小的距离时,函数值大约按照变化率 2 增大;
  • 第二个分量 4 表示:只沿 y 轴正方向移动一个很小的距离时,函数值大约按照变化率 4 增大;
  • 两个分量都是正数,所以梯度箭头指向右上方;
  • y 方向的分量 4 是 x 方向分量 2 的两倍,所以箭头向上延伸得更多,看起来更靠近 y 轴。

梯度箭头所指的方向,就是当前位置函数值增长最快的方向。

6. 方向导数和梯度到底是什么关系?

  • 梯度:梯度是一个向量,其方向为变化最快的那个方向,其大小就是那个方向的变化率。 梯度方向是函数值上升最快的方向。
  • 方向导数回答:“如果我指定往某个方向走,函数值变化得有多快?”

梯度提供当前位置的整体变化信息,方向导数则是从梯度中取出“某个指定方向上的变化率”。

当函数在该点可微时,方向导数可以直接写成:

\[D_{\mathbf u}f(\mathbf x) = \nabla f(\mathbf x)\cdot\mathbf u \]

也就是:方向导数 = 梯度与单位方向向量的点积。

根据点积公式:

\[\nabla f\cdot\mathbf u = \lVert\nabla f\rVert \lVert\mathbf u\rVert \cos\theta \]

因为 \(\mathbf u\) 是单位向量,所以 \(\lVert\mathbf u\rVert=1\)

\[D_{\mathbf u}f = \lVert\nabla f\rVert\cos\theta \]

这里的 \(\theta\) 是梯度与行走方向之间的夹角。

这条公式是理解梯度的关键。

夹角 cos 值 方向导数 直观含义
\(\theta=0^\circ\) 1 最大 与梯度同向,上升最快
\(\theta=90^\circ\) 0 0 沿等高线走,高度暂时不变
\(\theta=180^\circ\) -1 最小 与梯度反向,下降最快

所以:

  • 梯度方向,是函数值上升最快的方向;
  • 负梯度方向,是函数值下降最快的方向;
  • 梯度的模长,是最大的方向导数,也就是最陡上坡的坡度。

这不是一个需要死记的结论,而是由点积里的 \(\cos\theta\) 直接决定的:\(\cos\theta\) 最大只能是 1,此时两个方向完全相同。


7. 算一个完整例子

仍然使用:

\[f(x,y)=x^2+2y^2 \]

\((1,1)\) 处,梯度为:

\[\nabla f(1,1)=(2,4) \]

现在沿方向 \(\mathbf v=(3,4)\) 行走。它的长度是 5,所以单位方向为:

\[\mathbf u= \left( \frac35,\frac45 \right) \]

方向导数为:

\[D_{\mathbf u}f(1,1) = (2,4)\cdot \left( \frac35,\frac45 \right) = \frac{22}{5} = 4.4 \]

也就是说,在 \((1,1)\) 附近沿这个方向每前进一个很小的单位,函数值大约增加 4.4 个单位。

那么上升最快的方向是什么?

就是梯度本身的方向。把梯度单位化:

\[\frac{\nabla f}{\lVert\nabla f\rVert} = \frac{(2,4)}{\sqrt{20}} \]

沿这个方向的最大变化率为:

\[\lVert\nabla f\rVert = \sqrt{20} \approx4.472 \]

可以看到,4.472 的确比刚才方向上的 4.4 更大。沿负梯度方向时,方向导数则是 \(-\sqrt{20}\),函数下降最快。


8. 从等高线再看一次:为什么梯度垂直于等高线?

地图上的等高线表示“高度相同的点”。对于函数来说,等高线上的函数值保持不变。

如果沿等高线方向移动,瞬间的高度变化率为 0,因此:

\[D_{\mathbf u}f = \nabla f\cdot\mathbf u = 0 \]

两个向量点积为 0,说明它们互相垂直。

所以,梯度总是垂直于当前位置的等高线,并指向函数值增大的一侧。

直观地说:

  • 沿等高线绕着山走,高度暂时不变;
  • 垂直穿过等高线,才会最快改变高度;
  • 梯度选择的是其中上坡的一边,负梯度选择的是下坡的一边。

9. 梯度下降:把“最快下坡”变成算法

假设函数 \(C(\boldsymbol\theta)\) 是我们想要减小的损失函数,其中 \(\boldsymbol\theta\) 表示全部参数。

梯度下降的更新公式是:

\[\boldsymbol\theta_{t+1} = \boldsymbol\theta_t - \eta\nabla C(\boldsymbol\theta_t) \]

可以把它拆成三步:

  1. 计算当前参数位置的梯度;
  2. 取梯度的反方向;
  3. 沿这个方向走一小步,再重复。

其中 \(\eta\) 是学习率,它控制每一步走多远。

一个小例子

设损失函数为:

\[C(w,b)=(w-3)^2+(b+1)^2 \]

这个函数的最低点显然是 \((3,-1)\)。假设从 \((0,0)\) 出发:

\[\nabla C(w,b)=\bigl(2(w-3),2(b+1)\bigr) \]

所以:

\[\nabla C(0,0)=(-6,2) \]

若学习率 \(\eta=0.1\),更新一次:

\[(w,b)_{\text{new}} = (0,0)-0.1(-6,2) = (0.6,-0.2) \]

更新前的损失:

\[C(0,0)=10 \]

更新后的损失:

\[C(0.6,-0.2)=6.4 \]

损失确实降低了。不断重复,就会逐渐靠近最低点 \((3,-1)\)


10. 为什么学习率不能太大,也不能太小?

负梯度只告诉我们“该往哪边走”,学习率决定“每步迈多大”。

  • 学习率太小:方向可能没错,但走得非常慢;
  • 学习率太大:可能一步跨过谷底,在两侧来回震荡,甚至越走越远;
  • 学习率合适:前期快速下降,靠近低点后逐渐稳定。

需要特别注意:梯度下降通常只能保证不断寻找更低的位置,不能简单理解为“一定找到全局最低点”。初始位置、学习率、局部极小值、鞍点和平坦区域都会影响训练过程。


11. 回到神经网络:参数空间其实是一座高维大山

在我第一个章节提到的手写数字识别示例中,神经网络共有 13,002 个权重和偏置。我们可以把它们排成一个很长的参数向量:

\[\boldsymbol\theta= (w_1,w_2,\ldots,b_1,b_2,\ldots) \]

损失函数接收这 13,002 个参数,最后只输出一个数:

\[C(\boldsymbol\theta)=\text{模型预测得有多差} \]

损失越大,说明模型预测得越差;损失越小,说明预测结果越接近正确答案。

此时,梯度也有 13,002 个分量:

\[\nabla C= \left( \frac{\partial C}{\partial\theta_1}, \frac{\partial C}{\partial\theta_2}, \ldots, \frac{\partial C}{\partial\theta_{13002}} \right) \]

每个分量都在回答一个很具体的问题:

如果把对应的权重或偏置轻轻改变一点,损失会发生多大变化?

  • 分量的正负告诉我们参数应该增大还是减小;
  • 分量绝对值越大,说明损失对这个参数越敏感;
  • 整个负梯度向量,则给出让所有参数共同配合、使损失下降最快的一组调整方向。

因此,所谓神经网络“学习”,从数学上看就是不断执行:

\[\text{计算梯度} \longrightarrow \text{沿负梯度调整参数} \longrightarrow \text{损失降低} \]

反向传播解决的是另一个问题:怎样高效计算损失函数对所有权重和偏置的梯度。


12. 几个注意点

概念 结果是什么 它回答的问题
导数 一个数 一元函数在这里有多陡?
偏导数 一个数 只改变某一个变量时,函数变化多快?
方向导数 一个数 沿指定方向走时,函数变化多快?
梯度 一个向量 哪个方向上升最快,各变量有多敏感?
负梯度 一个向量 哪个方向下降最快?

误区一:梯度就是斜率

不完全对。斜率通常是一个数,梯度是一个向量。梯度的方向表示最陡上坡方向,梯度的长度表示这个方向有多陡。

误区二:偏导数和方向导数互不相关

偏导数其实是方向导数的特殊情况:它只选择了坐标轴方向。

误区三:方向向量不需要单位化

如果只是代入点积计算,未单位化也能得到数值,但它混入了向量长度的影响。若要把结果解释为“每单位距离的变化率”,必须使用单位方向。

误区四:负梯度永远能一步到达最低点

负梯度只提供当前位置的局部最陡下降方向。函数弯曲后,下一步的方向也会变化,所以需要走一小步、重新计算,再继续走。


13. 总结

导数描述一条路上的坡度;偏导数描述坐标轴方向的坡度;方向导数描述任意指定方向的坡度;梯度把所有局部变化信息整理成一根“最陡上坡箭头”;沿着它的反方向走,就是梯度下降

理解到这里,再看神经网络中的损失函数、梯度下降和反向传播,就不会只剩下一堆孤立公式了:模型训练,本质上是在一个我们无法画出来的高维地形中,一步一步寻找更低的位置。

← 返回列表