ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

双层优化统一视角:从超参数优化到元学习与视觉应用

双层优化统一视角:从超参数优化到元学习与视觉应用 双层优化Bi-Level OptimizationBLO这几年在机器学习、计算机视觉和强化学习里几乎成了一种隐藏标配。翻到那篇《Investigating Bi-Level Optimization for Learning and Vision from a Unified Perspective: A Survey》时我的第一反应是终于有人把散落在超参数优化、元学习、神经架构搜索、图像复原、数据增强策略这些方向里的共同数学骨架拿出来做了一次系统的盘点。这篇综述的核心观点其实很朴素——很多我们习以为常的训练流程本质上都是外层决定某种配置内层基于配置优化模型的两层嵌套结构只不过在不同任务里换了名字。这篇文章就按这条主线从问题动机、统一框架、求解算法一路聊到复现时我实际踩过的坑希望能帮你把 BLO 的知识点真正串起来。1. 双层优化到底在优化什么先理解嵌套决策1.1 从一个日常的调参流程说起想象你在调一个图像分类模型的权重衰减系数 λ。正常流程是给定 λ把训练集上的损失压到最低得到一组网络权重 w*(λ)再用验证集评估这组权重好不好。注意这里的顺序——λ 选得对不对完全取决于网络权重在 λ 之下训练出来之后的效果而不是某个静态指标。这个流程天然就是嵌套的内层是在固定 λ 时解一个训练问题外层是在内层问题的最优解之上做决策。双层优化的标准数学形式是min_{w∈W} F(w, v*(w))约束条件 v*(w) ∈ argmin_{v∈V} G(w, v)其中 G 是内层目标F 是外层目标。关键在于 v*(w) 不是普通变量而是内层问题对 w 的最优响应函数。在泛函分析里这叫值函数或反应函数在博弈论里这叫 Stackelberg 博弈在运筹学里这叫双层规划到了机器学习里它换了一堆马甲超参数优化、元学习、AutoML、可微架构搜索、数据增强策略搜索。名字不同骨架完全一样。我最早接触这个结构是早年做超参数网格搜索后来发现很多 paper 里的novel algorithm去掉包装后就是双层优化在不同场景下的实例化。理解这一点最大的好处是你不用为每个任务重新发明轮子求解方法是可以跨场景复用的。1.2 学习与视觉任务里的双层都藏在哪把常见任务映射成 BLO 之后画面会变得非常清晰。我整理了一张表基本覆盖了综述里讨论的主要场景任务场景上层变量 w下层变量 v上层目标 F下层目标 G超参数优化学习率、权重衰减、批大小网络权重验证集损失训练集损失Few-shot 元学习元参数初始权重各任务适配后权重跨任务期望损失单任务损失神经架构搜索 NAS架构参数 α网络权重 θ验证集损失训练集损失数据增强策略搜索增强策略分布模型权重验证集损失增广训练集损失图像复原/反问题正则化参数/先验参数复原图像重建误差数据保真项正则项对抗训练模型权重对抗扰动最坏情况损失扰动生成目标这张表里几个方向特别值得展开。元学习对应的是所谓learning to learn外层是学一个初始化内层是每个任务在自己数据上微调几步神经架构搜索则是把 Vision Transformer 这类网络的结构参数当作上层变量把网络权重当作下层变量至于像 HGFormer 里利用超图学习来建模拓扑结构本质上也可以理解为外层在学一个结构超参数内层在学这个结构下的表示参数。还有一个容易被忽略的是强化学习——元强化学习里 meta-reward 的设计、课程学习的课程参数选择都能套进同样的框架只是内层往往不可导求解难度更大。这种统一视角带来的直接收益是你在图像复原里验证过的算法稳定性技巧大概率可以迁移到超参数搜索上剩下的只是调参功夫。2. 为什么需要统一视角避开换汤不换药的坑2.1 统一框架的价值从算法命名混乱中解放出来我读综述类文章有一个习惯先看它有没有给出能覆盖大部分场景的统一定义再看它怎么处理不同方法之间的关系。这篇综述在这两点上都做得比较扎实。它没有把 BLO 局限于某一个应用而是把它定位成一个优化范式然后从五个维度去刻画一个双层问题上层变量类型连续/离散/混合、下层问题的性质凸/非凸、可微/不可微、上下层之间的信息流、是否允许内层多解乐观/悲观准则、以及求解时对梯度信息的需求。这套维度让我想起当年从传统 CNN 转到理解 Vision Transformer 和 CSPNet 这类骨干网络的过程——一开始每个新结构都像全新东西但看得多了会发现设计空间就那么几个轴感受野怎么扩大、通道怎么分组、信息怎么跨层流动。BLO 也是这样轴一旦清晰后面所有方法都只是在这个轴空间里取不同的点。统一视角还有一个很实际的好处方便复现和 debug。很多时候你跑一个元学习算法发现发散根本不知道是外层学习率问题、内层迭代不充分问题还是 Hessian 近似引入的噪声。但如果你知道它属于基于隐函数梯度的隐式方法就会第一时间去检查内层是否收敛到足够优的点因为这类方法的前提假设就是下层梯度为零。问题定位路径一下就短了。2.2 乐观准则与悲观准则一个容易被忽略的细节在真实的双层问题里内层问题未必只有唯一最优解。比如内层是一个高度非凸的深度网络训练问题给定不同的随机种子可能收敛到不同的局部极小值。这时v*(w) 究竟是哪一个解就会影响外层的梯度计算。综述里特别提到两个准则乐观准则optimistic和悲观准则pessimistic。乐观准则假设内层会选择对上层最有利的那个解这样上层会表现得非常激进但会导致梯度估计偏差很大悲观准则假设内层选择最不利于上层的解这在对抗博弈里其实是更真实的安全假设。大多数深度学习场景其实偷懒采用了乐观准则甚至没有意识到自己做了这个假设。一旦你发现了这个细节很多看起来 work 但有时候突然崩掉的现象就解释得通了——内层落入不同的局部极小值集合外层梯度估计的自然波动就变大了。2.3 与相邻概念的边界不是所有嵌套都叫 BLO还需要澄清一下边界。多任务学习里常见的联合损失 L L1 L2两个任务同时优化这不是 BLO因为两个问题之间是并行关系没有嵌套约束。强化学习里的 actor-critic 虽然有两个网络交替更新但 critic 并不以求解一个最优化问题的形式嵌套在 actor 的目标里通常也被视为交替优化而非严格的双层结构。只有当一个问题的解必须作为另一个问题的可行域或目标函数输入时才是严格意义的 BLO。还有人会把 BLO 和约束优化搞混。约束优化里 v 是决策变量同时受限于 g(v) ≤ 0BLO 里的约束却是v 必须是最优化另一个目标后的输出。换句话说BLO 的约束是一个隐式定义的函数关系这比普通约束复杂得多也是它难求解的根本原因。理解了这一点你再看那些把 BLO 简化为单层问题的做法就会明白它们在做怎样的近似。3. 求解方法怎么选梯度来源决定一切3.1 显式展开法Unrolling直观但吃内存求解 BLO 的第一类主流方法是把内层优化过程当成一个可以微分的计算图显式地展开若干步。比如内层做 k 步梯度下降就能近似 v*(w)那么外层就可以对展开后的 k 步操作反传梯度得到外层变量 w 的超梯度hypergradient。这类方法的代表是 MAML 及其变体它在 few-shot 学习里表现得非常稳定。核心公式可以这么理解内层每步更新 v_{t1} v_t - η ∇_v G(w, v_t)展开 k 步之后外层目标 F(w, v_k) 对 w 的梯度就可以通过链式法则直接求出。代码实现也直白PyTorch 里只需要把内层的前向过程用计算图保留下来。但它的代价也很明显内层展开多少步计算图就要保存多少步内存开销近似线性增长。我实测过在 ImageNet 规模的数据上跑带 5 步内层展开的 NAS 风格实验GPU 显存直接翻了两倍多。而且展开步数越多梯度回传的路径越长容易出现梯度消失或爆炸外层优化对内层学习率的敏感度也越高。3.2 隐函数梯度法Implicit Differentiation省内存但要求内层收敛第二类方法绕开了展开轨迹直接对最优点施加隐函数定理。假设内层收敛到稳定点有 ∇_v G(w, v*(w)) 0对 w 求导得到dv*/dw -[∇²_vv G]⁻¹ ∇²_vw G于是外层超梯度∇_w F ∂F/∂w ∂F/∂v · dv*/dw。关键就在于要算 Hessian 逆矩阵或者它和向量的乘积。实际工程里很少直接求逆而是用共轭梯度法、Neumann 级数展开或者干脆用有限差分近似 Hessian-vector product。这套方法最大的优点是内存开销和常数级不需要保存内层迭代轨迹只需要最终收敛点附近的 Hessian 信息。但代价是它要求内层问题真的收敛到足够好的稳定点否则隐函数定理的前提就不成立。换句话说这类方法对内层训练是否充分非常挑剔。我在超参数优化实验里就遇到过一种典型失败内层只训了 10 个 epoch 就拿来算隐式梯度结果外层梯度方向和真实梯度方向偏差很大η 怎么调都发散。后来把内层 epoch 加到 50问题立刻消失。3.3 单层化近似与无梯度方法处理不可导的内层问题还有一大类方法把双层问题转化为带约束的单层问题。常见的做法是引入 KKT 条件或者罚函数项把内层最优性条件作为约束加入外层。这类方法理论上严谨但约束数量大、数值处理麻烦在深度学习场景里用得不多更多出现在运筹学里的小规模问题中。另一个方向是彻底放弃梯度信息。当内层是一个不可导的目标时——比如网络量化里离散的位宽搜索、NAS 里离散的候选操作——梯度类方法就失效了。这时可以用进化算法、贝叶斯优化或者强化学习去搜索上层变量把内层当成一个黑盒评估器。综述里把这些方法统一归为无梯度类。这类方法能处理任意内层但样本效率低每次评估都要完整跑一遍内层训练成本极高。我把三类主流方法的特征整理成了对比表方法类别梯度来源内存成本关键假设典型场景显式展开Unrolling反传展开轨迹随展开步数线性增长内层可微、步数有限MAML、可微 NASDARTS 类隐式梯度Implicit隐函数定理常数级内层收敛到稳定点超参数优化、大规模元学习无梯度/单层化无梯度或约束改写取决于评估成本内层可黑盒评估离散 NAS、量化搜索、混合整数双层三者的选择原则我的经验是这样如果你的内层只有几步、模型不大用 unrolling 最省心因为它对收敛要求低、实现简单如果模型很大、显存紧张、内层又确实能训到接近收敛优先考虑 implicit如果内层本身不可导那就只能认命用黑盒搜索同时尽可能减少内层评估次数。实际项目里很多人是混着用的比如外层用 unrolling但对展开轨迹做截断每几步才做一次反向传播这本质上是在内存和梯度方差之间做权衡。4. 从理论到场景学习与视觉里的高价值应用拆解4.1 超参数优化与元学习BLO 最成熟的自留地超参数优化是理解 BLO 最自然的入口。传统的网格搜索把超参空间离散化暴力尝试贝叶斯优化把它当黑盒优化而 BLO 的视角是把它当可微优化问题直接用梯度下降更新 λ。这个思路十几年前就有人提但真正工程可用是近几年的事得益于自动微分框架的完善。元学习则是 BLO 在深度学习里最出圈的场景。MAML 的外层目标是初始权重在多个任务上微调后的平均损失内层是每个任务上若干步梯度更新。注意这里的微妙之处内层做几步更新其实是一个人为设定的近似展开步数越少v*(w) 的近似越粗糙但对应的是更快的训练速度。我在做 few-shot 图像分类实验时发现步数从 1 加到 5验证准确率会有明显提升但步数超过 10 之后收益就趋于饱和反而显存吃紧。这个经验同样适用于其他 unrolling 类方法。4.2 神经架构搜索与骨干网络设计结构也是上层变量NAS 里最著名的 DARTS 就是 BLO 的直接应用。它把候选操作卷积、池化、注意力等的权重 α 作为上层变量把网络权重 θ 作为下层变量两层交替更新。最初 DARTS 用的是很简单的 one-step 交替可以看作 BLO 的最朴素近似也因此会碰到上层梯度不稳定的问题后面一批改进工作本质上都指向如何更准确地估计超梯度。这让我想到一个更广的趋势当你设计 CSPNet 这类骨干网络或者给视觉 Transformer 选择合适的 token 交互策略时结构选择本身就带有双层色彩——你希望找到一个结构让在该结构下训练得到的网络在验证集上表现最好。超图学习里的 HGFormer 也是类似的逻辑在固定的表示学习任务之上外层还在学一个拓扑结构结构的好坏只有在内层表示参数收敛后才能评估。把这些统一看成 BLO就能复用前面讲的所有求解技巧。4.3 图像复原、数据增强与任务驱动视觉图像复原是一个特别适合 BLO 的领域。传统的正则化方法要人工设正则化参数BLO 则可以把正则化参数或先验网络的参数作为外层变量内层去解复原问题。这样得到的先验是任务驱动的比手工调参可靠得多。数据增强策略搜索同理。AutoAugment 这类方法用强化学习搜索策略其实也可以用 BLO 来做上层学一个增强策略分布下层在增强后的数据上训练模型。我在做小样本视觉任务时试过这种方式相比固定增强确实能带来几个点的提升但对增强策略的表示形式非常敏感离散的、带不可导采样的策略会让隐式梯度失效最后只能用重参数化技巧或者强化梯度去绕。无监督表示学习里的对抗式方法也能从 BLO 视角看。生成器和判别器的博弈其实是一个 min-max 问题当把其中一个当成内层、另一个当成外层时就和 BLO 有了天然的联系只是在哪个先优化上略有差异。理解这一点再看那些生成式预训练方法的收敛稳定性分析会有更统一的体会。4.4 强化学习与更大尺度的决策问题强化学习领域的 BLO 应用越来越常见。元强化学习的目标是学一个能快速适应新任务的策略初始化内层是在每个任务的奖励函数下做策略优化奖励设计也是典型的双层结构——外层设计奖励函数内层基于奖励去训练策略目标是最后策略的高层指标比如通关率最大化。这里最大的难点在于内层几乎总是不可导的策略优化是采样驱动的。所以实践中常用无梯度或者近似梯度的方法外层评估一次就要完整跑一遍策略训练成本非常高。我见过不少把 reward design 做成 BLO 的工作真正能落地的很少多数在小规模环境上验证。这个领域还有很大的工程优化空间如果你准备切入建议从减少内层评估次数这个方向入手比如用代理模型或者共享多任务信息。5. 复现 BLO 论文时的实操避坑指南5.1 先做一个冒烟测试用二次型问题验证梯度我踩过的最大一个坑是在真实模型上调了半天超梯度不收敛最后发现是梯度的数学实现错了。从那以后我养成了一个习惯任何 BLO 算法落地第一件事是拿一个解析可解的小问题做冒烟测试。最经典的测试床是双层二次型问题。内层是 G(w, v) v² - wv解析解 v* w/2外层的 F(w, v*(w)) 是某个简单函数。这种问题可以直接数值验证超梯度的准确性用有限差分核对你的实现误差是否在可接受范围内。如果这一步就偏差超过 1%后面的深度模型实验基本不用做了梯度实现一定有问题。5.2 显存爆炸时的降级策略在真实数据集上跑 unrolling 类的 BLO显存是对着展开步数线性涨的。踩过几次爆显存之后我总结出三个降级策略从轻到重排列。第一用梯度截断。内层展开到第 k 步时把前面几步的计算图 detach 掉只保留最近几步的梯度路径。这会让梯度变近视但往往能保持大部分性能。第二换用隐式梯度。如果你的内层确实能训到接近收敛直接用隐函数定理内存直接降到常数级代价是实现复杂度和对收敛的要求变高。第三混合精度加梯度检查点。把内层前向过程用 checkpoint 机制重算用计算换显存这个方案改造成本最低适合所有 unrolling 实现。下面是一个用 PyTorch 实现显式展开 BLO 的最小骨架我在项目里基本都是从这版改的import torch # w: 上层变量, v: 下层变量 w torch.nn.Parameter(torch.tensor(1.0)) v torch.tensor(0.0, requires_gradTrue) inner_lr 0.1 outer_lr 0.01 unroll_steps 5 def inner_grad(w, v): # 内层目标对 v 的梯度示例为二次型 return v - w def outer_loss(w, v): # 外层评估比如验证集损失 return (v - w) ** 2 w ** 2 for _ in range(100): # 外层更新时保留内层展开的计算图 v_cur v.detach().requires_grad_(True) for _ in range(unroll_steps): g inner_grad(w, v_cur) v_cur v_cur - inner_lr * g v_cur.retain_grad() # 保留中间梯度以便回传 loss outer_loss(w, v_cur) grad_w torch.autograd.grad(loss, w, create_graphFalse)[0] w.data - outer_lr * grad_w注意内层变量每一步都需要 retain_grad否则中间节点的梯度会被释放外层反传就断了。这个小细节当年让我 debug 了整整一个下午。5.3 常见问题速查表现象可能原因排查与修复外层损失震荡发散内层迭代不充分v*(w) 近似误差大增加内层步数或 epoch降低外层学习率外层梯度方向异常内层收敛到坏的局部极小值换随机种子增大内层批量检查内层稳定性显存随迭代线性增长unrolling 计算图未截断用 checkpoint 或截断梯度改隐式梯度内层不能收敛内层学习率过大或外层更新过快分开调两层的学习率外层通常要比内层小一个量级隐式梯度不准确内层没有真正收敛到稳定点加大内层训练量降低内层终止阈值外层优化几乎不更新Hessian-vector product 数值误差大用更精确的线性求解器共轭梯度迭代次数加多还有一个很多人忽略的点两层学习率的关系。我见过不少人把外层和内层都用同一个学习率结果要么外层被内层的快速变化带偏要么内层跟不上外层的节奏。经验值上外层学习率通常取内层的 1/5 到 1/10并且外层更新频率不宜过高很多成功实验都是内层训几个 epoch外层才更新一次。6. 什么时候别用 BLO我的几点真实体会写到最后我想泼一点冷水。BLO 框架看着通用但不是所有嵌套问题都值得用双层求解器。如果你的内层问题可以用一步 closed-form 解出来或者内层迭代对外层目标的影响很小那直接做单层近似或者交替优化就够了。我见过不少工作为了讲故事把问题硬包装成 BLO最后因为超梯度估计的方差太大反而比朴素的交替优化更不稳定。工程里够用比优雅重要得多。判断要不要上 BLO我一般问自己三个问题内层和外层的目标是否真的存在依赖关系内层是否能比较稳定地收敛算超梯度的成本我是否承担得起三个都是肯定答案才值得投入。从更大的视角看综述里提出的统一视角让我很受用的一点是它把机器学习里一堆看起来很新的议题比如可微架构搜索、元学习、任务驱动复原、奖励设计全部收敛到同一个数学框架下。这并不意味着所有问题变得简单但至少我们可以把各自领域调参时获得的直觉迁移出来用统一的方法论去 debug。我个人在实际操作中的体会是BLO 最难的从来不是数学形式而是工程上的梯度估计与稳定性控制你只要跨过那几道坎这套框架能给你带来很多常规训练流程看不到的新可能性。最后再分享一个小技巧如果你刚开始接触 BLO别一上来就啃完整综述先拿一个已知解析解的双层二次型问题把三类求解器都手写一遍再在超参数优化这个最简单场景上跑通整体流程。这个十块钱起步的路子比直接上 NAS 或元学习大场景靠谱得多也更能帮你建立对双层的直觉。
返回列表