拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

强化学习稀疏奖励破解之道:HER目标重标注原理与实践

强化学习稀疏奖励破解之道:HER目标重标注原理与实践

1. 先搞清楚hindsight到底在解决什么问题

如果你在强化学习社区里搜“hindsight”这个词,十有八九会撞到那篇经典的论文《Hindsight Experience Replay》。我第一次看到这个标题时其实有点困惑:hindsight直译过来是“事后聪明”、“事后诸葛亮”,这跟强化学习有什么关系?直到把论文读完、把代码跑通,我才意识到这个词选得有多妙——它说的正是“等事情失败之后,回头再看,才发现原本可以学到点什么”。

这个算法解决的是强化学习里最让人头疼的问题之一:稀疏奖励。想象你让一个机械臂去抓桌上的杯子,设定是“抓到了给1分,没抓到给0分”。在训练初期,机械臂的动作完全是随机的,别说抓到杯子,连靠近杯子都费劲。于是整条轨迹跑完,奖励始终是0,没有任何信号告诉它“你刚才那个动作方向是对的”。这种情况下,常规的强化学习算法基本学不动,就像考生看到一整张卷子全是不会的题,交白卷之后也没人告诉他哪一步能得分,他下次还是只能瞎蒙。

这类问题在机器人操控、游戏AI、导航决策里比比皆是。目标导向的任务尤其严重:状态空间大、目标又精确,随机探索撞上目标的概率低到可以忽略。hindsight这个算法最大的贡献,就是把一条条“失败”的轨迹也变成训练样本——它不改变环境,不改变奖励,只改变经验回放里用来计算奖励的“目标”。说白了,它教会算法从失败中榨取学习信号。

这篇文章我把HER从直觉到原理、从代码到调参、从踩坑到落地,按我自己的实践脉络完整梳理一遍。适合正在做机器人控制、目标导向强化学习研究或工程落地的朋友,也适合刚接触稀疏奖励问题、想搞明白这套经典方案为什么有效的学习者。我会尽量少讲空话,多讲实际跑实验时真正起作用的东西。

2. 核心思路拆解:为什么“事后重标注目标”能work

2.1 先理解强化学习里的“目标”到底是什么

在目标导向的强化学习任务里,通常把一条经验写成这样的五元组:(state, goal, action, reward, next_state)。智能体要做的事情,是在给定goal的条件下,学到一个策略,让状态尽量接近这个目标。reward则往往定义成“当前状态是否已经达到目标”的指示函数——达到给0,没达到给-1;或者用状态与目标之间的距离取负值,越近分数越高。

问题就出在这个reward上。稀疏奖励意味着大多数状态下reward都是同一个值(比如-1),梯度完全无法区分“这一步比上一步更好”。训练时策略网络的梯度几乎为零,actor不知道该往哪个方向调整,critic也学不到有价值的状态价值估计。这就是稀疏奖励环境下训练进展极其缓慢的根本原因。

那我们换个角度看:一条轨迹跑下来,虽然原始目标g没达成,但它实实在在地经过了一大串状态。如果把这些状态里的某一个当作“目标”重新定义问题呢?机械臂没抓到杯子,但它在轨迹中间确实把手臂从A点移到了B点。如果目标改成“移动到B点”,那这条轨迹可太成功了——它是完美的正样本。这个重新定义目标的过程,学术上叫“goal relabeling(目标重标注)”。

这就是hindsight的核心:反正策略都是在同一个物理环境里探索,经验本身是真实的,只是我们给它贴的“任务标签”让样本看起来全是失败的。换一个标签,成功样本的数量立刻翻倍。用人类学习的例子来类比就特别简单:你今天想学做红烧肉,做出来味道一般,但你在过程中学会了“炒糖色要小火”。如果用“做红烧肉”这个目标评价这次尝试,它失败了;但如果把目标换成“学会炒糖色”,你这次尝试就是一次成功的练习。事后回头看,失败里全是可复用的经验。

2.2 目标重标注与经验回放如何组合在一起

HER并不是一个新算法,它是加在经验回放(Experience Replay)上面的一个数据增强技巧。原本的强化学习流程是:智能体与环境交互,把每一步的transition存进缓冲区;训练时随机采样一批transition,用来更新价值函数和策略。HER在这个流程里额外多做一步:采样到一条原始transition后,不是直接用这条transition自带的goal去算reward,而是有一定概率把它换成另一个“事后目标”。

换目标不是随便换的。最常见的做法是,从这条transition所属轨迹的后续状态里随机挑一个状态,作为新的goal。比如一条轨迹是t=0, 1, 2, ..., T,采样到其中一步t时,从t+1到T之间随机挑一个状态,设为新目标g'。然后把这条transition里的reward和done也一并重算:新的奖励是“在新目标g'下,当前状态是否达到了它”,新的done同样按新目标判断。

有一个细节务必注意:重标注之后,必须把reward和done同步重算。很多人实现的时候只替换了goal,保留原来的reward,训练时损失函数直接飞到天上去了。原因不复杂——原始reward是针对原始goal算出来的,现在goal换了,奖励语义就完全变了。比如原始目标是抓杯子,奖励是-1;重标注成“移动到坐标B”,如果当前状态已经到达B,奖励应该是0而不是-1。标签错了,整个价值函数学习的就是一个前后矛盾的东西。

这里也顺便解释一个问题:HER为什么必须搭配off-policy算法使用。经验回放本身就是off-policy的专利,PPO这类on-policy算法对数据新鲜度有严格要求,不能随便拿几个月前的老数据来更新策略。而HER人为地制造了一批“伪造目标”的经验,这批经验的有效性完全依赖于价值的泛化能力——同一个状态,在不同目标下有不同的价值,价值函数如果能泛化,这批数据就有用。DDPG、SAC、TD3、TQC这类算法天然符合这个要求,所以实践中HER基本都是和它们搭档。

3. 算法细节与工程实现:不是光理解就能跑起来的

3.1 四种目标采样策略:final、future、episode、random

论文里对比了四种目标重标注的采样策略,分别是final、future、episode、random。我用自己的话解释一下:

  • final:直接把这条轨迹的最后一个状态作为新目标。这个策略最简单,但缺点也明显:如果轨迹很长,中间状态到最终状态之间的路径信息会很稀疏,早期transition离最终状态太远,重标注后的目标距离当前状态太远,反向传播的信号依旧很弱。
  • future:从当前时刻之后的某个时刻对应的状态中采样一个作为目标。这是论文实验里表现最好的策略,原因也很直观:它选出的目标离当前状态不远不近,既有效覆盖了轨迹中实际经过的状态,又不会像final那样目标太远。
  • episode:从整条轨迹后续部分的某个状态里采样,实际上可以看作future的一种实现细节差异——future选的目标在时间顺序上必须更靠后,而episode同样是选后续状态,两者本质接近。
  • random:从环境中随机采样一个状态作为目标。这个策略不依赖当前轨迹,目标分布更接近全局的真实目标分布,能缓解HER长期训练时“目标分布偏离任务分布”的问题,但短期内学习信号不如future集中。

我的实操体会是:在稀疏奖励任务里,future作为主策略、random作为辅助,效果最稳。final在小规模任务里可以省事,但遇到FetchPickAndPlace这类复杂操控任务,final的收敛速度明显慢于future。random单独用效果不好,但可以作为一个补充目标源,防止策略过拟合到“轨迹经过状态”的分布上。

3.2 重标注比例:her_ratio这个参数怎么调

HER的工程实现里有一个关键超参:her_ratio。它表示每条原始transition额外生成几条重标注样本。比如her_ratio=4,意味着原始轨迹里的每一条transition,除了保留原始目标版本,还会额外生成4条重标注版本,存进经验池。

这个参数直接影响目标分布的构成。调太小,重标注样本太少,稀疏奖励问题没有真正缓解;调太大,经验池里全是“重新定义后的成功样本”,策略会过度偏向于处理这类伪造目标,面对真实目标时的表现反而下降。我在FetchPush环境上试过,her_ratio从1加到4,训练初期成功率提升非常明显;继续加到8,收敛速度没有进一步提高,反而因为经验池里伪造样本占比过高,真实目标上的最终成功率有轻微下降。

比较合理的做法是:先固定her_ratio=4,这是论文里最常用的经验值;等训练中后期,如果想进一步打磨真实目标上的表现,可以逐步降低这个比例。不用一开始就在这个超参上花太多时间调优,因为相比其他因素,它带来的收益边际递减得很早。

3.3 一个完整的DDPG+HER代码骨架

这里我给出一个基于Stable-Baselines3的可用实现框架,环境用经典的FetchReach。之所以选SB3,是因为它的HerReplayBuffer已经内置了future重标注逻辑,不需要我从零写回放缓冲区,省掉很多烦人的debug时间。

import gymnasium as gym import numpy as np from stable_baselines3 import SAC from stable_baselines3.her import HerReplayBuffer from stable_baselines3.common.callbacks import CallbackList, CheckpointCallback from stable_baselines3.common.monitor import Monitor # 优先使用gymnasium-robotics提供的Fetch环境 try: import gymnasium_robotics except ImportError: print("请先安装 gymnasium-robotics: pip install gymnasium-robotics") env = gym.make("FetchReach-v4") env = Monitor(env) # 关键:使用MultiInputPolicy,因为Fetch环境的观测是dict结构 model = SAC( "MultiInputPolicy", env, replay_buffer_class=HerReplayBuffer, replay_buffer_kwargs=dict( n_sampled_goal=4, goal_selection_strategy="future", ), learning_rate=1e-3, buffer_size=1_000_000, batch_size=256, gamma=0.98, tau=0.05, policy_kwargs=dict(net_arch=[512, 512, 512]), verbose=1, ) # 定期保存模型,方便训练中断后恢复 checkpoint_callback = CheckpointCallback(save_freq=10000, save_path="./checkpoints") model.learn(total_timesteps=500_000, callback=checkpoint_callback) model.save("./her_fetchreach_sac")

有几个细节需要特别说明。FetchReach这类环境的观测空间是dict结构,里面包含三个部分:observation(当前状态)、desired_goal(当前任务设定的目标)、achieved_goal(当前实际到达的状态)。HerReplayBuffer会利用achieved_goal来做重标注,所以千万不要手动把这个字段删掉。其次,策略网络结构我用了三层512,这不是必须的,但实测在Fetch系列任务上比默认的小网络收敛更稳。

有人可能会问:为什么不用DDPG而用SAC?DDPG+HER是论文原作者的组合,经典,但SAC自带熵正则,探索更充分,训练稳定性更好。在FetchReach上两者都能很快达到100%成功率,但在FetchPickAndPlace这类更难的任务上,SAC+HER比DDPG+HER的方差小很多。如果你坚持要完全复现论文,用DDPG也完全没问题,核心逻辑是一样的。

3.4 训练效果怎么看:别只盯着平均reward

不少新手跑完训练,只看平均reward曲线,发现它一直不涨,就觉得算法没用。这里我必须提醒:Fetch系列环境的目标是稀疏奖励,reward长期保持在一个很低的值上非常正常,真正要看的是每个episode的success rate。SB3的日志里会输出ep_success_mean,这个指标才是衡量目标达成率的黄金标准。

以FetchReach为例,在我本机(单张消费级GPU)上,SAC+HER大概训练10万步就能看到成功率爬升到接近100%,总训练时长也就十几分钟。FetchPush要慢一些,至少需要30万到50万步才能稳定到80%以上。FetchPickAndPlace则要几百万步的量级,而且对随机种子非常敏感,同一个超参在不同种子下成功率可能从40%到90%剧烈波动。做实验时务必固定随机种子,并且在多个种子上取平均,否则得出的结论可能完全是噪声。

4. 实操中的高频翻车现场与排查思路

4.1 价值函数和策略在重标注后为什么仍可能崩

HER虽然能缓解稀疏奖励问题,但代价是人为改变了经验池中的目标分布。原始经验池里的目标是环境给定的真实任务,重标注样本里的目标则是“事后挑选”的状态。这两种目标分布长期混合训练,价值函数要同时拟合两个分布上的预测,压力很大。

一个典型现象是:训练早期一切正常,成功率稳步上升;训练到中后期,已经学到的技能反而开始退步。这种情况我遇到过好多次。原因通常是重标注目标占比过高,策略过度拟合到“容易达成”的伪造目标上,而真实目标在经验池里被稀释了。解决思路有两个:一是降低her_ratio,二是增加random采样策略生成的目标,让经验池里的目标分布更接近全局真实分布。

另一个容易崩的点是:如果你在环境包装里额外做了reward shaping,比如把稀疏奖励改成了连续距离奖励,HER重标注时的reward计算逻辑必须和这个shaping保持完全一致。否则重标注样本的奖励跟原始样本的奖励尺度、分布都不匹配,critic学出来的价值尺度就乱了。我在一个自定义环境里犯过这个错:原始reward用了“距离的负值”,重标注时却只用了“是否到达的0/1判断”,两条样本的奖励尺度差了数量级,价值网络直接发散。检查的时候一定要确认环境里reward函数是唯一的,重标注逻辑里复用的就是那同一个函数。

4.2 长期不收敛和GPU利用率过低

训练很久成功率纹丝不动,这是稀疏奖励问题的常态,但加上HER之后仍然不动,问题往往出在探索上。SAC自带的随机探索在动作维度较低的任务里够用,但如果是高维机械臂控制,随机探索几乎不可能碰到目标。这时需要检查动作空间是否归一化到了[-1,1],观测是否做了归一化处理。Fetch环境默认是float32且在合理尺度内,但自定义环境就需要手动处理。我见过不少案例,观测值范围在几百到几千,神经网络的输入陷入饱和区,梯度消失,收敛当然无从谈起。

GPU利用率低也是常见抱怨。HER的经验回放操作、dict观测的拼接、重采样逻辑本身是CPU密集型的,如果你用GPU训练但数据管线没有做异步预取,GPU可能大部分时间在空转。SB3的默认数据管线并不为这个场景做极致优化,如果你要大规模跑实验,建议考虑用GPU版的向量环境、加大batch_size、或改用JAX/RLlib这类对数据管线控制更灵活的工具。小规模实验则不用纠结,跑通流程、验证算法效果才是重点。

4.3 常见问题速查表

现象可能原因排查与解决
训练很久成功率始终为0目标空间过大,随机探索永远碰不到目标;reward或done计算有误先检查重标注后reward/done是否同步重算;尝试在固定初始状态下训练;确认观测中achieved_goal字段正确
前期收敛好,后期成功率回退her_ratio过高,目标分布偏离真实任务分布降低her_ratio;加入random目标采样;检查经验池中原始样本是否被挤出
平均reward稳定但不升,success也不升reward shaping与重标注逻辑不一致统一reward计算函数;确认重标注后是否调用了环境原始的判定逻辑
不同随机种子效果差异巨大稀疏奖励任务本身方差大,网络初始化影响大至少跑3~5个种子,取成功率中位数;固定seed并记录seed值
训练速度极慢,GPU利用率低数据预处理占用大量CPU,训练管线下游瓶颈增大batch_size;使用向量化环境;考虑升级到更高效的数据管线
仿真里成功,换到真实环境完全失效sim-to-real gap,环境动态差异大加入domain randomization;用更多真实数据finetune;降低对仿真成功率的预期

5. 往真实场景延伸:HER的适用边界与扩展方向

5.1 不是所有稀疏奖励任务都能用HER

HER有效的前提是:任务里有一个“可以事后重新定义的目标”,并且这个目标对应具体可观测的状态。机械臂抓取、物体推箱子、导航到达某个坐标、游戏中到达指定位置,这些都是典型适配场景。因为它们都有一个明确的achieved_goal,可以拿出来换个标签重新定义任务。

但有些任务就不太行。比如目标是“把桌面上散落的7个积木叠成特定形状”,这种目标的评价强依赖整体状态组合,不能简单拆成“某个状态坐标是否可达”。你没法说“这次虽然没叠成塔,但把积木A移近了一点,所以目标改成移动积木A就算成功”——可以是可以,但这样的伪目标跟真实任务差距太大,学出来的策略对真实目标几乎没用。再比如目标是“写一段没有bug的代码”,状态空间根本不是一个可度量的连续空间,重标注无从下手。

所以判断一个任务适不适合HER,就问自己三个问题:目标能否表示为可采样的状态?状态之间能否定义距离度量?失败轨迹中是否包含“部分接近目标”的信息?三个答案都是肯定的,HER大概率有效;有一项不满足,就要考虑目标生成、课程学习、curiosity-driven探索等其他手段。

5.2 HER和课程学习、自动目标生成的组合

HER本身没有引入任何外部知识,它只是把轨迹中经过的状态当作目标。这意味着它能学的目标范围受限于策略实际探索过的状态范围。早期策略探索范围很窄,重标注目标大多是初始状态附近的点,学习信号覆盖面有限。一个自然的改进方向是:先用课程学习生成一批有挑战但不算太难的目标,让策略在这些目标上练熟,再逐步提高目标难度;HER负责把每一条轨迹的价值榨干,两者叠加效果比单独用任何一个都好。

我跑过一个抓取实验,单纯用HER训练,成功率卡在60%左右;改成“优先选择距离初始状态中等距离的目标”,再配合HER重标注,成功率最终稳定在90%以上。这里面有两层提升:课程目标给策略提供了更合理的学习顺序,HER则保证了每个目标下的失败经验也能被利用。

5.3 从仿真到真机:重标注目标必须物理可实现

仿真环境里重标注随便挑一个状态当目标没问题,反正状态只是一个向量。但真机上有个致命约束:重标注出来的目标必须是一个物理上可达、传感器能测量的状态。机械臂轨迹里经过的每一个关节角度当然都是可达的,但如果目标定义的是物体位置,而物体在被机械臂碰了一下之后就滚远了,那轨迹中“物体位置”的变化不是机械臂完全可控的,重标注这个目标就会让策略学到一些不真实的因果关系。

做真实机器人部署时,我的建议是:只在轨迹中机械臂本身的状态上做重标注,物体状态只作为原始目标参与训练;或者在重标注目标时加一个“该状态是否已稳定保持一段时间”的过滤条件。这算是一个踩坑经验,论文里不会写,但真机调试时非常关键。

最后再分享一个实操里的小技巧:HER跑完之后,别急着撤掉重标注逻辑直接部署。先在真实目标分布上单独做一小段精调训练,让策略从“整天面对伪目标”的状态适应回真实任务的分布。这一步通常只需要少量步数,但能明显降低仿真与真实之间的差距。我实际项目里靠这个简单操作,把抓取成功率从部署初期的50%拉到了接近70%。HER是个好工具,但用好它,重点永远在于理解它会如何扭曲你训练数据里的目标分布,并对症下药。

返回列表