1. 从最速下降到共轭:梯度法的本质差异
我第一次接触优化算法时,最速下降法就像数学课上的"hello world"——简单直接。但真正用起来才发现,这个看似完美的算法有个致命缺陷:锯齿现象。想象你在山谷里找最低点,每次都是沿着最陡的方向走,结果却像喝醉酒一样左右摇摆,迟迟到不了目的地。
最速下降法的数学表达很简单:
x_{k+1} = x_k - α_k ∇f(x_k)其中α_k是通过线搜索确定的最优步长。但问题就出在这个"最速"上——局部最优方向不等于全局最优路径。我做过一个实验,用最速下降法优化f(x,y)=x²+10y²,迭代20次才收敛,轨迹像蛇形一样曲折。
而共轭梯度法的精妙之处在于,它在第二次迭代时就展现了完全不同的行为。关键区别在于搜索方向的构造:
d_{k+1} = -∇f(x_{k+1}) + β_k d_k这个β_k就是魔法发生的地方。它让新的搜索方向与之前的方向满足共轭性,从而避免了重复搜索相同的方向。就像在山谷中行走时,每次都能找到与前一步互补的新路径。
2. 手算推演:二次型问题的完整解法
让我们用一个具体例子f(x)=x₁²+x₂²-x₁x₂来演示两种算法的差异。这个函数在x=(0,0)处取得最小值0,非常适合教学演示。
2.1 初始设置
假设起点x⁰=(1,2),误差阈值ε=0.01。计算初始梯度:
∇f(x⁰) = [2x₁ - x₂, 2x₂ - x₁] = [0, 3]2.2 最速下降法的第一步
搜索方向d⁰=-∇f(x⁰)=[0,-3] 通过最小化f(x⁰+αd⁰)得到步长α=5/18≈0.278 更新点x¹=x⁰+αd⁰≈[1, 0.167]
2.3 共轭梯度法的转折点
第二次迭代时,最速下降法会继续计算当前梯度∇f(x¹)=[1.833, -0.833],而共轭梯度法会引入一个修正项:
- 计算β₀=(∇f(x¹)·∇f(x¹))/(∇f(x⁰)·∇f(x⁰))≈0.093
- 新搜索方向d¹=-∇f(x¹)+β₀d⁰≈[-1.833, 0.463]
- 这个方向神奇地指向了最小值点!
我用MATLAB重现这个过程时发现,共轭梯度法在二维二次型问题上最多2步就能收敛,而最速下降法需要10次以上迭代。这就是共轭方向的威力——它记住了之前搜索的信息,避免了重复劳动。
3. 共轭方向的数学奥秘
为什么共轭方向如此有效?这要从二次型的几何性质说起。对于正定矩阵Q,如果两个向量dᵢ和dⱼ满足:
dᵢᵀQdⱼ = 0就称它们关于Q共轭。这种性质带来的好处是:
- 有限步收敛:对于n维二次型问题,最多n步就能找到精确解
- 正交性保证:每个新方向都自动与之前的残差正交
- 内存高效:不需要存储完整的Hessian矩阵
我在实现时踩过一个坑:很多人误以为共轭梯度法只需要计算梯度,其实关键在于如何选择β系数。目前主流有三种公式:
- Fletcher-Reeves:β_k = (∇f_{k+1}·∇f_{k+1})/(∇f_k·∇f_k)
- Polak-Ribière:更擅长处理非二次型
- Hestenes-Stiefel:数值稳定性最好
4. 工程实践中的调参经验
虽然理论很美好,但实际应用中还是有很多技巧:
- 重启机制:对于非二次型问题,我建议每n步重置一次搜索方向
- 预处理技术:使用对角预处理矩阵可以显著加速收敛
- 收敛判断:不要只看梯度范数,还要检查函数值变化率
这里有个实用的Python代码片段展示了预处理共轭梯度法的核心:
def pcg(A, b, x0, max_iter=1000, tol=1e-6): r = b - A @ x0 z = precond(r) # 预处理步骤 p = z.copy() for k in range(max_iter): alpha = (r.T @ z) / (p.T @ A @ p) x = x + alpha * p r_new = r - alpha * A @ p if norm(r_new) < tol: break z_new = precond(r_new) beta = (z_new.T @ r_new) / (z.T @ r) p = z_new + beta * p r, z = r_new, z_new return x在神经网络训练中,虽然主流是Adam等自适应方法,但共轭梯度法在特定场景仍有优势。比如在训练最后一层分类器时,配合L-BFGS预处理可以达到比SGD更快的收敛速度。不过要注意,当参数量超过百万时,存储问题会变得棘手。