首页 / 资讯中心 / 文章详情

FARO优化器:收益—风险约束下的自适应更新机制与实操调参

FARO优化器:收益—风险约束下的自适应更新机制与实操调参 ★ FEATURED ARTICLE
1. 从梯度下降到决策视角为什么要把优化器当成投资组合来管做深度学习训练的人都有一个共同的体感模型能不能收敛、收敛得快不快、最终泛化好不好很大程度上不取决于网络结构有多花哨而取决于优化器在参数空间里怎么走。SGD、Momentum、Adam、AdamW、LAMB这一路演进下来本质上都在回答同一个问题——当前这个梯度信号我该信多少该走多远。但绝大多数优化器的设计思路是信号处理式的把梯度看成带噪声的观测用滑动平均、二阶矩估计、偏差校正去滤波。这套思路很有效但它有个隐含假设——所有参数、所有时刻的更新风险是均等的。FARO换了个视角。它把神经网络的参数更新建模成一个收益—风险约束下的自适应决策问题每一步更新既想拿到损失下降带来的收益又要控制这一步更新可能带来的风险比如更新过猛导致loss spike、或者方向偏差导致泛化变差。这个思路其实和金融里的投资组合优化高度同构——你手里有一堆资产参数维度/参数组每个资产有预期收益梯度方向上的下降潜力和风险更新方差、曲率、历史波动你要在风险预算约束下分配更新幅度。我第一次看到这个建模方式的时候第一反应是这不就是把优化器当成了一个带风险约束的控制器吗。确实如此。FARO的核心贡献不在于发明了某个新的动量公式而在于把更新多少这件事从启发式超参变成了一个带约束的优化问题然后在线求解它。这就解释了为什么标题里强调收益—风险约束和自适应更新——前者是建模框架后者是求解结果。这篇文章我打算按我自己复现和调参的顺序来讲先拆清楚FARO到底在优化什么目标再讲它的自适应更新是怎么在线算出来的然后是实操层面的参数配置和踩坑记录最后是排查技巧。适合已经写过训练循环、调过Adam/AdamW、想进一步理解优化器还能怎么设计的读者。如果你只是想让模型跑起来AdamW够用但如果你想搞清楚优化器背后的决策逻辑或者你的任务对训练稳定性极其敏感比如大batch、长序列、强化学习里的策略更新FARO这套思路值得细看。2. FARO的核心建模收益项、风险项与约束到底怎么定义2.1 收益项不是loss本身而是这一步更新能带来多少下降很多人一看到收益就以为是loss的下降量其实在FARO的建模里收益项更接近一阶近似下的预期下降。给定当前参数θ和梯度g如果沿方向d走一步一阶泰勒展开给出的损失变化是 gᵀd。我们要最大化下降就是最大化 -gᵀd也就是让更新方向和负梯度方向尽量对齐、步长尽量大。但这里有个关键细节FARO不会天真地直接用原始梯度g而是用一个平滑后的梯度估计ĝ 作为收益方向的依据。原因很直接——单batch梯度噪声太大直接拿它当预期收益会导致更新方向抖动剧烈。这和金融里用历史均值估计预期收益是一个道理你不能拿某一天的涨跌当长期预期。所以收益项可以写成R(d) -ĝᵀd我们要最小化R(d)也就是让更新方向d尽可能沿着-ĝ。注意这里d是待求的更新向量不是梯度本身。这一点是理解FARO的钥匙它求解的是更新量而不是梯度。2.2 风险项更新幅度、方向方差与曲率的综合度量风险项是FARO区别于普通优化器的地方。普通SGD只关心方向对不对Adam额外关心每个维度的梯度尺度但都没有显式地给这一步更新可能带来的不确定性建模。FARO的风险项通常包含三块更新幅度风险d的范数越大单步扰动越大越容易跳出当前loss盆地。这一项一般用 ‖d‖² 的某种加权形式权重可以是逐参数的。方向方差风险如果某个参数维度的梯度历史波动很大说明这个方向上的预期收益不可靠应该降低更新幅度。这对应金融里的方差惩罚。曲率风险用Hessian近似或者梯度变化率来估计局部曲率曲率大的方向走大步容易震荡。把这三块合起来风险项可以抽象成 dᵀΣd 的形式其中Σ是一个正定的风险度量矩阵。Σ的构造方式决定了FARO的具体变体——有的用对角近似类似Adam的二阶矩有的用分块近似有的用梯度外积的滑动平均。提示Σ必须是正定的否则整个约束优化问题会失去凸性求解会不稳定。实操中一般加一个小的对角正则项 εI 来保证。2.3 约束风险预算怎么定为什么不能拍脑袋有了收益和风险接下来就是约束。FARO的约束形式通常是dᵀΣd ≤ ρρ就是风险预算。这个ρ怎么定是整个方法里最需要经验的地方。ρ太小更新被压得死死的收敛慢ρ太大约束形同虚设退化成普通SGD。我自己的经验是ρ不应该设成固定常数而应该和当前梯度的尺度挂钩。一个比较稳的做法是让ρ正比于 ‖ĝ‖² 的滑动平均这样在梯度大的阶段允许更大的更新在梯度小的阶段自动收紧。这其实和Adam的自适应步长有异曲同工之妙但FARO是从约束角度推出来的而不是从二阶矩估计推出来的。从拉格朗日视角看这个约束优化问题的解有一个很漂亮的形式d (Σ λI)⁻¹ ĝ其中λ是拉格朗日乘子由约束是否紧binding决定。如果约束不紧λ0解就是Σ⁻¹ĝ相当于在风险度量下的最速下降如果约束紧λ0更新被缩放。这个形式解释了为什么FARO是自适应的——λ会随着梯度尺度和风险预算的变化自动调整不需要人工设学习率。学习率的作用被λ和Σ吸收了。2.4 为什么这个建模比Adam更讲道理Adam的自适应来自二阶矩估计 v β₂v (1-β₂)g²然后用 g/√v 做归一化。这个操作很有效但它的理论解释一直有点绕——你可以说它在做逐参数的步长缩放但为什么是g/√v而不是别的并没有一个统一的决策框架。FARO的建模把这件事讲清楚了g/√v 本质上是在对角风险度量Σdiag(v)下的约束解。当Σ是对角阵时(ΣλI)⁻¹ĝ 的第i个分量就是 ĝᵢ/(vᵢλ)如果λ相对vᵢ很小就退化成 ĝᵢ/vᵢ也就是Adam的归一化形式。换句话说Adam是FARO在对角风险、无约束或约束极松情况下的特例。这个视角一旦建立你就能理解为什么Adam在某些任务上会失效——因为它的风险度量太简单只有对角而且没有显式的风险预算控制。3. 自适应更新的在线求解从拉格朗日乘子到可落地的迭代格式3.1 拉格朗日乘子的求解二分法还是近似闭式理论上给定ĝ和Σ求解λ就是找一个值使得 dᵀΣd ρ。这是一个一维求根问题可以用二分法或者牛顿法。但问题是每一步训练都要解一次如果Σ是完整矩阵求逆的代价是O(n³)对百万级参数根本不现实。所以实操中必须做近似。常见的做法有两类对角近似Σ取对角那么 dᵢ ĝᵢ/(σᵢλ)约束变成 Σ ĝᵢ²σᵢ/(σᵢλ)² ρ。这仍然是一维求根但每步只需要O(n)计算完全可接受。分块近似把参数按层或按模块分块每块内部用低秩近似块间独立。这样既保留了一部分曲率信息又把复杂度压到可控范围。我实测下来对角近似在大多数CV和NLP任务上已经够用分块近似主要在参数量极大或者层间耦合很强的场景比如深层Transformer才有明显收益。3.2 一个可复现的对角FARO更新流程下面是我自己实现时用的流程用PyTorch风格伪代码写方便你直接对照# 初始化 m 0 # 收益方向的一阶滑动平均 v 0 # 对角风险度量 rho_ema 0 # 风险预算的滑动平均 beta1, beta2 0.9, 0.999 eps 1e-8 for step, batch in enumerate(loader): loss model(batch) loss.backward() for p in model.parameters(): g p.grad # 1. 更新收益方向估计 m beta1 * m (1 - beta1) * g # 2. 更新对角风险度量 v beta2 * v (1 - beta2) * (g * g) # 3. 更新风险预算正比于梯度尺度 rho (m * m).sum() * rho_scale rho_ema beta2 * rho_ema (1 - beta2) * rho # 4. 二分法求lambda lam solve_lambda(m, v, rho_ema, eps) # 5. 计算更新量 d m / (v.sqrt() lam eps) # 6. 应用更新 p.data.add_(-d)这段代码里最关键的是solve_lambda。它的输入是m、v、rho输出一个标量λ使得 Σ mᵢ²vᵢ/(√vᵢλ)² ≈ rho。二分法的上下界可以这样定下界0上界取 max(‖m‖/√rho, 某个安全值)。一般迭代20次以内就能收敛到足够精度。注意这里的v.sqrt()是对角风险的开方对应Σdiag(v)时的σᵢ。如果你用的是完整Σ这一步要换成Cholesky分解或者共轭梯度求解复杂度会上去但精度更高。3.3 风险预算的缩放系数rho_scale怎么调rho_scale是FARO里最像学习率的超参。它控制风险预算相对于梯度尺度的比例。我的经验值任务类型rho_scale建议范围说明小模型CV分类0.5 ~ 2.0梯度尺度稳定可以稍大深层Transformer0.1 ~ 0.5梯度尺度波动大需要收紧强化学习策略0.05 ~ 0.2策略更新对风险极敏感微调预训练模型0.2 ~ 1.0取决于微调数据量这个表只是起点实际还要看训练曲线的稳定性。如果loss在前几百步就剧烈震荡说明rho_scale太大如果loss下降极慢且梯度范数一直很小说明rho_scale太小。3.4 和AdamW的权重衰减怎么共存FARO本身不处理权重衰减但实操中几乎一定要加。我的做法是把权重衰减作为独立的更新项和FARO算出的d解耦p.data.add_(-d - weight_decay * p.data)这样做的理由是权重衰减是正则化不是收益—风险框架里的东西混进去会污染风险度量的估计。AdamW之所以把权重衰减从梯度里拿出来也是同样的道理。FARO和AdamW在这一点上是一致的。4. 实操配置与调参从零复现FARO的完整步骤4.1 环境与依赖准备FARO本身不需要特殊依赖纯PyTorch就能实现。但如果你想用分块近似或者完整Σ建议装torch.linalg相关的工具或者用scipy做小规模验证。我的环境是PyTorch 2.0NumPy 1.24可选einops用于张量重排方便分块不需要CUDA特殊版本FARO的计算都是逐元素或者小矩阵操作对GPU算子没有额外要求。4.2 参数初始化与预热策略FARO在训练最开始几步会不稳定因为m和v都还是零rho_ema也没建立起来。我的做法是前100~200步用AdamW预热之后再切FARO。这个预热窗口不是随便定的——它要足够长让v的滑动平均覆盖至少几个batch的梯度分布但又不能太长否则FARO的自适应优势体现不出来。预热切换的时候有个细节m和v要从AdamW的状态里继承过来而不是重新初始化。这样切换瞬间的更新量不会突变。具体做法是把AdamW的exp_avg和exp_avg_sq直接赋给FARO的m和vrho_ema用当前步的梯度尺度初始化。4.3 完整训练循环的配置示例下面是一个可以直接抄的配置以Transformer微调为例config { optimizer: FARO, rho_scale: 0.3, beta1: 0.9, beta2: 0.999, eps: 1e-8, weight_decay: 0.01, warmup_steps: 200, warmup_optimizer: AdamW, warmup_lr: 1e-4, lambda_solver: bisection, lambda_iters: 20, grad_clip: 1.0, }注意这里还有grad_clip。虽然FARO自带风险约束理论上不需要梯度裁剪但实操中我建议保留一个宽松的裁剪比如1.0防止极端batch导致的梯度爆炸把v的估计带偏。这算是工程上的保险不是FARO本身的要求。4.4 训练过程中的监控指标跑FARO的时候光看loss是不够的。我一般会记录这几个量lambda值如果λ长期接近0说明约束没起作用rho_scale可能太大如果λ一直很大说明约束太紧收敛会慢。更新范数‖d‖和梯度范数‖g‖对比正常情况下‖d‖应该和‖g‖同量级或者略小。风险预算利用率dᵀΣd / ρ理想情况在0.8~1.0之间说明约束在起作用但没过度限制。v的分布如果某些维度的v异常大说明那些方向梯度噪声极大可能需要单独处理。这些指标用TensorBoard或者WandB记录训练几百步就能看出配置是否合理。4.5 一个真实的调参案例我在一个中等规模的文本分类任务上做过对比。基线是AdamWlr2e-5weight_decay0.01。FARO的初始配置是rho_scale1.0结果前500步loss震荡严重λ几乎一直是0说明约束没起作用。把rho_scale降到0.3之后λ稳定在0.01~0.05之间loss曲线明显平滑最终验证集准确率比AdamW高0.8个点。后来又试了rho_scale0.1结果λ太大训练变慢最终准确率和AdamW持平但收敛步数多了30%。所以0.3这个值是这个任务上的甜点。这个甜点因任务而异没有万能值必须自己扫。5. 常见问题与排查技巧实录5.1 loss突然飙升怎么办这是FARO实操中最常见的问题。原因通常有三个rho_scale设太大约束太松某一步更新过猛。排查方法是看飙升那一步的λ如果λ接近0基本可以确认。v估计滞后beta2太大v跟不上梯度分布的变化。可以临时把beta2降到0.99试试。梯度爆炸某个batch的梯度异常大把m和v都带偏了。这时候grad_clip就派上用场了。我的处理流程是先看λ再看v的分布最后看梯度范数。三步基本能定位。5.2 训练前期收敛比AdamW慢这通常是预热策略的问题。如果预热步数太少FARO的m和v还没稳定就切过来前期会浪费一些步数。我的建议是预热至少200步如果模型很大比如10B以上预热500步也不过分。另一个原因是rho_scale偏小约束太紧。可以适当放大rho_scale或者把lambda_iters从20降到10让λ的求解不那么精确反而能加快前期收敛。这个技巧有点反直觉但实测有效——前期不需要太精确的风险控制。5.3 显存占用比AdamW高FARO需要额外存rho_ema和λ但这两个都是标量显存开销可以忽略。真正占显存的是v它和AdamW的exp_avg_sq一样大。如果你用的是分块近似还会多存块内的低秩因子这时候显存会上去。如果显存紧张可以把v用fp16存计算的时候再转fp32。实测对最终精度影响很小但能省一半v的显存。5.4 常见问题速查表现象可能原因排查方法解决方式loss震荡rho_scale太大看λ是否接近0降低rho_scale收敛慢rho_scale太小看λ是否持续很大提高rho_scale前期不稳定预热不足看前100步的v增加预热步数显存高v用fp32看显存分布v改fp16更新方向抖动beta1太小看m的平滑度提高beta1到0.95λ求解不收敛二分上下界不对打印二分过程调整上界为‖m‖/√rho5.5 几个我踩过的坑第一个坑是把rho_scale当成学习率来调。一开始我按调lr的习惯从1e-3开始扫结果完全不对。rho_scale是无量纲的它的合理范围在0.05~2.0之间和lr不是一个量级。第二个坑是忘了同步更新rho_ema。有次我改了rho的计算方式但忘了改rho_ema的更新结果约束一直用的是旧的预算训练完全跑偏。这种bug很隐蔽因为loss不会立刻爆炸只是收敛变差。第三个坑是在分布式训练里各卡独立算λ。如果每张卡用自己的m和v算λ会导致各卡更新量不一致等效于引入了额外的噪声。正确做法是用all_reduce把m和v的平方和同步然后各卡算同一个λ。这个细节在单卡上完全看不出来一上多卡就出问题。6. 从FARO延伸出去这套建模还能怎么用FARO的收益—风险框架其实不限于优化器。我自己后来把这套思路用在了几个地方效果都不错。一个是学习率调度。传统调度是cosine或者step都是预设的。用FARO的思路可以把学习率看成风险预算ρ的函数ρ随训练进程自适应调整学习率就自动出来了。我试过一个简化版ρ正比于验证集loss的滑动方差结果在几个任务上都比cosine略好。另一个是梯度累积的权重分配。梯度累积时不同micro-batch的梯度重要性其实不一样。用收益—风险框架可以给每个micro-batch算一个权重让累积后的梯度在风险约束下最大化收益。这个思路在小batch大模型场景下特别有用。还有一个是多任务学习的损失加权。多任务里各个任务的loss尺度不同人工加权很痛苦。把每个任务的梯度看成一种资产用风险约束来分配更新幅度可以自动平衡各任务的训练速度。我试过一个两任务版本比人工调权重的效果好而且省了很多调参时间。这些延伸用法都还在实验阶段但核心思想是一致的把更新多少从启发式超参变成带约束的决策问题。这个视角一旦建立很多优化器设计的难题都会变得清晰。最后分享一个我在实际使用中的体会FARO不是银弹它在训练稳定性敏感的场景下优势明显但在小模型、简单任务上AdamW可能更省事。选不选FARO取决于你对训练稳定性的需求有多强以及你愿不愿意花时间调rho_scale。如果只是想让模型跑起来AdamW够用如果你在跟loss spike搏斗或者你的任务对更新风险极其敏感FARO值得一试。
阅读完成 · 觉得有帮助?
咨询建站