如何在使用scipy.optimize.minimize(L-BFGS-B)时记录步长
关于scipy L-BFGS-B提取线搜索步长的解决方案
scipy官方封装的scipy.optimize.minimize的L-BFGS-B接口默认没有直接返回线搜索步长的参数,你可以通过以下两种方式获取步长:
方案1:自定义回调+包装函数计算步长(无需修改源码,推荐)
L-BFGS-B每次迭代满足更新规则:x_new = x_old + α * p,其中α是线搜索得到的最终接受步长,p是本次迭代的搜索方向。你可以通过记录每次迭代的参数、梯度计算得到步长,参考实现代码如下:
import numpy as np import pandas as pd from scipy.optimize import minimize class LBFGSStepRecorder: def __init__(self, obj_func, grad_func, m=10): self.obj_func = obj_func self.grad_func = grad_func self.m = m # L-BFGS保留的历史更新对数量,和算法默认值保持一致 self.prev_x = None self.prev_grad = None self.prev_dir = None self.step_lengths = [] self.history_pairs = [] # 存储L-BFGS的{s,y}更新对 def wrapped_grad(self, x): grad = self.grad_func(x) # 首次迭代搜索方向为负梯度 if self.prev_x is None: self.prev_dir = -grad else: # 标准L-BFGS双循环逻辑计算搜索方向 s = x - self.prev_x y = grad - self.prev_grad sy_dot = np.dot(s, y) if sy_dot > 1e-10: # 避免数值问题 self.history_pairs.append((s, y, 1/sy_dot)) if len(self.history_pairs) > self.m: self.history_pairs.pop(0) q = grad.copy() alpha_list = [] for s_i, y_i, rho_i in reversed(self.history_pairs): alpha_i = rho_i * np.dot(s_i, q) q -= alpha_i * y_i alpha_list.append(alpha_i) # 初始逆海森缩放 if self.history_pairs: s_last, y_last, _ = self.history_pairs[-1] r = q * (np.dot(s_last, y_last) / np.dot(y_last, y_last)) else: r = q for (s_i, y_i, rho_i), alpha_i in zip(self.history_pairs, reversed(alpha_list)): beta_i = rho_i * np.dot(y_i, r) r += (alpha_i - beta_i) * s_i self.prev_dir = -r self.prev_grad = grad.copy() return grad def callback(self, x): if self.prev_x is not None and self.prev_dir is not None: delta_x = x - self.prev_x # 步长为delta_x在搜索方向上的投影长度 dir_norm = np.linalg.norm(self.prev_dir) alpha = np.linalg.norm(delta_x) / dir_norm if dir_norm > 1e-12 else 0 self.step_lengths.append(alpha) self.prev_x = x.copy() # 示例使用 def objective(x): return x[0]**2 + x[1]**2 def gradient(x): return np.array([2*x[0], 2*x[1]]) recorder = LBFGSStepRecorder(objective, gradient) res = minimize(objective, x0=np.array([10, 10]), method='L-BFGS-B', jac=recorder.wrapped_grad, callback=recorder.callback) # 导出步长到csv pd.DataFrame({ '迭代次数': range(1, len(recorder.step_lengths)+1), '步长': recorder.step_lengths }).to_csv('lbfgs步长记录.csv', index=False, encoding='utf-8-sig')
方案2:直接调用底层L-BFGS-B接口
如果你需要获取线搜索过程中产生的中间测试步长(而非最终接受的步长),可以直接调用scipy底层的scipy.optimize._lbfgsb.minimize_lbfgsb非公开接口,该接口的回调参数可以拿到更多内部状态,你可以直接在回调中提取步长变量。注意该接口属于scipy内部实现,不同版本的参数结构可能存在差异,不建议跨版本使用。
内容的提问来源于stack exchange,提问作者lhell
相关产品推荐
相关产品推荐

