当前位置: 首页 > article >正文

L-BFGS 方法实现

L-BFGS 方法实现详细笔记


1. L-BFGS 算法简介

L-BFGS (Limited-memory Broyden-Fletcher-Goldfarb-Shanno) 是一种拟牛顿优化算法,适用于处理高维、无约束的优化问题,特别是可能非凸或非光滑的函数。

算法流程
  1. 初始化 ( x 0 , g 0 ← ∇ f ( x 0 ) , B 0 ← I , k ← 0 ) (x^0, g^0 \leftarrow \nabla f(x^0), B^0 \leftarrow I, k \leftarrow 0 ) (x0,g0f(x0),B0I,k0)
  2. 进入循环:
    • 计算搜索方向 ( d ← − B k g k ) (d \leftarrow -B^k g^k ) (dBkgk)
    • 使用 Lewis-Overton 线搜索找到步长 ( t ) (t ) (t),满足弱 Wolfe 条件。
    • 更新变量 ( x k + 1 ← x k + t ⋅ d ) (x^{k+1} \leftarrow x^k + t \cdot d ) (xk+1xk+td)
    • 更新梯度 ( g k + 1 ← ∇ f ( x k + 1 ) ) (g^{k+1} \leftarrow \nabla f(x^{k+1}) ) (gk+1f(xk+1))
    • 使用 Cautious-Limited-Memory-BFGS 更新近似 Hessian 信息 ( B k + 1 ) (B^{k+1} ) (Bk+1)
    • 检查是否满足收敛条件或停止准则。
  3. 返回最优解 ( x ∗ ) (x^* ) (x) 和对应的目标函数值 ( f ( x ∗ ) ) (f(x^*) ) (f(x))

2. 代码主要逻辑对照公式


1. 初始化阶段

公式:
x 0 ,   g 0 ← ∇ f ( x 0 ) ,   B 0 ← I ,   k ← 0 x^0, \, g^0 \leftarrow \nabla f(x^0), \, B^0 \leftarrow I, \, k \leftarrow 0 x0,g0f(x0),B0I,k0
对应代码:

Eigen::VectorXd xp(n);  // 当前点
Eigen::VectorXd g(n);   // 当前点的梯度
Eigen::VectorXd gp(n);  // 前一次的梯度
Eigen::VectorXd d(n);   // 搜索方向

// 计算初始目标函数值和梯度
fx = cd.proc_evaluate(cd.instance, x, g);
pf(0) = fx; // 存储目标函数值
d = -g;     // 初始搜索方向,负梯度方向
  • 目标:初始化变量 ( x 0 ) (x^0 ) (x0)、梯度 ( g 0 ) (g^0 ) (g0)、目标函数值 ( f ( x 0 ) ) (f(x^0) ) (f(x0)),并设置搜索方向为负梯度。
  • 隐含假设:初始近似 Hessian 矩阵为单位矩阵 ( B 0 = I ) (B^0 = I ) (B0=I)

2. 主循环条件

公式:
while   ∣ ∣ g k ∣ ∣ > δ \text{while} \, ||g^k|| > \delta while∣∣gk∣∣>δ
对应代码:

gnorm_inf = g.cwiseAbs().maxCoeff();  // 梯度无穷范数
xnorm_inf = x.cwiseAbs().maxCoeff();  // 变量无穷范数

if (gnorm_inf / std::max(1.0, xnorm_inf) < param.g_epsilon) {
    ret = LBFGS_CONVERGENCE; // 收敛
    break;
}
  • 目标:当梯度的无穷范数小于阈值 ( δ ) (\delta ) (δ) 时,认为优化已收敛。
  • 代码:比较梯度范数与参数 param.g_epsilon,如果满足条件,终止循环。

3. 计算搜索方向

公式:
d ← − B k g k d \leftarrow -B^k g^k dBkgk
对应代码:

d = -g;  // 初始使用单位矩阵近似 Hessian
  • 目标:使用当前梯度 ( g k ) (g^k ) (gk) 和 Hessian 近似 ( B k ) (B^k ) (Bk) 计算搜索方向。
  • 代码:初始迭代假设 ( B k = I ) (B^k = I ) (Bk=I),搜索方向为负梯度。

4. 线搜索

公式:
t ← Lewis Overton line search t \leftarrow \text{Lewis Overton line search} tLewis Overton line search
对应代码:

ls = line_search_lewisovertons(x, fx, g, step, d, xp, gp, step_min, step_max, cd, param);
  • 目标:通过线搜索找到满足 Armijo 条件弱 Wolfe 条件的步长 ( t ) (t ) (t)
  • 代码:调用 line_search_lewisovertons 实现 Lewis-Overton 线搜索。

5. 更新变量和梯度

公式:
x k + 1 ← x k + t ⋅ d , g k + 1 ← ∇ f ( x k + 1 ) x^{k+1} \leftarrow x^k + t \cdot d, \quad g^{k+1} \leftarrow \nabla f(x^{k+1}) xk+1xk+td,gk+1f(xk+1)
对应代码:

x = xp + step * d; // 更新变量
f = cd.proc_evaluate(cd.instance, x, g); // 更新目标函数值和梯度
  • 目标:根据步长 ( t ) (t ) (t) 和方向 ( d ) (d ) (d) 更新变量,并重新计算目标函数值和梯度。

6. 更新 Hessian 信息

公式:
B k + 1 ← Cautious-Limited-Memory-BFGS ( g k + 1 − g k , x k + 1 − x k ) B^{k+1} \leftarrow \text{Cautious-Limited-Memory-BFGS}(g^{k+1} - g^k, x^{k+1} - x^k) Bk+1Cautious-Limited-Memory-BFGS(gk+1gk,xk+1xk)
对应代码:

lm_s.col(end) = x - xp;    // s = x^{k+1} - x^k
lm_y.col(end) = g - gp;    // y = g^{k+1} - g^k

ys = lm_y.col(end).dot(lm_s.col(end)); // ys = y^T s
yy = lm_y.col(end).squaredNorm();      // yy = y^T y
lm_ys(end) = ys;

cau = lm_s.col(end).squaredNorm() * gp.norm() * param.cautious_factor;
if (ys > cau) {
    // 更新 BFGS 信息
    ...
}
  • 目标:计算变量变化量 ( s ) (s ) (s) 和梯度变化量 ( y ) (y ) (y),并更新有限记忆存储。
  • 代码
    • 通过 ( y s = y T s ) (ys = y^T s ) (ys=yTs) ( y y = y T y ) (yy = y^T y ) (yy=yTy) 计算 Hessian 更新所需的比例因子。
    • 判断 ( y s > 阈值 ) (ys > \text{阈值} ) (ys>阈值),执行谨慎更新。

7. 收敛和停止条件

公式:
若满足  ∣ ∣ g k ∣ ∣ ≤ δ   或其他停止准则 \text{若满足 } ||g^k|| \leq \delta \, \text{或其他停止准则} 若满足 ∣∣gk∣∣δ或其他停止准则
对应代码:

if (gnorm_inf / std::max(1.0, xnorm_inf) < param.g_epsilon) {
    ret = LBFGS_CONVERGENCE; // 梯度收敛
    break;
}

if (param.past > 0 && param.past <= k) {
    rate = std::fabs(pf(k % param.past) - fx) / std::max(1.0, std::fabs(fx));
    if (rate < param.delta) {
        ret = LBFGS_STOP; // 满足目标函数下降准则
        break;
    }
}

if (param.max_iterations != 0 && param.max_iterations <= k) {
    ret = LBFGSERR_MAXIMUMITERATION; // 达到最大迭代次数
    break;
}
  • 目标:检测是否满足收敛条件(梯度、目标函数下降率、迭代次数)。
  • 代码
    • 梯度收敛:梯度的无穷范数小于 param.g_epsilon
    • 目标函数下降率:变化量小于 param.delta
    • 最大迭代次数:超过 param.max_iterations

3. 总结

L-BFGS 主要步骤
  1. 初始化:设置初始变量、梯度、目标函数值,假设初始 Hessian 为单位矩阵。
  2. 主循环
    • 计算搜索方向:通过负梯度和 Hessian 信息计算方向。
    • 线搜索:使用 Lewis-Overton 方法找到满足条件的步长。
    • 更新变量:通过步长更新变量和梯度。
    • 更新 Hessian:根据变量和梯度变化更新近似信息。
  3. 检查停止准则:判断梯度、目标函数或迭代次数是否满足条件。
  4. 返回结果:输出最优解和目标函数值。
关键实现的亮点
  • 线搜索:Lewis-Overton 方法保证适用于非光滑函数。
  • 有限记忆:通过存储变量和梯度差向量节省内存。
  • 谨慎更新:避免非正定的 Hessian 更新,提高稳定性。

L-BFGS 方法中 Hessian 更新


1. L-BFGS 方法中 Hessian 的作用

在优化问题中,Hessian 矩阵 ( B k ) ( B^k ) (Bk) 是目标函数的二阶导数,用于描述函数的曲率信息。在 L-BFGS 方法中:

  1. 目标:通过近似 Hessian 矩阵 ( B k ) ( B^k ) (Bk),找到更高效的搜索方向 ( d = − B k g ) ( d = -B^k g ) (d=Bkg)
  2. 特点:L-BFGS 不显式存储和计算完整的 Hessian 矩阵,而是通过有限记忆的递归公式更新搜索方向,从而降低内存和计算成本。

2. 公式回顾

更新公式

Hessian 更新公式:
B k + 1 ← Cautious-Limited-Memory-BFGS ( g k + 1 − g k , x k + 1 − x k ) B^{k+1} \leftarrow \text{Cautious-Limited-Memory-BFGS}(g^{k+1} - g^k, x^{k+1} - x^k) Bk+1Cautious-Limited-Memory-BFGS(gk+1gk,xk+1xk)

  • ( g k + 1 − g k ) ( g^{k+1} - g^k ) (gk+1gk):梯度变化。
  • ( x k + 1 − x k ) ( x^{k+1} - x^k ) (xk+1xk):变量变化。
  • Cautious Update:通过添加阈值控制,确保更新的 Hessian 近似矩阵是正定的。
使用公式

在每次迭代中,使用更新后的 ( B k + 1 ) ( B^{k+1} ) (Bk+1) 计算搜索方向:
d = − B k + 1 g k + 1 d = -B^{k+1} g^{k+1} d=Bk+1gk+1


3. 如何实现 Hessian 的更新

代码片段
/* 更新步长差向量和梯度差向量 */
lm_s.col(end) = x - xp;    // s = x^{k+1} - x^k
lm_y.col(end) = g - gp;    // y = g^{k+1} - g^k

/* 计算 ys 和 yy */
ys = lm_y.col(end).dot(lm_s.col(end)); // ys = y^T s
yy = lm_y.col(end).squaredNorm();      // yy = y^T y
lm_ys(end) = ys;

/* 如果 ys > 某一阈值,则进行更新 */
cau = lm_s.col(end).squaredNorm() * gp.norm() * param.cautious_factor;
if (ys > cau)
{
    ++bound; // 更新有限记忆的边界
    bound = m < bound ? m : bound; // 限制边界不超过存储容量 m
    end = (end + 1) % m; // 更新存储指针
}
实现解析
  1. 步长差向量 ( s = x k + 1 − x k ) ( s = x^{k+1} - x^k ) (s=xk+1xk)

    • 在每次迭代中,存储当前变量变化量 ( s ) ( s ) (s)
    • 对应代码:lm_s.col(end) = x - xp;
  2. 梯度差向量 ( y = g k + 1 − g k ) ( y = g^{k+1} - g^k ) (y=gk+1gk)

    • 在每次迭代中,存储当前梯度变化量 ( y ) ( y ) (y)
    • 对应代码:lm_y.col(end) = g - gp;
  3. 比例因子 ( y s = y T s ) ( ys = y^T s ) (ys=yTs)

    • ( y s ) ( ys ) (ys) 是 BFGS 更新的关键比例因子,必须满足 ( y s > 0 ) ( ys > 0 ) (ys>0) 才能确保 Hessian 近似矩阵正定。
    • 对应代码:ys = lm_y.col(end).dot(lm_s.col(end));
  4. 谨慎更新

    • 设置一个谨慎因子 ( 阈值 = ∥ s ∥ 2 ∥ g k ∥ ⋅ cautious_factor ) ( \text{阈值} = \|s\|^2 \|g^k\| \cdot \text{cautious\_factor} ) (阈值=s2gkcautious_factor),避免 ( y s ) ( ys ) (ys) 太小导致不稳定。
    • 对应代码:
      cau = lm_s.col(end).squaredNorm() * gp.norm() * param.cautious_factor;
      if (ys > cau) {
          ...
      }
      
  5. 有限记忆机制

    • 只保留最近 ( m ) ( m ) (m) 次的 ( s ) ( s ) (s) ( y ) ( y ) (y) 值,用于递归计算搜索方向。
    • 对应代码:
      ++bound;
      bound = m < bound ? m : bound;
      end = (end + 1) % m;
      

4. 如何使用 ( B k + 1 ) ( B^{k+1} ) (Bk+1)

公式

在每次迭代中,根据有限记忆的 ( s ) ( s ) (s) ( y ) ( y ) (y) 递归更新搜索方向 ( d ) ( d ) (d)
d = − B k + 1 g d = -B^{k+1} g d=Bk+1g

代码片段
j = end;
for (i = 0; i < bound; ++i)
{
    j = (j + m - 1) % m; /* 逆序访问 */
    lm_alpha(j) = lm_s.col(j).dot(d) / lm_ys(j); // 计算 \alpha_j
    d += (-lm_alpha(j)) * lm_y.col(j); // 更新方向 d
}

d *= ys / yy; // 缩放方向 d

for (i = 0; i < bound; ++i)
{
    beta = lm_y.col(j).dot(d) / lm_ys(j); // 计算 \beta_j
    d += (lm_alpha(j) - beta) * lm_s.col(j); // 最终方向更新
    j = (j + 1) % m; /* 顺序访问 */
}
实现解析
  1. 逆序访问 ( s ) ( s ) (s) ( y ) ( y ) (y)

    • 遍历存储的历史值,从最近的一次开始,逐步递归调整搜索方向。
    • 对应公式:
      α j = s j T d y j T s j \alpha_j = \frac{s_j^T d}{y_j^T s_j} αj=yjTsjsjTd
  2. 递归公式

    • 使用 ( α j ) ( \alpha_j ) (αj) ( β j ) ( \beta_j ) (βj) 更新方向 ( d ) ( d ) (d)
      d = − g + ∑ j ( α j − β j ) s j d = -g + \sum_j (\alpha_j - \beta_j) s_j d=g+j(αjβj)sj
    • 对应代码:
      d += (-lm_alpha(j)) * lm_y.col(j);
      d += (lm_alpha(j) - beta) * lm_s.col(j);
      
  3. 有限记忆约束

    • 只使用最近 ( m ) ( m ) (m) 步的历史数据,避免存储完整的 Hessian 矩阵。

5. 总结

Hessian 更新过程
  1. 计算步长差 ( s = x k + 1 − x k ) ( s = x^{k+1} - x^k ) (s=xk+1xk) 和梯度差 ( y = g k + 1 − g k ) ( y = g^{k+1} - g^k ) (y=gk+1gk)
  2. 检查 ( y s = y T s ) ( ys = y^T s ) (ys=yTs) 是否满足阈值,确保更新的 Hessian 近似矩阵正定。
  3. ( s ) ( s ) (s) ( y ) ( y ) (y) 存入有限记忆矩阵,用于后续递归更新。
Hessian 使用过程
  1. 在每次迭代中,通过递归公式使用 ( s ) ( s ) (s) ( y ) ( y ) (y) 计算搜索方向 ( d ) ( d ) (d)
  2. 递归公式避免了 Hessian 的显式存储,显著降低了内存和计算成本。
关键优化点
  • 谨慎更新:引入阈值 ( y s > cautious_factor ) ( ys > \text{cautious\_factor} ) (ys>cautious_factor) 避免不稳定。
  • 有限记忆:仅存储最近 ( m ) ( m ) (m) 次迭代的信息,适合高维优化问题。
  • 递归公式:通过历史数据快速近似计算 ( B k + 1 g ) ( B^{k+1} g ) (Bk+1g),无需显式存储 ( B k + 1 ) ( B^{k+1} ) (Bk+1)

这使得 L-BFGS 方法既具有二阶优化的高效性,又能保持低内存开销,非常适合高维数值优化问题。


http://www.kler.cn/a/428167.html

相关文章:

  • floodfill算法(6题)
  • vim操作简要记录
  • 快速分析LabVIEW主要特征进行判断
  • 【MQ】RabbitMq的可靠性保证
  • 《多阶段渐进式图像修复》学习笔记
  • C++,STL,【目录篇】
  • Hive 中 Order By、Sort By、Cluster By 和 Distribute By 的详细解析
  • 流量转发利器之Burpsuite概述(1)
  • 监控易管理平台7.0助力打造智慧运维体系
  • DataEase 是开源的 BI 工具
  • MISRA C2012学习笔记(10)-Rules 8.15
  • 如何将 JavaWeb 项目部署到云服务器
  • PyCharm 中使用 Flask 框架和数据库
  • uniapp实现轮播图效果
  • 添加TCP SYN扫描的Qt程序
  • 在python中使用布尔逻辑
  • 成像报告撰写格式
  • c++的类和对象(3)
  • 统计二叉树叶子结点个数
  • Unity3D运行设置物体为预制体
  • 福昕PDF低代码平台
  • Oracle EBS FA 如何打开关闭的资产会计期间?
  • CTF-WEB: 目录穿越与伪协议 [第一届国城杯 signal] 赛后学习笔记
  • 现代C++ 7 初始化
  • 高效开发 Python Web 应用:FastAPI 数据验证与响应体设计
  • 在vue3里使用scss实现简单的换肤功能