
1. 从事后聪明说起HER到底在解决什么问题做强化学习的人应该都有过这种经历辛辛苦苦搭好环境、写好智能体结果训练了半天reward曲线纹丝不动智能体就像没头苍蝇一样在环境里乱转。尤其是那些目标导向型任务比如机械臂抓取、机器人导航、迷宫寻路如果目标位置是随机生成的大多数回合里智能体连靠近目标这种最基本的信号都收不到更别提完成目标了。这里面最核心的症结就是稀疏奖励问题。环境只在智能体真正达成目标时给一个正反馈其他时候一律给0甚至给负值。在这样一辈子中一次彩票的反馈条件下常规的强化学习算法几乎无从下手——因为智能体需要靠奖励信号的梯度来调整策略而稀疏奖励意味着梯度信号基本为零探索全靠瞎蒙。那怎么办一个思路是人手工设计密集奖励比如离目标每近一点就给一点分。这招在简单环境里好用但一旦任务复杂手工设计奖励函数本身就变成了一场灾难而且很容易被智能体钻空子——经典案例就是某些智能体发现原地打转能刷分就再也不去做正事了。另一个思路更有意思让智能体学会从失败中学习。这就是这一篇要聊的Hindsight Experience Replay也就是常说的HER中文可以理解成后见之明经验回放。它的核心思想源于一个非常朴素的观察人类的事后聪明往往比事前预判简单得多。你投篮没投进但球落在了某个位置如果你把让球落到那个位置当作目标那刚才这次投篮就是一次完美的成功示范。HER正是把这种逻辑搬进了强化学习训练里把每一个失败的轨迹重新标记成另一个目标下的成功样本从而让稀疏奖励任务也能榨出足够的学习信号。这篇文章适合正在研究或者说正在使用强化学习做机器人控制、导航、推荐等方向的朋友特别是发现自己训练过程怎么都学不会的情况。我下面会把HER的原理、实现步骤、踩坑记录和排查经验都摊开来讲争取让你看完之后能直接在自己的代码里落地。2. 为什么稀疏奖励会让强化学习学不动2.1 探索与利用的困局先花点篇幅把问题本质说透。强化学习的优化逻辑本质上靠的是试错反馈agent在环境中采取动作环境返回下一状态和奖励agent根据奖励修正策略。这个过程能不能收敛关键取决于一个前提奖励信号要足够频繁、足够让agent区分不同动作的好坏。在稀疏奖励设定下假设一个episode长度为H智能体在没有任何先验知识的情况下随机探索成功概率只有PP往往远小于1。那么在训练初期每一万个episode里可能只有几个是成功的这些稀少的正样本根本不够让神经网络学到有意义的梯度方向。更麻烦的是即使智能体已经摸到了一些成功的路子它也没法把哪几步动作是有效的归因清楚因为成功和失败之间的因果链条被大量无关探索动作给淹没了。正常环境下这个问题可以通过reward shaping奖励塑形来缓解但就像上面说的手工设计奖励会带来新的问题一方面你需要对任务有非常深的理解知道哪些中间状态值得奖励另一方面智能体会产生奖励黑客行为一味追求高奖励的捷径而不是真正学会任务语义。HER的好处在于它不改变reward函数的设计逻辑而是改变数据的流向——把本来失败的数据改造成成功的数据从数据层面解决稀疏奖励问题。2.2 你的目标可能只是没设对HER最反直觉的地方在于它把目标未达成这件事换了一个角度去看。常规的强化学习里目标是由环境预先给定的agent在episode开始时就知道我要把机械臂移动到坐标(1,2,3)然后整个episode中它只能朝这一个目标努力。HER的改造思路是agent在episode结束时如果没能到达环境中给定的目标那就把实际到达的状态当成一个新的目标并把这整条轨迹当作一个成功轨迹存进经验池。比如说我们的任务是让机械臂抓取桌面上的一个红色方块方块位置是随机的。某个episode中目标位置在坐标(0.5, 0.3, 0.2)但智能体一顿操作之后机械臂末端最后停在了(0.7, 0.5, 0.1)——没抓到这个红色方块。按照传统经验这个episode就是纯失败的扔掉或者只留下负反馈。但在HER眼里这条轨迹可以改写成目标为(0.7, 0.5, 0.1)时智能体成功到达了目标。因为这个改写后的目标就是智能体实际达到的状态所以这条轨迹的每一步都可以打上达成目标的正标签成为监督信号浓烈的训练样本。一个心智模型是这样的你在学投篮教练让你投进篮筐你十投九不中每个球都偏离了。如果只让你看着篮筐练你练一天也不知道怎么调整手腕。但如果你把球飞到篮板左上角也算一种成功把球碰到篮筐前沿也算一种成功每个球就都有了反馈你就知道不同出手力度和角度会产生什么结果。HER做的就是这个把每个球落点都当成目标的事。2.3 从数学角度看HER为什么有效形式化一点。传统强化学习里一条transition可以表示为(s, a, r, s, done, g)其中g是环境给定的目标。HER在每一条transition上做一次目标替换新目标g被设定为该episode结束时实际达到的状态s_T更普遍的做法是对每个时间步都用整个episode的终止状态做替换但后面有更细的变体。替换后transition变成了(s, a, r, s, done, g)其中r是根据是否到达g重新计算的奖励。因为gs_T在episode的最后一步s必然满足到达目标的条件因此r0或正奖励doneTrue。在其他时间步智能体未必立即处于g的位置所以r可能是负奖励或0。这意味着什么呢意味着原来一整条全程无正反馈的轨迹现在每一步都有了明确的学习信号——虽然大部分是负信号但负信号在稀疏奖励任务中同样是金子因为负信号能告诉智能体哪些动作不值钱从而加速排除无效的探索分支。很多人在初看HER时以为它只是把失败轨迹粉饰成成功轨迹其实关键在于任何轨迹在以实际终点为目标的视角下都是一条精确的、逐步逼近目标的最优轨迹。因为智能体最终到达的位置必然是从起始状态通过一系列动作得到的所以状态序列天然从起点连续演进到目标状态这条轨迹可以视作一条可实现的成功示范这种示范比随机采样的成功演示质量更高因为它是agent自己的行为链与其当前策略的分布高度相关。3. HER背后的关键设计细节3.1 目标本身该不该参与状态表示这是很多人在工程实现时第一个犯迷糊的地方。在HER设定中目标g通常不是独立于状态之外的标签而是会作为一部分输入拼接到状态表示里。拿机械臂抓取来举例如果状态是(关节角度1..N, 末端位置x,y,z)目标g常常就是目标末端位置(xg, yg, zg)拼接后的网络输入就是7N维。这样做的原因是策略必须知道我要去哪里才能决定下一步动作。如果你把目标从输入中拿掉网络就完全不知道这个episode的目标是什么自然不可能学出正确的策略。这一点和普通强化学习环境有区别——很多入门demo比如CartPole、MountainCar里并没有显式的目标概念环境本身的目标就是不倒或者到山顶这种目标对每个episode是固定的。HER面向的是多目标环境每次episode的目标可以变化所以目标必须作为可变化的条件输入。实操中有一个细节目标坐标和状态坐标最好做同样的归一化。如果状态里的末端坐标范围是[-1,1]目标坐标也应该是同样范围否则网络很难学到两者之间的关系。我见过不少新手直接把真实物理坐标拼进去大小差了好几个数量级训练出来的策略要么NaN要么极不稳定。3.2 目标重标记的四种策略HER论文里对如何选取新目标g做了系统性对比后来也成为大家落地时的标配选项。概括下来是四种策略区别在于从哪个状态里抽样作为新目标final只取episode的最后一步状态作为新目标。最简单计算量最小但不一定最优。因为最后一步状态可能是一个极小概率到达的位置用它当目标会让样本比较单一。future从当前时间步之后的未来状态里抽样。论文里推荐这种策略因为未来状态是在当前策略下可达到的用作目标更自然不会出现目标太随机、训练不稳定的情况。episode从当前episode的所有状态里均匀抽样。random从replay buffer中随机抽取一些状态不限于当前episode作为新目标。实践下来future策略是最稳定的选择特别是在探月、抓取这类连续控制任务里。random策略覆盖面更广但目标过于发散容易让网络在多个目标之间摇摆。final策略在episode很短的环境比如几步就能走完的任务里表现尚可episode一长就成了噪声源。需要注意的是这四个策略没有绝对的优劣和任务特性关系很大。如果环境的状态空间具有强连续性且目标分布偏均匀future和random都可能表现不错如果状态空间存在明显的瓶颈区域比如迷宫只有一个窄通道final就会因为目标多落在瓶颈区域而让训练更聚焦。3.3 奖励函数如何配合HERHER本身不关心奖励函数的具体形式但它有一个隐含要求奖励函数必须能根据给定的目标g计算出对应的奖励值。在实际落地里最常见的搭配是稀疏奖励距离阈值如果distance(state, goal) thresholdr0否则r-1。注意这里0和-1的选择也有讲究。有些任务希望agent尽快达成目标会用r-distance这样的密集奖励。但在HER框架下我强烈建议不要用距离式奖励原因在于距离式奖励会把偏离目标很远时有微小惩罚和接近目标时有微小奖励混在一起梯度信号变得含糊HER带来的后见之明优势就被稀释了。反倒是一个简单的到达即0未到即-1能让每条重标记后的轨迹都变成清晰的关于某个目标的最优行为链学习效率最高。还有人会问那要不要给未到达更大的负值比如-5、-10我的经验是没必要甚至有害。在HER重标记后的样本里一个episode内每一步的未到达惩罚都是-1总回报就是-HH是episode长度。如果调成-5总回报变成-5H梯度尺度变大会影响学习率的稳定性得不偿失。3.4 HER必须搭配off-policy算法使用这是HER最大的使用边界。HER做的事情是改写经验池里的样本这本质上是一种数据增强它依赖离线使用历史样本的能力。如果算法是on-policy的比如PPO、REINFORCE、A2C那么每次策略更新后旧的轨迹就不再符合当前策略下产生的假设强行用它们更新策略会产生巨大偏差。而off-policy算法DQN、DDPG、TD3、SAC可以通过重要性采样或直接复用历史样本来缓解这个问题所以它们才是HER的正确搭档。实际使用中最常见的组合是HERDDPG和HERTD3机械臂抓取、灵巧手操作这类任务里的SOTA方案多有HER的身影。如果你用的是SAC也可以直接套用HER因为SAC本身就是off-policy的只需在replay buffer读写时同时处理两份transition即可。4. 实战落地一套完整的HER实现流程4.1 环境与架构准备我拿一个经典的二指机械臂推动滑块到目标位置的仿真任务来演示。状态空间定义为(tcp位置xyz, 滑块位置xyz, 目标位置xyz)动作空间是(水平位移x, 水平位移y, 夹爪开合)这些连续量。环境每次reset时随机化目标位置和滑块初始位置episode长度固定为H50。算法骨架选择TD3HER不选DDPG是因为TD3在连续控制上更稳少了很多调参的痛苦。网络结构用三层MLP隐层256激活函数ReLU优化器Adam学习率3e-4梯度裁剪1.0。replay buffer容量10万条mini-batch 256。4.2 replay buffer的双轨存储策略这是HER工程实现里最核心、也最容易出错的地方。每条transition不仅要存原始的经验元组(s, a, r, s, done)还要带上与之对应的原始目标和HER重标记目标的差异信息。实操中我的做法是存储原始的(s, a, r, done, g_origin)同时在该episode结束时根据选择的HER策略比如future采样若干个新目标g_her然后为每个g_her生成一条新transition(s, a, r_her, done, g_her)一并写入buffer。这里的重新计算r_her不是糊弄而是要严格按前面的奖励函数定义算一遍根据该时间步的s和g_her的距离判断是否到达。有些实现为了省事直接最后一步设为0其余设为-1这在future策略下问题不大但在episode或random策略下就是错的——因为中间时间步也可能恰好到达了未来某个状态这时应该给0而不是-1。4.3 关键训练循环的伪代码下面给出一个可以直接参考的伪代码框架注释里标了每一步的意图初始化TD3智能体actor, critic1, critic2, target网络 初始化replay buffer R for episode 1, 2, ... do 随机初始化环境得到初始状态 s 随机采样一个原始目标 g_origin episode_buffer [] for t 0, 1, ..., H-1 do 根据当前策略加探索噪声选择动作 a 执行动作得到下一状态 s、奖励 r基于g_origin计算 存储transition (s, a, r, s, done, g_origin) 到episode_buffer s s if done: break end for # HER重标记阶段 for 每一步 transition in episode_buffer: # 写入原始transition R.store(transition) # 采样K个新目标 for k 1, ..., K do g_her 根据future策略从episode_buffer的未来状态中采样 r_her 根据g_her重新计算奖励 R.store(s, a, r_her, s, done, g_her) end for end for # 策略更新阶段 for 每G步 do 从R中sample一个mini-batch 更新critic 每若干步更新actor和target网络 end for end for关于K值每条轨迹重标记的目标数量论文里做了实验范围从1到8效果在大范围内都比较稳定。我自己的经验是初始阶段用K4训练中后期可以降到K1或K2。原因很简单训练初期策略太差原始轨迹的有效信息极少需要大量重标记样本来撑起训练中后期策略开始走上正轨原始成功轨迹变多重标记太多反而会让网络被事后目标主导弱化真实目标上的表现。4.4 关于done标志的一个大坑这是一个知道的人不多、但踩到就非常头疼的问题。在HER重标记后的transition里done的标志要特别小心。在原始目标下某个episode最后的done可能是False因为episode只是时间耗尽并没有达成目标或者True达成了目标。但重标记后以g_her为目标时最后一步的done应该设为True——因为s_T就是目标本身无论环境原本是否认为episode结束到达目标这个语义下任务已经完成了。这个细节直接关系到TD3/DDPG这类算法中target critic的计算。如果你维持doneFalse目标网络的更新就会引入bootstrapping到未来的错误信号把已达成目标误判成还要继续探索直接拉低训练效率。反过来如果你在原始目标已经达成的情况下把done设成False也会让智能体以为自己仍然需要探索导致策略在达成目标后反复震荡。所以我在代码里的做法是原始transition的done按环境真实值保存重标记transition的done按是否到达g_her重新计算一旦到达就置True不是简单地沿用原始done。这个细节值得在代码里单独加注释防止以后自己都看迷糊了。# 伪代码示意重标记done if distance(s_next, g_her) threshold: done_her True else: done_her False4.5 训练过程中的参数调节建议参数方面有四个我后来反复调了很久的地方奖励阈值threshold在仿真里这个值决定多近算到达。设太大目标很容易满足但策略精度很差设太小样本里正样本率太低训练初期很容易崩。我一般从环境尺度的手掌/夹具尺寸出发取夹具直径的1/2到1/3这样既不至于太宽松也不至于太难达到。探索噪声stdDDPG/TD3训练早期都需要较大的探索噪声让agent见识更多状态。HER环境下目标分布广探索不足会让重标记出来的样本过于集中在小区域。我常用std0.3起步训练到成功率高后再衰减到0.1。replay buffer里原始样本和重标记样本的比例实际训练中如果重标记样本占比太高比如K8网络会过度拟合事后目标在真实目标上表现反而下降。我见过一个粗暴但有效的做法按比例8:2混合原始目标和HER样本即每条原始轨迹配4条HER轨迹但采样时限制HER样本不超过一个batch的80%。episode长度HHER对H的长度非常敏感。H太长重标记的目标与该步状态之间的因果关系弱因为中间隔了太多步网络很难把当前状态和几十步之后的目标关联起来H太短一个episode采集不到足够多样的状态做重标记。我的经验是先固定H在任务最短成功上限的1.2到2倍之间。5. 训练中常见的坑与排查实录5.1 训练初期loss就炸了问题在奖励尺度遇到loss爆炸第一反应不是去调网络架构而是先检查reward的尺度。在HER里有一类隐蔽问题重标记后的目标来自未来状态而这些未来状态和当前状态的距离差异很大奖励值会骤变。如果在奖励函数里用了距离式惩罚比如r-distance前几步可能是-0.1后几步突然变成-2.5梯度爆炸几乎是必然的。解决方案有两个一是换回稀疏奖励到达0、未到-1二是对奖励做归一化。贸然改变网络结构解决不了根本问题因为梯度爆炸的源头在奖励尺度的不连续。我现在每次新环境上来第一件事就是print几个episode的reward范围看看有没有异常。5.2 学会了重标记目标真实目标反而学不会这个现象很诡异但也有明确的成因。当K值过大、重标记样本占比过高时policy会过度优化能达到的后见目标而真实目标在经验池里的占比被稀释。打个比方一个人如果每天训练的任务都是把球扔到随机位置就算成功那他自然会选择最省力的扔法而不是去瞄准真正的篮筐。解决办法是降低K值、限制重标记样本的batch占比同时可以加一个技巧对重标记样本和原始样本分别维护两个loss权重让原始样本的loss权重更大。这个在TD3里实现成本很低就是在计算critic loss时给HER样本乘以0.5~0.8的系数。5.3 为什么reward明明在涨成功率却没涨这是最让人头疼的情况之一。reward上涨但成功率不上涨通常说明网络学到了如何在目标附近获得更密集的奖励而不是如何达到目标。这种情况在高维连续控制任务里很常见因为TD3/DDPG这类actor-critic方法很容易被局部最优的Q值带偏——critic对目标附近的Q值估计过于乐观actor就倾向于把动作末端停留在目标附近反复试探而不是真正完成到达-离开-再到达的完整轨迹。这里我分享两个实操技巧降低critic的学习率比如从3e-4降到1e-4让Q值的更新更平滑减少乐观偏差的放大。增加真实目标的成功样本做法是训练过程中定期把成功episode完整保留不做任何重标记处理单独放进一个精英buffer里采样时以一定比例混入batch。这相当于给actor一个真实成功的示范抵消HER样本对Q值的污染。5.4 环境状态空间太大、目标维度爆炸怎么办有些任务的目标不是一个坐标点而是一整张图片比如视觉抓取。这时HER直接拿原始像素做目标替换是不现实的因为状态维度太高距离计算也失去意义。常规做法是先训练一个encoder把视觉observation压缩成低维向量再用这个向量作为目标参与HER。比如抓取任务里先用一个VAE把当前相机图像编码成32维的latent code然后目标g就是这个latent code到达目标就定义为当前图像编码和目标编码的欧氏距离小于阈值。HER照样可以工作只是重标记的目标从实际到达的坐标变成了实际到达时的图像编码。这种做法在不少视觉机械臂论文里都能看到工程上也比较成熟。5.5 连续控制中动作尺度不一致导致策略抖动如果你在机械臂任务上训练动作空间里有的是位移0.1量级有的是角度1.0量级有的夹爪开合0.5量级量纲不一致会让网络难以学习。我通常对动作做scale把所有动作分量先归一化到[-1,1]再乘上对应的物理上限去执行。这不仅让训练更稳定也让HER重标记后的愿望目标分布更规整——因为目标g往往来自状态空间状态空间的归一化同样重要。# 归一化示例 action_scaled np.clip(action, -1.0, 1.0) * action_scale这个技巧听上去很基础但在HER这种大量重标记样本的训练模式下任何输入分布的不均匀都会被放大所以在工程阶段值得提前处理。6. HER的适用边界与扩展方向6.1 不适合用HER的场景HER不是万能药它只适用于目标可达的任务。如果你的任务目标是不能让小车翻倒这种stay-safe类型没有显式的goal空间可替换HER无从下手。另外如果环境里没有多目标设定只有单一固定目标HER的效果也会大打折扣——因为重标记后目标全都集中在同一个终点附近样本多样性不足训练收益有限。还有一类情况是原材料上就不该硬套HER如果任务本身奖励并不稀疏比如每一步都有明确的进度反馈HER带来的额外计算和复杂性反而可能拖慢训练。我见过不少项目本来用简单reward shaping就能收敛结果套了HER之后反而训练半天不如之前原因就是HER用重标记样本换数据多样性但这种方法在dense reward下没有优势。6.2 两个值得继续深挖的方向HER这几年有不少扩展我这里提两个方向供你参考。一个是Curriculum HER思路是把目标分布从简单逐步过渡到难。具体做法是先从容易到达的目标附近空间开始训练等策略稳定后再逐步扩大目标采样范围。这个和课程学习类似但它和HER结合时有一个天然的协同重标记让简单目标的样本量自动增多课程压力比普通RL小很多。另一个是Goal-conditioned Hierarchical RL HER也就是把任务分两层高层定目标低层用HER去完成目标。这种方式在长时程任务上有奇效比如先走到门口再穿过走廊最后按下按钮这类需要多阶段规划的任务。低层每个阶段的目标是短而可实现的高层则通过调度这些短目标来解长任务。7. 我的几点实操体会把HER从论文搬到实际项目里中间我断断续续花了好几周。回头看有几个体会特别值得沉淀下来。第一别急着写代码先把目标空间、状态空间、奖励函数、done语义四件事在纸上理清楚。HER的代码本身不复杂复杂的是每个模块之间的语义配合。上面提到的done重标记、reward重算、归一化任何一个环节疏忽都会让训练效果大打折扣而且这种问题往往藏得很深从loss曲线上很难一眼看出。第二训练可视化要比普通RL多做一级。除了reward、Q值我强烈建议额外记录真实目标成功率和HER重标记目标成功率这两个指标。如果你看到后者涨得飞快而前者原地踏步就能快速定位到HER样本比例失衡这个问题上不需要一次次猜测。第三HER的效果和探索策略耦合很深。很多人以为HER能替代探索其实不是——HER是事后利用失败样本但首先你得有失败样本而样本的多样性全靠探索来保证。如果探索太保守重标记出来的千篇一律训练照样拉胯。我在工程里最常用的组合是较大探索噪声HER中等K值这套组合在多种仿真环境里都表现得超出预期。第四论文里的超参数只是起点。HER论文给出的K4、future策略等选择并不是金科玉律。实际环境里K2、K4、K6的效果差异非常明显需要你亲自实验记录每个配置下的训练曲线再做取舍。这个经验可能在很多强化学习内容里都不会被提到但它在真实工程中确实是决定成败的细节之一。要说最值得记住的一句话我觉得是**HER改变的不是算法而是数据的使用方式。**它教会我们的不是让模型更聪明而是让模型从同样的历史数据里汲取更多信号。这种从数据视角解决问题的思路放到很多工程场景里都适用——别急着改模型先看看数据能不能换个方式用。