Part I · 第 2 章 微积分 · 2.2

链式法则

机器人里的函数几乎都是一层套一层:电机角度 → 关节角 → 末端位置 → 误差 → 损失。链式法则说:每一层的"放大倍数"相乘,就是整条链的放大倍数。神经网络的反向传播、机械臂速度的逐关节传递,都只是这一句话。

约 20 分钟 前置:2.1 导数 4 个交互

    1为什么机器人需要它

    一台机械臂的电机经过减速器带动关节,关节带动连杆,连杆末端的高度又决定任务完成得好不好。你想知道"电机多转一点点,末端高度变多少",答案不会写在任何一个零件上,而是每一级放大倍数的乘积:减速比 × 连杆几何 × 误差对高度的敏感度。

    训练一个控制策略时也一样:损失依赖于动作,动作依赖于网络输出,网络输出依赖于几百万个参数。要用梯度下降调参数,就得算出"损失对每个参数的导数",这正是链式法则,从后往前反复使用。这个过程叫反向传播,是现代机器学习和可微仿真的核心。

    2直觉:放大倍数相乘

    2.1 节说,导数是"输入动一点点,输出动多少倍"。现在把两个函数接起来:\(u=g(x)\),\(y=f(u)\)。\(x\) 动了 \(\Delta x\),\(u\) 先动 \(g'\,\Delta x\);\(u\) 的这一点点变化再被 \(f\) 放大 \(f'\) 倍,所以 \(y\) 动了 \(f'\cdot g'\,\Delta x\)。倍数是一级一级乘上去的。

    下面三条数轴依次是 \(x\)、\(u=g(x)\)、\(y=f(u)\)。细线表示"这个点被送到了哪里"。拖动 \(x_0\),看琥珀色小区间怎么被一级一级拉长或压扁。

    FIG 1三条数轴
    琥珀色是 \(x\) 附近一小段区间 \([x_0,\,x_0+\Delta x]\) 及其在两级之后的像。细线是"一格一格"的对应:线挤在一起表示被压缩(倍数小于 1),线分开表示被拉伸。
    • 找到一个位置,使整条链的放大倍数几乎为 0(某一级把区间压成了一个点)。
    • 找到一个位置,第一级 \(g'\) 为正,但整条链的倍数为负(第二级把方向翻了过来)。
    核心洞见

    复合函数的导数 = 各级导数的乘积,但每一级的导数要在它自己的输入点上求:\(g'\) 在 \(x_0\) 处求,\(f'\) 在 \(u_0=g(x_0)\) 处求。这一条是最常见的出错点。

    3动手玩:前向与反向

    真实的程序没有"数轴",只有一连串小运算。把计算写成一张图,每个节点只做一件简单的事,那么每条边上的局部导数都很好写。前向从左往右算出各节点的值;反向从右往左,把 \(\partial L/\partial(\text{节点})\) 一级一级乘回来。下面的例子是 \(L=(\sin(x^2)-c)^2\),可以想成"一个非线性传感器读数与目标值 \(c\) 的平方误差"。

    FIG 2计算图:反向传播一步一步走
    青色是前向的节点值,琥珀色是反向的 \(\bar v=\partial L/\partial v\)("这个节点动一下,损失动多少"),紫色是每条边上的局部导数。反向时每一步只做一次乘法:\(\bar v_{\text{前}} = \bar v_{\text{后}}\times\text{局部导数}\)。最后一行用数值差分独立检验结果。
    • 点"下一步",一步一步走完前向和反向。
    • 调 x 和 c,让 \(\partial L/\partial x\) 几乎为 0 但 \(L\) 并不为 0(哪一级的局部导数为 0?)。

    很长的链:乘积会消失,也会爆炸

    链越长,乘积里的因子越多。若每一级的因子都略小于 1,乘起来就趋于 0(梯度消失);若略大于 1,就爆炸。下面把同一个小函数 \(y_i=\tanh(w\,y_{i-1})\) 连接 \(n\) 层,看输出对输入的敏感度 \(\mathrm{d}y_n/\mathrm{d}x\) 怎样随层数变化。这和深度网络难训练、长时间序列的状态对初值极其敏感,是同一个原因。

    FIG 3长链的敏感度
    输出 yₙ 对 x(拖动 x₀)
    log₁₀ |dyᵢ/dx| 随层数 i
    左:第 n 层输出随输入 x 变化的曲线(淡色是第 1 层)。右:在 \(x_0\) 处,每增加一层,敏感度乘上一个因子 \(w(1-y_i^2)\);虚线是 \(\log_{10}w\cdot i\),即"不饱和时"的增长趋势。\(\tanh\) 饱和后因子骤降,敏感度就塌下去了。
    • 让 20 层以上的链敏感度小于 \(10^{-6}\)(梯度消失)。
    • 找一组 w、x₀,使敏感度在前几层先增长到 10 以上,然后再塌下去。

    4再看数学

    提示:悬停在公式带底色的符号上,FIG 1 里对应的那一级会高亮。

    链式法则

    设 \(u=g(x)\),\(y=f(u)\),两者都可导,则

    $$ \frac{\mathrm{d}y}{\mathrm{d}x} \;=\; \hl{f}{f'(\,g(x)\,)}\cdot \hl{g}{g'(x)}\;=\;\frac{\mathrm{d}y}{\mathrm{d}u}\cdot\frac{\mathrm{d}u}{\mathrm{d}x}. $$

    为什么?用 2.1 节的线性近似:\(\Delta u\approx g'(x)\Delta x\),\(\Delta y\approx f'(u)\Delta u\),代入就得 \(\Delta y\approx f'(u)\,g'(x)\,\Delta x\)。右边那个"分式相消"只是记忆口诀,\(\mathrm{d}y/\mathrm{d}u\) 并不是真的分数;真正成立的是上面这条线性近似。

    多层与多元

    三层就是三个因子:\(\dfrac{\mathrm{d}}{\mathrm{d}x}f(g(h(x)))=f'(g(h(x)))\cdot g'(h(x))\cdot h'(x)\)。输入输出是向量时,"倍数"变成雅可比矩阵(3.3 节),乘法变成矩阵乘法(1.4 节):

    $$ J_{f\circ g}(\mathbf x) \;=\; J_f\big(g(\mathbf x)\big)\; J_g(\mathbf x). $$

    矩阵乘法不可交换,所以顺序必须与"先做哪一级"一致。

    前向模式与反向模式

    因子相乘满足结合律,可以从左往右乘,也可以从右往左乘,结果相同,代价却不同。设输入 \(n\) 维、输出 \(m\) 维:从输入端开始乘(前向模式)一遍只能得到对一个输入的导数,要跑 \(n\) 遍;从输出端开始乘(反向模式)一遍得到一个输出对所有输入的导数,要跑 \(m\) 遍。训练神经网络时损失是标量(\(m=1\))而参数有百万个(\(n\) 很大),所以用反向模式:一次反向传播的代价只相当于一次前向。

    反向传播的递推式

    对计算图中的节点 \(v_i\),设它的后继节点为 \(v_j\),则 \(\bar v_i=\sum_j \bar v_j\,\dfrac{\partial v_j}{\partial v_i}\),从 \(\bar L=1\) 开始,按拓扑序倒着算。若一个节点被多条路径使用,各路径的贡献要相加。

    5机器人中的它:带减速器的机械臂

    一根长为 \(\ell\) 的连杆由电机经减速比 \(N\) 驱动:关节角 \(q=\theta_m/N\),末端高度 \(z=\ell\sin q\)。我们希望末端到达目标高度 \(z^\star\),损失 \(L=(z-z^\star)^2\)。用链式法则:

    $$ \frac{\partial L}{\partial\theta_m}\;=\;\underbrace{2(z-z^\star)}_{\partial L/\partial z}\cdot\underbrace{\ell\cos q}_{\partial z/\partial q}\cdot\underbrace{\tfrac1N}_{\partial q/\partial\theta_m}. $$

    减速比越大,最后一个因子 \(1/N\) 越小,梯度整体变小,同样的学习率就走得很慢。拖动末端或目标线,调整减速比和学习率,点"梯度下降"看看。

    FIG 4用链式法则驱动一根杆
    log₁₀ 损失 随步数
    左:灰色是连杆,青色是末端,琥珀色线是目标高度(可拖动)。拖动末端可以手动设定关节角。右:每一步的损失(对数坐标)。学习率相对 \(N^2\) 太小会爬得很慢;太大则会来回震荡甚至不收敛。
    • 用默认设置点"梯度下降",观察它要走很多步才靠近目标。
    • 把减速比调到 40 以上,调大学习率,让下降在 200 步内把末端误差压到 0.01 以内。
    真实系统里

    不同量纲的参数(电机弧度、关节弧度、米)混在一起时,梯度大小可以差几个数量级,就像这里的 \(1/N\)。所以优化器要做尺度归一化(预条件、Adam、给关节加权),6.3 节和 6.6 节会回到这件事。机械臂速度运动学也是同一条链:关节速度 → 每个连杆 → 末端速度,雅可比就是这条链上各级矩阵的乘积(3.4 节)。

    6常见误区

    误区 1:把外层导数算在 \(x\) 上

    \((\sin x^2)'\) 是 \(\cos(x^2)\cdot 2x\),不是 \(\cos(x)\cdot 2x\)。外层的导数要在内层的输出上求。

    误区 2:漏掉内层导数

    \((\sin 3x)'=3\cos 3x\)。少乘的那个 3,在机器人里常表现为"单位忘了换":角度换成度、时间换成毫秒、经过减速比,都是内层函数,都会在导数里多出一个常数。

    误区 3:矩阵链乘的顺序写反

    \(J_{f\circ g}=J_f J_g\),外层在左。检查维度是最简单的自检:\(J_g\) 是 \(p\times n\),\(J_f\) 是 \(m\times p\),乘出来才是 \(m\times n\)。

    误区 4:忽视长链里的梯度消失 / 爆炸

    梯度是很多因子之积,只要典型因子偏离 1,乘积就是指数级的。长时程的轨迹优化和循环网络都会遇到。常见对策:缩短链(多重打靶)、归一化、残差连接、梯度裁剪。

    7小练习

    1. \(y=\sin(3x)\),\(\mathrm{d}y/\mathrm{d}x\) 等于?
    外层 \(\sin\) 的导数是 \(\cos(3x)\),内层 \(3x\) 的导数是 3,相乘得 \(3\cos 3x\)。
    2. \(y=(x^2+1)^3\),\(\mathrm{d}y/\mathrm{d}x\) 等于?
    令 \(u=x^2+1\):\(\mathrm{d}y/\mathrm{d}u=3u^2\),\(\mathrm{d}u/\mathrm{d}x=2x\),乘起来是 \(6x(x^2+1)^2\)。
    3. 连杆长 \(\ell=0.5\) m,减速比 \(N=50\),此时 \(q=0\)。末端高度对电机角度的导数 \(\partial z/\partial\theta_m\) 是多少?
    \(\partial z/\partial\theta_m=\ell\cos q\cdot\tfrac1N=0.5\times1\times\tfrac1{50}=0.01\)。
    4. 一条 20 层的链,每层的局部导数都约为 0.8。总导数大约是多少?
    \(0.8^{20}\approx 0.0115\)。因子只比 1 小一点,乘上 20 次就几乎消失了。这就是梯度消失。
    5. 为什么训练神经网络用反向模式(反向传播)而不是前向模式?
    两种模式给出同样的导数,区别只在代价:前向模式每次只能求对一个输入的导数,要跑和参数个数一样多遍。

    8延伸阅读

    • 3Blue1Brown, Essence of Calculus — 第 4 章:链式法则与乘积法则的可视化
    • A. Griewank & A. Walther, Evaluating Derivatives — 自动微分的前向 / 反向模式
    • I. Goodfellow, Y. Bengio & A. Courville, Deep Learning, Ch. 6.5 — 反向传播与计算图
    • K. Lynch & F. Park, Modern Robotics, Ch. 5 — 速度运动学与雅可比