链式法则与复合函数求导

一句话概述

链式法则(Chain Rule)是微积分中最强大且最常用的求导法则,它告诉我们如何对"函数套函数"这样的复合结构求导。链式法则的精髓在于:外层导数乘以内层导数,层层传递,直到最内层。在AI领域,链式法则就是反向传播算法(Backpropagation)的数学灵魂——它将输出层的误差逐层传递回输入层,高效地计算出数百万参数各自的梯度。没有链式法则,就没有现代深度学习。

💡 核心要点:①链式法则处理复合函数的求导 ②规则:外导乘内导,逐层传递 ③多元链式法则处理多路径依赖 ④计算图是链式法则的可视化工具 ⑤反向传播的本质就是链式法则的高效实现

教学与演示

一、复合函数——函数套函数

是什么(定义,可选):复合函数是将一个函数的输出作为另一个函数的输入:h(x) = f(g(x))。其中 g 称为内层函数(inner function),f 称为外层函数(outer function)。复合过程可以嵌套多层:h(x) = f₃(f₂(f₁(x))),形成函数调用的链条。

大白话 复合函数就像俄罗斯套娃——一个函数里面套着另一个函数,再套另一个。比如"先给数字加3,再把结果平方"——这就是复合:外层的平方函数套着内层的加3函数。在生活中,"先打折再凑整"也是一个复合运算:折扣函数套着取整函数。

为什么(原理,可选):复合函数是人类描述复杂系统的基本方式——任何一个复杂过程都可以分解为多个简单步骤的串联。在数学建模中,我们很少直接写出从输入到输出的复杂函数,而是通过层层组合简单函数(多项式、指数、三角函数等)来构建。神经网络本身就是复合函数的极致体现:每一层网络都是前一层的非线性函数,整个网络就是数百万个简单函数的超级嵌套。 怎么做(实现,可选)

import numpy as np

# 演示复合函数的概念
# 定义基本函数
def g(x): 
    """内层函数:平方加1"""
    return x**2 + 1  # g(x) = x² + 1

def f(u): 
    """外层函数:正弦"""
    return np.sin(u)  # f(u) = sin(u)

def h_composite(x):
    """复合函数 h(x) = f(g(x)) = sin(x² + 1)"""
    inner = g(x)      # 先计算内层:g(x) = x² + 1
    outer = f(inner)  # 再计算外层:f(g(x)) = sin(g(x))
    return outer

print("=== 复合函数:函数套函数 ===\n")

# 演示复合过程
x_vals = np.array([0.0, 0.5, 1.0, 1.5, 2.0])
print("h(x) = sin(x² + 1)")
print("  x\t\tg(x)=x²+1\tf(g(x))=sin(x²+1)")
for x in x_vals:
    inner_val = g(x)
    outer_val = h_composite(x)
    print(f"  {x:.2f}\t{inner_val:.4f}\t\t{outer_val:.4f}")

# 多层嵌套示例
print("\n--- 三层嵌套 ---")
print("h(x) = exp(sin(x² + 1))")
for x in x_vals:
    # 层层计算
    layer1 = x**2 + 1        # 第一层:x² + 1
    layer2 = np.sin(layer1)  # 第二层:sin(·)
    layer3 = np.exp(layer2)  # 第三层:exp(·)
    print(f"  x={x:.1f}: x²+1={layer1:.3f} → sin={layer2:.4f} → exp={layer3:.4f}")

# 演示神经网络作为复合函数
print("\n--- 神经网络 = 海量复合函数 ---")
print("一个两层神经网络可以看作:")
print("  ŷ = σ₂(W₂ · σ₁(W₁ · x + b₁) + b₂)")
print("其中 σ₁, σ₂ 是激活函数,W₁, W₂ 是权重矩阵")
print("整个网络就是 [线性变换 → 非线性激活 → 线性变换 → 非线性激活] 的层层嵌套")
复合函数的形式化定义若 y = f(u) 且 u = g(x),则复合函数 y = f(g(x)) 记作 y = (f ∘ g)(x)。复合函数的值域依赖于外层和内层函数的值域与定义域的匹配关系:g 的值域必须是 f 的定义域的子集。

什么用(应用,可选):复合函数的结构直接映射到神经网络架构——残差网络(ResNet)添加了"跳跃连接"(输入直接加到输出上),对应 f(x) + x 这种非标准复合。Inception网络并行地复合多个不同的操作然后拼接。理解复合函数有助于理解网络架构设计和模块化思想。 哪些坑(缺点,可选):复合函数的定义域是内层函数值域与外层函数定义域的交集——这可能导致实际可用的输入范围缩小。例如 f(u)=ln(u), g(x)=x²-4,则 h(x)=ln(x²-4) 只对 |x|>2 有定义。在数值计算中,多层复合可能放大了数值误差——每层复合都是对误差的一层传播。

二、链式法则——导数传递

是什么(定义,可选):对于复合函数 h(x) = f(g(x)),其导数为 h'(x) = f'(g(x)) · g'(x)。即:外层函数对内层函数的导数,乘以内层函数对自变量的导数。用莱布尼茨记法:dy/dx = dy/du · du/dx。这个规则自然推广到任意多层嵌套。

大白话 链式法则的核心思想是"变化率的传递"。想象一条流水线:原材料经过机器A变成半成品(变化率du/dx),半成品再经过机器B变成成品(变化率dy/du)。最终成品随原材料的变化率 dy/dx = (成品的产量变化率) × (半成品的产量变化率)。变化率就像接力棒,一站一站传下去。

为什么(原理,可选):链式法则是基于差商极限的严格推导:Δy/Δx = (Δy/Δu) · (Δu/Δx),两边取 Δx→0 的极限(同时 Δu→0),得 dy/dx = dy/du · du/dx。这个看似简单的"抵消"技巧背后有深刻的数学保证——只要 f 和 g 均可微,复合函数就自动可微且导数满足链式法则。这就像乐高积木:每个模块单独可微,拼起来整体也可微。 怎么做(实现,可选)

import numpy as np

# 手动实现链式法则验证
# 复合函数:h(x) = sin(x² + 1)
# 解析分解:
#   外层 f(u) = sin(u) → f'(u) = cos(u)
#   内层 g(x) = x² + 1 → g'(x) = 2x
#   链式:h'(x) = cos(x² + 1) · 2x

def h_analytical_derivative(x):
    """解析链式法则求导"""
    return np.cos(x**2 + 1) * (2 * x)  # f'(g(x)) · g'(x)

def h_numerical_derivative(x, h=1e-6):
    """数值求导作为对照"""
    return (np.sin((x+h)**2 + 1) - np.sin((x-h)**2 + 1)) / (2*h)

print("=== 链式法则验证:h(x) = sin(x² + 1) ===\n")
print("h'(x) = cos(x² + 1) · 2x  (外层导数 · 内层导数)")

x_vals = np.array([0.0, 0.5, 1.0, 1.5, 2.0])
for x in x_vals:
    chain = h_analytical_derivative(x)
    numerical = h_numerical_derivative(x)
    print(f"  x = {x:.1f}: 链式 = {chain:.6f}, 数值 = {numerical:.6f}, 误差 = {abs(chain-numerical):.2e}")

# 三层链式法则示例
print("\n--- 三层嵌套:h(x) = exp(sin(x²)) ---")
# 分解:y = exp(u), u = sin(v), v = x²
# dy/dx = dy/du · du/dv · dv/dx
#       = exp(u) · cos(v) · 2x
#       = exp(sin(x²)) · cos(x²) · 2x

def three_layer_derivative(x):
    """三层链式法则"""
    return np.exp(np.sin(x**2)) * np.cos(x**2) * (2*x)

x_test = 1.0
dl = three_layer_derivative(x_test)
dn = (np.exp(np.sin((x_test+1e-6)**2)) - np.exp(np.sin((x_test-1e-6)**2))) / (2*1e-6)
print(f"h(x) = exp(sin(x²)), x=1:")
print(f"  三层链式: h'(1) = {dl:.6f}")
print(f"  数值验证: h'(1) = {dn:.6f}")
print(f"  误差 = {abs(dl-dn):.2e}")

# 反向演示:从外层逐层向内传播
print("\n--- 导数传播(链式法则的反向视角)---")
print("正向:x → v=x² → u=sin(v) → y=exp(u)")
print("反向:dy/du=exp(u) → du/dv=cos(v) → dv/dx=2x → 乘积得dy/dx")
x = 1.0
v = x**2  # 中间值
u = np.sin(v)
# 反向传播梯度
dL_dy = 1.0  # 损失对输出的梯度(起点)
dL_du = dL_dy * np.exp(u)          # dy/du
dL_dv = dL_du * np.cos(v)          # du/dv
dL_dx = dL_dv * (2*x)              # dv/dx = 最终对输入的梯度
print(f"x=1时,反向传播得到的梯度 dL/dx = {dL_dx:.6f}")
print(f"与解析值 {dl:.6f} 一致 ✓")
链式法则的一般形式对于复合函数 h(x) = fₙ(fₙ₋₁(...f₂(f₁(x))...)),其导数为 h'(x) = fₙ'(fₙ₋₁(...)) · fₙ₋₁'(...) · ... · f₂'(f₁(x)) · f₁'(x)。即每一层的导数相乘,从最外层乘到最内层。

什么用(应用,可选):链式法则是反向传播算法的数学核心。在PyTorch和TensorFlow中,当你调用 loss.backward() 时,框架自动使用链式法则从输出层向输入层逐层计算梯度。理解链式法则能帮助你理解自动微分的工作原理、调试梯度问题(如为什么某层梯度为零)、以及设计自己的自定义层和损失函数。 哪些坑(缺点,可选):链式法则中每一步的导数必须存在且被正确计算——任一层的导数计算错误都会导致整个梯度链出错。由于乘法效应,即使每层的导数都是有限的,多层相乘也可能导致梯度消失或爆炸。反函数的链式法则 (f⁻¹)'(y) = 1/f'(x) 需要额外注意,因为分母不能为零。

三、多元链式法则

是什么(定义,可选):当复合函数涉及多个变量时,链式法则扩展为多元链式法则。如果 z = f(u₁, u₂, ..., uₘ) 且每个 uᵢ = gᵢ(x₁, x₂, ..., xₙ),则 ∂z/∂xⱼ = Σᵢ (∂z/∂uᵢ) · (∂uᵢ/∂xⱼ)。即 z 对每个自变量的偏导数是所有"通路"上导数的乘积之和。

大白话 现在不再是单行道了,而是一个交叉路口——函数的值可能通过多个中间变量"流"回同一个自变量。多元链式法则告诉你:把每条通路上的变化率乘起来,再把所有通路的结果加在一起。就像一条河流分叉成多条小溪然后又汇合——最终的水量是所有支流的总和。

为什么(原理,可选):在现实世界的复杂系统中,一个输出通常依赖多个中间因素,每个中间因素又可以追溯到同一个输入。神经网络中,一个损失函数值取决于网络所有层的所有参数,而每个参数通过其所在层的输出和后续层的传递,间接影响损失。多元链式法则提供了计算这种"多对多"映射关系的完整框架——这也是全微分(total derivative)的数学基础。 怎么做(实现,可选)

import numpy as np

# 多元链式法则示例
# z = f(u, v) = u² + uv + v²
# u = g₁(x, y) = 2x + y
# v = g₂(x, y) = x - y
# 求 ∂z/∂x 和 ∂z/∂y

def f(u, v):
    """外层函数"""
    return u**2 + u*v + v**2  # z = u² + uv + v²

def u_func(x, y):
    """中间变量 u"""
    return 2*x + y  # u = 2x + y

def v_func(x, y):
    """中间变量 v"""
    return x - y  # v = x - y

def compute_gradient_chain_rule(x, y):
    """使用多元链式法则计算 ∂z/∂x 和 ∂z/∂y"""
    # 先计算中间变量值
    u = u_func(x, y)
    v = v_func(x, y)
    
    # ∂f/∂u = 2u + v, ∂f/∂v = u + 2v
    df_du = 2*u + v  # 外层对 u 的偏导数
    df_dv = u + 2*v  # 外层对 v 的偏导数
    
    # ∂u/∂x = 2, ∂u/∂y = 1, ∂v/∂x = 1, ∂v/∂y = -1
    du_dx, du_dy = 2.0, 1.0
    dv_dx, dv_dy = 1.0, -1.0
    
    # 多元链式法则:沿每条路径相乘后相加
    dz_dx = df_du * du_dx + df_dv * dv_dx  # 经过u的路径 + 经过v的路径
    dz_dy = df_du * du_dy + df_dv * dv_dy
    
    return dz_dx, dz_dy, u, v

print("=== 多元链式法则演示 ===\n")
print("z = u² + uv + v², 其中 u=2x+y, v=x-y")
print("∂z/∂x = ∂z/∂u · ∂u/∂x + ∂z/∂v · ∂v/∂x(两条路径求和)")

test_points = [(0, 0), (1, 0), (0, 1), (1, 1), (2, -1)]
for x, y in test_points:
    dz_dx, dz_dy, u, v = compute_gradient_chain_rule(x, y)
    print(f"\n  点 ({x}, {y}): u={u:.1f}, v={v:.1f}")
    print(f"    ∂z/∂x = ({2*u+v:.1f})·2 + ({u+2*v:.1f})·1 = {dz_dx:.1f}")
    print(f"    ∂z/∂y = ({2*u+v:.1f})·1 + ({u+2*v:.1f})·(-1) = {dz_dy:.1f}")

# 数值验证
print("\n--- 数值验证 ---")
x, y = 1.0, 2.0
h = 1e-6
dz_dx_num = (f(u_func(x+h, y), v_func(x+h, y)) - 
             f(u_func(x-h, y), v_func(x-h, y))) / (2*h)
dz_dy_num = (f(u_func(x, y+h), v_func(x, y+h)) - 
             f(u_func(x, y-h), v_func(x, y-h))) / (2*h)
dz_dx_chain, dz_dy_chain, _, _ = compute_gradient_chain_rule(x, y)
print(f"∂z/∂x: 链式={dz_dx_chain:.6f}, 数值={dz_dx_num:.6f}")
print(f"∂z/∂y: 链式={dz_dy_chain:.6f}, 数值={dz_dy_num:.6f}")
全导数与雅可比矩阵对于向量值函数 F: Rⁿ → Rᵐ,其雅可比矩阵 Jₚ 是一个 m×n 矩阵,其中 Jᵢⱼ = ∂Fᵢ/∂xⱼ。多元链式法则简洁地表示为雅可比矩阵的乘法:J_{F∘G} = J_F · J_G。

什么用(应用,可选):多元链式法则直接用于神经网络中各层梯度计算——每一层的输出通过权重矩阵连接到下一层,形成了典型的"多路径"依赖结构。理解"路径求和"原理有助于理解为什么全连接层的梯度计算涉及矩阵乘法(每个输出神经元对每个输入都有路径贡献)。这正是反向传播中 weight gradient = activationᵀ · delta 的数学来源。 哪些坑(缺点,可选):当路径十分复杂或多层嵌套很深时,手工用多元链式法则计算容易出错——遗漏某条路径或错误的偏导数都会导致结果偏差。自动微分框架的价值在于它们自动正确地处理所有这些路径。此外,雅可比矩阵在高维情况下是一个巨大的矩阵(百万×百万),绝不能显式存储,必须通过向量-雅可比乘积来高效实现。

四、计算图——链式法则的可视化

是什么(定义,可选):计算图(Computational Graph)是一种用节点表示运算、用边表示数据流动的有向无环图(DAG)。每个叶子节点是输入变量,每个内部节点表示一个数学运算,最终输出节点是函数值。链式法则在计算图上表现为从输出节点向输入节点的梯度累积过程。

大白话 计算图就像一张"施工流程图"——每个方块是一个计算步骤,箭头表示数据流动方向。正向传播时数据从左向右流;反向传播时梯度从右向左流,在每个节点处用链式法则把梯度"分发"给它的输入们。想象水从上游流到下游(正向),然后在下游测出了污染,污染物要逆流追溯到每个上游支流(反向)。

为什么(原理,可选):计算图是自动微分(Automatic Differentiation)的理论基础。PyTorch 和 TensorFlow 在每次前向传播时动态(或静态)构建计算图,记录每一步的计算操作。当调用 backward() 时,框架从图的输出端反向遍历,在每个节点应用链式法则累积梯度。这种方式只需要一次正向计算和一次反向遍历,就能获得所有参数对损失的梯度——复杂度仅为前向计算的两倍。 怎么做(实现,可选)

import numpy as np

class Node:
    """计算图中的一个节点(简化版自动微分)"""
    def __init__(self, value, children=(), op=''):
        self.value = value        # 节点当前的数值
        self.grad = 0.0           # 累积的梯度(反向传播时填充)
        self.children = children  # 子节点列表(输入节点)
        self.op = op              # 操作名称
        self._backward = lambda: None  # 反向传播函数
    
    def __add__(self, other):
        """加法操作:self + other"""
        other = other if isinstance(other, Node) else Node(other)
        out = Node(self.value + other.value, (self, other), '+')
        def _backward():
            # 加法的梯度:d(a+b)/da = 1, d(a+b)/db = 1
            self.grad += out.grad * 1.0  # 经过加法节点,梯度不变
            other.grad += out.grad * 1.0
        out._backward = _backward
        return out
    
    def __mul__(self, other):
        """乘法操作:self * other"""
        other = other if isinstance(other, Node) else Node(other)
        out = Node(self.value * other.value, (self, other), '*')
        def _backward():
            # 乘法的梯度:d(a*b)/da = b, d(a*b)/db = a
            self.grad += out.grad * other.value  # 梯度乘以另一个操作数的值
            other.grad += out.grad * self.value
        out._backward = _backward
        return out
    
    def tanh(self):
        """双曲正切激活函数"""
        out = Node(np.tanh(self.value), (self,), 'tanh')
        def _backward():
            # d(tanh(x))/dx = 1 - tanh²(x)
            self.grad += out.grad * (1 - out.value**2)
        out._backward = _backward
        return out
    
    def backward(self):
        """执行反向传播:拓扑排序后从输出向输入反向传播梯度"""
        # 拓扑排序:确保在计算节点的梯度前,所有依赖的梯度已就绪
        topo = []
        visited = set()
        def build_topo(v):
            if v not in visited:
                visited.add(v)
                for child in v.children:
                    build_topo(child)
                topo.append(v)
        build_topo(self)
        
        # 初始化输出梯度为 1(∂L/∂L = 1)
        self.grad = 1.0
        # 从输出到输入反向传播
        for v in reversed(topo):
            v._backward()

print("=== 计算图与自动微分 ===\n")

# 构建计算图:模拟一个神经元的计算
# z = tanh(x₁ * w₁ + x₂ * w₂ + b)
# 即:输入 * 权重 + 偏置 → tanh激活

# 定义输入和参数
x1 = Node(2.0, op='x1')  # 输入 x₁
x2 = Node(0.5, op='x2')  # 输入 x₂
w1 = Node(-3.0, op='w1')  # 权重 w₁
w2 = Node(1.0, op='w2')  # 权重 w₂
b  = Node(0.5, op='b')   # 偏置 b

# 前向传播
z1 = x1 * w1  # x₁·w₁
z2 = x2 * w2  # x₂·w₂
z_sum = z1 + z2 + b  # x₁w₁ + x₂w₂ + b
output = z_sum.tanh()  # tanh(·)

print("计算图:output = tanh(x₁·w₁ + x₂·w₂ + b)")
print(f"前向传播结果:output = {output.value:.6f}")

# 反向传播
output.backward()

print("\n各节点的梯度(∂output/∂node):")
for node in [x1, x2, w1, w2, b]:
    print(f"  {node.op}: value={node.value:.1f}, grad={node.grad:.6f}")

# 数值验证
print("\n--- 数值验证 ---")
h = 1e-6
# 验证 ∂output/∂w1
f_val = lambda w1v: np.tanh(2.0 * w1v + 0.5 * 1.0 + 0.5)
num_grad_w1 = (f_val(-3.0 + h) - f_val(-3.0 - h)) / (2*h)
print(f"∂output/∂w1: 计算图={w1.grad:.6f}, 数值={num_grad_w1:.6f}")

什么用(应用,可选):计算图是现代深度学习框架的核心数据结构。理解计算图对于调试复杂的自动微分问题非常关键——当梯度不是你预期的值时,可以通过检查计算图来追溯哪一步的梯度计算出了问题。此外,计算图使得梯度检查点(gradient checkpointing)等内存优化技术成为可能。 哪些坑(缺点,可选):计算图在训练时会占用大量内存——每个中间结果都需要保存以便反向传播时使用。对于大模型和长序列,这可能导致内存溢出。PyTorch 使用动态图(每次前向传播重建图),这提供了灵活性但也带来了图重建开销。TensorFlow 1.x 使用静态图,虽然效率高但调试困难。

五、AI中的应用——反向传播的本质

是什么(定义,可选):反向传播(Backpropagation)是按链式法则逐层计算神经网络梯度的算法。它从输出层的损失函数开始,将梯度(误差信号)逐层向输入层传递,在每一层计算该层权重和偏置对损失的偏导数。反向传播 = 计算图上的链式法则 + 动态规划(避免重复计算)。

大白话 想象一个多层流水线,最终质检员发现了次品(损失)。他要追溯到源头找出哪台机器出了问题——但他不是逐台单独检查,而是从最后一台机器开始,把"误差信号"逆向传递给每一台前序机器,这样每台机器都知道自己"搞砸了多少"。这种高效的信息回溯方式,就是反向传播。

为什么(原理,可选):如果没有反向传播,计算一个含百万参数的神经网络的梯度将需要百万次前向传播(对每个参数扰动一次)。反向传播通过动态规划的思想,使得所有参数的梯度可以在约两倍于一次前向传播的时间内算出。这是因为各层的梯度共享了共同的计算子路径——反向传播一次计算所有梯度,避免了天文数字般的冗余计算。 怎么做(实现,可选)

import numpy as np

# 演示:反向传播 = 动态规划的链式法则
# 考虑一个简单的两层线性网络:
#   隐藏层: a₁ = W₁x + b₁
#   输出层: ŷ = W₂a₁ + b₂
#   损失: L = ½(ŷ - y)²

np.random.seed(0)

def forward_pass(x, W1, b1, W2, b2):
    """前向传播:计算预测值和中间激活值"""
    a1 = W1 @ x + b1      # 隐藏层线性输出
    y_pred = W2 @ a1 + b2  # 输出层预测
    return y_pred, a1

def compute_loss(y_pred, y):
    """MSE 损失"""
    error = y_pred - y
    return 0.5 * np.sum(error**2), error  # 返回损失值和误差

def backward_pass(x, a1, error, W2):
    """反向传播:利用链式法则计算所有梯度"""
    # 输出层梯度
    # ∂L/∂W₂ = (∂L/∂ŷ)·(∂ŷ/∂W₂) = error · a₁ᵀ
    dW2 = np.outer(error, a1)
    db2 = error.copy()  # ∂L/∂b₂ = error
    
    # 隐藏层梯度(链式法则关键步骤)
    # ∂L/∂a₁ = W₂ᵀ · error
    da1 = W2.T @ error  # 误差从输出层反传到隐藏层
    
    # ∂L/∂W₁ = (∂L/∂a₁)·(∂a₁/∂W₁) = da1 · xᵀ
    dW1 = np.outer(da1, x)
    db1 = da1.copy()  # ∂L/∂b₁ = da1
    
    return {'W1': dW1, 'b1': db1, 'W2': dW2, 'b2': db2}

# 设置网络参数
W1 = np.array([[0.5, 0.2], [0.3, 0.8]])  # 2×2 权重矩阵
b1 = np.array([0.1, 0.1])                 # 隐藏层偏置
W2 = np.array([0.4, 0.6])                 # 输出层权重
b2 = np.array([0.0])                      # 输出层偏置
x = np.array([1.0, 0.5])                  # 输入向量
y = np.array([2.0])                       # 目标值

print("=== 反向传播:链式法则的工程实现 ===\n")

# 前向传播
y_pred, a1 = forward_pass(x, W1, b1, W2, b2)
loss, error = compute_loss(y_pred, y)

print("【前向传播】")
print(f"  输入 x = {x}")
print(f"  隐藏层输出 a₁ = {a1}")
print(f"  最终预测 ŷ = {y_pred[0]:.4f}")
print(f"  目标 y = {y[0]}")
print(f"  损失 L = {loss:.6f}")

# 反向传播
grads = backward_pass(x, a1, error, W2)

print("\n【反向传播——链式法则逐层计算梯度】")
print("  第1步(输出层):∂L/∂ŷ = ŷ - y = error")
print(f"    error = {error[0]:.4f}")
print("  第2步(输出层参数):利用 error 计算 ∂L/∂W₂ 和 ∂L/∂b₂")
print(f"    ∂L/∂W₂ = {grads['W2']}")
print(f"    ∂L/∂b₂ = {grads['b2']}")
print("  第3步(误差反传到隐藏层):∂L/∂a₁ = W₂ᵀ · error")
print(f"    ∂L/∂a₁ = {W2.T @ error}")
print("  第4步(隐藏层参数):利用 ∂L/∂a₁ 计算 ∂L/∂W₁ 和 ∂L/∂b₁")
print(f"    ∂L/∂W₁ =\n{grads['W1']}")
print(f"    ∂L/∂b₁ = {grads['b1']}")

print("\n结论:仅需一次前向传播 + 一次反向传播,就获得了所有权重和偏置的梯度。")
print("这正是链式法则的力量——它使得训练百万参数的网络在计算上可行。")

什么用(应用,可选):反向传播是深度学习的"能量源泉"——没有它,参数更新就没有方向。理解反向传播的链式法则本质后,你可以设计自定义的损失函数、激活函数和网络层,只需确保前向传播和反向传播(梯度计算)一致。这也是为什么PyTorch要求你为自定义 autograd.Function 同时实现 forward 和 backward 方法。 哪些坑(缺点,可选):反向传播要求每一层操作都是可微的——不可微操作(如argmax、采样)会阻断梯度流(常用策略:重参数化技巧或直通估计器)。反向传播需要存储中间激活值,在长序列(如RNN、大语言模型)中内存消耗巨大。此外,反向传播的链式乘法意味着浮点误差也会累积,高精度训练(fp32 vs fp16)的选择直接影响训练质量。

概念关系图谱

概念核心含义与AI的关系关联概念
复合函数函数套函数的嵌套结构神经网络的多层架构本质嵌套、模块化
链式法则复合函数求导规则反向传播的数学基础导数传递、乘法法则
多元链式法则多变量多路径的求导全连接层和卷积层的梯度计算偏导数、雅可比
计算图运算的DAG表示自动微分的核心数据结构正向传播、反向传播
雅可比矩阵多输出对多输入的导数矩阵层间梯度传递的矩阵形式向量-雅可比乘积
梯度累积多条路径梯度的求和多神经元输出共享参数时的梯度加法节点、广播
自动微分程序化计算精确导数PyTorch/TF的核心机制计算图、backward()
反向传播高效计算网络所有梯度神经网络训练的核心算法链式法则、动态规划
全微分多元函数的总变化量网络输出对各参数的总敏感度偏导数和、链式法则

重点答疑

Q1: 链式法则为什么"层层相乘"?

从导数定义出发,Δy/Δx = (Δy/Δu) · (Δu/Δx)。当 Δx→0 时 Δu→0(可微性保证),两边取极限得到 dy/dx = dy/du · du/dx。直观上,变化量通过中间变量传递——输入 x 的微小变化先引起 u 的变化(du/dx),然后 u 的变化再引发 y 的变化(dy/du),总的传递率就是两者的乘积。

Q2: 反向传播和链式法则是什么关系?

反向传播是链式法则在多层神经网络上的工程化实现。纯链式法则只是数学规则——告诉你复合函数的导数是层层乘积。反向传播更进一步:①用动态规划避免重复计算共享子路径的梯度;②规定了从输出向输入的计算顺序(拓扑排序);③在计算图上高效地累积每条路径的梯度贡献。

Q3: 多层嵌套中的"梯度消失"是如何通过链式法则产生的?

链式法则中梯度是逐层相乘的。如果每层导数因子都小于 1(如 Sigmoid 导数最大 0.25),多层相乘后浅层梯度以指数级衰减。10层后梯度缩小到 0.25¹⁰ ≈ 10⁻⁶。这就是梯度消失——浅层参数几乎收不到有效的梯度信号,无法学习。ReLU 的导数要么为 0 要么为 1,避免了指数衰减。

Q4: PyTorch 的 backward() 内部做了什么?

当你调用 loss.backward() 时,PyTorch 执行以下步骤:①从 loss 节点开始,沿计算图反向拓扑遍历;②在每个非叶子节点,调用其 backward 函数(在节点创建时已注册);③使用链式法则:传入的上游梯度乘以本节点的局部雅可比矩阵;④将结果梯度累加到该节点的每个输入节点上。最终每个参数(requires_grad=True 的叶子节点)的 .grad 属性被填充。

Q5: 为什么说"反向传播 = 计算图 + 链式法则 + 动态规划"?

这三者缺一不可:计算图提供了所有计算操作的拓扑结构;链式法则提供了每个节点梯度的数学规则;动态规划(记忆化)确保共享子路径只计算一次——如果没有动态规划,对每个参数都需要从输出重新遍历整个计算图,总复杂度将从 O(N) 变为 O(N²),使训练大规模网络不可行。

章节单词汇总

英文音标术语/释义
chain rule/tʃeɪn ruːl/链式法则
composite function/kəmˈpɒzɪt ˈfʌŋkʃən/复合函数
inner function/ˈɪnər ˈfʌŋkʃən/内层函数
outer function/ˈaʊtər ˈfʌŋkʃən/外层函数
backpropagation/bækˌprɒpəˈɡeɪʃən/反向传播
computational graph/ˌkɒmpjʊˈteɪʃənl ɡræf/计算图
Jacobian matrix/dʒəˈkoʊbiən ˈmeɪtrɪks/雅可比矩阵
total derivative/ˈtoʊtl dɪˈrɪvətɪv/全导数
automatic differentiation/ˌɔːtəˈmætɪk ˌdɪfəˌrɛnʃɪˈeɪʃən/自动微分
topological sort/ˌtɒpəˈlɒdʒɪkl sɔːrt/拓扑排序
vector-Jacobian product/ˈvektər dʒəˈkoʊbiən ˈprɒdʌkt/向量-雅可比乘积(VJP)
gradient accumulation/ˈɡreɪdiənt əˌkjuːmjəˈleɪʃən/梯度累积
implicit differentiation/ɪmˈplɪsɪt ˌdɪfəˌrɛnʃɪˈeɪʃən/隐式微分
higher-order gradient/haɪər ˈɔːrdər ˈɡreɪdiənt/高阶梯度
forward-mode AD/ˈfɔːrwərd moʊd/前向模式自动微分
reverse-mode AD/rɪˈvɜːrs moʊd/反向模式自动微分
activation checkpointing/ˌæktɪˈveɪʃən ˈtʃɛkpɔɪntɪŋ/激活检查点(梯度检查点)
directed acyclic graph/dɪˈrɛktɪd eɪˈsaɪklɪk ɡræf/有向无环图(DAG)

面试练习

Q1 [单选] 链式法则 dy/dx = dy/du · du/dx 可以推广到几层嵌套?

  • A. 最多两层
  • B. 最多三层
  • C. 任意层
  • D. 取决于函数类型
解答:链式法则对任意层嵌套都成立——只需逐层求导并相乘。这是数学归纳法可以证明的结论。

Q2 [单选] 对于 h(x) = sin(x³),h'(x) 等于?

  • A. cos(x³)
  • B. 3x²
  • C. cos(x³) · 3x²
  • D. sin(x³) · 3x²
解答:外层 f(u)=sin(u) → f'(u)=cos(u),内层 g(x)=x³ → g'(x)=3x²。链式法则:h'(x)=cos(x³)·3x²。

Q3 [多选] 以下哪些是反向传播的特点?

  • A. 梯度的计算顺序是从输出到输入
  • B. 利用动态规划避免重复计算
  • C. 需要存储前向传播的中间激活值
  • D. 对每个参数都需要独立的前向传播
解答:D错误——反向传播的美妙之处在于一次前向+一次反向即获得所有参数的梯度,无需独立前向。A描述计算方向,B是关键优化,C是内存代价(可用梯度检查点缓解)。

Q4 [单选] 在一个DAG计算图中,应用链式法则的正确顺序是?

  • A. 从输入到输出(正向顺序)
  • B. 从输出到输入(反向拓扑顺序)
  • C. 按字母顺序
  • D. 随机顺序
解答:反向传播时梯度从输出端流向输入端,需要按反向拓扑顺序——在执行节点的 backward 之前,保证该节点的所有输出(下游)梯度已经计算完毕。

Q5 [多选] 下列关于雅可比矩阵的说法正确的是?

  • A. 当函数是标量时,雅可比矩阵退化为梯度向量的转置
  • B. 链式法则可以表示为雅可比矩阵的乘法
  • C. 雅可比矩阵总是方阵
  • D. 向量-雅可比乘积是实现反向传播的关键操作
解答:C错误——雅可比矩阵 m×n,当 m≠n 时不是方阵。A:标量输出时 m=1,退化为 1×n 的行向量(梯度转置)。B:J_{F∘G} = J_F · J_G。D:反向传播时不断计算 VJP 而非显式存储雅可比。

Q6 [单选] 自动微分框架中使用"反向模式"(reverse mode)而非"前向模式"(forward mode)的原因是?

  • A. 反向模式总是更准确
  • B. 反向模式不需要计算图
  • C. 对于多输入→少输出(标量损失)的场景,反向模式计算量更小
  • D. 前向模式已被淘汰
解答:反向模式的复杂度为 O(n_outputs × operations),前向模式为 O(n_inputs × operations)。深度学习典型场景是数百万参数(输入)→一个标量损失(输出),反向模式远优于前向模式。

Q7 [多选] 链式法则在以下哪些AI场景中被使用?

  • A. 神经网络的反向传播训练
  • B. 变分自编码器(VAE)的重参数化梯度
  • C. 生成对抗网络(GAN)的生成器梯度
  • D. 强化学习中的策略梯度
解答:以上全部。A是标准用法;B中重参数化把随机节点转化为可微节点;C中判别器梯度通过链式法则传给生成器;D中策略梯度涉及 log策略 对参数的导数。

Q8 [单选] 对于 z = f(x, y) 且 x = g(t), y = h(t),全导数 dz/dt 等于?

  • A. ∂f/∂x
  • B. ∂f/∂x · dx/dt
  • C. ∂f/∂x · dx/dt + ∂f/∂y · dy/dt
  • D. ∂f/∂x + ∂f/∂y
解答:多元链式法则——z同时通过x和y两条路径依赖t,因此需要将两条路径的贡献求和。

Q9 [单选] 在计算图中,"加法"节点的反向传播行为是?

  • A. 将上游梯度原封不动地传递给所有输入
  • B. 将上游梯度乘以另一个输入值
  • C. 将上游梯度除以输入数量
  • D. 将梯度设为零
解答:加法的局部导数为 1(∂(a+b)/∂a = 1, ∂(a+b)/∂b = 1),因此上游梯度被"原封不动"地传递给每个输入(广播/distribute)。

Q10 [多选] 数值梯度和反向传播梯度的区别?

  • A. 数值梯度存在截断误差和舍入误差
  • B. 反向传播梯度是精确的(在浮点精度内)
  • C. 数值梯度计算复杂度为 O(n) 次前向传播(n = 参数数量)
  • D. 反向传播主要用于梯度检查(验证自动微分是否正确)
解答:A正确——数值微分受 h 选择和浮点精度影响。B正确——反向传播使用精确解析导数。C正确——每个参数需要一次独立的前向扰动。D的表述有歧义——反向传播是主要的梯度计算方法,数值梯度才是用于梯度检查的辅助手段。