资讯动态

步长搜索与Wolfe条件:从线性搜索到L-BFGS的工程实践

发布时间:2026/10/26 9:55:57 来源:尧图企业网站定制
简介针对优化求解中步长选择的常见难题这份资源围绕Wolfe条件与线性搜索方法展开面向机器学习参数调优、数值优化和工程最优设计等应用场景适合具备一定优化基础的读者用于加深理解。压缩包共三个文件总大小约2KB包含两份MATLAB脚本以及一个macOS系统辅助文件脚本负责实现Wolfe条件的判定逻辑可供单机实验和二次修改。已有420人浏览学习。虽然包体极小但核心代码密度高读者可对比定步长与自适应步长的行为差异理解Armijo回溯、黄金分割等一维搜索策略与Wolfe准则的配合方式掌握非精确线性搜索中步长调节的关键技巧。通过修改参数能直观复现弱Wolfe与强Wolfe条件的判别过程观察不同准则对收敛路径的影响。这些能力对后续处理复杂目标函数、训练模型或工程优化设计都有直接借鉴价值脚本结构简洁便于扩展。1. 步长搜索优化算法里最容易被当成黑匣子的那个旋钮做优化工程的人几乎都遇到过这种场景梯度下降的方向明明是对的迭代却绕圈甚至发散换一个学习率又慢得像爬坡。问题往往不在方向而在搜索步长步长搜索。线性搜索方法linear search解决的就是“沿已知方向走多远才算合适”这个问题而 Wolfe 条件wolfe condition是其中应用最广的步长验收标准也是 L-BFGS、牛顿法实现里的标准部件。定步长搜索看起来省心实际上把每一次迭代的成败押在运气上。这篇笔记从定步长的数学边界讲起落到可复现的回溯与两阶段 Wolfe 实现并给出参数设置、方向协同和真实踩坑记录。适合正在调优化算法、被发散或龟速收敛折磨的从业者。2. 从定步长到Wolfe条件先搞清楚步长到底在解什么数学问题2.1 定步长搜索的数学边界为什么固定步长常让人翻车定步长搜索是最朴素但也是翻车率最高的方案每次迭代沿方向 d 走固定长度 α即 x_{k1} x_k α d_k。对梯度下降d -∇f在二次型目标 f(x) 0.5 x^T A x - b^T x 上迭代格式变成 x_{k1} (I - αA)x_k αb。收敛的充要条件是迭代矩阵谱半径小于 1即 0 α 2/λ_max(A)。这个上界看起来宽问题是实际收敛速度还受小特征值端控制。设 λ_max 1、λ_min 0.001条件数 1000。α 若取 1.9高频分量大特征值方向衰减很快但低频分量沿 1 - 0.0019 ≈ 0.998 的因子慢慢磨每 350 步才能把误差降一个量级α 若保守取 0.01低频分量衰减因子 0.99同样很慢高频分量也几乎不变。这就是定步长的两难想利用大步长加速就必须精确知道 Hessian 谱上界不知道谱信息时任何固定值都在“发散”和“龟速”之间赌博。我在工程里见过不少把 α 设为 0.01 后训练曲线平坦如直线的案例根因就在这。所以定步长真正适用的场景很窄。第一种是目标函数接近二次型且谱半径有可靠估计比如正则化最小二乘、直接线性系统求解第二种是函数求值极贵、不值得每步做线搜索但仍要先跑几轮确定 α 的量级第三种是随机优化——SGD 的“学习率”本质是定步长但它面对的是随机梯度噪声步长调度逻辑由统计量驱动和确定性非线性优化完全不同不能拿 Wolfe 条件往 SGD 上硬套。我自己写迭代算法时如果暂时不想接更完整的线搜索也会把步长取 1/||g|| 而不是拍脑袋定 0.01至少它在方向幅值变化时能自动缩放稳一点。2.2 Armijo条件与曲率条件Wolfe准则到底在约束什么Wolfe 条件由两条不等式组成。设当前点 x、下降方向 p要求 p^T ∇f(x) 0、候选步长 α 0Armijo 条件充分下降条件写为f(x αp) ≤ f(x) c1 α p^T ∇f(x)其中 c1 ∈ (0,1)。它要求函数值的实际下降量至少达到“按起点方向导数线性外推”的下降量的 c1 倍。c1 工程上常取 1e-4故意取得很小目的是把标准放宽到“只要没明显上升”都算通过真正筛选交给曲率条件。如果把 c1 设成 0.5 甚至 0.9条件会苛刻到大量有效步长被拒绝回溯要缩很多轮函数求值次数暴涨。几何上右端项是一条过 (0, f(x)) 的直线Armijo 条件要求点 (α, f(xαp)) 落在这条线下方。曲率条件分为弱、强两种。弱形式为∇f(xαp)^T p ≥ c2 ∇f(x)^T pc2 ∈ (c1, 1)强形式为|∇f(xαp)^T p| ≤ c2 |∇f(x)^T p|。由于 p 是下降方向起点处 p^T∇f 0弱形式的意思是候选点处的方向导数不再像起点那样大幅为负即已经把大部分下降潜力用尽步长不会过小强形式进一步要求它落在起点方向导数的 c2 比例邻域内避免停在弯曲剧烈的区域。这两个条件合起来就是文献里常说的 Wolfe 步长它保证可接受步长存在只要函数有下界且方向下降就存在一个非空区间同时满足两式。一个容易被新手忽略的点是线搜索的目标从来不是找一维极小值。精确线搜索需要解 min_α f(xαp)每步成本极高而且对拟牛顿法的收敛性并非必要。Wolfe 是典型的非精确判据用两次廉价不等式换来了全局收敛保证。配合 Zoutendijk 条件可以证明在合理假设下梯度范数趋于 0。这个区分值回票价如果你写的求解器每次线搜索要几十次函数求值停下来想想是不是在偷偷做精确搜索。2.3 强Wolfe与弱Wolfe分别用在什么地方强、弱两种形态不是越严越好选错会让收敛变慢甚至破坏算法结构。我一般按下面这张选型表决策。方法推荐准则理由牛顿法、信赖域法弱Wolfe 即可牛顿方向自带二阶信息曲率条件通常自然满足L-BFGS、非线性共轭梯度强Wolfe防止步长落在过度弯曲区域保持近似 Hessian 正定非光滑、噪声目标仅 Armijo 回溯曲率条件在不可微/含噪点没有稳定定义深度学习训练通常不用线搜索随机梯度噪声使 Wolfe 判决本身失效牛顿方向上的一个实操细节是单位步长α1是二次收敛证明的前提所以线搜索应以 α_init1 起步。如果强Wolfe 在 α1 被拒通常是 Hessian 近似或方向有问题而不是“步长该更小”。非线性共轭梯度则相反它需要的是“次优但稳定”的步长c2 取 0.9 时步长太接近精确极值后续共轭方向与老方向趋于近似相关频繁重启动c2 取 0.1 时步长保留一点余量方向组合才稳定。这套选型做完线搜索就从“玄学”变成了有明确验收标准的数值过程。3. 手写满足Wolfe条件的线搜索回溯法与插值的落地代码3.1 最小可用的回溯法实现Armijo条件的代码骨架先给一个可以立刻放进梯度下降里的回溯实现。它只验 Armijo 条件适合作为第一版跑通也适合非光滑或噪声问题。import numpy as np def backtracking_armijo(f, x, d, f0, g0, c11e-4, rho0.5, alpha_init1.0, max_iter64): # 方向预检不是下降方向直接拒绝 dphi0 np.dot(g0, d) if dphi0 0: return None, 0 alpha alpha_init for i in range(max_iter): f_new f(x alpha * d) # 非有限值直接缩步长不要参与比较 if not np.isfinite(f_new): alpha * rho continue # Armijo 充分下降条件 if f_new f0 c1 * alpha * dphi0: return alpha, i alpha * rho return alpha, max_iter逻辑说明每一步把 α 等比收缩直到 Armijo 条件成立。三个设计点值得强调。第一dphi0 ⟨g0, d⟩ 必须预先检查否则方向可能根本不在下降方向条件右端反而是正的迭代会走上坡路。第二NaN/Inf 要先处理因为 f_new f0 ... 遇到 NaN 恒为 False代码会一路缩到 max_iter 然后返回一个几乎为 0 的步长这个步长既不给下降也不给报错排查时非常隐蔽。第三把 f0、g0 从外面传进来避免线搜索内部重复计算起点函数值和梯度在真实迭代里起点处的 g 是上一轮就必须算好的传进来等于白省一次梯度求值。参数说明c11e-4放宽到 1e-3 可以容忍更多噪声目标但不要超过 0.1rho0.5 保守0.8 高效但容易在振荡函数上来回弹max_iter64 给了从 1 到约 1e-19 的动态范围绝大多数问题用不了这么深。3.2 用二次插值加速回溯少算函数值多走一步纯等比回溯每次只用当前拒绝点一个信息效率偏低。常见加速做法是在发现 α_cur 不满足 Armijo 时用起点信息加当前点做二次插值一步跳到近似极小位置再继续验。实现如下。def quadratic_interp_step(alpha_cur, f_cur, alpha_prev, f_prev, dphi0): # 用三个信息拟合二次函数f(0), f(0) dphi0, f(alpha_cur) denom 2.0 * (f_cur - f_prev - dphi0 * alpha_cur) if not np.isfinite(denom) or abs(denom) 1e-12: return alpha_cur * 0.5 alpha_new - (dphi0 * alpha_cur ** 2) / denom # 限制候选步长在 [0.1, 0.9]*alpha_cur 内防止插值失效乱跳 lo 0.1 * alpha_cur hi 0.9 * alpha_cur return min(max(alpha_new, lo), hi)逻辑说明一维函数 φ(α) f(x αd) 在 α0 处的值、导数和当前点的值共三个条件一个二次多项式刚好由这三个点唯一确定其极小点就是上面的闭式解。分母恰好是二次项系数的两倍若接近 0 说明三点近似共线此时插值模型失效退回 0.5 倍收缩。把新步长限制在原步长的 10%~90% 范围内是有意的否则当数值误差把极小点算到负值或远超当前步长时回溯流程会被拉飞到无意义区间。我把插值步接进回溯时循环里始终维护两个候选alpha_prev 是最近一个满足 Armijo 的点最初就是起点 0alpha_cur 是当前被拒绝的点。插值出的新 alpha 落在两者之间天然具备括号性质。在光滑函数上这种“回溯插值”通常 3~5 次函数求值就能收敛而纯等比回溯要 10~20 次。求值成本高的问题里差值就是省下的真金白银。3.3 c1、c2、rho、max_iter 四个必调参数取值区间与调参方向线搜索 90% 的调参都落在这四个参数上。把它们摆成参数表参数经典取值作用调参建议c11e-4充分下降的斜率比例噪声大时升到 1e-3别超过 0.1c20.9牛顿/ 0.1拟牛顿曲率条件的松弛度拟牛顿/共轭梯度用 0.1~0.3rho0.5回溯收缩因子求值便宜用 0.5求值贵用 0.8max_iter32~64收缩动态范围上限动态范围约等于 rho^max_iter需要注意的取值范围约束是 0 c1 c2 1Armijo 中的斜率必须比曲率条件上限的斜率更陡否则两个条件互斥永远找不到步长。我看到过把 c1 设 0.1、c2 设 0.05 的代码线搜索直接进入死循环就是违反了这个顺序。另一个容易被忽视的点是 rho 与 max_iter 的耦合max_iter 决定的是动态范围而不是“最大尝试次数”。rho0.5、max_iter32 可以缩到约 2e-10rho0.8 只能缩到约 8e-4。如果问题的最小有效步长在 1e-6 量级rho0.8 配 32 次迭代根本不够必须把 max_iter 加到 80 以上或者直接上插值而不是干等等比收缩。提示回溯法只检查 Armijo 条件严格说它不是完整的 Wolfe 线搜索。若你的求解器说明书里写“采用 Wolfe 条件”一般指两个条件都要验证只满足 Armijo 的实现会被对数值优化熟悉的人一眼看出偷工减料。完整两阶段 Wolfe 搜索的轮廓是这样的bracketing 阶段从 α_init 出发若当前 α 满足 Armijo 则尝试放大×3一旦出现函数值上升或曲率条件满足就得到包含可接受步长的括号区间 [lo, hi]zoom 阶段在区间内做二次/三次插值缩小区间直到两个条件同时满足。Nocedal 的 Numerical Optimization 里有完整伪代码。用 zoom 代替无脑回溯对 L-BFGS 这类依赖曲率信息的算法是必要的但普通梯度下降用回溯加插值已经够好。4. 把Wolfe线搜索接进梯度下降与拟牛顿法接口设计与方向协同4.1 线搜索函数接口设计f0、g0 传参与非下降方向回退把线搜索接进真实迭代循环的第一步是设计好接口。最常见的错误是每步重复求起点函数值与梯度以及非下降方向直接抛异常把求解器打死。我一般用下面的接口。def line_search_wrapper(f, g, x, d, f0, g0, alpha_init, c11e-4, c20.9, max_iter64): dphi0 np.dot(g0, d) if dphi0 0: # 非下降方向交给上层回退不在此处硬抛异常 return None # 这里是实现细节3.1 的回溯或带 zoom 的强Wolfe alpha strong_wolfe(f, x, d, f0, g0, alpha_init, c1, c2, max_iter) return alpha逻辑说明返回 None 表示“这一步没有合法步长”上层收到后回退到负梯度方向 -g0 再调用一次。这个策略在处理非光滑问题和数值梯度误差时非常管用能避免求解器死在一个坏方向上。f0、g0 传参则是性能关键起点梯度是上一轮迭代结束时就已知的量线搜索里再算一次等于翻倍梯度开销。strong_wolfe 内部要做多次候选点函数值与梯度求值但起点处的求值只做一次让调用方与线搜索共享。在迭代主循环里接法通常是x x0.copy() alpha 1.0 for it in range(max_iter): gk g(x) if np.linalg.norm(gk) tol: break d -gk # 或 L-BFGS 计算出的方向 alpha line_search_wrapper(f, g, x, d, f(x), gk, alpha, ...) if alpha is None: d -gk alpha line_search_wrapper(f, g, x, d, f(x), gk, 1.0, ...) x x alpha * d这里把上一次的 alpha 作为下一次的 alpha_init就是典型的“JIT 步长复用”平滑目标上连续迭代的步长往往在一个量级这个方法能让相当一部分迭代在 0~1 次回溯内结束整体提速明显。注意回退路径也走同一套线搜索而不是直接走固定步长保证回退后的方向同样被验收。4.2 拟牛顿方向为什么要配强Wolfec20.1 与矩阵正定性的微妙关系拟牛顿法L-BFGS、BFGS对步长的要求比梯度下降苛刻得多。核心机制在 BFGS 的曲率修正公式里有一个除法项 y^T s其中 s x_new - x_old αpy g_new - g_old。若 α 过大、步长越过目标函数的弯曲极值y^T s 可能接近 0 甚至变号曲率修正直接失效矩阵正定性被破坏。强Wolfe 的曲率条件正是用来阻止这种越界它要求 |g_new^T p| 足够小等价于让步长停在一个“方向导数已经很小”的地方保证 y^T s 为正。实操里把 c2 从 0.9 调到 0.1 的效果非常明显。我曾在病态二次型上做过对比c20.9 时 L-BFGS 收敛到目标精度需要约 150 次迭代且过程中多次因步长异常触发重启动c20.1 时约 40 次中间不需要任何干预。原因是 0.9 让线搜索结果逼近精确一维极值而精确极值在病态问题里几乎总是落在曲率符号即将翻转的位置。非线性共轭梯度同理次优步长反而让方向组合更稳定重启动频率显著降低。这里有个反直觉结论值得记住对拟牛顿/共轭梯度方向“走不满”的步长往往比“走到极值”的步长更好。不要一看到步长偏小就怀疑线搜索实现有问题先看 c2 设的是多少。4.3 初始步长的三个经验法则牛顿单位步、梯度下降动态步、步长复用初始步长 α_init 的设定直接决定回溯和 zoom 阶段的工作量。有三种常见做法按场景取舍。第一种是牛顿类方向固定 α_init1。单位步长是二次收敛的前提线搜索只用来在异常时做纠正正常迭代应尽量在 α1 处一次性通过。若 α1 频繁被拒检查 Hessian 近似或梯度是否正确而不是调小初始步长掩耳盗铃。第二种是梯度下降用动态步长。常见做法有 α_init 1/||g||或者按问题尺度发一个粗略估计如特征值上界的倒数。相比固定 0.01动态步长至少能跟随梯度幅值变化把回溯次数控制在个位数。第三种是步长复用JIT把上一轮线搜索返回的 α 乘 1.5~2 作为本轮初始步长。对光滑函数相邻两步的有效步长变化是连续的这一招能省掉大量回溯迭代。注意非光滑目标上不要用因为步长会在不同区域之间跳变复用反而引入振荡。与上述场景对照还有一个常见误用把深度学习里的学习率退火和线搜索混为一谈。Adam 的“自适应学习率”是对每个参数做对角缩放只用了梯度的一阶矩和二阶矩统计量不涉及任何函数值验收和 Wolfe 线搜索解决的问题完全不是一回事。把 Adam 的步长换成一个常数然后宣称“定步长搜索”是两码事。5. 步长搜索避坑指南5个让人头皮发麻的常见问题线搜索的坑往往不在数学而在工程数值误差、NaN、方向失效、随机噪声每一个都能让精心写的回溯在真实数据上翻车。下面 5 条是我在实际求解器中反复踩到的类型按现象、原因、解决的顺序记录。5.1 数值梯度让Wolfe误判条件满足但函数就是不降现象目标函数没有解析梯度用一阶有限差分近似。线搜索每次都能返回合规步长但整体迭代十几轮后函数值不降反升日志里看不到任何异常。原因差分梯度有 O(ε) 的误差当步长缩小到与误差同量级时Armijo 右端项被误差污染更糟的是梯度方向本身偏离真实下降方向线搜索在为一个错误方向做验收条件满足也没有意义。解决能换解析梯度就换解析梯度只能差分时用中心差分误差 O(ε²)并给差分步长做两轮试验如 1e-6 和 1e-7 对比目标若含随机噪声直接把 c1 放宽到 1e-3 或干脆放弃 Wolfe 改用定步长加动量。我调一个蒙特卡洛参数拟合问题时连续几天都在折腾 c1、c2最后发现罪魁是差分步长设太大梯度方向被噪声带偏了几度这一度之差在病态问题上被放大得不成比例。5.2 步长冲进NaN区域回溯救不回来的死循环现象目标函数里带 log、sqrt 或除法候选步长把参数带进无定义域函数返回 NaN回溯循环里的比较恒为 False步长持续收缩但 f_new 一直是 NaN最终返回一个趋近于 0 的垃圾步长或直接抛错。原因NaN 与任何数比较都返回 False这是 IEEE 754 的硬规则同时目标函数本身没有做定义域裁剪候选点的计算路径已经崩了。解决第一步在比较前判断 np.isfinite(f_new)不是有限值就按 rho 收缩并继续第二步在目标函数内部对定义域做裁剪或投影让函数永远返回可比较的有限值。更狠的做法是让越界点直接返回 1e20 这样的惩罚值引导线搜索快速退回合法区域。带 log 的似然函数我两步都会做先投影参数再对投影后的点算函数值双保险。没有这两步回溯法会在 NaN 上白耗几十次求值。5.3 方向导数不下降线搜索本身没错但方向已经是上升方向现象dphi0 ⟨g, d⟩ ≥ 0线搜索拒绝方向并返回 None上层如果直接终止迭代停在原地日志里梯度范数却还很大。原因数值梯度误差把方向带偏梯度和方向的内积接近 0 时最容易翻号或者 Hessian 近似长期不正定牛顿方向从下降变成上升非光滑目标在不可微点并没有方向导数概念。解决把“非下降方向”当作正常返回 None 而不是异常上层统一回退到负梯度方向再走一次线搜索。这个策略我几乎在每个求解器里都保留它把数值误差的影响降了一档。如果回退后仍频繁失败优先检查梯度的有限差分步长和 Hessian 修正是否在强制正定化而不是线搜索逻辑本身。5.4 把Armijo回溯当Wolfe用拟牛顿法的隐性翻车点现象求解器里用的是 3.1 节的回溯实现文档却写着“Wolfe 线搜索”跑 L-BFGS 时局部收敛速度忽快忽慢多次更换初值后行为不稳定。原因回溯只保证 Armijo充分下降不检查曲率条件。对梯度下降这通常够用但对 L-BFGS步长过小会让 s 和 y 的模长都接近 0曲率修正项 y^T s 失去代表性矩阵近似长期得不到有效更新表现为收敛拖尾和初值敏感。解决严格实现 bracketing zoom 的两阶段 Wolfe 搜索曲率条件必须显式验证求值成本可接受时宁可多几次函数求值也要保住拟牛顿方向的曲率信息。回头看自己代码时最直接的信号是线搜索返回的步长经常比 1e-4 还小而梯度范数仍远大于停机阈值——这种情况十有八九是曲率条件缺失。5.5 把Wolfe条件用在随机目标上每一步都在跟噪声赌博现象强化学习策略梯度或随机仿真优化里照搬 L-BFGS 加 Wolfe 线搜索步长输出剧烈抖动训练曲线发散偶尔还出现“条件满足但目标值暴涨”的现象。原因随机梯度是随机变量函数值也带噪声Armijo 和曲率条件比较的是两个含噪声的量等价于用噪声做阈值判决。线搜索在随机目标上没有可验证性c1、c2 调再小都没用。解决这类场景不用线搜索用固定步长加学习率调度或用 Adam 这类基于梯度统计量的自适应方法如果一定要在随机目标上走线搜索比如做对照实验至少用一批样本的平均函数值和平均梯度来降方差并把 c1 放宽、把 max_iter 调大。这条和 5.1 同源但更极端5.1 是梯度带误差这里连函数值都是噪声Wolfe 的整个验收前提都没了。6. 把每一步的搜索步长记下来验证实现与调参的实操技巧线搜索实现得对不对不需要看证明看一组日志就够。我在迭代循环里固定记录四样东西每步返回的 α、线搜索内的函数求值次数、是否触发 max_iter、以及 Armijo 条件拒绝率。对二次函数做验证时正确的 Wolfe 线搜索配合牛顿方向α 应稳定在 1 附近梯度范数以二次率下降如果 α 频繁掉到 0.01 以下方向、梯度或曲率条件三者中必有一个出错。对 Rosenbrock 这类非凸函数α 前期应平滑下降、后期回升若它全程贴住下界多半是 c2 设得过大或初始步长得重新考虑。调参顺序我建议固定下来第一步在二次型上用 c11e-4、c20.9 的强Wolfe 跑通确认 α≈1第二步换成 L-BFGS 方向把 c2 调到 0.1观察 α 是否仍然稳定第三步接入真实问题盯拒绝率——若拒绝率长期超过 50%考虑把 rho 从 0.5 调到 0.8 并配上二次插值通常能把平均函数求值次数压回个位数。拒绝率本身是很好的健康指标健康线搜索的拒绝率应在 10%~30% 之间过高说明方向与步长不匹配过低说明初始步长太保守把每一次迭代都变成了接近精确线搜索浪费求值。我自己的教训是很长一段时间把步长搜索当黑匣子方向用 L-BFGS 却把 c2 设成 0.9收敛慢到怀疑人生。后来闲下来把每一步的 α 打出来才发现拒绝率接近 70%大量迭代都消耗在回溯里。从那以后每接一个新优化问题我都先花半小时跑最小验证例把 α 序列拉出来看一眼再谈调参。这个习惯让我少走很多弯路希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑