首页 / 模块 1 · 线性代数与微积分 / 第 6 课(共 10 课)

导数与梯度

从零到前沿 ML 自学课程 · 阶段0:数学与工具基础 · 能力点:梯度下降——训练一切模型的引擎

读完这一课,你将能够

  • 对给定多元函数(如 \(x^2+xy+3y^2\))逐变量求偏导,把它们排成列向量写出梯度 \(\nabla f\)。
  • 用 \(D_u f=\nabla f\cdot u=\|\nabla f\|\cos\theta\) 算方向导数,并说出梯度为何指向最速上升、\(-\nabla f\) 为何是最速下降方向。
  • 给定起点、梯度和学习率,手算一步 \(w\leftarrow w-\eta\nabla f\),并验证 loss 确实下降。
  • 解释梯度为何处处垂直于等高线(沿等高线 \(f\) 不变 \(\Rightarrow\) 点积为 0)。
  • 区分极小、极大与鞍点,并说出梯度下降在这三类临界点都会停下。
  • 解释学习率太大会震荡甚至发散、太小则收敛极慢,并知道更新一定用减号。

上一课我们用线性代数搭好了"向量与矩阵"的地基;这一课,我们把高中熟悉的导数 derivative 推广到多变量,得到机器学习里最核心的工具——梯度 gradient,它会告诉我们:在一座由参数构成的"损失之山"上,往哪个方向走才下坡最快。

1. 从一元导数说起

回忆高中的导数。对一元函数 \(f:\mathbb{R}\to\mathbb{R}\),在点 \(x\) 处的导数定义为差商的极限:

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

它的几何意义是切线斜率 slope:当 \(x\) 增加一个极小量 \(h\),函数值大约增加 \(f'(x)\,h\)。把这句话写成式子,就是局部线性化 local linearization

\[ f(x+h)\approx f(x)+f'(x)\,h. \]

导数的本质是"放大后看局部":把曲线在一点附近不断放大,它会越来越像一条直线,这条直线的斜率就是导数。这个"曲线局部像直线、曲面局部像平面"的思想,是整门微分学(以及深度学习反向传播)的灵魂。

2. 多元函数与偏导数

机器学习里的损失函数往往依赖成千上万个参数。我们先看最朴素的多元标量函数 scalar function

\[ f:\mathbb{R}^n\to\mathbb{R},\qquad f(x_1,x_2,\dots,x_n). \]

输入是 \(n\) 个数(一个向量 \(x=[x_1,\dots,x_n]\)),输出是一个实数。例如 \(f(x,y)=x^2+3y^2\) 把平面上每个点映成一个"高度",画出来是一个碗状曲面。

怎么对它求导?关键想法:一次只动一个变量,其余当常数。这样多元就退化回一元,照搬一元导数即可。这就是偏导数 partial derivative

\[ \frac{\partial f}{\partial x_i} =\lim_{h\to 0}\frac{f(x_1,\dots,x_i+h,\dots,x_n)-f(x_1,\dots,x_n)}{h}. \]

对 \(f(x,y)=x^2+3y^2\):把 \(y\) 当常数对 \(x\) 求导得 \(\partial f/\partial x=2x\);把 \(x\) 当常数对 \(y\) 求导得 \(\partial f/\partial y=6y\)。每个偏导数衡量"只沿这一个坐标轴走"时函数变化的快慢。

3. 梯度:把所有偏导数打包成一个向量

单看一个偏导数,只知道沿某条坐标轴的陡峭程度。把所有偏导数排成一个列向量,就得到梯度 gradient,记作 \(\nabla f\)(读作 "nabla f" 或 "del f"):

\[ \nabla f(x)= \begin{bmatrix} \dfrac{\partial f}{\partial x_1}\\[4pt] \dfrac{\partial f}{\partial x_2}\\[2pt] \vdots\\[2pt] \dfrac{\partial f}{\partial x_n} \end{bmatrix}. \]

注意:\(\nabla f\) 与输入 \(x\) 同形(都在 \(\mathbb{R}^n\) 里),它在不同位置取不同的值(是 \(x\) 的函数)——即空间每个点都挂着一个向量,这样的对象叫向量场 vector field。对 \(f(x,y)=x^2+3y^2\):

\[ \nabla f(x,y)=\begin{bmatrix}2x\\6y\end{bmatrix}. \]

例如在点 \((1,1)\) 处,\(\nabla f=[2,6]\)。

4. 方向导数:朝任意方向走有多陡

偏导数只问了坐标轴方向。但我们想问更一般的问题:站在点 \(x\),沿任意单位方向 unit vector \(u\)(\(\|u\|=1\))走,函数上升多快?这就是方向导数 directional derivative

\[ D_u f(x)=\lim_{h\to 0}\frac{f(x+hu)-f(x)}{h}. \]

要算它,我们需要把第 1 节的"局部线性化"推广到多元。怎么推广?先固定其余变量、只让 \(x_i\) 动一点点 \(\Delta x_i\),由偏导数的定义,这一个方向贡献的函数变化约为 \(\dfrac{\partial f}{\partial x_i}\Delta x_i\)。在一阶近似下,各个坐标方向的贡献可以叠加,把它们求和:

\[ f(x+\Delta)-f(x)\approx\sum_{i=1}^{n}\frac{\partial f}{\partial x_i}\,\Delta x_i . \]

而这个求和 \(\sum_i \dfrac{\partial f}{\partial x_i}\Delta x_i\),恰好就是梯度与位移的点积 \(\nabla f(x)\cdot\Delta\)。于是多元的局部线性化自然落地(把一元的 \(f'(x)h\) 换成 \(\nabla f(x)\cdot\Delta\)):

\[ f(x+\Delta)\approx f(x)+\nabla f(x)\cdot\Delta . \]

这也让我们看清:\(\nabla f\) 不是凭空"打包"出来的,它正是线性化里自然冒出来的系数向量。(严格成立需要 \(f\) 可微,本课默认涉及的光滑函数都满足。)

现在取 \(\Delta=hu\) 代入,相减除以 \(h\) 再令 \(h\to 0\),立刻得到方向导数等于梯度在该方向上的点积

\[ \boxed{\,D_u f(x)=\nabla f(x)\cdot u\,} \]
方向导数是梯度在方向 u 上的投影 D_u f = ‖∇f‖cosα 从公共起点 O 画出梯度向量与单位方向向量 u,从梯度末端向 u 作垂线,投影长度即方向导数;下方三个小图展示 α=0、90、180 三种情形。 α O ∇f ‖∇f‖ u ‖u‖=1 D_u f = ‖∇f‖·cosα 方向导数 = 梯度在方向 u 上的投影 α = 0° ∇f 与 u 同向 投影 = 全长 (最大) 同向 → 上升最快 α = 90° u 投影 = 0 垂直 → 不变 α = 180° ∇f 与 u 反向 u 投影 = −全长 反向 → 下降最快
方向导数是梯度的投影:在固定点处,沿单位方向 u 的方向导数 D_u f = ∇f·u = ‖∇f‖cosα。当 u 与梯度 ∇f 夹角 α=0(同向)时投影最长、方向导数最大;α=90° 时投影为零、函数瞬时不变;α=180° 时投影最负、为最速下降。

换句话说,方向导数就是 \(\nabla f\) 在单位方向 \(u\) 上的标量投影 scalar projection(上一课的概念)。因为 \(\|u\|=1\),标量投影 \((\nabla f\cdot u)/\|u\|\) 正好等于点积 \(\nabla f\cdot u\)。

5. 核心定理:梯度指向最速上升方向

现在到本课的高潮。设梯度非零,把点积写成模长与夹角的形式(直接复用上一课的几何点积 \(a\cdot b=\|a\|\,\|b\|\cos\theta\),这里夹角仍用上一课的记号 \(\theta\)):

\[ D_u f=\nabla f\cdot u=\|\nabla f\|\,\|u\|\cos\theta=\|\nabla f\|\cos\theta, \]

其中 \(\theta\) 是 \(u\) 与 \(\nabla f\) 的夹角(用了 \(\|u\|=1\))。\(\|\nabla f\|\) 是固定的,所以 \(D_u f\) 完全由 \(\cos\theta\) 决定:

要点

  • 梯度 \(\nabla f\) 指向函数上升最快的方向,其模长 \(\|\nabla f\|\) 就是这个最大上升率。
  • 因此 \(-\nabla f\) 指向下降最快的方向——这正是梯度下降要走的方向。

6. 梯度与等高线正交

把曲面 \(f\) 在某高度切平,得到等高线 / 等值集 level set:所有满足 \(f(x)=c\) 的点。沿等高线移动时,函数值恒为 \(c\)、不变,所以方向导数为 0。设 \(t\) 是等高线的切向(单位向量),则

\[ D_t f=\nabla f\cdot t=0. \]

点积为零意味着正交 orthogonal。于是得到一个漂亮的几何事实:

要点

梯度处处垂直于等高线,并指向函数值增大的一侧(上坡)。这就是为什么在等高线图上,梯度场总是"横穿"等高线、与之成直角。

f(x,y)=x²+3y² 的等高线与梯度场,梯度与等高线正交并指向外侧上坡等高线与梯度场 f(x,y)=x²+3y²xyf=1f=4f=9f=16最小值 (0,0)梯度 ∇f=[2x,6y]:垂直等高线、指向外侧上坡,越陡越长−∇f:最速下降方向,指向碗底最速下降方向
等高线与梯度场:曲面 f(x,y)=x²+3y² 的等高线为同心椭圆,箭头表示梯度场 ∇f=[2x,6y]。每个梯度箭头都与所在等高线正交,并指向函数值增大的外侧(上坡);箭头越长表示该处越陡(‖∇f‖越大)。负梯度 −∇f(虚线短箭头)指向碗底,即梯度下降前进的方向。

7. 例题:梯度、方向导数、走一步梯度下降

例题

设 \(f(x,y)=x^2+xy+3y^2\)。

(a) 求梯度。 对 \(x\) 求偏导(\(y\) 当常数):注意交叉项 \(xy\) 对 \(x\) 求导时把 \(y\) 当常数,得 \(y\),所以 \(\partial f/\partial x=2x+y\);对 \(y\) 求偏导(\(x\) 当常数,交叉项 \(xy\) 对 \(y\) 求导得 \(x\)):\(\partial f/\partial y=x+6y\)。故

\[ \nabla f(x,y)=\begin{bmatrix}2x+y\\[2pt]x+6y\end{bmatrix}. \]

(b) 在点 \(p=(1,2)\) 求梯度。 代入:\(2(1)+2=4\),\(1+6(2)=13\)。所以

\[ \nabla f(1,2)=\begin{bmatrix}4\\13\end{bmatrix}. \]

(c) 在 \(p\) 处沿方向 \(d=(3,4)\) 求方向导数。 先把 \(d\) 单位化:\(\|d\|=\sqrt{3^2+4^2}=5\),故 \(u=(3/5,\,4/5)=(0.6,\,0.8)\)。则

\[ D_u f(p)=\nabla f(p)\cdot u=4(0.6)+13(0.8)=2.4+10.4=12.8. \]

(正数,说明沿这个方向是上坡,上升率为 12.8。)

(d) 从 \(p\) 做一步梯度下降,学习率 \(\eta=0.1\)。 更新规则 \(w\leftarrow w-\eta\nabla f(w)\):

\[ \begin{bmatrix}1\\2\end{bmatrix}-0.1\begin{bmatrix}4\\13\end{bmatrix} =\begin{bmatrix}1-0.4\\2-1.3\end{bmatrix} =\begin{bmatrix}0.6\\0.7\end{bmatrix}. \]

验证函数值确实下降:\(f(1,2)=1+2+12=15\);\(f(0.6,0.7)=0.36+0.42+1.47=2.25\)。从 15 降到 2.25,一步就大幅下坡。

8. 梯度下降与学习率

把上面的"走一步"重复很多次,就是梯度下降 gradient descent

\[ w_{k+1}=w_k-\eta\,\nabla f(w_k). \]

每一步都沿当前点的最速下降方向 \(-\nabla f\) 迈出一小步,步长由学习率 learning rate \(\eta>0\) 控制。直觉上像一个盲人下山:每走到一处,用脚感受哪边最陡(梯度),然后朝最陡的下坡方向迈 \(\eta\) 那么长一步。

易错

  • 梯度本身不是步长,它只给方向(和局部陡度)。真正走多远由 \(\eta\) 决定,要乘上去。
  • \(\eta\) 太大:会越过谷底、在两壁间来回震荡 oscillate 甚至发散(loss 越来越大)。
  • \(\eta\) 太小:每步挪一点点,收敛极慢,浪费大量计算。
  • 更新要用减号。写成加号就成了"梯度上升"——朝 loss 增大的方向走,与训练目标背道而驰(对常见的无下界损失,会让 loss 不断升高)。这是初学者最常见的符号 bug。

9. 临界点:极小、极大与鞍点

梯度下降走到 \(\nabla f=0\) 的地方就停下(没有下坡方向了)。这种点叫临界点 critical point,分三类:

在高维深度学习里,鞍点比局部极小多得多,是优化的主要障碍之一。区分这三类点需要看二阶信息(曲率),由下一课的海森(Hessian)矩阵刻画——下一课会先讲雅可比、再讲海森。

ML 和 ML 的联系

训练一个模型,本质就是在损失曲面上做梯度下降。模型参数 \(w\)(权重)是坐标,损失 \(L(w)\) 是高度,我们要找让 \(L\) 最小的 \(w\)。反向传播 backpropagation 高效地算出 \(\nabla L\)(即 \(\partial L/\partial w\)),优化器再执行 \(w\leftarrow w-\eta\nabla L\)。

本课记号铺垫了后续约定:我们采用分母布局 denominator layout,让 \(\partial L/\partial W\) 与参数 \(W\) 同形,这样"参数减去梯度"才在形状上对得齐——这个约定会在第 9 课正式声明。学习率 \(\eta\) 是最重要的超参数之一:调大了 loss 震荡发散,调小了训练龟速,这正是上面数学的直接后果。

10. 代码:用 numpy 最小化一个凸二次函数

我们对 \(f(x,y)=x^2+3y^2\)(梯度 \(\nabla f=[2x,\,6y]\),最小值在原点)做梯度下降,每步打印 \(x\)、\(y\) 与 loss。注意 \(y\) 方向曲率(系数 6)比 \(x\) 方向(系数 2)大得多,等高线是狭长椭圆。下面特意把学习率取在 \(\eta\in(1/6,\,1/3)\) 之间(取 \(0.3\)),这时 \(y\) 方向的迭代因子 \(1-6\eta=-0.8\) 为负,\(y\) 分量会正负交替、来回震荡着收敛,而 \(x\) 方向(因子 \(1-2\eta=0.4\))平滑收敛——这就是经典的之字形 zig-zag 轨迹。

import numpy as np

# f(x,y) = x^2 + 3 y^2 ,  最小值在 (0,0)
def f(w):
    return w[0]**2 + 3.0 * w[1]**2

def grad(w):
    return np.array([2.0 * w[0], 6.0 * w[1]])

w = np.array([4.0, 4.0])   # 起点
eta = 0.3                   # 学习率(落在 (1/6, 1/3) 内,y 方向会之字形震荡)

print("step |        x         y    |   loss")
for k in range(12):
    L = f(w)
    print(f"{k:3d}  | {w[0]: .5f} {w[1]: .5f} | {L: .6f}")
    w = w - eta * grad(w)   # 核心一行:朝 -梯度 走一步

print("final w =", w, " final loss =", f(w))

你会看到 \(y\) 列在 \(4\to-3.2\to2.56\to-2.048\to\dots\) 之间正负跳动、幅度缓慢衰减,而 \(x\) 列平滑趋于 0——典型的之字形。把 eta 改成 0.34 试试:此时 \(y\) 方向因子 \(|1-6\times0.34|=1.04>1\),\(y\) 会一边变号一边发散(每步放大 1.04 倍,loss 越来越大);改成 0.05 则两个方向都平滑、但收敛得很慢。亲手感受学习率的威力。

梯度下降在狭长椭圆等高线上的之字形轨迹对 f(x,y)=x²+3y² 的同心椭圆等高线,从 (4,4) 出发的负梯度迭代在峡谷两壁间来回横跨,收敛到 (0,0)。梯度下降之字形轨迹 f(x,y)=x²+3y², η=0.3xyw₀ (4,4)w₁ (2.8, 0.4)w₂w₃最小值 (0,0)η 过大 → 发散振幅逐步放大y 方向曲率大→ 步子跨过谷底形成之字形
梯度下降在狭长椭圆等高线上的之字形轨迹:对 f(x,y)=x²+3y²,由于两方向曲率不同(y 方向更陡),从起点 (4,4) 出发的迭代点沿负梯度方向前进时不断在峡谷两壁间来回横跨,形成 zig-zag 路径,逐步逼近碗底 (0,0)。学习率越大、之字形越剧烈甚至发散。

调一调,观察现象

下面三个小实验都基于本课已验证的函数与公式,改一个数、跑一下、对照"预期现象",亲手把抽象的梯度变成能看见的东西。三段都只用 numpy 与 print,几秒就能跑完。

实验 1:改学习率,看收敛 / 之字形 / 发散

改什么:对 \(f(x,y)=x^2+3y^2\)(梯度 \([2x,6y]\)),把 \(\eta\) 依次设成 0.05、0.3、0.34,起点都取 \((4,4)\)。
预期看到:\(\eta=0.05\) 两个方向都平滑下降,但 12 步后终点 \(x\) 仍在 1.13 附近、离原点还很远(loss≈1.29,收敛慢);\(\eta=0.3\) 时 \(y\) 列在 \(4\to-3.2\to2.56\to-2.05\to\dots\) 正负交替、幅度缓慢衰减(之字形,loss≈0.23);\(\eta=0.34\) 时 \(y\) 列同样正负跳动,但每步绝对值越跳越大(\(4\to-4.16\to4.33\to\dots\)),loss 飙到上百(发散,约 123)。
为什么:\(y\) 方向每步乘以因子 \(1-6\eta\):\(\eta=0.05\) 得 \(+0.70\)(同号、慢慢缩小),\(0.3\) 得 \(-0.80\)(变号但 \(|{\cdot}|<1\),震荡着收敛),\(0.34\) 得 \(-1.04\)(变号且 \(|{\cdot}|>1\),震荡着发散)。曲率大的方向决定了学习率的上限。

import numpy as np

def f(w):    return w[0]**2 + 3.0*w[1]**2
def grad(w): return np.array([2.0*w[0], 6.0*w[1]])

for eta in [0.05, 0.3, 0.34]:
    w = np.array([4.0, 4.0])
    ys = []
    for k in range(12):
        ys.append(round(float(w[1]), 3))
        w = w - eta * grad(w)
    print(f"eta={eta}: y轨迹={ys}")
    print(f"         final w={np.round(w,4)}  loss={f(w):.4f}")
    print(f"         y方向因子 1-6eta={1-6*eta:+.2f}\n")

实验 2:沿不同方向取方向导数,看梯度方向最大

改什么:在点 \((1,1)\) 处对 \(f=x^2+3y^2\)(梯度 \([2,6]\)),分别沿"与梯度同向、反向、垂直"三个特殊方向算 \(D_u f=\nabla f\cdot u\),再让单位方向 \(u\) 绕一圈采样。
预期看到:同向时方向导数最大,等于 \(\|\nabla f\|\approx6.3246\);反向时最小,约 \(-6.3246\);垂直时几乎为 0(数值上是 1e-16 量级的舍入误差)。绕圈采样里没有任何采样角恰好对准梯度(梯度方向约 71.6°),所以采样到的最大值落在最接近梯度的 60°(≈6.20),由此能直观看出峰就在梯度方向附近。
为什么:\(D_u f=\|\nabla f\|\cos\theta\),整条曲线就是一条余弦:峰在同向、谷在反向、零在垂直,这正是"梯度指向最速上升"的直接体现。

import numpy as np

g = np.array([2.0, 6.0])          # (1,1) 处的梯度
gn = np.linalg.norm(g)            # ≈ 6.3246
print("||grad|| =", round(gn, 4))

ug = g / gn                       # 沿梯度方向(同向,单位向量)
uperp = np.array([-ug[1], ug[0]]) # 与梯度垂直的方向
print("同向 Du =", f"{g.dot( ug): .4f}", " == +||grad||")
print("反向 Du =", f"{g.dot(-ug): .4f}", " == -||grad||")
print("垂直 Du =", f"{g.dot(uperp): .2e}", " (数值 ~0)")

print("-- 绕一圈采样,看峰在梯度方向附近 --")
for deg in [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330]:
    t = np.deg2rad(deg)
    u = np.array([np.cos(t), np.sin(t)])   # 单位方向
    print(f"角度{deg:>3}度:  Du = {g.dot(u): .4f}")

实验 3:用差商逼近偏导,看 h 越小越准

改什么:不用解析公式,对 \(f(x,y)=x^2+xy+3y^2\) 在 \((1,2)\) 处用差商 \((f(x+h,y)-f(x,y))/h\) 近似 \(\partial f/\partial x\),让 \(h\) 取 1e-1、1e-3、1e-5。
预期看到:近似值逐步逼近解析答案 \(2x+y=4\):\(h=0.1\) 时恰为 4.1,\(h=10^{-3}\) 时为 4.001,\(h=10^{-5}\) 时为 4.00001,误差与 \(h\) 同阶地缩小(依次约 1e-1、1e-3、1e-5)。
为什么:偏导数本就是 \(h\to0\) 的差商极限,"一次只动一个变量"让多元退化回一元导数;缩小 \(h\) 就是在数值上逼近这个极限。(此处误差恰好等于 \(h\),是因为这个二次函数沿 \(x\) 的二阶项让前向差商多出一个 \(h\) 的线性偏差。)

import numpy as np

def f(x, y): return x*x + x*y + 3*y*y

x, y = 1.0, 2.0
analytic = 2*x + y                 # ∂f/∂x = 2x+y = 4
for h in [1e-1, 1e-3, 1e-5]:
    approx = (f(x+h, y) - f(x, y)) / h
    print(f"h={h:.0e}:  差商={approx:.6f}  误差={abs(approx-analytic):.2e}")
print("解析 ∂f/∂x =", analytic)

动手练习

  1. 手算梯度。 对 \(f(x,y)=2x^2+y^2-xy\),求 \(\nabla f\),并在点 \((1,1)\) 求出梯度向量。再求沿 \(u=(0,1)\) 的方向导数。(验证:\(\nabla f=[4x-y,\,2y-x]\),在 \((1,1)\) 为 \([3,1]\),方向导数 \(=1\)。)
  2. 验证正交。 对 \(f(x,y)=x^2+y^2\),等高线是圆。在 \((3,4)\) 处求梯度,并验证它与该点处圆的切向量正交(提示:圆在该点的切向与半径垂直,半径方向就是 \((3,4)\))。
  3. 改代码做一步对比。 用下面骨架,比较 \(\eta=0.1\) 与 \(\eta=0.5\) 走一步后的 loss,看谁下降更多、谁可能反而上升。
    import numpy as np
    
    def f(w):  return w[0]**2 + 3.0*w[1]**2
    def grad(w): return np.array([2.0*w[0], 6.0*w[1]])
    
    w0 = np.array([1.0, 1.0])
    for eta in [0.1, 0.5]:
        w1 = w0 - eta * grad(w0)
        print(f"eta={eta}:  w0->w1 = {w0} -> {w1} ,  loss {f(w0):.4f} -> {f(w1):.4f}")
    
  4. 找鞍点。 对 \(f(x,y)=x^2-y^2\),求 \(\nabla f\) 并解 \(\nabla f=0\)。从 \((0.01,\,0.01)\) 跑十步梯度下降(\(\eta=0.1\)),观察 \(y\) 分量是被吸引还是被排斥,体会鞍点的不稳定。
  5. 数值偏导。 不用解析公式,用差商 \(\big(f(x+h,y)-f(x,y)\big)/h\)(取 \(h=10^{-5}\))近似 \(\partial f/\partial x\),与解析梯度对比误差,验证偏导数的定义。

掌握自检

下一课,我们把导数从"标量函数"推广到向量值函数:当输出也是一个向量时,所有偏导数排成一个矩阵,就是雅可比 Jacobian;而损失曲面的二阶曲率(用来区分极小与鞍点)则由海森 Hessian 矩阵刻画。