news 2026/10/17 5:22:39

HIRO算法解析:分层强化学习如何通过目标重标记提升数据效率

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HIRO算法解析:分层强化学习如何通过目标重标记提升数据效率

做强化学习做到连续控制这个方向,基本都碰到过一个尴尬:单层策略在仿真环境里练得再顺,任务一旦变成“多阶段、长时序”,比如让四足机器人走迷宫、推开障碍物、翻越地形,策略就容易各种摆烂——要么原地绕圈,要么学出一套“看起来很努力但就是不解决问题”的固定动作。那时候你自然会想到分层:让高层做规划,低层做执行。可真去动手实现分层强化学习,又会撞上新的麻烦——两层之间的目标怎么定义、经验怎么复用、梯度怎么传,每一步都像在踩雷。

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_{t+c},并留下了一系列转移数据(s_t, a_t, s_{t+1}, ..., s_{t+c})。上层随后收到奖励,再设定下一个目标。这样的完整片段会被存进高层的经验池,供后来训练使用。

问题来了:假设低层策略已经升级了好几轮,我们重新从经验池里取出一个月前存的一条数据。数据里记录的是“当时那个低层策略在目标g_t下的行为”。现在我们拿这条数据去更新当前的高层策略,等于是让高层学习一个“已经不存在的行为”——旧低层不会再用同样的方式执行这个目标了,这不就乱套了吗。

HIRO的处理方式是:把目标g_t替换成一个假想目标g',使得“当前的低层策略在状态s_t、目标g'下,最有可能做出数据里记录的那个动作a_t”。也就是说,我们不改变数据里的状态转移,只重新给它配一个目标。论文给出了两种近似做法。

第一种做法是梯度上升。我们可以把g'初始化成g_t,然后对“当前低层策略输出动作a_t的log概率”做几步梯度上升,让g'朝着更容易触发a_t的方向优化。这种做法比较精确,但要多一次反向传播,实现稍重。

第二种做法非常简单,就是让目标跟着状态一起平移:

g' = g_t + (s_{t+c} - s_t)

这个公式乍一看有点像偷懒,但它背后的逻辑非常漂亮。它相当于把原来的目标g_t顺着低层实际走过的位移方向平移了一段,平移量正好是整段轨迹的总位移。这样处理之后,目标g'和结束状态s_{t+c}之间的相对位置,与原始目标g_t和起始状态s_t之间的相对位置完全一致。换句话说,“任务做到一半还剩多少没完成”这种感觉被原样保留了下来。低层策略本质上是一个“不断缩小与目标距离”的控制器,它对目标与自身位置的相对关系更敏感,所以你把这个相对关系保持住,低层的行为统计特性就不会发生突变。

我在实际复现里非常推荐先试第二种。一来它几乎不增加计算量,二来论文实验也表明它在多数任务上效果和梯度上升版本相当。如果你遇到复杂任务发现效果不理想,再切换成梯度上升的版本也不迟。

2.2 高层奖励函数的设计逻辑

有了重标记后的目标,还得有配套的奖励。HIRO把上层在时间t的奖励定义为:

r_t^high = -‖s_{t+c} - 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_{t+c} - 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_{t+1} - 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_{t+c}),方向搞反,结果训练直接发散。

正确的做法是g' = g_t + (s_{t+c} - s_t),也就是目标跟着低层实际位移一起平移。注意如果你取的是c步的位移,那g'的位数和状态完全一致,直接用向量加法就能完成。但是如果你在一个batch里处理多条transition,每条transition中的s_t和s_{t+c}都不同,一定要在维度上保持对齐,避免广播逻辑出错。

另外,重标记之后的transition要重新计算高层的目标。如果你是分步训练的,那么更新高层所用的transition应该包含重标记后的goal与对应奖励,否则梯度方向不一致,训练会忽左忽右。

5.2 训练不稳定的排查思路

如果你发现训练曲线震荡得很厉害,我有一套固定的排查顺序。

先看低层是否已经具备基本的goal-reaching能力。怎么判断?直接把低层拿出来,随机给它一些目标任务,看它能不能在c步内显著靠近目标。如果低层本身就像个无头苍蝇,那高层无论怎么规划都没有用。这个检查点很多新手会跳过,一上来就盯着高层的loss看,浪费时间。

再看高层的经验池里是不是堆积了大量旧策略时代的数据。重标记能解决一部分非平稳问题,但重标记依赖“当前低层在重标记目标下可能做出同样动作”这个假设。如果低层策略早在几百轮更新前就完全变了个样,即使重标记也难以修正。稳妥的做法是定期清理或降低旧样本的采样权重。

最后检查奖励的数值尺度。高层奖励是目标距离的负值,这个距离的绝对值取决于任务空间范围。如果空间很大,奖励动辄-50、-100,梯度会非常猛;如果空间很小,奖励又可能小到无法推动训练。我一般会把奖励除以一个与任务尺度相关的系数,让它在负十到零之间浮动,训练会舒服很多。

5.3 值得试一试的扩展细节

虽然HIRO原版是用TD3做底层和上层的强化学习器,但我在实际项目里把底层换成了SAC,也有不错的效果。SAC对目标条件化任务的探索更稳定,尤其在低层需要多种不同行为方式来逼近同一个目标时,优势会更明显。

还有一个我后来才领悟到的点:尽量让低层的目标从训练初期的“容易达到”逐渐过渡到“困难目标”。你可以用课程学习的方式,前期给低层分配离当前位置很近的目标,等它掌握了基本控制,再慢慢把目标拉远。这个技巧配合HIRO的重标记机制,训练速度会再上一个台阶。

最后,如果你在跑HIRO时发现自己的任务“看起来简单但怎么都跑不出来”,请先别急着怀疑算法。把低层单独拎出来验证,把高层单独验证,再把两者接起来验证,逐层排查问题出在哪一层。我遇到过很多次,问题根本不在算法本身,而在数据流、奖励尺度或者维度处理的某个小bug上。

我在复现这个算法的过程中最大的体会,是“重标记”这种操作看似简单,却在设计思路上打开了一扇门:它让人意识到,训练数据的有效性不一定要靠数据本身的信息量,而是可以通过改造数据的“观察角度”来重新激活。这也是HIRO对后续分层RL影响最大的地方。如果你也在做分层或长时序控制,强烈建议亲手把它复现一遍,踩过一次坑,才能真正理解那些论文里一笔带过的细节有多关键。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 5:22:20

AI写完90%代码后,工程师的核心价值在哪里?

先说我最近的一个真实感受。我接了个内部工具的小需求,功能不复杂,就是一个数据清洗脚本,从几个 CSV 里读取记录,按规则去重、补全字段、输出报告。老规矩,我先把这个需求拆成四五个子任务,扔给 AI 写第一版…

作者头像 李华
网站建设 2026/10/17 5:22:12

用Claude Code模板体系,让AI编程稳定高效

我是从一次打脸经历开始触碰 claude-code-templates 的。第一次在真实项目里大规模让 Claude Code 干活,我的提示词只有一句:“排查一下这个页面缓存为什么失效。”结果它一口气改了七个文件,其中两个和问题完全无关,还顺手把本地…

作者头像 李华
网站建设 2026/10/17 5:21:55

15-puzzle 用 IDA* 做到极致优化:从可解性到模式数据库

简介:这份资源面向人工智能初学者与算法进阶者,聚焦用IDA*(迭代加深A*)算法求解经典15-puzzle滑动拼图问题,帮助读者理解启发式搜索在状态空间规划中的落地方式。包内共1个文件,为单个Python脚本&#xff0…

作者头像 李华
网站建设 2026/10/17 5:20:45

Log4Shell漏洞检测实战:log4shell-detector原理与使用指南

2021年底Log4j爆出远程代码执行漏洞的时候,整个安全圈都跟过年一样紧张。Log4Shell这个名字估计没人陌生——一个几乎所有人都依赖的日志组件,结果攻击者往日志消息里塞一段特殊字符串,就能让业务系统去远程加载恶意代码。那阵子我排查业务日…

作者头像 李华
网站建设 2026/10/17 5:20:39

ARM裸机SWI软件中断异常机制详解:从异常向量表到系统调用实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华