通俗易懂讲透BFGS优化算法(本科生/研究生都能看懂)
本文用大白话+形象比喻+公式拆解+可运行代码+对比总结,把BFGS从原理、流程、优缺点到适用场景讲得清清楚楚,适合数值优化、机器学习复习、课程笔记与面试。
一、先搞懂:我们为什么需要BFGS?
在机器学习/深度学习里,我们的目标是最小化损失函数,找到最优参数。
常见的优化方法有:
- 梯度下降(GD):只看一阶导数(坡度),走得慢、震荡多
- 牛顿法:用二阶导数(曲率/Hessian),收敛极快,但计算Hessian矩阵代价太高,高维场景根本跑不动
于是就有了BFGS:
不用算真实Hessian,只用梯度信息,迭代构造近似Hessian逆,实现超线性收敛
一句话总结:
BFGS = 拟牛顿法的王者,比梯度下降快百倍,比牛顿法省算力。
二、BFGS 是什么?(超形象比喻)
BFGS(Broyden–Fletcher–Goldfarb–Shanno)是最经典的拟牛顿算法。
你可以把优化过程比作下山找谷底:
- 梯度下降:蒙着眼,只看脚下坡度,一步一步挪,容易绕路、震荡
- 牛顿法:带着完整地形图(Hessian),一步到位,但地图太重、太贵
- BFGS:边走边画简易地图,不用提前画全,每走一步就修正地图,又快又省资源
核心逻辑:
用历史位置差和梯度差,迭代更新近似Hessian逆矩阵,不用直接计算二阶导数。
三、BFGS 核心思想(不用公式也能懂)
- 初始假设:最开始把地形当成平地,Hessian逆用单位矩阵
- 计算方向:结合当前梯度 + 近似Hessian逆,得到更优的下降方向
- 线搜索:在这个方向上找最合适的步长
- 更新近似矩阵:用新走的位移和梯度变化,修正“简易地图”
- 重复迭代:直到梯度接近0(到达谷底)
四、BFGS 公式一步步拆解(详细易懂)
1. 先看牛顿法(对照)
xk+1=xk−[∇2f(xk)]−1∇f(xk)x_{k+1} = x_k - [\nabla^2f(x_k)]^{-1}\nabla f(x_k)xk+1=xk−[∇2f(xk)]−1∇f(xk)
- ∇2f\nabla^2f∇2f:Hessian矩阵(二阶导),计算/求逆代价极大
- 优点:二阶收敛,超快
- 缺点:高维算不动,可能不正定
2. BFGS 核心变量
- xkx_kxk:当前位置
- gk=∇f(xk)g_k=\nabla f(x_k)gk=∇f(xk):当前梯度
- HkH_kHk:近似Hessian逆矩阵(BFGS真正维护的东西)
- sk=xk+1−xks_k = x_{k+1}-x_ksk=xk+1−xk:位置变化
- yk=gk+1−gky_k = g_{k+1}-g_kyk=gk+1−gk:梯度变化
- ρk=1/(ykTsk)\rho_k=1/(y_k^Ts_k)ρk=1/(ykTsk):简化系数
3. 搜索方向
pk=−Hkgkp_k = -H_k g_kpk=−Hkgk
这一步融合了梯度和二阶近似,方向比纯梯度更准。
4. BFGS 逆Hessian更新公式(最常用)
Hk+1=(I−ρkskykT)Hk(I−ρkykskT)+ρkskskTH_{k+1} = \left(I-\rho_k s_k y_k^T\right) H_k \left(I-\rho_k y_k s_k^T\right) + \rho_k s_k s_k^THk+1=(I−ρkskykT)Hk(I−ρkykskT)+ρkskskT
✅ 直白解释:
- 保留上一轮的近似矩阵
- 用新的位移sss和梯度差yyy修正
- 保证矩阵对称正定,确保是下降方向
5. 参数更新
xk+1=xk+αkpkx_{k+1} = x_k + \alpha_k p_kxk+1=xk+αkpk
αk\alpha_kαk由线搜索(Armijo/Wolfe条件)得到。
五、BFGS 完整算法流程(7步背会)
- 初始化:x0x_0x0,H0=IH_0=IH0=I(单位矩阵),阈值ϵ\epsilonϵ
- 计算梯度gkg_kgk,若∥gk∥<ϵ\|g_k\|<\epsilon∥gk∥<ϵ,停止
- 计算搜索方向pk=−Hkgkp_k=-H_k g_kpk=−Hkgk
- 线搜索求最优步长αk\alpha_kαk
- 计算sks_ksk、yky_kyk、ρk\rho_kρk
- 用BFGS公式更新Hk+1H_{k+1}Hk+1
- 更新xk+1x_{k+1}xk+1,回到步骤2
六、代码实战:BFGS 优化 Rosenbrock 函数
直接复制可运行,包含BFGS实现 + 梯度下降对比 + 可视化。
import numpy as np
import matplotlib.pyplot as plt
import time
from mpl_toolkits.mplot3d import Axes3D
# ===================== 1. 定义测试函数:Rosenbrock(香蕉函数)=====================
def rosenbrock(x):
return 100 * (x[1] - x[0]**2)**2 + (1 - x[0])**2
def grad_rosenbrock(x):
dx = -400 * x[0] * (x[1] - x[0]**2) - 2 * (1 - x[0])
dy = 200 * (x[1] - x[0]**2)
return np.array([dx, dy])
# ===================== 2. 线搜索(Armijo 条件)=====================
def line_search(f, grad, x, p, alpha=1.0, rho=0.8, c=1e-4):
fx = f(x)
gx = grad(x)
while f(x + alpha * p) > fx + c * alpha * np.dot(gx, p):
alpha *= rho
return alpha
# ===================== 3. BFGS 算法实现 =====================
def bfgs(f, grad, x0, tol=1e-6, max_iter=1000):
x = x0.copy()
n = len(x)
H = np.eye(n) # 初始近似Hessian逆
path = [x.copy()]
f_val = [f(x)]
for _ in range(max_iter):
g = grad(x)
if np.linalg.norm(g) < tol:
break
# 搜索方向
p = -H @ g
# 线搜索
alpha = line_search(f, grad, x, p)
# 更新
s = alpha * p
x_new = x + s
y = grad(x_new) - g
# BFGS更新
rho = 1.0 / (y @ s)
I = np.eye(n)
H = (I - rho * np.outer(s, y)) @ H @ (I - rho * np.outer(y, s)) + rho * np.outer(s, s)
# 记录
x = x_new
path.append(x.copy())
f_val.append(f(x))
return np.array(path), f_val
# ===================== 4. 梯度下降(对比用)=====================
def gradient_descent(f, grad, x0, lr=1e-3, tol=1e-6, max_iter=50000):
x = x0.copy()
path = [x.copy()]
f_val = [f(x)]
for _ in range(max_iter):
g = grad(x)
if np.linalg.norm(g) < tol:
break
x = x - lr * g
path.append(x.copy())
f_val.append(f(x))
return np.array(path), f_val
# ===================== 5. 运行对比 =====================
x0 = np.array([-1.5, 1.5])
start = time.time()
path_bfgs, f_bfgs = bfgs(rosenbrock, grad_rosenbrock, x0)
t_bfgs = time.time() - start
start = time.time()
path_gd, f_gd = gradient_descent(rosenbrock, grad_rosenbrock, x0)
t_gd = time.time() - start
print(f"BFGS 迭代:{len(f_bfgs)-1} 次,时间:{t_bfgs:.4f}s")
print(f"GD 迭代:{len(f_gd)-1} 次,时间:{t_gds:.4f}s")
# ===================== 6. 可视化:优化路径 =====================
X = np.linspace(-2, 2, 400)
Y = np.linspace(-1, 3, 400)
XX, YY = np.meshgrid(X, Y)
ZZ = 100 * (YY - XX**2)**2 + (1 - XX)**2
plt.figure(figsize=(10, 6))
plt.contour(XX, YY, ZZ, levels=np.logspace(-0.5, 3.5, 20), cmap="viridis")
plt.plot(path_bfgs[:,0], path_bfgs[:,1], "ro-", label="BFGS", markersize=4)
plt.plot(path_gd[:,0], path_gd[:,1], "bx--", label="Gradient Descent", markersize=2)
plt.title("BFGS vs GD 优化路径")
plt.xlabel("x")
plt.ylabel("y")
plt.legend()
plt.grid(True)
plt.show()
# ===================== 7. 收敛曲线 =====================
plt.figure(figsize=(10, 6))
plt.plot(f_bfgs, label="BFGS", linewidth=2)
plt.plot(f_gd, label="GD", linewidth=2)
plt.yscale("log")
plt.title("收敛曲线对比")
plt.xlabel("迭代次数")
plt.ylabel("函数值")
plt.legend()
plt.grid(True)
plt.show()
七、BFGS 优点(面试必背)
- 超线性收敛:比梯度下降快几十~几百倍
- 不用计算真实Hessian:避开O(n3)O(n^3)O(n3)求逆噩梦
- 稳定可靠:自动保持矩阵对称正定,保证下降方向
- 中小维度最优:参数维度几百~几千时效果最好
- 实现简单:只需线搜索 + 矩阵更新
八、BFGS 缺点(必须知道)
- 内存开销大:要存n×nn×nn×n矩阵,高维(>1000)吃不消
- 不适合随机梯度:深度学习批量SGD噪声大,BFGS容易崩
- 依赖线搜索:步长没选好会不收敛
- 非凸场景不稳定:鞍点多、地形复杂时容易失效
九、BFGS 家族对比(速记表)
| 算法 | 二阶信息 | 内存 | 收敛速度 | 适用场景 |
|---|---|---|---|---|
| 梯度下降 | 无 | O(n)O(n)O(n) | 慢(线性) | 大规模/深度学习 |
| 牛顿法 | 完整Hessian | O(n2)O(n^2)O(n2) | 极快(二阶) | 低维、Hessian好算 |
| BFGS | 近似Hessian逆 | O(n2)O(n^2)O(n2) | 快(超线性) | 中小规模、光滑凸优化 |
| L-BFGS | 有限记忆近似 | O(mn)O(mn)O(mn) | 快 | 高维大规模(机器学习常用) |
十、什么时候用 BFGS?
✅ 推荐用 BFGS
- 无约束、光滑、接近凸的优化问题
- 维度:几十 ~ 几千(n ≤ 1000)
- 梯度可以精确计算,无噪声
- 追求快速收敛 + 高精度
❌ 不要用 BFGS
- 深度学习(CNN/Transformer)→ 用 Adam / SGD
- 维度极高(n > 10^4)→ 用 L-BFGS
- 梯度带噪声 → 用一阶自适应方法
- 强非凸、鞍点多 → 用信赖域方法
十一、一句话总结
BFGS 是拟牛顿法的标杆,用梯度构造近似二阶信息,不计算真实Hessian却能超线性收敛,是中小规模光滑优化的首选算法。
高维场景请用它的升级版 L-BFGS。

被折叠的 条评论
为什么被折叠?



