1. 从一次失败的机械臂抓取说起:为什么需要HER
去年做一个机械臂抓取小项目时,我差点把DDQN、PPO全部轮了一遍,模型就是学不会把桌面上的方块推到目标点位。奖励给的是稀疏版本:只有方块进入目标圈,才返回+1。听起来特别合理,可智能体压根找不到这个+1。一位师兄说:要不试试Hindsight。他说的是Hindsight Experience Replay,后见经验回放,简称HER。我当时觉得这名字太玄了,后见之明不就是事后诸葛吗?直到跑完实验才发现,这恰恰是打通稀疏奖励问题最实用的套路之一。这篇文章想把HER的原理、完整实操和踩坑经验一次讲清楚,给同样被稀疏奖励折磨的人一份能直接抄的作业。
很多刚接触强化学习的人第一反应是:稀疏奖励不好学,那把奖励做密一点不就行了吗?我一开始也这么想,结果被现实教育了一顿。先说结论:稀疏奖励并不是环境设计得烂,而是很多真实任务天生就是稀疏的;真正的问题是你缺一层“重新解读经验”的机制,而这层机制正是HER的核心价值。
1.1 稀疏奖励问题的本质
想象一下你在玩射击游戏,只有正中十环才有奖励,擦边九环也没有分,而且游戏还要靠这个分数来教你瞄准。对模型来说,大部分动作都像打在棉花上,奖励为0,梯度也为0,策略更新自然原地踏步。强化学习的本质是“奖励驱动”,但稀疏奖励让整个空间的反馈几乎全是空的,模型根本不知道该往哪个方向改。打个更贴切的比方:你想让一个小孩学会投飞镖,但只有正中靶心才告诉他“对了”,其他时候一言不发,那他大概率只能靠瞎蒙。
机械臂抓取任务里最常见的设定是:每一帧观测到机械臂末端位置、物体位置、目标点位置;动作是末端的三维移动。如果只看“物体是否进圈”来打分,要么给0,要么给1,智能体一开始几乎随机乱试,能碰到目标圈的概率非常低。我从训练日志里看到的景象是:回合平均回报长期在-0.01附近波动,策略网络输出的动作基本像无头苍蝇,训练曲线看起来像一条快死掉的心电图。
如果你手动把奖励设计成“距离目标圈越近分数越高”,模型确实会动起来,但它很容易钻空子。我踩过的实际案例:模型发现高分数只跟“接近”有关,就开始在目标圈外围画小圈,永远不进去。这就是稀疏奖励的价值,它把任务定义得很干净,代价是难学。HER就是用来解决这个“难学”问题的,但它解决的不是奖励函数,而是经验回放里的标签。
1.2 奖励塑形的副作用,以及为什么HER不用塑形
奖励塑形是很多人最先想到的方案,它有几种常见做法:按距离给连续奖励、按相对距离变化给增量奖励、或者对危险动作加惩罚。设计得好确实能加速学习,但这些公式建立在“人类以为正确的路径”上,机器人常常在优化一个和你的直觉完全不同的目标。一个经典例子:你想让机械臂把瓶盖拧上,你给“盖子越水平分越高”的奖励,机械臂可能学会用蛮力把盖子顶翻,然后盖子水平朝上,这根本不是“拧紧”。
HER的思路完全不同。它不引入任何额外的距离公式,也不靠人工知识定义中间步骤,而是改变经验样本的标签:把一个失败片段用“假如目标本该在这里”的方式重新标记,从而让原本全零的奖励里长出新样本。一句话概括:它把“我原以为的目标”替换成“事后来看其实已经完成的目标”,在经验层面制造成功。
所以我在实际项目里,把奖励塑形代码几乎删光了,只留了最朴素的任务判定。看起来模型能获得的正反馈更少了,但因为HER给这些失败片段重新赋予了目标,数据里的“有效信号”反而更多。这也解释了为什么很多论文会说HER配合稀疏奖励效果更好,因为它的着眼点不在奖励设计,而在经验利用效率。
2. HER的“后见之明”到底是怎么偷师的
HER这个名字听起来玄,核心思路其实很直白:人搞砸一件事后复盘,往往会意识到“原来我做到的那一步,如果目标换成另一件事,就是成功的”。机器人也可以这样。强化学习里我们把这类经验叫做“失败中的成功片段”,HER就是把这些片段重新写进记忆的方法。
2.1 核心思想:把失败样本当成成功样本
先快速铺垫一点背景。在目标条件强化学习里,策略输入通常包含两部分:当前观测状态s和期望目标g。环境会在下一个状态s'里给出achieved_goal,也就是实际到达的位置。如果s'和g足够接近,奖励为1,否则为0。这个设定在机械臂、机器人导航和很多操作任务里都很常见。
经典的经验回放会存下这样的五元组:在当前状态s、目标g下,做了动作a,到达s',获得奖励r。问题在于,当目标g很困难时,绝大多数样本的r都是0。比如机械臂从正下方往上抬,目标是桌面上某个角落,它怎么乱动都不够接近,所以经验池里几乎没有正样本,策略网络学不到任何方向。
HER的做法是在回放阶段额外构造一个虚拟目标g'。怎么构造?方法很简单:拿这条经验里事后实际到达的achieved_goal作为新目标。也就是说,原来的经验是“从s出发,想完成g,结果到了achieved_goal,失败”;新的经验变成“从s出发,想到达achieved_goal,结果也到了achieved_goal,成功”。同样一组动作,因为目标变了,奖励从0变成了1。
用生活化的例子解释:你原计划做番茄炒蛋,结果做成了番茄蛋花汤。如果按原计划评价,这锅菜是失败;但如果把“做一碗番茄蛋花汤”当作目标,它其实是标准成功示范。HER就是把这些额外成功示范重新写进记忆,让模型知道“从当前状态出发,通过这样的动作,可以达到什么样的状态”。这种知识多了以后,模型就能逐渐学会从任意状态逼近目标。
2.2 目标采样策略:四种玩法
虚拟目标不能乱造,造得不好会让训练发散。目前最常用的有四种目标采样策略:final、future、episode、random。区别在于从哪取“事后实际到达的状态”。
| 策略 | 虚拟目标来源 | 特点与适用场景 |
|---|---|---|
| final | 整条轨迹最终状态 | 实现简单,适合单阶段任务,但会让成功集中在轨迹尾部 |
| future | 离当前时间点t之后1到k步的状态 | 最常推荐,目标自然连贯,适合连续控制 |
| episode | 从同一条轨迹里随机抽一个状态 | 比future更随机,目标分布广,稳定性略差 |
| random | 从整个回放池里随机抽一个状态 | 目标可能与当前转移毫无关系,训练方差大 |
我最常用future策略。具体来说,一条轨迹是由一串转移组成的,比如第0步、第1步一直到第T步。对第t步的状态转移,我从[t+1, t+k]这个区间里随机抽一个时间索引h,然后把第h步的achieved_goal当作虚拟目标。这样虚拟目标在时间上比当前更靠后,模型学到的意思是:从t步这个状态出发,继续往前走,本来就能到达h步那个位置。它把“前向动力学”教给了策略,而不是硬造一个只能靠巧合到达的目标。
final策略我也会用,但只用一小部分,比如30%到50%的HER样本用final,剩下的用future。原因是final会让许多不同时刻的转移都指向同一个最终目标,如果整条轨迹很长,中间很多状态离最终目标很远,反而会拉高任务的难度。future则更温和,目标距离当前状态更近,学习曲线更平稳。random我基本不用,因为目标可能来自完全不相干的任务,会导致Q值更新强烈震荡。
2.3 为什么HER有效:把探索变成利用
强化学习里有一个“死循环”:探索不到成功,就没法利用经验;没有经验利用,探索效率又很低。HER的核心价值在于,它把探索过程中无意达到的状态重新定义为目标,让那些“差一点成功”的轨迹也能变成正样本。换句话说,它没有增加探索能力,但让探索结果被更充分地利用。
我在实验中看到的很明显现象:训练早期,机械臂完全随机摆动,几乎没有任何一次真的走到目标点。但HER会把这些随机轨迹里的某个中间状态当作目标,于是回放池里立刻出现了“类成功样本”,Critic开始有东西可学。随着策略慢慢学会到达这些“容易目标”,它就越来越容易在真实探索中接近原始目标,形成一个正循环。
这也能解释为什么HER对稀疏奖励特别有效:它不需要你手动设计中间奖励,也不需要你精心安排课程,而是自动地从失败中挖掘出成功。它并不神奇,只是更聪明地利用数据。尤其是当环境维度不算太高时,HER甚至能把原本几乎不可能收敛的任务,在几小时内训练出能用的策略。
3. 实操:用DDPG+HER训练机械臂抓取任务
原理聊完,接下来进入正题。我在项目里用的是DDPG加HER,这是OpenAI Robotics里最经典的组合。下面会把环境、代码、训练配置全部跑一遍,并给出一份可以直接复用的关键实现。
3.1 环境选型与安装
我选的是Gym仿真环境里的FetchReach任务。FetchReach的目标是让机械臂末端移动到指定位置,状态空间包含机械臂末端坐标、物体坐标、目标坐标等,动作是三维连续控制。之所以选这个环境,是因为它的奖励是标准稀疏奖励:末端位置与目标点的距离小于某个阈值,才返回1,否则返回0。
安装上,如果你用的是新版Gymnasium,可以执行:
pip install gymnasium-robotics然后在代码里创建环境:
import gymnasium as gym import gymnasium_robotics gym.register_envs(gymnasium_robotics) env = gym.make("FetchReach-v1", render_mode=None)如果你本地装了老版gym,也可以用gym.make("FetchReach-v1")。需要注意版本兼容问题,尤其是observation_space结构和info里的achieved_goal字段。新版接口默认把achieved_goal放在observation的字典里,读取时别搞混。
FetchReach的状态维度不高,动作只有3个,拿来验证HER再合适不过。如果你的目标是抓取物体并放进托盘,可以换成FetchPush或FetchPickAndPlace,代码逻辑几乎不用动,只需额外处理物体坐标和关节角度。
3.2 网络与训练流程
DDPG分成两个网络:Actor负责根据当前观测和目标输出动作,Critic负责评估这个动作的价值。输入部分需要把observation和desired_goal拼起来,动作空间归一化到[-1, 1],连续控制场景一般用tanh输出。我在项目里没有用太花哨的网络结构,Actor是两层256个神经元的MLP,Critic也是两层256,配合LayerNorm,训练稳定性明显好很多。
HER的代码核心不在网络,而在经验回放的写入逻辑。我贴一段关键伪代码,这段是我在项目里实际用的简化版:
def her_replay(episode, episode_goal, strategy="future", k=4, n_her=4): for t, (obs, action, reward, next_obs) in enumerate(episode): # 原始样本先入池 replay_buffer.add( obs, action, reward, next_obs, desired_goal=episode_goal ) # 构造 HER 样本 if strategy == "future": low = t + 1 high = min(t + k, len(episode) - 1) if low > high: continue for _ in range(n_her): h = random.randint(low, high) virtual_goal = next_obs["achieved_goal"][h] new_reward = reward_func( obs["achieved_goal"], virtual_goal ) replay_buffer.add( obs, action, new_reward, next_obs, desired_goal=virtual_goal ) # final 策略只取轨迹最后一步作为虚拟目标 if strategy == "final": virtual_goal = episode[-1]["achieved_goal"] new_reward = reward_func( obs["achieved_goal"], virtual_goal ) replay_buffer.add( obs, action, new_reward, next_obs, desired_goal=virtual_goal )reward_func这里就是判断两个坐标距离是否小于阈值,小于给1,否则给0。注意一个容易被忽略的细节:虚拟目标用的是“未来某个时刻的achieved_goal”,而不是状态本身。如果直接把next_obs的完整向量当目标,那维度会和desired_goal不一致,训练直接报错。
训练循环里,每次从回放池随机抽一个batch,Critic更新时用原始奖励和HER奖励混合的样本,Actor更新时只使用原始目标来计算目标条件策略梯度。这也是为什么我强调HER不是简单地“把失败标成成功”,而是要在数据流层面确保目标分布合理。
3.3 训练效果分析与对比
我先跑了一版纯DDPG,没有HER,在FetchReach上训练了80万步。成功率曲线像一条死线,一直贴着5%到10%,偶尔还会跌到0。然后我加上future策略的HER,每个转移额外生成4个虚拟目标样本,同样训练80万步,最终成功率稳定在90%以上。当然,这个数字依赖随机种子和网络宽度,但趋势非常明显。
为了公平对比,我固定了网络结构、优化器、学习率和探索噪声,唯一变量是HER开关。下面是这个配置下的简化对比:
| 配置 | 40万步成功率 | 80万步成功率 | 训练稳定性 |
|---|---|---|---|
| DDPG无HER | 约2% | 约6% | 曲线长期贴地 |
| DDPG加HER(future) | 约45% | 约92% | 稳步上升,波动较小 |
这个对比让我当时挺震惊的,不是因为HER多厉害,而是因为它只是改了回放样本的“目标标签”,就把一个几乎学不动的任务救活了。后来我又试了把n_her从4调到8,发现成功率没有再明显提升,反而训练前期的Q损失波动更大。所以“HER样本越多越好”这句话是错的,需要控制比例。
4. 训练中遇到的坑与排查技巧实录
纸上谈兵容易,真正动手跑实验,问题一堆。我把这段时间踩过的坑梳理成四个比较典型的场景,每个都有明确的排查思路。
4.1 目标采样漂移导致Q值振荡
第一次跑HER时,我为了让模型快速学到东西,把final策略和future策略混在一起,每5步换一个策略,结果Critic损失像过山车,时不时冒出几个巨大的尖峰。原因其实很简单:同一个transition,刚才被分配的目标还是近在眼前的状态,下一次就被分配成轨迹末尾的遥远状态,奖励要么1要么0,很容易让Q值更新互相打架。
我的解决办法是把目标采样策略固定下来,不要频繁切换。实际项目里我用的是70%的future样本加30%的final样本,并且保证在一个batch里,来自同一条轨迹的样本不要超过batch_size的三分之一。另外,每个转移生成HER样本的数量也固定为4,不搞动态调整。稳定之后,Critic损失曲线虽然还有锯齿,但不再出现爆炸式尖峰。
4.2 归一化不能省
FetchReach里末端坐标的范围大概在1.5米左右,目标坐标也在这个范围,看起来数字不大。但Critic要同时处理原始目标距离和0/1奖励,尺度差异会让训练非常敏感。我踩过一个具体坑:reward直接设为1,Q值上线就发散,loss跑到几百;reward改成0.01,又觉得训练太慢。后来把所有obs和goal先做归一化,减均值除标准差,再把奖励固定成0.1,问题才稳定下来。
这里的关键是,HER样本中虚拟目标分布会比原始目标分布更集中,如果不对输入做归一化,Critic很难从不同尺度的目标里提取一致的价值信号。我建议在环境返回观测后立刻做标准归一化,而不是丢给网络里自己学。网络自己有归一化层也可以,但收敛会慢不少。
4.3 探索噪声与确定性策略的平衡
DDPG本身是确定性策略,必须靠额外噪声来探索。HER需要的是“随机的失败”,因为只有足够多样的失败,才有足够丰富的“后见目标”。我的第一个版本给动作加的高斯噪声标准差是0.05,结果机械臂几乎不动,因为动作范围是[-1,1],0.05噪声太小了。训练了10万步,回放池里所有轨迹都缩在初始位置附近。
后来我把探索噪声调大,初始标准差设为0.3,并在前30万步线性衰减到0.1,训练速度明显加快。这里给个小经验:如果训练一开始成功率就是0,不要先怀疑HER,先检查探索噪声。没有多样性,后见之明也白搭。
4.4 常见问题速查表
我把几个高频问题整理成一个简单表格,方便你对照处理。
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 损失下降但成功率不涨 | 目标采样太单一,Q值指标失真 | 混合future和final,调整HER样本比例 |
| 训练前期梯度爆炸 | 奖励尺度太大,输入未归一化 | 奖励乘以0.1,obs和goal做标准化 |
| 机械臂一直不动 | 探索噪声太小 | 增大高斯噪声到0.2到0.3 |
| 成功率卡在50%不动 | 虚拟目标分布与真实目标分布差异大 | 增加随机种子,提高未来采样步数k到8 |
| 行动作很抖,来回震荡 | 探索噪声没衰减 | 设置噪声衰减计划,后期降到0.05以下 |
这五个问题我在不同环境里至少遇到过四次,每次排查完都有一种“原来才是真正原因”的感觉。如果你也卡住,不妨从这张表开始查。
5. 从算法到做事:一点个人心得与后续扩展
HER对我最大的冲击不是提升训练指标,而是改变了我设计强化学习方案的思路。以前我一遇到稀疏奖励,脑子里的第一个念头永远是加奖励函数,现在我会先问自己:这个经验数据里,有没有被忽略的成功片段?这个问题听起来很朴素,但非常有用。
5.1 我在实际调参时最看重的一个设定
如果只让我保留一个经验,那就是:HER的目标分布一定要和策略当前的能力匹配。训练早期,目标应该偏向容易达到的状态;训练中后期,再逐渐增加原始目标的比例。很多人直接上手就疯狂加final样本,反而让策略被困难目标压垮。我把采样策略配成70% future + 30% final后,训练曲线是最平滑的,成功率也最高。
另外,回放池容量不要太抠。HER会在每个转移上额外生成4个样本,所以回放池的写入量会变大,需要相应扩大容量。我最初用50000条经验,很快就被原始样本挤满了,HER样本根本没空间存。后来改成300000条,效果立刻不一样。内存能扛就尽量给大一点。
5.2 让HER走出机械臂任务
HER的思路不止适用于机械臂。很多带目标条件的任务,比如导航、游戏、推荐系统,都可以借鉴这套“事后重新标记目标”的方法。搭配Offline RL时,HER也可以补充数据多样性,让离线数据集里的失败轨迹重新发光。我在另一个项目里,把HER用在一个简单的仿真环境中,用不到几万条交互数据就训练出一个能绕开障碍的智能体,效果出乎意料。
不过也有不适合HER的场景。如果任务的成败完全取决于一步决策,比如“猜硬币正反”,事后没有连续状态可作目标,HER就无从下手。我的判断标准很简单:只要任务存在连续的状态过渡,并且可以定义achieved_goal,HER就有发挥空间。
最后分享一个踏实的建议:如果你正在被稀疏奖励折磨,别急着写复杂的奖励塑形公式。先搭一套DDPG或TD3,把HER的future策略接进去,固定随机种子跑一夜。很多时候,一个简单的“后见之明”,比你想破头的奖励设计更靠谱。