"hindsight"直译过来就是"后见之明"。考试对答案的时候、复盘一场比赛的时候,我们总能清醒地意识到"刚才那步应该那样走"。但在强化学习里,让算法也拥有这种"事后视角",却是很多机器人控制任务从"学不出来"到"学得出来"的关键突破口。
这篇文章想聊的,就是基于Hindsight Experience Replay(HER)思想的强化学习项目。简单说,HER是一种让智能体学会从失败中提取经验的技术,它特别擅长处理一类让人头疼的问题——稀疏奖励。如果你正在做机械臂抓取、导航、拼积木之类的任务,训练半天loss死活不降,那这篇文章大概率能帮你找到原因,顺便给出一套可以直接上手的解决方案。
我会从问题本身讲起,把HER的原理用最直白的话拆开,再带你跑一遍最简实现,最后把我调参过程中踩过的坑全部摆出来。有理论基础,有代码,有排查表,读完就能用。
1. 项目背景:稀疏奖励问题为什么是强化学习的"无米之炊"
1.1 先搞清楚什么叫稀疏奖励
大多数人对强化学习的理解是"给奖励,让智能体学"。但真正的难点从来不是"怎么给奖励",而是"压根没有奖励可给"。
拿机械臂抓取举例。你给机械臂一个任务:把桌上的红色方块抓到蓝色盒子里。常规做法是设一个奖励函数——如果方块最终进入蓝盒,给+1;否则,每一步给0。这就是典型的稀疏奖励:在成千上万个时间步里,绝大部分动作都不会触发任何正反馈。智能体随机探索时,几乎不可能靠运气成功一次,于是它永远得不到一个非零的反馈信号,梯度无从算起,策略也就一直停留在原地。
这就像一个人被关在迷宫里,你不知道出口在哪,也没有脚步声的提示,只有当你真正走出迷宫的那一刻,才会听到一声铃声。问题是,你根本走不到那一刻。
为了缓解这个问题,工程上常用的是奖励塑形(reward shaping),比如给"方块靠近蓝盒"一个小的正向奖励。这个办法在简单场景里有效,但需要大量人工设计,而且很容易引入局部最优——智能体学会了把手伸向蓝盒,却不学会抓方块,因为接近蓝盒的奖励被优先榨干了。
1.2 传统算法为什么扛不住
把问题再往深一层看:稀疏奖励的本质,是把一个连续动作空间里无比漫长的搜索过程,硬生生变成了"一次性猜中密码"的极端情况。传统强化学习算法(比如DQN、DDPG、PPO)在每一步都能拿到有区分度的奖励时表现很好,因为TD误差能正常传播。
但一旦奖励全是零,整个价值网络就学不到任何有用的梯度。所有样本的回报都是同一个数值,Q值的更新变成无意义震荡。你可能遇到过这样的场景:训练日志里reward始终为0,critic的loss居高不下,智能体像无头苍蝇一样乱撞。这不是代码bug,而是问题结构本身超出了经典算法的能力边界。
这类问题的另一个特点在于,"失败"其实是有信息的。抓取任务中,即使这轮训练机械臂没把方块放进盒子,但它至少成功把方块推动了一点、靠近了一点,这些进度在传统的样本回放里全部被丢掉了。数据本身不缺,缺的是从数据中挖掘正向信号的手段。HER要解决的,正是这件事。
2. HER核心原理:用"后见之明"重写历史
2.1 事后目标重标记到底做了什么
HER(Hindsight Experience Replay)是OpenAI在2017年提出的思路,核心叫"事后目标重标记"(goal relabeling)。理解它之前,你先要接受一个设定:在强化学习里,一个完整回合(episode)是由"状态、动作、新状态、目标"四要素构成的。
普通强化学习记录一条经验时,目标是被固定的。比如目标就是"方块在蓝盒里",这一整回合不管发生了什么,目标都不改。于是如果最后没成功,这条经验就被当成纯粹的负样本扔掉。
HER的做法则非常"事后诸葛亮":它允许你在回合结束后,重新换一个"事后看来恰好达成了"的目标,再把这整条经验存进回放池。举个例子,机械臂这次没能把方块放进蓝盒,但意外把方块推到了桌面的右上角。OK,那我们就伪造一段历史——把这一回合的目标重写成"把方块推到右上角"。在这个新目标下,机械臂其实成功完成了任务,这条经验不再是负样本,而是带着+1奖励的正样本。
下次训练时,智能体就从"如何把方块推到右上角"这个相对容易的目标开始学起,逐步逼近最终目标。
这就是HER的全部秘密:把未达成的目标替换成已达成的状态,把失败轨迹重新标定为成功轨迹。
2.2 为什么这条路走得通
很多人第一次听说HER时都会疑惑:这样伪造目标,学到的策略还有意义吗?我们需要真正想清楚这个问题的答案。
关键在于,HER并不是要欺骗智能体,而是在制造一条"目标难度阶梯"。最终目标很难,但路径上的中间目标很多。比如"方块靠近蓝盒一点""方块被抬离桌面""方块到了盒子正上方"——这些目标在正常训练里没有任何显式奖励,但通过事后重标记,它们全部被转化成带正反馈的训练样本。智能体相当于先学会走,再学会跑,最后学会跳。
从优化角度看,这其实是改变了经验回放池的分布。原始回放池里几乎所有样本的奖励都是0,梯度信号极其稀缺。重标记之后,回放池里混合了大量"伪成功"样本,奖励非零的比例大幅提高,价值网络能学到的梯度信号密度也大大增加。一旦Q函数开始对"接近目标"的状态产生正面估值,策略梯度就有了方向,整个训练从"瞎猜密码"变成了"逐步逼近"。
在数学上,有一个更朴素的解释:多目标强化学习里,不同目标之间的价值函数存在共享结构。学会"推方块到右上角"的策略,天然包含了对"推方块到某个点在附近"的泛化能力。HER就是利用这种共享结构,让相对容易探索的目标来引导困难目标的学习。
2.3 适用条件与边界
HER不是万灵药,它有明确的适用前提。首先,任务必须可以被形式化为"目标达成型问题"。目标必须是一个可以事后检查是否达成的条件,你需要能判断"某个状态是否等于某个目标状态"或"某个状态与目标是否足够接近"。纯粹的打斗、对抗类游戏,目标无法简单量化,HER就很难套用。
其次,状态表示最好连续。HER的常见版本是为连续状态空间设计的(搭配DDPG、TD3这类连续动作算法)。离散状态理论上也可以,但重标记的效果会打折扣,因为离散状态之间没有距离概念,你很难判断"这个状态和目标够不够近"。
最后要意识到,HER解决的是"稀疏奖励"问题,不是"探索"问题。如果任务本身需要复杂的长程探索(比如先在迷宫里找到一个钥匙,再用钥匙开门),仅仅重标目标并不能让智能体凭空获得探索策略。实际应用中,HER经常需要配合更好的探索策略、课程学习或随机重置一起使用。
3. 实操复现:从零搭一个HER训练脚本
3.1 环境和工具选型
我自己复现HER时,最顺手的组合是Gymnasium + Stable-Baselines3(SB3)。SB3从2.0版本开始内置了HER支持,不需要从头写算法逻辑,省掉很多边角问题。
环境方面推荐两个:FetchReach和FetchPush。这两个都是经典的机器人操作环境,任务目标就是控制机械臂把物体推到目标位置,动作空间连续,回报设计天然是稀疏的——每一步奖励为0,只有在物体足够靠近目标时给一个非零奖励。非常适合验证HER的效果。
如果你的机器上没有这些环境,安装命令很简单:
pip install gymnasium pip install stable-baselines3 pip install gymnasium-robotics注意gymnasium-robotics需要额外注册,环境导入时偶尔会报版本冲突,建议用Python 3.9或3.10,并且固定gymnasium版本在0.29.x左右,兼容性最稳。
3.2 核心代码结构
用SB3实现HER训练,核心代码其实很短,但每一步都有讲究。先看整体训练脚本:
import gymnasium as gym from stable_baselines3 import HER from stable_baselines3.common.vec_env import DummyVecEnv from stable_baselines3.her.goal_selection_strategy import GoalSelectionStrategy # 创建环境 env = gym.make("FetchReach-v2") env = DummyVecEnv([lambda: env]) # HER要求向量化环境 # 选择目标选择策略 strategy = GoalSelectionStrategy.FUTURE # 构建HER模型,使用DDPG作为底层算法 model = HER( "MlpPolicy", env, policy_kwargs=dict(net_arch=[256, 256, 256]), goal_selection_strategy=strategy, buffer_size=int(1e6), gamma=0.95, batch_size=256, learning_rate=1e-3, verbose=1, ) model.learn(total_timesteps=int(2e6)) model.save("fetch_reach_her")这段代码看起来简单,但你要是不理解里面每个参数干了什么,后面调参会非常痛苦。
3.3 关键参数逐个拆解
第一个重点是goal_selection_strategy。SB3提供四种策略:FINAL(只重标为最终状态)、FUTURE(随机选取未来某个时间步的状态作为目标)、EPISODE(从同回合里随机选取任意状态)、VECTOR(与EPISODE类似但按向量方式采样)。我在FetchReach上对比过,FUTURE效果最稳,因为它选的目标总是在当前状态之后,天然满足因果性,不会引入"未来状态被提前预测"的伪样本问题。FINAL太保守,只用一个目标,样本多样性不足;EPISODE偶尔会出现目标状态和当前状态差距过大的情况,早期训练容易震荡。
第二个重点是buffer_size。HER对回放池大小非常敏感。池子太小,重标记产生的多样本很快被覆盖,训练不稳定;池子太大,老样本占比过高,新策略学到的经验无法及时反馈。FetchReach这种简单任务用1e6就够,如果换成FetchPush这种稍复杂的,建议上到1e6到2e6之间。
第三个重点是batch_size。HER每一条样本其实包含了多个"重写后的目标视角",相当于变相增大了有效batch。256是我日常调试的起点,显存不足或环境特别简单可以降到128,但低于128会让DDPG的梯度过噪,我实测效果会下降一截。
第四个是gamma。稀疏奖励任务里,折扣因子需要相对保守。0.95在FetchReach上够用,但如果任务的时间步很长(比如超过50步),建议降到0.9,让长期回报的贡献不要过早衰减。
还有一个容易踩的坑是policy_kwargs里的网络结构。网上的很多教程直接用SB3默认的MlpPolicy,两层64维的隐层。这种配置在CartPole上足够,但在机械臂这类高维连续控制任务里表达能力偏弱。我习惯用[256, 256, 256]三层结构,每层256个神经元。结构太大会导致过拟合和训练变慢,但在这类仿真任务里不太明显,稳一点更重要。
3.4 训练流程中的代码级细节
如果你不用SB3,想手写HER,那核心就在重标记和采样逻辑上。我建议按下面这个流程走:
每个回合结束后,先拿到完整的轨迹数据,包括每个时间步的状态、动作、奖励和新状态。然后按你选定的策略生成一组新目标,一般取4到8个。对每个新目标,重新计算该轨迹每一步的奖励:如果新目标恰好达成,奖励为0(注意,在很多实现里"成功"的奖励是0而不是1,因为环境用的是"到达目标距离小于阈值"这种稠密判定,成功本身是终止信号);否则,所有步的奖励还是0。把这条"重写目标后的轨迹"连同原始轨迹一起存入回放池。
实际训练时,采样batch数据后,对每条样本需要重新拼接目标向量。这里最容易出bug:目标向量必须和观测里的目标部分对齐。Fetch类环境的观测通常是一个字典,里面包含observation和desired_goal两个字段,重标记时只替换desired_goal,observation里的achieved_goal一般不需要动,但注意有些环境里的achieved_goal也会被拼进observation向量里,这时候如果直接替换desired_goal而不同时更新observation里的对应部分,智能体看到的状态和目标就不匹配了,等于学了个错误映射。
我从刚开始写HER到现在,踩过最多次的就是这个坑,后面排查表里会再提。
4. 训练效果对比与调参经验
4.1 开HER和不开HER的差距有多大
我拿FetchPush环境做过一组对比实验,基准算法是DDPG,一组开HER,一组不开,其余配置完全一致。训练200万步后,不开HER的DDPG几乎没有学到任何东西,平均回馈一直徘徊在-50左右(这个环境里步数与负奖励挂钩,等效于每回合都无法在限制步数内完成任务);开了HER的版本在第50万步左右成功率就开始明显爬升,最终收敛到95%以上的任务成功率。
这不是个别现象。在FetchReach上差距更极端——不开HER的DDPG基本无法启动,成功率一直贴着0;开HER之后大约10万步就能学到60%左右,30万步接近100%。
这个对比说明一个很朴素的事实:HER不是微调技巧,而是决定了你"能不能学到"的开关。如果任务满足目标达成型,且奖励稀疏到让人绝望,HER几乎应该成为默认选择。
4.2 k值到底该取多少
HER论文里有一个影响非常大的超参数,叫k——每个回合结束后,额外抽样重标记多少个目标。我实际测试的意见是:k取4是一个性价比极高的起点。
取k=1时,样本利用率太低,相当于每个回合只带来一份额外信息,训练速度肉眼可见地慢。取k=4时,回放池里"伪成功"样本的密度已经比较健康,训练速度和稳定性达到一个不错的平衡。取k=8到16时,样本多样性更高,但训练时间会明显变长,而且如果目标是那种特别容易达成的(比如物体就在原地附近),高k值会产生大量重复度极高的冗余样本,反而拖慢训练。
如果你用SB3,goal_selection_strategy=FUTURE时,k值是固定的,不需要手动调整。但如果你想精细控制,可以尝试EPISODE策略并手工调节每回合的重标次数。从经验来看,先在k=4上跑通,再横向对比k=8,收益比纠结其他超参数更高。
4.3 实际调参中的经验和几个坑
第一个坑在目标阈值的设置上。HER里判断"新目标是否达成",通常用欧几里得距离小于某个阈值。这个阈值如果设得太小,重标记后的样本依然全部是负奖励,HER就退化了;如果设得太大,重标记的样本全是假的成功,策略会学出一个"差不多就行"的偏差。我一般用环境原本的成功判定阈值,必要时放大1.2到1.5倍,但不能超过2倍。
第二个坑是HER和网络初始化的关系。HER很吃探索初期的样本多样性,如果你的策略网络初始权重导致智能体反复做同样几个动作,重标记出来的目标也很单一,回放池多样性不够。解决办法是适当提高动作噪声。DDPG的探索噪声标准差我习惯从0.2起步,如果训练初期成功率长期不动,试着提到0.3。
第三个坑是HER和奖励缩放之间的配合。HER里的奖励本身就是{0,1}跳变的,如果你在奖励上人为乘以一个系数(比如10),会导致Q值预测的方差变大,收敛变慢。我的原则是:HER所在的实验,奖励尽量裸奔——成功给1,失败给0,最多在末端加一个小额的生存惩罚(比如每步-0.01)来鼓励高效路径。
下面的表格是我在FetchPush上尝试不同参数组合后的效果摘要,仅供参考:
| 参数组合 | 100万步成功率 | 最终成功率 | 训练稳定性 |
|---|---|---|---|
| DDPG,无HER | 0% | 2% | 完全无信号 |
| DDPG + HER,k=1 | 10% | 65% | 后期有波动 |
| DDPG + HER,k=4 | 45% | 95% | 稳步上升 |
| DDPG + HER,k=8 | 50% | 96% | 前期略震荡 |
| TD3 + HER,k=4 | 42% | 98% | 最稳 |
5. 常见问题与排查技巧实录
5.1 训练不起来?八成是这几个原因
我把实际项目里反复出现的几类问题整理成了一张速查表,非常适合第一次跑HER的人。
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 成功率和reward始终为0 | 环境判定阈值过小,或者HER重标记目标判定失败 | 检查achived_goal与目标之间的距离分布,放大阈值 |
| 训练早期正常,后期突然崩坏 | 回放池里伪成功样本占比过高 | 降低k值,或增大buffer_size稀释旧数据 |
| 目标重标记后模型依然学不会 | achieved_goal与observation里目标部分不一致 | 打印obs字典结构,确认替换desired_goal时同步更新obs对应片段 |
| 两个不同目标之间的路径学乱 | 网络容量不足,无法区分多目标 | 加宽网络,从[256,256,256]起步 |
| 训练速度极其缓慢 | 每个回合都采样过多重标样本 | 检查k值,把不参与训练的纯出局目标过滤掉 |
| 评估时成功率很高,推演时失败 | 环境初始状态分布与训练不一致 | 检查环境是否使用固定的初始随机种子,统一设置seed |
这六类问题里,前三类的出现频率占到80%以上。特别是第二个"训练后期崩坏",我一度以为是学习率太大,后来发现在HER框架下,问题往往出在回放池状态分布被"假成功样本"污染了。回放池里如果超过50%的样本都是重标后的伪成功样本,智能体会越来越倾向于认为"随便动一动就能成功",真实策略反而退化。解决思路有两个:一是把k值从8降到4;二是每25万步清空一次回放池里的部分旧样本,让新策略的经验占据主导。
5.2 观察空间与目标空间的微妙关系
用HER之前,必须先弄清楚你所使用环境的状态表示方式。以Fetch类环境为例,它返回的观测不是单一数组,而是一个类似字典的结构,通常包含三大块:当前机械臂的关节角度、物体当前位置的坐标、期望目标的坐标。这个结构本身就是为了支持HER设计的。
但不同版本的环境,这个结构可能略有不同。我踩过的坑是:某些修改版环境会把achieved_goal直接拼进observation数组里,导致目标和观测的维度发生变化。如果你用的是这类环境,训练脚本里重标目标的时候,必须先修改observation里的对应切片段,再去改desired_goal字段。否则,你的网络输入是"旧目标+新动作"的组合,但它学到的目标相关性完全是混乱的。
调试方法很简单:每次训练后打印一批buffer里的样本,把obs、achieved_goal、desired_goal都单独列出来,肉眼检查一遍是否对齐。这个动作看起来原始,但能省掉后面一整天排查。
5.3 关于环境归一化的一个建议
如果你跑的是仿真环境并且长期训练,要留意状态空间的归一化问题。FetchReach这类环境的状态量纲已经比较合理,但换到自定义环境(比如真实机器人仿真里关节角度以弧度计、位置以米计、速度以每秒米计),这几个维度的数值范围差异极大。HER对Q函数的精度要求比普通任务高,因为它的目标是在多目标共享网络里同时逼近多个"差值状态",输入量纲不一致会让网络很难收敛。
我的做法是在训练前对所有观测维度做z-score归一化,均值和方差从初始随机采样的10万条数据里统计。HER的buffer里目标向量也要使用同样的归一化参数,否则重标后的目标与观测就不在一个坐标系里,模型直接在学垃圾数据。
6. 我的体会和收尾建议
我在实际训练机械臂抓取类任务时,切身体会到HER这类"事后视角"技术的意义远超算法本身。它本质上改变了一种看待失败的姿势:传统强化学习把失败当作要丢弃的负数,而HER把失败重新编码成了通往成功路上的里程碑。这个思路放在现实项目里也有启发——很多时候我们复盘失败,并不是为了否定过去,而是为了给下一步行动提供可以学习的正反馈。
如果你是第一次尝试HER,我强烈建议不要直接上最复杂的任务。先用FetchReach跑通整个流程,感受一下重标目标带来的训练信号变化;然后换成FetchPush,体验一下"目标难度提升后,HER什么时候开始不够用";最后再考虑把它接入你自己的自定义环境。
另外有一个小提醒:HER是样本级的技巧,它不排斥其他成熟方案。实际项目里,我经常把HER和TD3配合使用,再叠加一个简单的随机基于状态的课程学习(对初始状态和目标距离做渐进式调整)。这一套组合下来,很多原本学不动的任务都能在可接受的时间内跑出满意的成功率。
如果你现在的项目正卡在"奖励全零、训练不动"这个阶段,不妨先问自己一句:我们能不能换一个事后看来已经做到的目标,先把这局"失败的轨迹"变成一堂"成功的课"?这就是hindsight这个单词在强化学习里最迷人的地方。