
1. 为什么稀疏奖励任务让人头疼——HER要解决的核心问题先把话说在前头如果你已经在强化学习里折腾过一段时间大概率碰过这种场景——训练一个机械臂去把积木推到指定位置或者让智能体在迷宫里找到出口结果跑了十几万步reward曲线跟心电图一样毫无起色模型就像个泄了气的皮球怎么踢都不动弹。问题多半不在算法本身而在环境的奖励信号太稀疏agent根本不知道自己做得对不对。这种任务在机器人操控、游戏AI、自动导航等领域特别常见。它们的共同点是目标状态在绝大多数时间都无法达成agent只能在随机探索中偶尔碰到一次成功而这个“偶尔”可能要等上成千上万个episode。等一次正面反馈的成本太高策略收敛自然变成一场漫长的煎熬。标准强化学习框架在奖励稀疏面前非常脆弱。你可以把传统策略梯度方法想象成一个没有地图、只能靠踩雷来判断方向的盲人。踩到一个雷才知道这里不能去踩到十个雷才大概知道哪条路相对安全。如果雷区里只有一条正确的路而你每次踩雷都要付出巨大代价那么学习效率会低到让人怀疑人生。以DQN这类value-based方法为例它依赖对每个状态-动作对的回报估计。在稀疏奖励环境里绝大多数状态-动作对的Q值长期为零梯度信号只在极少数瞬间出现网络很容易陷入“什么都学不到”的僵局。PGO、PPO这类policy-based方法也没有好到哪去它们依赖当前策略的采样质量如果探索得到的正样本太少更新的方向基本都是噪声方向。这里引出一个本质矛盾agent亟需反馈来调整策略而环境偏偏在绝大多数时候都保持沉默。打破这种沉默的方法要么是改造奖励函数本身要么是让已有的经验发挥更多价值。HER走的正是第二条路——这也是它作为一项重要强化学习技术的价值所在。2. HER的核心思想让失败的经验变成有效数据2.1 事后诸葛亮式的目标重标记从失败中挖金矿HER的全称是Hindsight Experience Replay直译过来就是“事后经验回放”。这个词本身就点出了它的核心思路既然真正想达成的目标太难完成那不如在经验回放的时候把那些没有完成目标的轨迹重新标注为完成了另一个目标的轨迹来学习。听起来像自欺欺人但实际上非常聪明。举一个最简单的例子。假设你在训练一个智能体让小球到达位置A。某次运行中小球最终停在了位置B——目标A没达成这次轨迹在传统框架里是一次彻底的失败。但如果换个视角把这次轨迹的目标改写为“到达位置B”那小球实际上是成功完成了一次任务。它学到了“如何到达B”的知识。当系统积累了“到达各种不同位置”的成功经验之后模型对“如何控制小球运动”这件事本身就建立了更丰富的因果认知。等到下一次真正想让小球到达A时这些经验就变成了可迁移的知识。用通俗的话说失败的经验被重新包装成了可用的教材。这就是HER最简单的直觉解释。它不是让agent把失败当成功来麻醉自己而是把失败轨迹中真实存在的局部正确信息提取出来变成有价值的学习信号。2.2 目标重标记背后的数学直觉多任务视角从数学层面看HER把稀疏奖励问题转化为多任务学习问题。标准RL里策略往往围绕单一目标拟合Q函数或策略函数。HER引入目标条件策略让同一个策略接受“目标g”作为额外输入同时训练适应多个目标。这样做的理论价值在于不同目标之间共享了大量底层知识。控制一个机械臂去抓红色方块和抓蓝色方块底层的运动控制、路径规划逻辑基本一致只有终点不同。HER通过重标记机制让每次采样到的轨迹都能为多个目标提供学习信号相当于一份数据反复用于多个任务的学习。论文在多个连续控制环境中做了验证效果非常显著。以Fetch系列操控任务为例在仅有二值奖励1或0的最严格条件下普通DDPG几乎学不动成功率长期为零。加入HER之后训练几十个epoch就开始出现明显上升最终成功率稳定在80%以上。这个结果彻底改变了强化学习社区对稀疏奖励问题的看法。2.3 HER的四种重标记策略对比HER论文给出了四种选择“事后目标”的策略实际使用中效果差异很大。final直接取轨迹的最终状态作为事后目标。简单粗暴在大多数单终点任务里效果已经不错。future从当前时刻之后的某个随机状态中采样作为事后目标。这种策略能让同一条轨迹产生更多样化的重标记目标信息利用率最高。episode从整条轨迹中随机挑一个状态作为目标。样本更均匀但对单个转移的指导作用偏弱。random从完整目标空间中随机采样。本质上没有利用轨迹信息一般不太推荐。我的实际体验是future策略综合表现最好特别是与较大的经验回放池和适度K值搭配时。final策略实现最简单但学到的是“逐渐偏向最终位置”的偏差探索多样性稍差。你可以把final想象成只盯着终点线训练而future相当于在跑道上设置了很多个虚拟终点每个虚拟终点都在教模型一段有用的局部知识。2.4 HER为什么必须搭配off-policy算法还有一个很关键的认知HER的核心机制高度依赖经验回放池本质上和off-policy算法天然契合。DDPG、DQN、SAC这类算法本身就在从回放池中批量采样HER只是在采样之后对部分转移做了目标重标记再塞回训练流程。改动极小收益却极大。on-policy算法比如PPO就不太适合直接套HER——它要求数据来自当前策略而重标记后的目标并不对应当前策略的真实行为分布。强行使用会让importance ratio出现严重偏差。所以如果你想上手HER建议直接从DDPG或SAC这类演员-评论家架构开始后面的实操部分我也会围绕DDPGHER这套组合来展开。3. 从原理到代码HER的完整实现步骤3.1 环境与数据结构准备目标条件MDP在动笔写代码之前需要先把环境建模改造成HER能吃的形式。传统MDP用状态s、动作a、奖励r、下一状态s来定义转移。HER场景下需要引入目标维度g环境变成目标条件MDP。目标条件MDP的关键差异有两点一是策略的输入从s变成[s,g]模型必须知道当前要完成的目标是什么二是奖励函数r(s,a,g)不再是固定值而是依赖当前目标g动态计算。通常做法是定义一种二元稀疏奖励如果状态s与目标g的距离小于阈值奖励为1否则为0。实际操作中还需要设定一个目标空间的概念。目标空间不一定和状态空间完全相同。以抓取任务为例机械臂的关节角度属于状态空间目标是物体末端位置的三维坐标目标空间就是位置坐标空间。你可以把状态空间理解为“身体目前怎么样”目标空间理解为“任务希望达成什么状态”。两套维度可以不一致但需要有一个可计算的距离函数来衡量“状态s有多接近目标g”。下面是我在实操中常用的Reward计算与目标距离判断模板def compute_reward(achieved_goal, desired_goal, threshold0.05): # 计算当前状态与目标状态的距离 dist np.linalg.norm(achieved_goal - desired_goal, axis-1) # 二值奖励距离小于阈值视为成功 reward (dist threshold).astype(np.float32) return reward这种二值奖励最大的好处是不需要人工设计稠密奖励函数。你在很多项目里会看到有人花大量时间调奖励函数试图让每一步都有一个温柔的梯度信号但调试起来非常痛苦而且很容易让agent学会钻奖励函数的空子。HER配合二值奖励反而把问题化简了——你不去教agent每一步怎么做只告诉它成功长什么样其余靠重标记机制自己悟。3.2 目标重标记的具体流程HER训练流程的核心环节是采样一批轨迹之后对其中每条轨迹的每个转移进行目标重标记。完整流程可以用下面这段伪代码描述for episode in range(total_episodes): 收集一条完整轨迹 (s_0, a_0, r_0, s_1, ..., s_T) for t in range(T): # 原始转移送入回放池 replay_buffer.add(s_t, a_t, r_t, s_{t1}, g_original) # 重标记从未来状态中选择一个新目标 for _ in range(K): g_new random_choice(achieved_goals[t1:]) # future策略 r_new compute_reward(s_{t1}, g_new) replay_buffer.add(s_t, a_t, r_new, s_{t1}, g_new)关键在于循环里的K值它决定了每条转移被重标记几次。K值越大样本越丰富但回放池占用也线性增长训练速度会明显下降。论文里K4是常用取值我在大部分任务里也验证了K4是一个性价比很高的基准值。future策略的采样范围也值得留意是只从当前时刻之后的状态里选还是从整条轨迹的后续状态里选对结果有一定影响。HER论文里用的是从“当前时刻到轨迹末尾”的范围中均匀采样。这么做的好处是同一时刻的经验在不同目标下被反复利用训练时的数据多样性大幅提升。3.3 与DDPG算法的集成实操现在把HER挂到DDPG上。DDPG由Actor网络和Critic网络组成HER的集成点有两个输入维度扩展和采样后的重标记。Actor网络输入维度从s变为[s,g]。假设状态维度是10维目标维度是3维那么网络输入就是13维。Critic也一样。拿到重标记后的转移后标准DDPG的更新逻辑完全不需要改动。我用TensorFlow 2.x写过一个最小实现关键片段如下class Actor(tf.keras.Model): def __init__(self, state_dim, goal_dim, action_dim): super().__init__() self.input_dim state_dim goal_dim self.fc1 tf.keras.layers.Dense(256, activationrelu) self.fc2 tf.keras.layers.Dense(256, activationrelu) self.fc3 tf.keras.layers.Dense(action_dim, activationtanh) def call(self, state, goal): x tf.concat([state, goal], axis-1) x self.fc1(x) x self.fc2(x) return self.fc3(x)Critic部分同样是把[s,g,a]拼在一起作为输入输出Q值。训练流程里的重标记逻辑我已经封装成一个函数def hindsight_transform(episode_transitions, k4): hindsight_transitions [] for t, (obs, act, _, next_obs, goal) in enumerate(episode_transitions): # 保留原始转移 hindsight_transitions.append((obs, act, compute_reward(next_obs, goal), next_obs, goal)) # 对未来状态子序列进行采样重标记 future_goals [trans[4] for trans in episode_transitions[t1:]] if len(future_goals) 0: sampled_goals np.random.choice(len(future_goals), sizek, replaceTrue) for idx in sampled_goals: new_goal future_goals[idx] new_reward compute_reward(next_obs, new_goal) hindsight_transitions.append((obs, act, new_reward, next_obs, new_goal)) return hindsight_transitions这里有一个细节容易被忽略重标记后新目标的reward必须用同一套距离阈值重新计算不能沿用原始失败轨迹的reward0。因为新目标大概率是可以达成的按照同样的成功标准这个转移应该被记为成功经验。如果忘了重新计算reward等于给数据灌入了互相矛盾的标签模型训练出来会表现得很混乱。3.4 网络训练循环与超参选择DDPGHER的完整循环和普通DDPG基本一致唯一区别是每次采样后要处理回放池里那些已经重标记好的数据。我习惯对整个训练流程做这样的节奏安排前几个epoch用随机策略探索收集轨迹让回放池先积累足够的数据再开始更新网络参数。实践中值得注意的超参数有这么几个回放池容量建议至少设定为单次训练轨迹数的100倍。因为HER会让每个转移产生多个变体数据量膨胀快池子太小会导致早期优秀经验被过早覆盖。batch size建议128到256之间。HER重标记后的数据相关性比常规回放更强batch太小会让梯度更新频繁抖动。探索噪声DDPG默认使用OU噪声或高斯噪声。稀疏奖励任务里我倾向于用较大的初始高斯噪声让agent前期更激进地探索状态空间。目标网络软更新参数tau0.05左右比较稳过大会导致学习不稳定过小则收敛变慢。一个我踩过的坑是一开始把回放池容量设得太小只有几万条转移结果训练中期开始出现明显的灾难性遗忘——模型已经学会的技能过一段时间又突然消失了。后来把池子加大到几十万情况好了很多。HER对经验回放池的依赖性比普通RL高得多这条一定要牢记。4. 训练中的常见问题与调试经验4.1 重标记策略选型对比我在多个仿真环境里横向对比过前面提到的四种重标记策略结论和论文一致但也有不少细节补充。final在终点单一的任务里效果最稳定比如“把方块推到固定位置”。但如果目标空间比较空旷final会让重标记的目标高度集中在轨迹终点附近容易学成一个“只会前往轨迹末端”的偏斜策略。future在移动目标、随机多目标的任务里优势明显数据多样性最好。缺点是实现稍复杂且K值需要配合调大才有效果。episode在长轨迹任务中会引入太多与当前状态无关的目标训练时对梯度方向的干扰最大不太推荐单独使用。random基本相当于没利用轨迹信息效果在大多数情况下明显弱于前两者。建议是新项目先从final入手验证HER是否能跑通跑通之后再切换到future策略提升上限。先在简单模式里建立baseline再逐步推进这是最稳的上手路径。4.2 二值奖励与距离阈值的坑二值奖励虽然省心但阈值的选择直接影响学习信号的质量。阈值设得太大agent很容易“蒙混过关”明明离目标还很远却被判定成功学到的是粗糙的策略阈值设得太小学习初期几乎没有成功样本HER也无法提供有效反馈。我在实践中的经验是阈值设置为目标空间典型尺寸的1%到5%比较合适。比如目标是通过距离判断的抓手任务目标空间的坐标范围在0到1之间阈值设在0.05是比较合理的起点。如果你的环境特征尺寸更大可以线性放大这个比例。距离函数的选择也要注意一般默认欧氏距离。如果目标空间包含角度分量比如要让机械臂末端朝向某个方向角度分量需要单独处理不能直接和位置分量一起算欧氏距离——角度通常用弧度制数值上可能很大直接拼在一起会干扰距离计算的主次关系。4.3 训练不收敛的常见原因排查以下问题是我在多次DDPGHER训练中真正遇到过的整理成速查表方便大家对照排查现象可能原因解决思路前几万步reward全为0回放池还没积累足够成功样本增大初始探索噪声或先用随机策略收集一段轨迹训练中期突然能力下降回放池太小早期经验被覆盖加大回放池容量或使用优先经验回放策略收敛到局部最优K值太小目标多样性不足将K从4增加到8观察曲线变化目标达到率停滞在很低水平距离阈值过严适当放宽阈值到3%左右先让agent学会粗粒度控制损失函数剧烈振荡学习率偏高或batch过小将Actor/Critic学习率降到1e-4量级batch加大到256明明成功但reward不增长重标记时没有重新计算reward检查重标记函数确认使用了新目标的距离阈值4.4 多目标同时训练的正确姿势HER在训练时会反复重标记目标这会导致一个隐性问题同一批经验被用于训练多个不同目标时梯度方向可能互相打架。我在实验中发现如果K值过大且目标空间高度异构比如把一个三维位置目标和一个角度目标混在一起训练会明显变慢。解决思路是给目标的不同分量分配不同的距离度量甚至分别设置阈值。比如位置分量的阈值为0.05角度分量的阈值为15度约0.26弧度二值奖励只有当两个分量都达标时才为1。这样重标记后的目标空间更符合任务的真实物理含义学习效率反而更高。另外如果你的项目目标是让agent完成多步骤任务比如先抓取再放置单靠HER无法解决时序依赖问题。HER擅长的是解决“最终状态稀疏但可分步逼近”的问题不是“完整技能链条”的问题。这类情况可以考虑HER和HOChierarchical objective conditioning这类层级化方法配合但那是另一个层次的话题了。5. 写在最后的几点经验HER是我在稀疏奖励任务里第一次感受到“原来败多胜少的经验也能教出好模型”的方法。回过头想它能有效的根本原因其实很朴素传统RL把所有未达目标的数据直接丢进垃圾桶而HER只是弯腰把它们捡起来擦掉灰尘重新贴上一个能派上用场的标签。这个思路在许多领域都有类似的影子——失败的项目复盘、被拒稿的论文返修、练废了的方案迭代都是把“没达到预期”的经历重新审视从中提炼出有价值的信息。技术之外HER给我的最大启发其实是很多看似没用的数据缺少的只是一个重新定义目标的角度。如果你准备在自己的项目里用HER我的建议是先不要纠结于复杂的实现细节把二值奖励、future策略、K4这一套最小组合跑通观察成功曲线是否开始上升再逐步优化。强化学习本来就是一个实验驱动的领域模型的性能提升往往建立在一次次失败的调试之上——这本身也是另一种hindsight。