做强化学习做到连续控制这个方向基本都碰到过一个尴尬单层策略在仿真环境里练得再顺任务一旦变成“多阶段、长时序”比如让四足机器人走迷宫、推开障碍物、翻越地形策略就容易各种摆烂——要么原地绕圈要么学出一套“看起来很努力但就是不解决问题”的固定动作。那时候你自然会想到分层让高层做规划低层做执行。可真去动手实现分层强化学习又会撞上新的麻烦——两层之间的目标怎么定义、经验怎么复用、梯度怎么传每一步都像在踩雷。2018年有一篇标题就是Data-Efficient Hierarchical Reinforcement Learning的工作后来大家习惯直接叫它HIRO。它发表在NeurIPS 2018上算是我认为“让分层强化学习真正变得实用”的一个关键节点。这篇文章的思路并没有想象中复杂核心是给高层设置的目标做重标记但就是这一下把分层强化学习的数据效率拉到了可用的水平也成了后面很多分层RL工作绕不开的参考点。这篇内容适合谁看如果你在做机器人长程任务、在复现分层RL算法、或者只是好奇“为什么分层RL老是训不动”都可以读一下。我会从问题拆解、方法原理、实现细节、避坑经验几个层面来展开尽量把论文里没写透的软细节也补上。放心我不会只堆公式而是尽量把模型变成能“抄作业”的东西。1. 分层强化学习到底在分什么以及HIRO针对的痛点1.1 分层RL的基本范式分层强化学习的出发点非常直观。单层策略之所以在长程任务上吃力本质是因为它既要学“该往哪个方向走”的规划能力又要学“具体怎么迈腿”的控制能力。这两个任务的时间尺度完全不一样规划可能几十步才需要调整一次而控每一步都在做。你让同一个网络同时背这两件事优化的信号很容易互相干扰最终学出来的策略往往是“局部正确、全局迷路”。HIRO采用的分层结构是经典的两层设计上层策略每隔固定的步数c给出一条指令这个指令通常是一个目标状态下层策略接收这个目标在c步之内尽量去接近它。整个训练过程中下层只关心“怎么达到目标”不关心“为什么要去那里”上层只关心“下一步该选哪个目标”不关心“怎么迈腿”。这种拆分的优势很明显下层可以跨任务复用只要目标定义一致上层的决策频率低适合用更慢、更全局的算法来学习。但它也引出了一个非常麻烦的问题下层策略本身一直在变化上层要怎么才能在这个“会动的地基”上稳定学习1.2 朴素分层为什么总是训不动很早之前就有人试过直接这样训练两级策略但效果普遍不好。我见过不少项目在这上面栽跟头总结起来有三个典型原因。第一个原因是目标非平稳问题。上层给下层设定一个目标之后下层在训练过程中会持续更新自己的策略。这个epoch下层还走“先直行再右转”的路线下一个epoch可能就变成“直接斜穿过去了”。上层发现同一个目标对应的下层行为每天都在变它其实是在对一个不断漂移的“控制系统”做规划。如果上层用的是off-policy训练从经验池里抽出旧数据里面记录的行为还是“旧下层”的行为拿这些数据训练当前的上层策略信号自然就歪了。第二个原因是目标空间和动作空间不匹配。有些早期方案让上层输出一个低维的embedding向量作为下层的输入条件。问题在于embedding空间里的“语义”是下层自己慢慢摸索出来的上层并不清楚这个空间的含义。上层以为自己输出的是一组有意义的指令实际上下层可能把它当成噪声两者对不上。第三个原因是奖励信号太稀疏。上层每隔c步才收到一次反馈这个反馈常常是“有没有最终完成任务”。如果任务本身需要很多个子目标才能完成上层在前期几乎拿不到任何有效梯度基本靠随机搜索瞎猜。这也是为什么很多分层RL方案“理论上成立实际上跑不动”。1.3 HIRO的核心思路把目标当作“期望状态”HIRO针对上面三个问题给出的方案非常干净。第一它让上层输出的目标直接就是状态空间里的一个点而不是某个抽象的embedding。这样一来目标空间的含义是明确的上层说“去这个坐标”下层就朝这个坐标走。第二它引入了一个目标重标记机制让上层在训练时能够用“当前下层策略看起来合理的假目标”替换掉原来的目标从而解决了非平稳问题。第三它给上层设计了一个稠密且符合直觉的奖励——每过c步就按照下层到达位置与目标位置的距离给反馈。正式展开方法之前先记住这句总结HIRO的关键是把分层训练中的“目标重放”和“经验重放”结合起来让上层能够像普通RL一样用历史数据做off-policy更新而不被下层策略的变化干扰。接下来我们就把这套机制拆开看。2. 两大核心机制拆解过渡目标重标记与奖励设计2.1 过渡目标重标记到底做了什么这一节是整个HIRO的灵魂值得多看两遍。为了说清楚先明确一下数据形态。在训练过程中上层在时刻t设定一个目标g_t下层从状态s_t开始执行经过c步后到达状态s_{tc}并留下了一系列转移数据(s_t, a_t, s_{t1}, ..., s_{tc})。上层随后收到奖励再设定下一个目标。这样的完整片段会被存进高层的经验池供后来训练使用。问题来了假设低层策略已经升级了好几轮我们重新从经验池里取出一个月前存的一条数据。数据里记录的是“当时那个低层策略在目标g_t下的行为”。现在我们拿这条数据去更新当前的高层策略等于是让高层学习一个“已经不存在的行为”——旧低层不会再用同样的方式执行这个目标了这不就乱套了吗。HIRO的处理方式是把目标g_t替换成一个假想目标g使得“当前的低层策略在状态s_t、目标g下最有可能做出数据里记录的那个动作a_t”。也就是说我们不改变数据里的状态转移只重新给它配一个目标。论文给出了两种近似做法。第一种做法是梯度上升。我们可以把g初始化成g_t然后对“当前低层策略输出动作a_t的log概率”做几步梯度上升让g朝着更容易触发a_t的方向优化。这种做法比较精确但要多一次反向传播实现稍重。第二种做法非常简单就是让目标跟着状态一起平移g g_t (s_{tc} - s_t)这个公式乍一看有点像偷懒但它背后的逻辑非常漂亮。它相当于把原来的目标g_t顺着低层实际走过的位移方向平移了一段平移量正好是整段轨迹的总位移。这样处理之后目标g和结束状态s_{tc}之间的相对位置与原始目标g_t和起始状态s_t之间的相对位置完全一致。换句话说“任务做到一半还剩多少没完成”这种感觉被原样保留了下来。低层策略本质上是一个“不断缩小与目标距离”的控制器它对目标与自身位置的相对关系更敏感所以你把这个相对关系保持住低层的行为统计特性就不会发生突变。我在实际复现里非常推荐先试第二种。一来它几乎不增加计算量二来论文实验也表明它在多数任务上效果和梯度上升版本相当。如果你遇到复杂任务发现效果不理想再切换成梯度上升的版本也不迟。2.2 高层奖励函数的设计逻辑有了重标记后的目标还得有配套的奖励。HIRO把上层在时间t的奖励定义为r_t^high -‖s_{tc} - g_t‖₂翻译成人话低层在c步之后到达的位置离目标越近高层拿到的奖励越高。注意这里用的是原始目标g_t不是重标记后的g。因为重标记只发生在“训练时”的样本构造阶段而在真实环境交互时高层确实是用原始目标去指导下层的。这个奖励设计比“完成任务给1不完成给0”要合理得多。它属于一种稠密奖励每一段c步都能给出反馈让上层不至于在前期完全瞎摸。同时它又是一个很“诚实”的奖励目标定得好不好直接体现在低层能不能接近它。如果高层定了一个不切实际的目标低层走到天涯海角也够不着高层就会收到一个大大的负反馈从而学会不去定那种目标。这里有一个容易被忽略的细节为什么奖励里只看“结束位置离目标多近”而不看“低层在中途有没有朝目标方向前进”。原因是高层不需要关心低层的内部执行过程它只关心最终结果。而且如果在中间每一步都给反馈会让高层过于关注低层的微观动作反而失去了“宏观规划”的意义。每隔c步给一个结果型奖励正好把两个层次的时间尺度切干净。2.3 为什么这套设计能提升数据效率很多第一次接触HIRO的人会觉得奇怪重标记不过就是改了一下训练数据里的目标怎么就能带来数据效率上的巨大提升关键在于它打通了“off-policy训练”这条路。常规分层RL里上层很难利用历史数据因为下层策略一变旧数据就“过期”了。你强行用旧数据训练上层相当于用一张过期的地图找路越找越偏。为了稳定很多方法只能让上层跟着下层一起慢慢地在线学习数据用一次就扔效率自然低。HIRO通过重标记把过期数据“洗”成了当前下层策略下看起来合理的数据。这样一来上层就能像普通RL算法一样从一个大经验池里反复采样、反复学习。数据利用率一下子提升了一个量级。打个不恰当的比方普通分层RL像是只允许你看今天刚拍的照片来认路HIRO则是把老照片全部重拍了一遍让它们看起来像今天的街景然后你就可以放心地反复翻相册了。这也是“Data-Efficient”这个名字的由来——它并不靠增加算法的花哨程度来提升效率而是靠让已有数据重新变得可用。3. 完整算法流程与可落地实现细节3.1 HIRO总体框架与伪代码我把HIRO的完整流程整理成了伪代码方便对照理解。实际项目里你可以在这一步先跑通主流程再去调细节。# 伪代码HIRO 主循环 # high_policy上层策略输入 state 输出 goal # low_policy下层策略输入 (state, goal) 输出 action init high_policy, low_policy init high_buffer, low_buffer for iteration in range(max_iterations): state env.reset() goal high_policy.get_goal(state) for t in range(0, horizon, c): # 低层执行 c 步 for step in range(c): action low_policy.sample_action(state, goal) next_state, reward, done env.step(action) low_buffer.add(state, goal, action, reward, next_state) state next_state if done: break # 高层获得反馈并更新目标 high_reward -distance(state, goal) # r -||s_{tc} - g_t|| high_buffer.add(transition) goal high_policy.get_goal(state) # 更新低层从 low_buffer 采样目标是缩小与 goal 的距离 low_policy.update(low_buffer.sample()) # 更新高层对 high_buffer 采样并进行目标重标记 transitions high_buffer.sample() relabeled_transitions relabel_goal(transitions, low_policy) high_policy.update(relabeled_transitions)看起来并不复杂但每一行背后都有值得展开的细节。3.2 低层策略目标条件化的连续控制低层本质上一个goal-conditioned的连续控制策略。在HIRO里它被实现成一个输入为state和goal拼接向量的Actor-Critic网络输出连续动作。目标就是在c步内最小化与目标的距离底层用TD3这类稳定算法来训练完全够用。这里有几个容易踩的细节。目标拼接方式会影响训练稳定性。我见过有人把state和goal直接拼成一个向量丢给网络有时没问题但如果你发现训练很不稳定可以试试把goal - state作为输入而不是分别输入state和goal。因为低层更关心“目标相对我有多远、在哪个方向”而不是目标的绝对坐标。实践里差分输入往往收敛更快。不过HIRO原论文使用的是拼接方式效果也OK具体看你自己的任务。低层的奖励也要设计好。最简单的方案是每一步都给出与目标距离负相关的奖励即r_low -‖s_{t1} - g_t‖。这样做是可行的但注意不要让奖励尺度过大或过小否则会和上层奖励的数值范围差距太远导致某个策略学得特别快、另一个完全跟不上。我一般在实验里会把低层奖励乘以一个缩放系数控制在0到1的量级。低层策略的更新频率通常要高于高层。因为低层是“快速响应”的那一层如果它更新太慢上层会一直面对一个反应迟钝的执行器目标重标记也救不回来。3.3 高层策略基于重标记经验的离线训练高层策略同样可以用TD3或类似算法来训练。它的输入是当前状态输出是一个连续的目标向量这个向量的维度与状态空间维度一致。真正复杂的部分在采样之后拿到一条高层transition后你不能直接拿去更新高层必须先用当前的低层策略对目标做一次重标记然后再更新。实现的时候我建议把“重标记”单独封装成一个函数。它接收一条transition和当前低层策略输出一条新transition其中原始目标已经被替换成g同时奖励也要根据重标记后的目标重新计算。注意奖励用的是低层实际达到的位置与g的距离而g已经变了所以奖励值也要跟着变。还有一点容易被忽略重标记之后高层经验池中的数据分布会与真实交互时的分布有偏差。你训练的高层其实是在学习“在假想目标下低层会怎么做”的分布而不是“真实目标下低层会怎么做”的分布。所以真实交互时高层给出的目标可能和它训练时见到的目标风格不完全一致。为了缓解这个偏差我习惯在训练到中后期逐渐降低重标记的比例让高层更多地去拟合真实交互数据。3.4 关键超参数与细节选择HIRO的主要超参数没有想象中多但有一个对结果影响最大低层执行周期c也就是高低层时间尺度的比值。c取得太小低层还没走几步就要听高层的下一句指令刷卡次数太多高层任务过重c取得太大低层有太多自由度高层在c步之后才看到结果反馈太稀疏训练变慢而且一次偏离目标会导致巨大的负奖励。我的经验是先从10开始试如果低层动作频率很高可以适当加大到几十甚至上百。具体值取决于你的任务对动作变化的敏感程度以及状态空间是不是平滑的。还有一个值得调的点是经验池大小。因为HIRO的数据效率高经验池会被频繁重用池子太大会让旧数据占太多比例训练早期不稳定池子太小又浪费了重标记带来的复用优势。我通常会把高层的经验池设得比低层大一些因为高层数据天生稀疏需要更大的池子来保证采样多样性。观测归一化也算一个隐性细节。机器人任务里不同维度的状态往往量级差很多比如关节角度和位置坐标可能差了几十倍。如果不做归一化目标向量里各维度的权重会被量级带偏。我在复现时会对状态和目标都做归一化处理效果提升非常明显。4. 实验效果数据效率到底提升了多少4.1 任务设计四足机器人的迷宫、推箱与翻越为了验证HIRO的效果论文作者团队设计了几组具有代表性的任务每一组都精准指向长程任务的核心难点。第一类是迷宫导航任务。四足机器人在一个迷宫里要从起点走到目标点迷宫本身有一定复杂度单层策略很容易在岔路口迷失。第二类是推箱任务。机器人需要先把箱子推到指定位置然后再走到目标点。这个任务的难点在于子任务之间有严格顺序推箱和走路不能同时进行必须先完成一个再完成另一个。第三类则是翻越地形类的任务要求机器人在移动过程中处理地形变化对下层控制能力要求更高。这些任务都有共同特点单一策略难以搞定但又不需要太高的智能——规划部分其实很直接关键是你能不能把“规划”和“控制”两个层次搓到一起。HIRO刻意选择了这些“难度适中但极考验分层能力”的任务来放大不同方法之间的差异。4.2 数据效率对比差距不是一点半点实验里最直观的结果就是数据效率上的差距。和早期分层方法相比HIRO在相同样本量下能达到的成功率高出一大截。在迷宫导航这种任务上早期分层方法往往需要数百万步才能开始有起色到训练结束时成功率还不高而HIRO在几十万步内就能稳定地拿到正解路径训练曲线也更平滑。更关键的是HIRO在推箱子这样的顺序型任务上做到了“从无到有”。我之前跑过类似任务单层策略在前期完全学不到任何有效的推到动作几乎全靠随机探索撞运气而HIRO由于高层有了重标记机制能够比较快地领悟到“先推箱、再走路”这个顺序结构成功率提升速度肉眼可见。当然HIRO也不是万能的。在需要非常精确的底层操控的任务上比如翻越地形时对落脚点要求极高低层策略本身就会成为瓶颈。这时候即使高层规划得再好低层执行不了也是白搭。所以HIRO的效果很大程度建立在“低层任务可以学习”这个前提上。4.3 消融实验的启示论文的消融实验部分是值得细读的。它验证了目标重标记和奖励设计各自的必要性。一个关键的对比是“去掉目标重标记”的版本。直接把原始目标存下来用于训练不进行重标记结果训练曲线变得非常震荡甚至在某些任务上无法收敛。这就清晰说明了重标记不是锦上添花的小技巧而是保证off-policy训练能跑起来的核心支柱。另一个有价值的观察是关于重标记中“梯度上升”与“简单平移”两种方式的对比。论文发现两者在多数任务上表现接近。这意味着低层策略对目标的响应确实主要集中在相对位置关系上而不是绝对坐标。这个结论也给后续很多工作提供了设计空间——很多后来者都直接采用了简单的目标平移公式。5. 复现避坑指南我自己踩过的几个坑5.1 重标记实现里的向量化细节真去写代码的时候最容易写错的就是重标记这一步。我见过有同学把g写成了g_t (s_t - s_{tc})方向搞反结果训练直接发散。正确的做法是g g_t (s_{tc} - s_t)也就是目标跟着低层实际位移一起平移。注意如果你取的是c步的位移那g的位数和状态完全一致直接用向量加法就能完成。但是如果你在一个batch里处理多条transition每条transition中的s_t和s_{tc}都不同一定要在维度上保持对齐避免广播逻辑出错。另外重标记之后的transition要重新计算高层的目标。如果你是分步训练的那么更新高层所用的transition应该包含重标记后的goal与对应奖励否则梯度方向不一致训练会忽左忽右。5.2 训练不稳定的排查思路如果你发现训练曲线震荡得很厉害我有一套固定的排查顺序。先看低层是否已经具备基本的goal-reaching能力。怎么判断直接把低层拿出来随机给它一些目标任务看它能不能在c步内显著靠近目标。如果低层本身就像个无头苍蝇那高层无论怎么规划都没有用。这个检查点很多新手会跳过一上来就盯着高层的loss看浪费时间。再看高层的经验池里是不是堆积了大量旧策略时代的数据。重标记能解决一部分非平稳问题但重标记依赖“当前低层在重标记目标下可能做出同样动作”这个假设。如果低层策略早在几百轮更新前就完全变了个样即使重标记也难以修正。稳妥的做法是定期清理或降低旧样本的采样权重。最后检查奖励的数值尺度。高层奖励是目标距离的负值这个距离的绝对值取决于任务空间范围。如果空间很大奖励动辄-50、-100梯度会非常猛如果空间很小奖励又可能小到无法推动训练。我一般会把奖励除以一个与任务尺度相关的系数让它在负十到零之间浮动训练会舒服很多。5.3 值得试一试的扩展细节虽然HIRO原版是用TD3做底层和上层的强化学习器但我在实际项目里把底层换成了SAC也有不错的效果。SAC对目标条件化任务的探索更稳定尤其在低层需要多种不同行为方式来逼近同一个目标时优势会更明显。还有一个我后来才领悟到的点尽量让低层的目标从训练初期的“容易达到”逐渐过渡到“困难目标”。你可以用课程学习的方式前期给低层分配离当前位置很近的目标等它掌握了基本控制再慢慢把目标拉远。这个技巧配合HIRO的重标记机制训练速度会再上一个台阶。最后如果你在跑HIRO时发现自己的任务“看起来简单但怎么都跑不出来”请先别急着怀疑算法。把低层单独拎出来验证把高层单独验证再把两者接起来验证逐层排查问题出在哪一层。我遇到过很多次问题根本不在算法本身而在数据流、奖励尺度或者维度处理的某个小bug上。我在复现这个算法的过程中最大的体会是“重标记”这种操作看似简单却在设计思路上打开了一扇门它让人意识到训练数据的有效性不一定要靠数据本身的信息量而是可以通过改造数据的“观察角度”来重新激活。这也是HIRO对后续分层RL影响最大的地方。如果你也在做分层或长时序控制强烈建议亲手把它复现一遍踩过一次坑才能真正理解那些论文里一笔带过的细节有多关键。
阅读完成 · 觉得有帮助?