梯度与偏导数
偏导数是多元函数对单个变量的导数,梯度 ∇f = (∂f/∂x₁, ..., ∂f/∂xₙ) 则是所有偏导数组成的向量。梯度指向函数增长最快的方向,它的反方向就是函数下降最快的方向——深度学习的参数更新公式 w ← w - η·∇L(w) 正是沿梯度反方向迈一步。在 ResNet-50 中,一次反向传播要计算约 2500 万个参数的偏导数;GPT-3 要对 1750 亿个参数分别求偏导。方向导数告诉我们沿任意方向的变化率,梯度方向的方向导数最大——这正是梯度下降比随机搜索高效的数学原因。Jacobian 矩阵将梯度推广到向量值函数(如 Softmax 的 Jacobian),Hessian 矩阵(二阶偏导)用于 Newton 法和判断鞍点。工程中的偏导数计算由自动微分(autograd)引擎在计算图上自动完成,不需要手写公式。
一、为什么需要偏导数
高中学的导数只针对一元函数 f(x)。但神经网络的损失函数有成千上万个参数:L(w₁, w₂, ..., wₙ)。我们需要知道"调整每个参数 wᵢ 对损失的影响有多大"——这就是偏导数。
偏导数的定义:
∂f/∂xᵢ = lim[Δxᵢ→0] (f(x₁,...,xᵢ+Δxᵢ,...,xₙ) - f(x₁,...,xᵢ,...,xₙ)) / Δxᵢ
关键:求对某个变量的偏导时,其他所有变量视为常数。
这和一元函数的导数完全一样——只是多了"把其他变量当常数"这一步。
∂f/∂xᵢ = lim[Δxᵢ→0] (f(x₁,...,xᵢ+Δxᵢ,...,xₙ) - f(x₁,...,xᵢ,...,xₙ)) / Δxᵢ
关键:求对某个变量的偏导时,其他所有变量视为常数。
这和一元函数的导数完全一样——只是多了"把其他变量当常数"这一步。
二、偏导数的逐步计算
例1:f(x, y) = 3x²y + 2xy² - 5x + 7
∂f/∂x(把 y 当常数,对 x 求导):
3x²y → 6xy(y 是常数系数,x² 求导得 2x)
2xy² → 2y²(y² 是常数系数,x 求导得 1)
-5x → -5
7 → 0
∂f/∂x = 6xy + 2y² - 5
∂f/∂y(把 x 当常数,对 y 求导):
3x²y → 3x²(x² 是常数系数,y 求导得 1)
2xy² → 4xy(x 是常数系数,y² 求导得 2y)
-5x → 0(不含 y,当作常数)
7 → 0
∂f/∂y = 3x² + 4xy
在点 (x=2, y=3) 处:
∂f/∂x = 6×2×3 + 2×9 - 5 = 36 + 18 - 5 = 49
∂f/∂y = 3×4 + 4×2×3 = 12 + 24 = 36
含义:在 (2,3) 这个点,x 每增大 1 单位,f 约增大 49;y 每增大 1 单位,f 约增大 36。
x 方向的变化率更大。
∂f/∂x(把 y 当常数,对 x 求导):
3x²y → 6xy(y 是常数系数,x² 求导得 2x)
2xy² → 2y²(y² 是常数系数,x 求导得 1)
-5x → -5
7 → 0
∂f/∂x = 6xy + 2y² - 5
∂f/∂y(把 x 当常数,对 y 求导):
3x²y → 3x²(x² 是常数系数,y 求导得 1)
2xy² → 4xy(x 是常数系数,y² 求导得 2y)
-5x → 0(不含 y,当作常数)
7 → 0
∂f/∂y = 3x² + 4xy
在点 (x=2, y=3) 处:
∂f/∂x = 6×2×3 + 2×9 - 5 = 36 + 18 - 5 = 49
∂f/∂y = 3×4 + 4×2×3 = 12 + 24 = 36
含义:在 (2,3) 这个点,x 每增大 1 单位,f 约增大 49;y 每增大 1 单位,f 约增大 36。
x 方向的变化率更大。
例2:和神经网络更相关的函数
L(w₁, w₂, b) = (y - (w₁x₁ + w₂x₂ + b))²
这就是单样本的 MSE 损失,其中 x₁=3, x₂=2, y=10
当前参数 w₁=1, w₂=2, b=1
ŷ = 1×3 + 2×2 + 1 = 8
L = (10-8)² = 4
令 e = y - ŷ = 10 - 8 = 2
∂L/∂w₁ = -2(y - ŷ) × x₁ = -2 × 2 × 3 = -12
∂L/∂w₂ = -2(y - ŷ) × x₂ = -2 × 2 × 2 = -8
∂L/∂b = -2(y - ŷ) × 1 = -2 × 2 × 1 = -4
含义:
w₁ 的偏导 = -12(绝对值最大)→ 调整 w₁ 对减小损失最有效
w₂ 的偏导 = -8 → 次之
b 的偏导 = -4 → 调整 b 效果最小
为什么 w₁ 的偏导最大?因为 x₁=3 > x₂=2 > 1,输入越大的特征对应的权重越敏感。
L(w₁, w₂, b) = (y - (w₁x₁ + w₂x₂ + b))²
这就是单样本的 MSE 损失,其中 x₁=3, x₂=2, y=10
当前参数 w₁=1, w₂=2, b=1
ŷ = 1×3 + 2×2 + 1 = 8
L = (10-8)² = 4
令 e = y - ŷ = 10 - 8 = 2
∂L/∂w₁ = -2(y - ŷ) × x₁ = -2 × 2 × 3 = -12
∂L/∂w₂ = -2(y - ŷ) × x₂ = -2 × 2 × 2 = -8
∂L/∂b = -2(y - ŷ) × 1 = -2 × 2 × 1 = -4
含义:
w₁ 的偏导 = -12(绝对值最大)→ 调整 w₁ 对减小损失最有效
w₂ 的偏导 = -8 → 次之
b 的偏导 = -4 → 调整 b 效果最小
为什么 w₁ 的偏导最大?因为 x₁=3 > x₂=2 > 1,输入越大的特征对应的权重越敏感。
三、梯度的定义与性质
梯度 = 所有偏导数组成的向量:
∇f(x₁,...,xₙ) = (∂f/∂x₁, ∂f/∂x₂, ..., ∂f/∂xₙ)
核心性质:
① 梯度的方向 = 函数在该点增长最快的方向
② 梯度的模 |∇f| = 函数在该点最大的变化率
③ -∇f 方向 = 函数在该点下降最快的方向
④ ∇f ⊥ 等高线(等值面)——梯度永远垂直于等高线
∇f(x₁,...,xₙ) = (∂f/∂x₁, ∂f/∂x₂, ..., ∂f/∂xₙ)
核心性质:
① 梯度的方向 = 函数在该点增长最快的方向
② 梯度的模 |∇f| = 函数在该点最大的变化率
③ -∇f 方向 = 函数在该点下降最快的方向
④ ∇f ⊥ 等高线(等值面)——梯度永远垂直于等高线
接上面例2的梯度:
∇L = (∂L/∂w₁, ∂L/∂w₂, ∂L/∂b) = (-12, -8, -4)
梯度的模:|∇L| = √(144 + 64 + 16) = √224 ≈ 14.97
梯度下降更新(学习率 α=0.01):
w₁_new = 1 - 0.01×(-12) = 1 + 0.12 = 1.12
w₂_new = 2 - 0.01×(-8) = 2 + 0.08 = 2.08
b_new = 1 - 0.01×(-4) = 1 + 0.04 = 1.04
更新后:ŷ = 1.12×3 + 2.08×2 + 1.04 = 3.36 + 4.16 + 1.04 = 8.56
L_new = (10-8.56)² = 2.0736 < 4 = L_old ✓ 损失确实减小了!
再算一轮梯度继续更新,损失会继续减小...经过数百轮后收敛。
∇L = (∂L/∂w₁, ∂L/∂w₂, ∂L/∂b) = (-12, -8, -4)
梯度的模:|∇L| = √(144 + 64 + 16) = √224 ≈ 14.97
梯度下降更新(学习率 α=0.01):
w₁_new = 1 - 0.01×(-12) = 1 + 0.12 = 1.12
w₂_new = 2 - 0.01×(-8) = 2 + 0.08 = 2.08
b_new = 1 - 0.01×(-4) = 1 + 0.04 = 1.04
更新后:ŷ = 1.12×3 + 2.08×2 + 1.04 = 3.36 + 4.16 + 1.04 = 8.56
L_new = (10-8.56)² = 2.0736 < 4 = L_old ✓ 损失确实减小了!
再算一轮梯度继续更新,损失会继续减小...经过数百轮后收敛。
四、方向导数——在任意方向上的变化率
函数 f 沿单位方向 u 的方向导数:Dᵤf = ∇f · u
由柯西-施瓦兹不等式:Dᵤf = |∇f| × cos(θ)
当 u = ∇f/|∇f|(梯度方向)时,cos(θ)=1,方向导数最大 = |∇f|
当 u = -∇f/|∇f|(梯度反方向)时,cos(θ)=-1,方向导数最小 = -|∇f|
由柯西-施瓦兹不等式:Dᵤf = |∇f| × cos(θ)
当 u = ∇f/|∇f|(梯度方向)时,cos(θ)=1,方向导数最大 = |∇f|
当 u = -∇f/|∇f|(梯度反方向)时,cos(θ)=-1,方向导数最小 = -|∇f|
数值示例:
f(x,y) = x² + 2y²,在点 (1, 1)
∇f = (2x, 4y) = (2, 4)
|∇f| = √(4+16) = √20 ≈ 4.47
沿梯度方向 u₁ = (2,4)/√20 = (0.447, 0.894):
Dᵤ₁f = ∇f · u₁ = 2×0.447 + 4×0.894 = 0.894 + 3.576 = 4.47(最大值)
沿 x 轴方向 u₂ = (1, 0):
Dᵤ₂f = 2×1 + 4×0 = 2.0(小于最大值)
沿 y 轴方向 u₃ = (0, 1):
Dᵤ₃f = 2×0 + 4×1 = 4.0(接近最大值但不是最大)
沿梯度反方向 u₄ = -u₁ = (-0.447, -0.894):
Dᵤ₄f = -4.47(最快下降方向)
这就是梯度下降的理论依据:沿梯度反方向走一步,函数值下降最快。
f(x,y) = x² + 2y²,在点 (1, 1)
∇f = (2x, 4y) = (2, 4)
|∇f| = √(4+16) = √20 ≈ 4.47
沿梯度方向 u₁ = (2,4)/√20 = (0.447, 0.894):
Dᵤ₁f = ∇f · u₁ = 2×0.447 + 4×0.894 = 0.894 + 3.576 = 4.47(最大值)
沿 x 轴方向 u₂ = (1, 0):
Dᵤ₂f = 2×1 + 4×0 = 2.0(小于最大值)
沿 y 轴方向 u₃ = (0, 1):
Dᵤ₃f = 2×0 + 4×1 = 4.0(接近最大值但不是最大)
沿梯度反方向 u₄ = -u₁ = (-0.447, -0.894):
Dᵤ₄f = -4.47(最快下降方向)
这就是梯度下降的理论依据:沿梯度反方向走一步,函数值下降最快。
五、AI 真实场景中的梯度计算
真实场景1——温度预测模型的梯度(回归问题):
用昨日温度 x₁ 和湿度 x₂ 预测今日温度
模型:ŷ = w₁x₁ + w₂x₂ + b
损失:L = (1/4)Σ(yᵢ - ŷᵢ)²
训练数据(4个样本):
当前参数 w₁=0.8, w₂=0.05, b=2
各样本预测值和误差:
样本1:ŷ = 0.8×25 + 0.05×60 + 2 = 20 + 3 + 2 = 25,误差 = 27-25 = 2
样本2:ŷ = 0.8×30 + 0.05×45 + 2 = 24 + 2.25 + 2 = 28.25,误差 = 32-28.25 = 3.75
样本3:ŷ = 0.8×20 + 0.05×80 + 2 = 16 + 4 + 2 = 22,误差 = 22-22 = 0
样本4:ŷ = 0.8×28 + 0.05×55 + 2 = 22.4 + 2.75 + 2 = 27.15,误差 = 30-27.15 = 2.85
MSE = (4 + 14.0625 + 0 + 8.1225)/4 = 26.185/4 = 6.546
梯度计算(∂L/∂wⱼ = -(2/n) Σ (yᵢ-ŷᵢ)xᵢⱼ):
∂L/∂w₁ = -(2/4)(2×25 + 3.75×30 + 0×20 + 2.85×28)
= -0.5 × (50 + 112.5 + 0 + 79.8) = -0.5 × 242.3 = -121.15
∂L/∂w₂ = -0.5 × (2×60 + 3.75×45 + 0×80 + 2.85×55)
= -0.5 × (120 + 168.75 + 0 + 156.75) = -0.5 × 445.5 = -222.75
∂L/∂b = -0.5 × (2 + 3.75 + 0 + 2.85) = -0.5 × 8.6 = -4.3
梯度 ∇L = (-121.15, -222.75, -4.3)
所有梯度为负 → 所有参数都应增大 → 因为模型普遍预测偏低(3个样本偏低)
用昨日温度 x₁ 和湿度 x₂ 预测今日温度
模型:ŷ = w₁x₁ + w₂x₂ + b
损失:L = (1/4)Σ(yᵢ - ŷᵢ)²
训练数据(4个样本):
| 昨日温度x₁ | 湿度x₂ | 今日温度y |
|---|---|---|
| 25 | 60 | 27 |
| 30 | 45 | 32 |
| 20 | 80 | 22 |
| 28 | 55 | 30 |
各样本预测值和误差:
样本1:ŷ = 0.8×25 + 0.05×60 + 2 = 20 + 3 + 2 = 25,误差 = 27-25 = 2
样本2:ŷ = 0.8×30 + 0.05×45 + 2 = 24 + 2.25 + 2 = 28.25,误差 = 32-28.25 = 3.75
样本3:ŷ = 0.8×20 + 0.05×80 + 2 = 16 + 4 + 2 = 22,误差 = 22-22 = 0
样本4:ŷ = 0.8×28 + 0.05×55 + 2 = 22.4 + 2.75 + 2 = 27.15,误差 = 30-27.15 = 2.85
MSE = (4 + 14.0625 + 0 + 8.1225)/4 = 26.185/4 = 6.546
梯度计算(∂L/∂wⱼ = -(2/n) Σ (yᵢ-ŷᵢ)xᵢⱼ):
∂L/∂w₁ = -(2/4)(2×25 + 3.75×30 + 0×20 + 2.85×28)
= -0.5 × (50 + 112.5 + 0 + 79.8) = -0.5 × 242.3 = -121.15
∂L/∂w₂ = -0.5 × (2×60 + 3.75×45 + 0×80 + 2.85×55)
= -0.5 × (120 + 168.75 + 0 + 156.75) = -0.5 × 445.5 = -222.75
∂L/∂b = -0.5 × (2 + 3.75 + 0 + 2.85) = -0.5 × 8.6 = -4.3
梯度 ∇L = (-121.15, -222.75, -4.3)
所有梯度为负 → 所有参数都应增大 → 因为模型普遍预测偏低(3个样本偏低)
真实场景2——图片分类的梯度流(CNN):
输入:一张 3×32×32 的 CIFAR-10 彩色图片(猫)
网络:Conv(3→16) → ReLU → Conv(16→32) → ReLU → FC(32×8×8→10) → Softmax
1. 前向传播:依次计算到 Softmax 输出概率 [0.05, 0.02, 0.08, 0.60, 0.03, 0.05, 0.02, 0.05, 0.07, 0.03]
(类别3=猫的概率 0.60,预测正确但不够自信)
2. 计算损失:L = -ln(0.60) = 0.511
3. 反向传播梯度流向:
Softmax 层:∂L/∂z = ŷ - y = [0.05, 0.02, 0.08, -0.40, 0.03, 0.05, 0.02, 0.05, 0.07, 0.03]
FC 层:∂L/∂W_fc = (∂L/∂z) × aᵀ(权重梯度),∂L/∂a = Wᵀ(∂L/∂z)(传给上一层)
ReLU:梯度 × 0 或 1(取决于前向传播时是否被激活)
Conv2:卷积核梯度 = 输入特征图与上游梯度的卷积
ReLU → Conv1 → 最终到输入图片的梯度
关键洞察:每个参数的梯度大小不同。靠近输出的参数梯度大(FC 层),
靠近输入的参数梯度可能很小(前几层卷积)——这就是"梯度消失"问题的表现。
输入:一张 3×32×32 的 CIFAR-10 彩色图片(猫)
网络:Conv(3→16) → ReLU → Conv(16→32) → ReLU → FC(32×8×8→10) → Softmax
1. 前向传播:依次计算到 Softmax 输出概率 [0.05, 0.02, 0.08, 0.60, 0.03, 0.05, 0.02, 0.05, 0.07, 0.03]
(类别3=猫的概率 0.60,预测正确但不够自信)
2. 计算损失:L = -ln(0.60) = 0.511
3. 反向传播梯度流向:
Softmax 层:∂L/∂z = ŷ - y = [0.05, 0.02, 0.08, -0.40, 0.03, 0.05, 0.02, 0.05, 0.07, 0.03]
FC 层:∂L/∂W_fc = (∂L/∂z) × aᵀ(权重梯度),∂L/∂a = Wᵀ(∂L/∂z)(传给上一层)
ReLU:梯度 × 0 或 1(取决于前向传播时是否被激活)
Conv2:卷积核梯度 = 输入特征图与上游梯度的卷积
ReLU → Conv1 → 最终到输入图片的梯度
关键洞察:每个参数的梯度大小不同。靠近输出的参数梯度大(FC 层),
靠近输入的参数梯度可能很小(前几层卷积)——这就是"梯度消失"问题的表现。
六、梯度的几何直觉
等高线与梯度的关系:
想象一座山的等高线地图。每条等高线上的点高度相同。
梯度永远垂直于等高线,指向上坡方向。
如果等高线是圆形(各方向变化率相同,如 f=x²+y²):
→ 梯度直接指向最高点,梯度下降路径是直线
→ 收敛最快
如果等高线是细长的椭圆(如 f=100x²+y²):
→ 梯度不指向最高点,而是指向最陡的方向
→ 梯度下降路径会来回震荡(锯齿形)
→ 收敛很慢
→ 这就是为什么需要 Adam 等自适应优化器
想象一座山的等高线地图。每条等高线上的点高度相同。
梯度永远垂直于等高线,指向上坡方向。
如果等高线是圆形(各方向变化率相同,如 f=x²+y²):
→ 梯度直接指向最高点,梯度下降路径是直线
→ 收敛最快
如果等高线是细长的椭圆(如 f=100x²+y²):
→ 梯度不指向最高点,而是指向最陡的方向
→ 梯度下降路径会来回震荡(锯齿形)
→ 收敛很慢
→ 这就是为什么需要 Adam 等自适应优化器
七、代码验证(C# / Rust)
C#(.NET 10)
// dotnet run 即可执行
// ===== 偏导数的数值验证 =====
double F(double x, double y) => 3*x*x*y + 2*x*y*y - 5*x + 7;
double DfDx(double x, double y) => 6*x*y + 2*y*y - 5;
double DfDy(double x, double y) => 3*x*x + 4*x*y;
double x0 = 2.0, y0 = 3.0, eps = 1e-7;
double numDx = (F(x0+eps, y0) - F(x0-eps, y0)) / (2*eps);
double numDy = (F(x0, y0+eps) - F(x0, y0-eps)) / (2*eps);
Console.WriteLine($"解析 ∂f/∂x = {DfDx(x0,y0)}, 数值 = {numDx:F6}");
Console.WriteLine($"解析 ∂f/∂y = {DfDy(x0,y0)}, 数值 = {numDy:F6}");
// ===== 线性回归梯度下降 =====
double[,] X = { {25,60}, {30,45}, {20,80}, {28,55} };
double[] y = { 27, 32, 22, 30 };
double[] w = { 0.8, 0.05 };
double b = 2.0, lr = 0.0001;
int rows = 4, cols = 2;
for (int epoch = 0; epoch < 5; epoch++)
{
double[] yp = new double[rows];
for (int i = 0; i < rows; i++)
{ yp[i] = b; for (int j = 0; j < cols; j++) yp[i] += X[i,j] * w[j]; }
double loss = 0;
for (int i = 0; i < rows; i++) loss += Math.Pow(y[i] - yp[i], 2);
loss /= rows;
double[] dw = new double[cols]; double db = 0;
for (int i = 0; i < rows; i++)
{
double d = y[i] - yp[i];
for (int j = 0; j < cols; j++) dw[j] += X[i,j] * d;
db += d;
}
for (int j = 0; j < cols; j++) { dw[j] *= -2.0/rows; w[j] -= lr*dw[j]; }
db *= -2.0/rows; b -= lr*db;
Console.WriteLine($"Epoch {epoch+1}: Loss={loss:F4}, w=[{w[0]:F4}, {w[1]:F4}], b={b:F4}");
}
// ===== 方向导数验证 =====
double[] pt = {1.0, 1.0};
double[] grad = {2*pt[0], 4*pt[1]}; // f=x²+2y², ∇f=(2x,4y)
double norm = Math.Sqrt(grad[0]*grad[0] + grad[1]*grad[1]);
Console.WriteLine($"\n梯度: [{grad[0]}, {grad[1]}], 模: {norm:F4}");
double dirD = grad[0]*(grad[0]/norm) + grad[1]*(grad[1]/norm);
Console.WriteLine($"梯度方向导数 = {dirD:F4} (应等于模)");
Console.WriteLine($"x方向导数 = {grad[0]}");
Console.WriteLine($"y方向导数 = {grad[1]}");
Rust
fn main() {
// ===== 偏导数的数值验证 =====
let f = |x: f64, y: f64| 3.0*x*x*y + 2.0*x*y*y - 5.0*x + 7.0;
let df_dx = |x: f64, y: f64| 6.0*x*y + 2.0*y*y - 5.0;
let df_dy = |x: f64, y: f64| 3.0*x*x + 4.0*x*y;
let (x0, y0, eps) = (2.0_f64, 3.0, 1e-7);
let nd_x = (f(x0+eps, y0) - f(x0-eps, y0)) / (2.0*eps);
let nd_y = (f(x0, y0+eps) - f(x0, y0-eps)) / (2.0*eps);
println!("解析 ∂f/∂x = {}, 数值 = {nd_x:.6}", df_dx(x0, y0));
println!("解析 ∂f/∂y = {}, 数值 = {nd_y:.6}", df_dy(x0, y0));
// ===== 线性回归梯度下降 =====
let xd = [[25.0,60.0],[30.0,45.0],[20.0,80.0],[28.0,55.0]];
let yd = [27.0, 32.0, 22.0, 30.0_f64];
let (mut w, mut b, lr, n) = ([0.8, 0.05_f64], 2.0_f64, 0.0001_f64, 4);
for epoch in 0..5 {
let mut yp = [0.0_f64; 4];
for i in 0..n { yp[i] = xd[i][0].mul_add(w[0], xd[i][1].mul_add(w[1], b)); }
let mut loss = 0.0_f64;
for i in 0..n { loss += (yd[i] - yp[i]).powi(2); }
loss /= n as f64;
let (mut dw, mut db) = ([0.0_f64; 2], 0.0_f64);
for i in 0..n {
let d = yd[i] - yp[i];
dw[0] += xd[i][0]*d; dw[1] += xd[i][1]*d; db += d;
}
dw[0] *= -2.0/n as f64; dw[1] *= -2.0/n as f64; db *= -2.0/n as f64;
w[0] -= lr*dw[0]; w[1] -= lr*dw[1]; b -= lr*db;
println!("Epoch {}: Loss={loss:.4}, w=[{:.4}, {:.4}], b={b:.4}",
epoch+1, w[0], w[1]);
}
// ===== 方向导数 =====
let pt = [1.0, 1.0_f64];
let grad = [2.0*pt[0], 4.0*pt[1]];
let norm = (grad[0]*grad[0] + grad[1]*grad[1]).sqrt();
println!("\n梯度: [{}, {}], 模: {norm:.4}", grad[0], grad[1]);
let dir_d = (grad[0]/norm).mul_add(grad[0], (grad[1]/norm) * grad[1]);
println!("梯度方向导数 = {dir_d:.4} (应等于模)");
println!("x方向导数 = {}", grad[0]);
println!("y方向导数 = {}", grad[1]);
}