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

雅可比与海森

从零到前沿 ML 自学课程 · 阶段0:数学与工具基础 · 能力点:二阶信息——用曲率理解优化为何有难有易

读完这一课,你将能够

  • 对一个 \(f:\mathbb{R}^n\to\mathbb{R}^m\),立刻说出雅可比的形状 \(m\times n\)、哪个下标是行哪个是列,并逐分量手写出一个 \(2\times2\) 雅可比。
  • 解释 \(f(x+\Delta)\approx f(x)+J\Delta\) 是"用切平面贴弯曲映射",并说明 \(m=n\) 时 \(|\det J|\) 是局部体积缩放、\(\det J<0\) 是镜像翻面。
  • 写出标量函数的海森、说清"海森 = 梯度的雅可比",并用二阶泰勒展开把临界点的局部形状归到二次型 \(\tfrac12\Delta^\top H\Delta\)。
  • 给一个 \(2\times2\) 海森,算特征值并判定极小/极大/鞍点,并说出"凸 ⇔ 海森半正定"。
  • 用一句话说出牛顿法 \(x\leftarrow x-H^{-1}\nabla f\) 的直觉,以及条件数 \(\kappa=\lambda_{\max}/\lambda_{\min}\) 为何决定一阶法快慢。

上一课我们为标量函数 \(f:\mathbb{R}^n\to\mathbb{R}\) 装上了"梯度 \(\nabla f\)"这台指南针,它在每个点指向上升最快的方向。但神经网络里到处是向量进、向量出的映射:一层把激活向量变成另一个激活向量。这一课我们把导数推广到向量值函数,得到雅可比矩阵 Jacobian;再回头给标量函数装上"二阶导数"——海森矩阵 Hessian,它描述函数的弯曲程度(曲率 curvature),正是优化难易、鞍点、二阶方法的几何根源。

一、雅可比:向量值函数的导数

考虑一个把 \(n\) 维向量映成 \(m\) 维向量的函数

\[ f:\mathbb{R}^n\to\mathbb{R}^m,\qquad f(x)=\begin{bmatrix} f_1(x)\\ f_2(x)\\ \vdots\\ f_m(x)\end{bmatrix},\quad x=\begin{bmatrix} x_1\\ \vdots\\ x_n\end{bmatrix}. \]

它有 \(m\) 个输出分量,每个分量又依赖 \(n\) 个输入变量。于是"导数"不再是一个数,也不再是一个向量,而是把所有偏导数 \(\partial f_i/\partial x_j\) 排成的一张表——这就是雅可比矩阵。

要点

雅可比矩阵 \(J\)(也写作 \(\frac{\partial f}{\partial x}\) 或 \(Df\))的元素定义为

\[ J_{ij}=\frac{\partial f_i}{\partial x_j},\qquad J=\begin{bmatrix} \dfrac{\partial f_1}{\partial x_1} & \cdots & \dfrac{\partial f_1}{\partial x_n}\\[2mm] \vdots & \ddots & \vdots\\[1mm] \dfrac{\partial f_m}{\partial x_1} & \cdots & \dfrac{\partial f_m}{\partial x_n} \end{bmatrix}. \]

形状 \(m\times n\)行对应输出分量 \(i\),列对应输入变量 \(j\)。记忆口诀——"第 \(i\) 行是第 \(i\) 个输出 \(f_i\) 的梯度(横着放)"。

两个熟悉的特例帮你定位它在知识地图上的位置:

雅可比 = 局部线性映射

一维时我们说"导数 = 切线斜率",本质是"在一个点附近,弯的函数看起来像直线"。多维的版本是:在一点附近,弯曲的映射 \(f\) 看起来像一个线性映射,而那个线性映射的矩阵就是雅可比 \(J\)。

把这句话写成公式,就是一阶线性化(first-order linearization)。设在点 \(x\) 处给一个微小扰动 \(\Delta\in\mathbb{R}^n\),则

\[ f(x+\Delta)\;\approx\; f(x)+J\,\Delta, \]

右边 \(J\Delta\) 是矩阵乘向量,结果是 \(m\) 维向量,与 \(f(x)\) 同形——量纲对上了。这就是"用一块切平面去贴住弯曲曲面"的多维推广。误差是 \(O(\|\Delta\|^2)\),扰动越小越准。

雅可比作为局部线性映射:输入小方块经 f 近似变为输出平行四边形,面积比为 |det J|左侧输入空间 R^2 中以 x 为中心的小正方形,两边为单位扰动 e1、e2;中间大箭头标注 f(局部约等于乘 J);右侧输出空间 R^2 中对应平行四边形,两边为 J·e1、J·e2,已不再正交,内部标注面积等于 |det J| 乘原面积。 输入空间 R² x₁ x₂ x e₁ e₂ 单位小方块 f 局部 ≈ 乘 J 输出空间 R² y₁ y₂ f(x) J·e₁ (J 第 1 列) J·e₂ (J 第 2 列) 面积 = |det J| × 原面积 拉伸 + 旋转 + 错切,不再正交
雅可比作为局部线性映射:输入空间一个微小方块经 f 映射后,在输出空间近似变成一个平行四边形,其边由雅可比的两列向量张成;方块/平行四边形的面积之比即 |det J|。

例题:写一个 \(f:\mathbb{R}^2\to\mathbb{R}^2\) 的雅可比

\[ f(x,y)=\begin{bmatrix} x^2 y\\[1mm] x+\sin y\end{bmatrix} =\begin{bmatrix} f_1\\ f_2\end{bmatrix}. \]

逐个分量求偏导(这里 \(m=n=2\),所以 \(J\) 是 \(2\times2\)):

  • \(\dfrac{\partial f_1}{\partial x}=2xy,\qquad \dfrac{\partial f_1}{\partial y}=x^2\)
  • \(\dfrac{\partial f_2}{\partial x}=1,\qquad\quad \dfrac{\partial f_2}{\partial y}=\cos y\)

按"行=输出、列=输入"排好:

\[ J(x,y)=\begin{bmatrix} 2xy & x^2\\ 1 & \cos y\end{bmatrix}. \]

代入点 \((x,y)=(1,\tfrac{\pi}{2})\),注意 \(\cos\tfrac{\pi}{2}=0\):

\[ J\!\left(1,\tfrac{\pi}{2}\right)=\begin{bmatrix} 2\cdot1\cdot\tfrac{\pi}{2} & 1^2\\ 1 & 0\end{bmatrix} =\begin{bmatrix} \pi & 1\\ 1 & 0\end{bmatrix}. \]

它告诉我们:在 \((1,\pi/2)\) 附近,给输入加一个小扰动 \(\Delta=[\Delta x,\Delta y]^\top\),输出大约变化 \(J\Delta=[\pi\,\Delta x+\Delta y,\ \Delta x]^\top\)。

方阵时:\(\det J\) 是局部体积缩放因子

当 \(m=n\) 时 \(J\) 是方阵,它的行列式有一个漂亮的几何意义。回忆线性代数:一个矩阵 \(A\) 把单位立方体映成平行多面体,体积变成 \(|\det A|\) 倍。既然 \(f\) 在局部就是线性映射 \(J\),于是:

要点

在点 \(x\) 附近,\(f\) 把一个微小体积元放大/缩小为原来的 \(|\det J(x)|\) 倍。若 \(\det J<0\),还附带一次"翻面"(定向反转 orientation reversal)。

"翻面"是什么直觉? 就像照镜子——左手照出来变成右手;在二维里,它相当于把图形沿某条线做一次镜像翻转,原本逆时针绕的小圈会被映成顺时针。所以 \(\det J\) 同时编码了两件事:绝对值 \(|\det J|\) 是面积/体积缩放倍数,符号则告诉你有没有镜像翻面。

这正是换元积分里那个雅可比行列式因子的来历(你在概率论里会反复见到)。设 \(y=f(x)\) 为正向变换,\(J=J_f(x)\) 是 \(f\) 在 \(x\) 处的雅可比。若随机向量 \(y=f(x)\) 是 \(x\) 的可逆变换,则概率密度满足变量替换公式

\[ p_Y(y)=p_X(x)\,\Big|\det J_f(x)\Big|^{-1},\qquad x=f^{-1}(y). \]

体积被 \(f\) 放大 \(|\det J_f|\) 倍,概率要守恒(总和为 1),所以密度就得除以这个因子。注意指数 \(-1\) 是因为这里 \(J_f\) 是正向映射 \(x\to y\) 的雅可比;若改用逆向雅可比 \(J_{f^{-1}}(y)\),由于 \(\det J_{f^{-1}}=1/\det J_f\),指数就变成 \(+1\)。两种写法等价,关键是认清你算的是哪个方向的雅可比。这条公式是生成模型里"标准化流(normalizing flows)"的数学心脏。

例题:上例的 \(\det J\)

沿用 \(J(1,\tfrac\pi2)=\begin{bmatrix}\pi&1\\1&0\end{bmatrix}\)。\(2\times2\) 行列式 \(ad-bc\):

\[ \det J=\pi\cdot 0-1\cdot 1=-1. \]

解读:在该点附近,\(f\) 几乎保持体积不变(\(|\det J|=1\)),但因为 \(\det J<0\),它把小邻域镜像翻了个面(定向反转)——逆时针的小圈被映成顺时针。

二、海森:标量函数的二阶导数(曲率)

梯度告诉我们函数往哪儿走、走多陡;但它说不清函数弯得多厉害——是平缓的碗,还是狭长的山谷,还是马鞍。要回答这个,得求二阶导数。对标量函数 \(f:\mathbb{R}^n\to\mathbb{R}\),把所有二阶偏导数排成方阵,就是海森矩阵。

要点

海森矩阵 \(H\)(记作 \(\nabla^2 f\) 或 \(H_f\))定义为

\[ H_{ij}=\frac{\partial^2 f}{\partial x_i\,\partial x_j},\qquad H=\begin{bmatrix} \dfrac{\partial^2 f}{\partial x_1^2} & \cdots & \dfrac{\partial^2 f}{\partial x_1\partial x_n}\\[2mm] \vdots & \ddots & \vdots\\[1mm] \dfrac{\partial^2 f}{\partial x_n\partial x_1} & \cdots & \dfrac{\partial^2 f}{\partial x_n^2} \end{bmatrix}. \]

它的形状是 \(n\times n\)。海森就是梯度的雅可比:把向量值函数 \(\nabla f:\mathbb{R}^n\to\mathbb{R}^n\) 求一次雅可比,得到的就是 \(H\)。所以"二阶导 = 一阶导的导"在多维里精确地表达为 \(H=J_{\nabla f}\)。

海森是对称的:只要二阶偏导连续,混合偏导可交换次序(克莱罗定理 Clairaut's theorem),即 \(\dfrac{\partial^2 f}{\partial x_i\partial x_j}=\dfrac{\partial^2 f}{\partial x_j\partial x_i}\),于是 \(H=H^\top\)。对称矩阵有实特征值、可正交对角化(这是对称矩阵谱定理的结论,可回顾线性代数那一课)——这一点后面判断曲率时至关重要。

二阶泰勒展开

一维泰勒展开 \(f(x+\Delta)\approx f+f'\Delta+\tfrac12 f''\Delta^2\) 的多维版本,把 \(f'\) 换成梯度、\(f''\) 换成海森:

要点(二阶泰勒展开)

\[ f(x+\Delta)\;\approx\; f(x)+\nabla f(x)^\top\Delta+\tfrac12\,\Delta^\top H(x)\,\Delta. \]

三项的量纲都是标量:\(\nabla f^\top\Delta\) 是点积(一阶、线性项),\(\tfrac12\Delta^\top H\Delta\) 是二次型(quadratic form)(二阶、曲率项)。在临界点处 \(\nabla f=0\),一阶项消失,函数的局部形状完全由那个二次型 \(\tfrac12\Delta^\top H\Delta\) 决定

曲率与海森的特征值

二次型 \(\Delta^\top H\Delta\) 的"脾气"由 \(H\) 的特征值(eigenvalue)决定。把 \(H\) 正交对角化 \(H=Q\Lambda Q^\top\)(即上面提到的对称矩阵谱定理,此处即引即用),沿特征向量(eigenvector)方向看,函数就是一族独立的抛物线,第 \(k\) 条的"曲率"正比于特征值 \(\lambda_k\):

海森矩阵曲率与临界点:正定碗(极小)、负定穹顶(极大)、不定马鞍(鞍点),上排曲面下排等高线 正定 positive 负定 negative 不定 indefinite x z y z=x²+y² x z y z=-(x²+y²) x z y z=x²-y² ↓ 对应等高线(俯视) 极小 min λ₁,λ₂>0 极大 max λ₁,λ₂<0 鞍点 saddle λ 有正有负 海森矩阵 H 的特征值 λ 符号决定曲率:同号→极值,异号→鞍点 中心点为临界点 ∇f=0;闭合圈=极值,双曲线=鞍点
三种曲率对应三类临界点:海森正定=向上的碗(极小)、负定=向下的穹顶(极大)、不定=马鞍(鞍点)。上排为三维曲面缩略,下排为对应的等高线。

由此得到临界点(\(\nabla f=0\) 的点)分类,只看 \(H\) 的特征值符号:

\(H\) 的特征值矩阵性质临界点类型几何形状
全为正正定 positive definite局部极小 minimum碗(处处向上弯)
全为负负定 negative definite局部极大 maximum穹顶(处处向下弯)
有正有负不定 indefinite鞍点 saddle马鞍面
含零、其余同号半定 semidefinite退化(需更高阶判断)有平谷/平脊

凸性 ⇔ 海森半正定

要点

一个二阶可微函数 \(f\) 是凸函数(convex) ⇔ 它在每一点的海森 \(H(x)\) 都半正定(所有特征值 \(\ge 0\))。凸函数最美的性质是:任何局部极小都是全局极小,没有"陷阱"。

这就是为什么线性回归、逻辑回归这类问题"好优化",而深层神经网络的损失非凸、海森到处变号、布满鞍点,优化才那么棘手。

例题:写海森并用特征值判断临界点类型

设 \(g(x,y)=x^2-y^2\)。先找临界点:梯度

\[ \nabla g=\begin{bmatrix}\partial g/\partial x\\ \partial g/\partial y\end{bmatrix} =\begin{bmatrix}2x\\ -2y\end{bmatrix}=\mathbf 0 \;\Rightarrow\; (x,y)=(0,0). \]

再求海森(对 \(\nabla g\) 求雅可比):\(\partial(2x)/\partial x=2,\ \partial(2x)/\partial y=0,\ \partial(-2y)/\partial x=0,\ \partial(-2y)/\partial y=-2\),于是

\[ H=\begin{bmatrix}2 & 0\\ 0 & -2\end{bmatrix}. \]

它已是对角阵,特征值就是对角元 \(\lambda_1=2>0,\ \lambda_2=-2<0\)。有正有负 ⟹ \((0,0)\) 是鞍点。几何上:沿 \(x\) 轴是向上的抛物线(碗),沿 \(y\) 轴是向下的抛物线(山顶),合起来正是马鞍——和函数名 \(x^2-y^2\) 完全吻合。

对照另一个例子 \(f(x,y)=x^2+xy+y^2\),临界点也在原点,海森 \(H=\begin{bmatrix}2&1\\1&2\end{bmatrix}\),特征值 \(1\) 与 \(3\)(都为正),故原点是极小;这个 \(f\) 是凸函数。

易错

  • 雅可比的形状别搞反:是 \(m\times n\)(行=输出、列=输入)。写成 \(n\times m\) 会让后面的链式法则 \(J\Delta\) 维度对不上。
  • "行列式 \(>0\) 且对角元 \(>0\)" 不等于正定。例如 \(\begin{bmatrix}-1&0\\0&-2\end{bmatrix}\) 行列式为 \(+2>0\) 却是负定。判类型要看每个特征值的符号(或用所有顺序主子式的西尔维斯特判据),不能只看行列式。
  • 海森的对称性依赖二阶偏导连续。实践中几乎总成立,但写数值海森时务必对称化(取 \(\tfrac12(H+H^\top)\)),否则有限差分噪声会让它略微不对称。

三、牛顿法:用海森修正梯度

梯度下降 \(x\leftarrow x-\eta\nabla f\) 只用了一阶信息,它不知道每个方向弯得多厉害,所以只能用一个全局学习率 \(\eta\) 凑合。牛顿法(Newton's method)更聪明:它在当前点用二阶泰勒展开搭一个抛物面,然后一步跳到这个抛物面的最低点

对二阶近似 \(q(\Delta)=f+\nabla f^\top\Delta+\tfrac12\Delta^\top H\Delta\) 求最小,令其梯度为零:\(\nabla f+H\Delta=0\),解出 \(\Delta=-H^{-1}\nabla f\)。于是牛顿更新为

\[ x \;\leftarrow\; x-H^{-1}\,\nabla f. \]

直觉:\(H^{-1}\) 同时修正了方向和步长。在弯得陡(\(\lambda\) 大)的方向自动迈小步,在平缓(\(\lambda\) 小)的方向自动迈大步——相当于给每个方向配了一个量身定制的学习率 \(1/\lambda_k\)。对一个凸(海森正定)二次函数,牛顿法一步就到达它唯一的极小点。

风险提示: 这个"一步到位"只在海森正定时成立。若海森不定(如鞍点形 \(g=x^2-y^2\)),牛顿步求的是"二次近似梯度为零的点",那是个临界点——可能是鞍点甚至极大点,于是牛顿步会径直奔向最近的临界点而非极小。这正是实践中要给牛顿法加正则化/信赖域修正的原因之一,也与后面 ML 小节"二阶方法用 \(H\) 的某种估计去拉直曲率"相呼应。

四、向量-雅可比积(VJP)——下一课的钥匙

牛顿法展示了二阶信息多有用,但代价是要算 \(H^{-1}\);回到现实,深度学习几乎全靠一阶方法——而支撑一阶反向传播的最小积木,就是下面要讲的 VJP。它把我们从"二阶(理想但昂贵)"自然带回"一阶(便宜且实用,下一课的主角)"。

反向传播的全部秘密,可以浓缩成一句话:它从不显式构造庞大的雅可比矩阵,只反复计算"向量乘雅可比"这个组合。给定某一层的雅可比 \(J\in\mathbb{R}^{m\times n}\),以及一个暂时看作任意给定的 \(m\) 维向量 \(v\in\mathbb{R}^m\)(沿用全篇约定:向量默认是列向量),所谓向量-雅可比积(vector–Jacobian product, VJP)就是把 \(v\) 转置后左乘 \(J\):

\[ v^\top J \;\in\; \mathbb{R}^{1\times n}, \qquad\text{等价地写成列向量}\quad J^\top v \;\in\; \mathbb{R}^{n}. \]

这两种写法只差一个转置:\(v^\top J\) 是行向量,\(J^\top v=(v^\top J)^\top\) 是列向量。机器学习库里习惯让"梯度与参数同形(列向量)",所以最终拿到手的是 \(J^\top v\in\mathbb{R}^n\)。\(v\) 是哪来的? 这里你只需把它看成一个给定的 \(m\) 维向量;在下一课它会是损失对本层输出的梯度,这里无需剧透,只要知道 VJP 是个比存下整个 \(J\) 便宜得多的运算即可。

为什么不直接存 \(J\)?因为在神经网络里 \(m,n\) 可能是几千几万,\(J\) 大到存不下;但 \(J^\top v\) 只是一个 \(n\) 维向量,而且对许多层(如逐元素激活、矩阵乘)有不依赖于显式 \(J\) 的快速算法。把每一层的 VJP 从输出端一路串到输入端,就是反向传播。

ML 和 ML 的联系

  • 海森解释"优化难易"。 损失曲面若是又圆又匀的碗(海森特征值都差不多),梯度下降几步就到底;若是狭长山谷(最大特征值 \(\gg\) 最小特征值),梯度会在陡壁间反复横跳、在谷底方向爬得极慢。两者之比 \(\kappa=\lambda_{\max}/\lambda_{\min}\) 叫条件数(condition number),它几乎决定了一阶方法的收敛速度。
    良态与病态二次型等高线及梯度下降轨迹对比二次型的条件数与梯度下降收敛良态 well-conditioned · κ≈1x*x0-∇f等高线近圆 · 梯度直指圆心 · 几步收敛病态 ill-conditioned · κ=100x*x0-∇f狭长椭圆 · 之字形横跳 · 收敛缓慢条件数 κ = λmax / λmin 左:H=diag(1,1),κ=1 右:H=diag(1,100),κ=100κ 越大,等高线越扁,负梯度越偏离指向 x* 的方向,振荡越剧烈
    良态 vs 病态二次型的等高线:海森特征值相近时等高线是近圆(梯度直指圆心,下降快);特征值悬殊时是狭长椭圆(梯度几乎垂直于通往最小点的长轴,梯度下降之字形横跳、收敛慢)。
  • 二阶方法(牛顿法及其近似:L-BFGS、自然梯度、Adam 中的对角缩放)本质都是想"用某种 \(H\) 的估计去拉直病态曲率"。真正的 \(H^{-1}\) 太贵(\(n\) 上百万时无法求逆),所以实践中只用便宜的近似。
  • 高维里鞍点比局部极小多得多。 一个临界点要成为局部极小,需要海森所有 \(n\) 个特征值都为正;随机情形下这像连抛 \(n\) 次硬币全为正面,概率随维度指数级衰减。所以深度网络训练时"卡住"的地方,多半是鞍点而非真正的极小——这也解释了为什么带噪声的 SGD 反而容易逃离鞍点。
  • VJP 是自动微分的原子操作(PyTorch 的 autograd、JAX 的 vjp),下一课会把它和计算图拼成完整的反向传播。

五、用 numpy 数值验证手算

有限差分(finite difference)能在不会求导的情况下逼近雅可比与海森,是检查手推公式的利器。下面这段只用 numpy,几秒跑完,把上面两道例题都验一遍。

import numpy as np

# ---------- 1) 数值雅可比:中心差分 ----------
def num_jacobian(f, x, h=1e-5):
    x = np.asarray(x, float)
    fx = np.asarray(f(x), float)
    m, n = fx.size, x.size
    J = np.zeros((m, n))
    for j in range(n):                 # 逐列:扰动第 j 个输入
        e = np.zeros(n); e[j] = h
        J[:, j] = (np.asarray(f(x + e)) - np.asarray(f(x - e))) / (2 * h)
    return J

# f: R^2 -> R^2 , f(x,y) = [x^2 y , x + sin y]
def f(v):
    x, y = v
    return np.array([x**2 * y, x + np.sin(y)])

p = np.array([1.0, np.pi / 2])
J_num = num_jacobian(f, p)
J_hand = np.array([[2*p[0]*p[1], p[0]**2],
                   [1.0,          np.cos(p[1])]])   # = [[pi,1],[1,0]]
print("数值雅可比 J =\n", np.round(J_num, 6))
print("手算雅可比 J =\n", np.round(J_hand, 6))
print("det(J) 数值 =", round(np.linalg.det(J_num), 6), " (手算 = -1)")

# ---------- 2) 数值海森:二阶中心差分 ----------
def num_hessian(g, x, h=1e-4):
    x = np.asarray(x, float); n = x.size
    H = np.zeros((n, n))
    for i in range(n):
        for j in range(n):
            ei = np.zeros(n); ei[i] = h
            ej = np.zeros(n); ej[j] = h
            H[i, j] = (g(x+ei+ej) - g(x+ei-ej)
                       - g(x-ei+ej) + g(x-ei-ej)) / (4*h*h)
    return 0.5 * (H + H.T)             # 对称化,抹掉差分噪声

# g: R^2 -> R , g(x,y) = x^2 - y^2 ,临界点 (0,0)
def g(v):
    x, y = v
    return x**2 - y**2

H = num_hessian(g, np.array([0.3, -0.4]))   # 海森处处为常数阵
eig = np.linalg.eigvalsh(H)                 # 对称阵用 eigvalsh
print("\n数值海森 H =\n", np.round(H, 4), " (手算 = [[2,0],[0,-2]])")
print("特征值 =", np.round(eig, 4))
kind = ("极小" if np.all(eig > 0) else
        "极大" if np.all(eig < 0) else "鞍点")
print("临界点 (0,0) 类型 ->", kind)        # -> 鞍点

运行后你会看到数值结果与手算几乎逐位吻合:雅可比为 \([[\pi,1],[1,0]]\)、行列式 \(-1\),海森为 \(\mathrm{diag}(2,-2)\)、特征值 \(\{2,-2\}\),判定为鞍点。

调一调,观察现象

下面三个微任务都基于本课已验证的公式,改一个数、看一个数怎么变,把"曲率、行列式、条件数"从符号变成你亲眼跑出来的现象。每段都只用 numpy 和 print,几秒跑完。

任务一:把扰动缩小 10 倍,看线性化误差缩小约 100 倍

改什么:对例题里的 \(f(x,y)=[x^2y,\ x+\sin y]\),在 \(p=(1,\pi/2)\) 处把扰动 \(\Delta\) 从 \([0.01,-0.02]\) 缩小 10 倍到 \([0.001,-0.002]\)。
预期现象:线性近似 \(f(p)+J\Delta\) 与真值之差的范数从约 \(3.16\times10^{-4}\) 掉到约 \(3.15\times10^{-6}\),即缩小约 100 倍。
为什么:一阶线性化的误差是 \(O(\|\Delta\|^2)\),\(\Delta\) 缩小 10 倍,二次项就缩小 \(10^2=100\) 倍。

import numpy as np
def f(v):
    x, y = v
    return np.array([x**2 * y, x + np.sin(y)])
p = np.array([1.0, np.pi/2])
J = np.array([[2*p[0]*p[1], p[0]**2],
              [1.0,          np.cos(p[1])]])   # = [[pi,1],[1,0]]
for d in [np.array([0.01, -0.02]), np.array([0.001, -0.002])]:
    err = np.linalg.norm(f(p+d) - (f(p) + J @ d))
    print("delta =", d, " 线性化误差 =", err)
# 第二行误差约为第一行的 1/100

任务二:换海森的对角元符号,看临界点在极小/极大/鞍点之间跳

改什么:把 \(2\times2\) 对角海森的两个特征值符号依次设为 (+,+)、(+,−)、(−,−)。
预期现象:判定依次输出"极小、鞍点、极大"——只要出现一正一负就是鞍点。
为什么:对角阵的对角元就是特征值;临界点类型只看这组特征值的符号,全正是碗(极小)、全负是穹顶(极大)、有正有负是马鞍(鞍点)。

import numpy as np
def classify(H):
    eig = np.linalg.eigvalsh(H)        # 对称阵用 eigvalsh
    if np.all(eig > 0): k = "极小"
    elif np.all(eig < 0): k = "极大"
    else: k = "鞍点"
    return np.round(eig, 2), k
for a, b in [(2, 2), (2, -2), (-2, -2)]:
    H = np.diag([a, b]).astype(float)
    print("对角元", (a, b), " 特征值/类型 ->", classify(H))

任务三:拉大条件数 \(\kappa\),看一阶法的收敛步数暴涨

改什么:对二次函数 \(f(x)=\tfrac12 x^\top H x\),把 \(H\) 从 \(\mathrm{diag}(1,1)\) 换到 \(\mathrm{diag}(1,10)\) 再到 \(\mathrm{diag}(1,100)\)(每个都用它各自的最优学习率)。
预期现象:收敛到 \(\|x\|<10^{-3}\) 的步数依次为 1 → 37 → 363,随 \(\kappa\) 几乎线性增长。
为什么:步长被 \(\lambda_{\max}\) 卡死(再大就发散),收敛被 \(\lambda_{\min}\) 拖死(平缓方向爬得慢),两者之比 \(\kappa=\lambda_{\max}/\lambda_{\min}\) 就是一阶法变慢的元凶。

import numpy as np
def steps_to_converge(H, tol=1e-3, max_iter=100000):
    lam = np.diag(H)
    eta = 2.0 / (lam.max() + lam.min())   # 各自的最优学习率
    x = np.ones(H.shape[0])
    for k in range(max_iter):
        if np.linalg.norm(x) < tol:
            return k
        x = x - eta * (H @ x)             # 梯度 = H x
    return max_iter
for H in [np.diag([1., 1.]), np.diag([1., 10.]), np.diag([1., 100.])]:
    print(f"kappa={np.linalg.cond(H):6.1f}  收敛步数={steps_to_converge(H)}")
# 1 -> 37 -> 363:条件数越大,一阶法越慢

动手练习

  1. 写雅可比。 对极坐标映射 \(f(r,\theta)=[\,r\cos\theta,\ r\sin\theta\,]\) 手推 \(J\),并证明 \(\det J=r\)(这正是二重积分换元里的 \(r\,dr\,d\theta\) 因子)。用下面骨架在 \((r,\theta)=(2,\pi/3)\) 处验证:
    import numpy as np
    def f(v):
        r, t = v
        return np.array([r*np.cos(t), r*np.sin(t)])
    # TODO: 复用上文 num_jacobian,打印数值 J 与 det,并与手算 det=r=2 对比
    
  2. 判类型。 对 \(f(x,y)=x^2+xy+y^2\),手算梯度找临界点、写出海森、求特征值,判断类型;再用 num_hessian 验证特征值是否为 \(\{1,3\}\)。
  3. 线性化的精度。 取第一道例题的 \(f\) 与点 \(p=(1,\pi/2)\),令 \(\Delta=[0.01,-0.02]^\top\),分别算真值 \(f(p+\Delta)\) 与线性近似 \(f(p)+J\Delta\),打印两者之差的范数 np.linalg.norm(...)。再把 \(\Delta\) 缩小 10 倍,观察误差大约缩小多少倍(提示:应约为 \(100\) 倍,因为误差是 \(O(\|\Delta\|^2)\))。
  4. 条件数与优化。 我们要让"病态曲率拖累一阶法"这个现象真正显现,关键是给每个 \(H\) 都配上它各自能用的最优学习率,再比较收敛到 \(\|x\|<10^{-3}\) 各需多少步。对二次函数 \(f(x)=\tfrac12 x^\top H x\)(梯度 \(\nabla f=Hx\)),对角海森的最优学习率约为 \(\eta^\star=\dfrac{2}{\lambda_{\max}+\lambda_{\min}}\)。比较 \(H=\mathrm{diag}(1,100)\)(病态,\(\kappa=100\))与 \(H=\mathrm{diag}(1,1)\)(理想,\(\kappa=1\)):前者约需数百步、后者一步即到,对比鲜明。
    原理点睛:一阶法的步长被 \(\lambda_{\max}\) 卡死(步子再大就发散),收敛却被 \(\lambda_{\min}\) 拖死(平缓方向爬得慢),两者之比 \(\kappa=\lambda_{\max}/\lambda_{\min}\) 才是元凶——这与正文 ML 小节的条件数论述闭环。
    import numpy as np
    
    def steps_to_converge(H, tol=1e-3, max_iter=100000):
        lam = np.diag(H)
        eta = 2.0 / (lam.max() + lam.min())   # 每个 H 各自的最优学习率
        x = np.ones(H.shape[0])
        for k in range(max_iter):
            if np.linalg.norm(x) < tol:
                return k, eta
            x = x - eta * (H @ x)             # f = 0.5 x^T H x 的梯度 = H x
        return max_iter, eta
    
    for H in [np.diag([1.0, 1.0]), np.diag([1.0, 100.0])]:
        k, eta = steps_to_converge(H)
        print(f"kappa={np.linalg.cond(H):6.1f}  eta*={eta:.4f}  收敛步数={k}")
    # 你会看到 kappa=1 只要 1 步,kappa=100 要数百步:条件数越大,一阶法越慢
    
  5. 牛顿一步到位。 同上二次函数(取正定的 \(H=\mathrm{diag}(1,100)\)),用牛顿更新 \(x\leftarrow x-H^{-1}\nabla f\) 走一步,验证无论 \(H\) 多病态都一步到达原点(打印更新后的 \(\|x\|\),应≈0)。再思考:若把 \(H\) 换成不定的 \(\mathrm{diag}(1,-100)\),牛顿一步会落到哪里?(提示:仍落到 \(\nabla f=0\) 的临界点,但那是鞍点,不是极小。)

掌握自检

下一课,我们把这些一阶导数(每层的雅可比/VJP)用链式法则沿计算图从输出端一路乘回输入端——这串 VJP 的连乘,就是驱动整个深度学习的反向传播