尧图精选

HER算法原理与实战:用事后经验重放破解稀疏奖励困境

🕒 发布时间:2026/11/5 12:54:42 📁 来源:尧图网络
hindsight这个词直译是事后聪明俗称马后炮。但在强化学习领域提起它大家想到的几乎都是同一个东西——OpenAI那篇《Hindsight Experience Replay》以及同名的HER算法。这个算法用一个比喻就能讲明白你本来想把球扔进A筐结果扔进了B筐正常训练时这算一次彻底失败而HER的想法是能不能把这次经验改写解读成成功地把球扔进B筐如果每次失败都这样事后换目标那么即使在稀疏奖励任务里一个正样本都拿不到智能体照样能从所有失败轨迹里学到东西。这篇文章会从稀疏奖励困境开始把HER的核心原理、目标采样策略、代码实现和实战中容易踩的坑一条条拆开讲适合正在做机器人操作、多目标RL或者对稀疏奖励头疼的读者。不需要把整篇论文读完跟着这篇文章走一遍就能在自己的训练管线里把HER用起来。1. 为什么RL训练需要hindsight稀疏奖励困境与HER的逆袭1.1 稀疏奖励为什么会让智能体学不动先说说痛点。强化学习的核心目标是最大化累计奖励但很多真实任务的奖励设计是稀疏的。机械臂抓取任务抓成功了给1没抓成功给0整个过程几十上百步中间一个梯度信号都没有。游戏里偶尔通关才有奖励迷宫走不出去就永远拿不到终点奖励。在这种情况下随机探索几乎等同于盲人摸象智能体走了几步看不到任何积极反馈策略梯度基本为零模型就只能一直在原地徘徊。我做实验时最直观的感受是用DDPG去训FetchReach给了上百万步还是不收敛loss曲线像心电图reward一直贴着-1不动。问题不是网络容量不够而是拿到有效样本这个事件本身的概率太低了。这种任务里随机策略在几百步内碰到目标附近的概率可能都不到千分之一经验回放池里全是失败的零奖励数据优化器看到的样本没有任何区分度学出来的东西自然是什么都不做。这种困境在学术上叫延迟奖励delayed reward问题。本质是产生奖励的决策和获得奖励之间隔着太长时间步或者目标区域太小导致探索命中率极低。如果目标区域在状态空间里占比只有百分之一那随机探索碰撞正样本的概率就低得可怜。过去大家常用的解法是手写密集奖励比如让机械臂离目标越近奖励越大。但手写密集奖励的代价谁都清楚要理解任务语义、要调距离函数、要处理各种奇异点换个任务就得全部推倒重来。这也是HER出现之后立刻被圈内大量采用的原因——它不需要重新设计奖励而是在数据层面变废为宝。1.2 hindsight的核心洞察失败轨迹也能变废为宝论文的核心洞察其实非常朴素。以操作任务为例轨迹里物体最终停留的位置本身就是一次实际达成状态。既然这个状态已经真实达到过那把物体放到这个实际位置这个虚拟目标在这条轨迹上就是被完成的任务。那么这条原本被视为失败的经验以虚拟目标作为desired goal重放一遍不就成了百分之百的成功经验吗这里的关键在于目标条件策略goal-conditioned policy的通用性。策略的输入是当前状态和目标任务也就是 $\pi(a | s, g)$。同一个动作序列在目标A下可能是失败的但在目标B下却是成功的。既然存在这种目标维度的可迁移性训练时就不必死守原本的目标可以事后诸葛亮地为每条经验重新指定一个已经达成的目标让模型看到一条正样本轨迹。多次这样做之后策略就能逐渐学到当我想要的目标值和真实达到状态一致时我之前的动作是有效的。用生活里的例子来解释教小孩投篮每次都要求投进A筐孩子一直投不进很容易放弃。但如果某次他随便一投进了B筐你立刻夸他进筐了真准孩子就有了一个正向反馈会更愿意去调整动作。HER干的就是这个事——给失败的尝试找一个筐让它变得有意义。它不改变动作只改变什么才算目标。这里要提醒一下HER能进行事后重放前提是环境必须提供achieved goal实际达到的目标状态。这个信息在很多机器人模拟环境里天然就有比如gym的Fetch系列环境给出的observation字典里同时包含observation、achieved_goal和desired_goal三个字段。如果自己做自定义环境必须保证有这个字段否则HER根本无法落地。1.3 HER适合哪些任务目标条件策略与机器人操作HER不是万能的它的适用范围有明显的边界。最核心的适用条件是任务可以用目标状态来形式化描述并且奖励是目标是否达成的函数。典型场景就是机器人操作推箱子、抓取、摆放、滑动。在这些环境里achieved goal是物体的坐标和机械臂夹爪位置等连续量desired goal是期望坐标奖励看两者距离是否小于阈值。另一类是结果容易评估、过程难以设计中间奖励的任务比如导航到某个点、到达指定区域。只要你能定义到了/没到完成/没完成这样的目标函数HER就能在背后把有效经验的密度提升起来。更适合HER的场景通常还有这么几个特征目标空间连续且低维比如二维坐标、三维坐标方便定义距离和阈值同一个动作序列能通往多种结果状态这样事后换目标才有足够的选择空间使用off-policy算法DDPG、TD3、SAC、DQN因为HER本质上是往回放池里加数据需要谨慎使用的场景是目标空间高维且离散、任务需要严格顺序步骤、奖励本身带有过程信息。比如先开锁再开门这类任务事后替换目标会破坏任务的因果结构HER很容易失效。这种时候需要结合分层RL或者课程学习来补足别指望一个算法解决所有问题。2. HER算法原理拆解事后诸葛亮是怎么变成训练信号的2.1 四种目标采样策略对比final、episode、random、future看HER的论文时一个容易被忽视但影响巨大的细节是事后目标到底怎么选。论文一共对比了四种采样策略这个选择直接影响算法的上限。策略采样方式特点final只用轨迹最后一步达到的状态实现最简单但最终状态距离早期时间步较远时前半段经验用这个目标会产生误导episode从整条轨迹中随机抽一个状态比final多样性好但可能抽到和时间步不相关、距离过远的中间状态random从整个经验池中随机抽一个状态多样性最高但过于随机模型容易收到互相矛盾的目标信号future从当前时间步之后达到的状态中采样论文实验效果最好符合因果直觉未来实际达成的状态才是真正的事后目标从论文里的实验数据看future策略几乎在所有基准任务上全面优于其他三种。原因在于当前时间步之后实际出现的状态和当前状态之间存在真实的时序关联模型学起来更顺而random抽到太远的状态等于往数据里注入了大量噪音。所以我自己的工程里默认就是future只有future不方便实现的时候比如在线流式训练、没法预知整条轨迹才会退而求其次用episode或final。但future有一个工程上的坑它在某个时间步t需要用t之后包括t的某个未来状态来设定目标。因此一条轨迹必须完整结束后才能生成HER经验。如果是真正的在线训练、轨迹边产生边入库你需要先攒起一个完整episode再做批量入库。我第一次实现时就掉进了这个坑直接把transition实时扔进回放池导致future策略根本没生效效果退化成final还以为是算法有问题。后面在第4章再展开讲。2.2 数学化拆解HER如何重新构造transition把HER放到数学层面其实很简单。原始经验是一条transition $(s_t, a_t, r_t, s_{t1})$其中 $s_t$ 实际是拼接向量 $[obs_t, g]$奖励 $r_t$ 由目标g决定。HER要做的是以原始transition为基础额外生成k条新的transition选择一个事后目标 $g$取自轨迹后续某个时间步的achieved goal新状态 $st [obs_t, g]$新 $s{t1} [obs_{t1}, g]$动作 $a_t$ 保持不变新奖励 $rt r(s{t1}, g)$也就是用实际达到状态里的achieved goal与虚拟目标 $g$ 重新算奖励奖励函数 $r(s_{t1}, g)$ 怎么设计论文实验里用的是二进制奖励如果 $| \text{achieved_goal} - g | \leq \epsilon$奖励为0成功否则为-1失败。$\epsilon$ 通常取0.05。用二进制稀疏奖励的好处是HER构造出的成功经验在奖励标签上是干净的不会和原有密集奖励混在一起产生尺度问题。直观理解一下这个机制原始transition里$r_t$ 可能几乎永远是-1但换了目标 $g$ 之后因为 $g$ 本身就取自某个achieved goal且往往离当前状态的next achieved goal很近$r_t$ 有很高概率是0成功。这样一来原本全是失败标签的经验池里就多出了一批成功标签的数据这就是HER提升样本密度的本质。但也要注意不是所有换目标后的transition都会变成成功比如future策略下t时刻的动作和长期后的目标不一定直接因果相关但至少方向是对的。2.3 为什么HER能提升样本效率奖励密度视角一个更直观的理解角度是密度。假设原始任务中目标区域在状态空间中的体积占比只有1%随机探索一下正样本率大约是1%。而HER构造的transition目标 $g$ 是从轨迹实际经过的状态中采样的模型看到的正样本率会显著提高在很多运动学任务里可以提升到30%以上。这个差异直接决定了模型能不能学到东西。要注意HER并没有改变探索策略它没让智能体更聪明地去探索改变的是训练数据的标签分布。也就是说它和curiosity等探索奖励算法是正交的完全可以叠加使用。我自己实测下来HER和探索奖励一起用、和SAC/TD3一起用、和后续的目标判断网络配合使用都是可行的。但别把HER当成万能钥匙它解决的是经验利用效率问题不解决探索策略太差的问题。如果策略连目标区域都靠近不了那事后换目标能提供的信号也很有限。3. 手把手实现HER训练管线从回放缓冲区到训练循环3.1 准备工作环境与依赖选择我建议初次接触HER的人直接用OpenAI的gym Fetch系列环境练手最经典的是FetchReach机械臂指尖到达目标点和FetchPush把物体推到指定点。原因是这些环境自带achieved_goal字段奖励结构合适任务难度适中收敛过程也直观。如果你用MuJoCo需要自己准备License不想折腾就用gym自带的环境或者云环境。软件依赖方面PyTorch版本1.10以上就够用其他库就是numpy、gym、matplotlib这一套。网络结构层面HER本身不挑算法配DQN、DDPG、TD3、SAC都行。为了入门快速跑通我强烈建议先用TD3或SAC这种稳定的基础算法再在上面接HER而不是一开始就从头搭复杂框架。我第一版实现用的是DDPGHER结果DDPG本身收敛很差一度误以为是HER的问题。后来换成TD3HER效果立刻正常了。先让基础算法稳定再引入数据增强手段排查问题时才不会互相干扰。3.2 核心实现HER专用回放缓冲区直接看代码。我给出一个简化但可跑的Python版本只保留HER的核心逻辑。注意在实际工程里缓冲区通常还要加优先经验回放PER这里为了讲清楚原理先不放进去。import numpy as np from collections import deque class HindsightReplayBuffer: def __init__(self, capacity, k4, strategyfuture): self.capacity capacity self.k k self.strategy strategy self.buffer deque(maxlencapacity) def add_episode(self, episode): # episode: list of dict每个dict包含 # obs, achieved_goal, action, reward, next_obs, next_achieved_goal episode_len len(episode) for t, trans in enumerate(episode): # 先存原始经验 self._push(trans) # 再根据策略生成k条HER经验 if self.strategy final: candidates [episode[-1][achieved_goal]] * self.k elif self.strategy episode: candidates [ episode[np.random.randint(0, episode_len)][achieved_goal] for _ in range(self.k) ] elif self.strategy future: # 只能从t及之后的状态采样体现事后因果 candidates [ episode[np.random.randint(t, episode_len)][achieved_goal] for _ in range(self.k) ] else: raise ValueError(unknown strategy) for g_hindsight in candidates: new_trans { obs: trans[obs], action: trans[action], reward: self._compute_reward( trans[next_achieved_goal], g_hindsight ), next_obs: trans[next_obs], goal: g_hindsight, } # 注意状态向量必须是 obs_goal 拼接后的形态 new_trans[obs_with_goal] np.concatenate( [new_trans[obs], new_trans[goal]] ) new_trans[next_obs_with_goal] np.concatenate( [new_trans[next_obs], new_trans[goal]] ) self._push(new_trans) def _push(self, trans): if len(self.buffer) self.capacity: self.buffer.popleft() self.buffer.append(trans) def sample(self, batch_size): indices np.random.choice(len(self.buffer), batch_size, replaceFalse) batch [self.buffer[i] for i in indices] return self._collate(batch) staticmethod def _compute_reward(achieved_goal, desired_goal, eps0.05): if np.linalg.norm(achieved_goal - desired_goal) eps: return 0.0 return -1.0这段代码里有几个关键点值得多说几句。第一add_episode必须在整条轨迹结束后调用尤其future策略依赖未来状态不能一边跑一边存。第二reward是二进制奖励阈值取0.05这个值在Fetch环境里通用但换环境后一定要根据实际量级调整别直接抄。第三obs带goal拼接这一步非常关键我单独做了obs_with_goal字段就是给actor和critic网络用的输入。有些同学的代码里忘了这个拼接或者把拼接顺序搞反训练表现会差很多。同样要说明的是这里为了代码精简容量到上限时直接用popleft覆盖没有做随机替换。如果要和PER混合需要维护额外的优先级树那属于进阶改造不在这次范围内。3.3 集成到SAC/TD3完整训练循环与超参配置接下来是HER怎么和主算法联动。以TD3为例训练循环骨架大概是这样的for epoch in range(total_epochs): episode_buffer [] # 暂存一整条轨迹 obs, info env.reset() achieved_goal info[achieved_goal] desired_goal info[desired_goal] s np.concatenate([obs, desired_goal]) for t in range(episode_len): a actor.select_action(s) # 带exploration noise obs_next, rew, done, infos env.step(a) achieved_next infos[achieved_goal] s_next np.concatenate([obs_next, desired_goal]) episode_buffer.append({ obs: obs, achieved_goal: achieved_goal, action: a, reward: rew, next_obs: obs_next, next_achieved_goal: achieved_next, }) obs, achieved_goal obs_next, achieved_next s s_next if done: break # 整条轨迹结束后一次性送入HER缓冲区 replay_buffer.add_episode(episode_buffer) # 每步都做off-policy更新 for _ in range(updates_per_step): batch replay_buffer.sample(batch_size) td3.update(batch) # actor-critic更新这段骨架里updates_per_step一般取1到2batch_size取256到512k值取4。论文里的默认配置是SAC或DDPG的学习率大约1e-3折扣因子 γ 取0.98这样一个偏小的值。为什么取0.98因为Fetch这类任务episode步数不长折扣因子太大反而会稀释对近期奖励的关注0.98这种偏小的值能让模型更聚焦在短时间视野内的成功信号上。一个必须强调的细节是episode_buffer中的reward仍按原始目标计算因为真实transition要存一份与此同时add_episode内部会额外生成HER版本。如果我在add_episode里把原始reward也改掉了数据就全乱了。实现时要把原始经验和HER经验分开存HER版本的reward用替换后的目标重算。很多网上的简化实现把这一块混在一起我踩过不少坑这里提醒一次。3.4 超参选择与实验效果拿FetchReach来说用TD3HER不调参正常运行大约几十万步左右就能让成功率达到90%以上。FetchPush会难一些需要上百万步甚至更多。FetchPickAndPlace的动作空间更复杂往往还要配合curiosity等额外的探索技巧。我不建议一上来就跑最难的场景先用FetchReach打通流程再用FetchPush检验算法是否真的学到了目标条件能力。如果看训练曲线你可能会观察到成功率的跳跃式上升而不是平滑上升。二进制奖励下一个episode只有成功/失败两种状态成功率本质是几个episode的滑动平均天然呈锯齿状。这是正常现象别看到波动就以为模型崩了。这时候应该去看成功率的滑动窗口趋势而不是单步reward的变化。训练过程里我最常做的事就是把最近100个episode成功率打点出来比看loss靠谱得多。4. 踩坑实录HER实战中的常见问题与排查技巧4.1 HER失效的场景什么时候事后诸葛亮也没用不能回避的问题是HER不是所有稀疏奖励问题的解药。我吃过亏的第一个场景是需要严格序贯操作的任务。比如任务要求先按红色按钮再按蓝色按钮最后进入终点区域。如果把目标事后改成按下红色按钮以后的状态可能生成大量违反任务因果的transition模型反而被误导。对待这类任务HER只能作为辅助信号不能作为主要学习信号。第二种失效场景是目标空间特别复杂比如目标是生成一段文本、一张图像或者一个离散组合。在这种情况下achieved goal和desired goal之间的距离度量很不稳定是否达到目标的判断本身就成问题事后替换目标自然也不靠谱。HER最适用的还是目标可以用低维连续向量表示的场景。第三种场景是reward不仅取决于最终状态还取决于到达过程。比如目标不是到达某个点而是携带钥匙通过门单纯最终坐标相同也未必代表任务成功。这类任务必须把关键状态比如钥匙是否在手也纳入achieved goal的定义否则事后目标根本无法代表真实任务语义。这个细节在工程里相当容易忽略我一度以为模型学得不错后来发现它只是把物体推到了目标点上根本没有完成携带钥匙的隐含要求。4.2 future策略的实现隐患与规避我在第2章提过future需要看到未来状态这就带来一个工程上的硬约束episode数据必须攒齐后才能做HER。如果你把transition实时写进回放池future策略实际退化成随机采样效果大打折扣而且很难排查因为训练曲线要么不收敛要么收敛极慢。规避方式是在代码结构上做硬性约束永远只允许整条episode对象进入add_episode方法不许零散transition入池。另外future采样时要特别注意时间索引如果目标 $g$ 取自比当前时间步t更早的状态就会出现用过去状态做未来目标的因果倒置数据同样会被污染。我见过有人把np.random.randint(0, episode_len)误当成future用这实际上是episode策略别搞混了。还有个细节future采样时要不要把t时刻本身的achieved_goal也纳入候选论文里其实是允许的因为状态 $s_{t1}$ 是当前transition的下一个状态如果它的achieved goal恰好离得很近这个transition就变成顺理成章的正样本。我的做法是把候选区间设为 [t, episode_len)也就是包含t时刻本身。这样能多出一些质量不错的正样本副作用也很小。4.3 工程实践中的细节坑归一化、奖励函数、网络结构第一个坑是目标空间和观测空间的尺度差异。如果obs的量级在0到1goal的量级在0到100拼接后网络会被大尺度数据带偏。所以做HER之前先把achieved goal和desired goal归一化到和obs接近的量纲。最简单的办法是除以环境定义的最大坐标范围或者算均值方差做标准化。从gym里拿现成环境时自带的值域基本在接受范围内一旦换到自定义机器人环境这个坑立刻爆炸。第二个坑是奖励函数的选择。HER最自然的是和稀疏奖励搭配但不少环境默认给的是负距离奖励。把负距离奖励和HER结合也不是不行但要小心尺度问题否则换目标后构造出的新奖励可能变成一个毫无区分度的微小负值模型照样练不动。我自己的经验是如果任务能用二进制奖励清晰表达就用二进制如果一定要用密集奖励那HER带来的增益会被明显削弱别期待奇迹。第三个坑是网络结构。目标条件策略的输入是obs和goal拼接后的向量很多网络结构里goal信息在深层就被忽略了。我通常在actor和critic里对obs和goal分开编码再用FFN融合效果比直接concat略好尤其在goal维度较高时更明显。但是也别急着加复杂结构先测简单的concat能不能收敛能收敛就保持简单。过度设计在RL工程里是大忌。4.4 同名项目避坑另一个hindsight最后提醒一个搜索时会遇到的项目混淆问题。在技术圈里hindsight这个名字其实有两个较为知名的出处一个是本文一直在说的强化学习算法Hindsight Experience Replay另一个是Mozilla团队开源的浏览器历史记录分析取证工具也叫Hindsight主要用于数字取证、隐私研究和浏览器历史分析。如果你搜索hindsight 浏览器历史或者hindsight charges看到的是SQLite历史数据库解析、Chrome/Firefox取证分析之类的内容那就说明搜索到的是另一个项目。两者的技术栈完全不同一个是强化学习和机器人控制另一个是取证和数据分析。确认清楚自己的需求再研究对应方向能节省不少时间。5. 我用HER的真实感受与后续扩展方向第一次把HER跑通的时候最深的感受是一种观念上的转变原来训练数据不一定非要真实不可事后修正标签也是合法操作。很多深度学习任务里给数据改标签是大忌但在目标条件强化学习里因为策略本身就是条件化的同一个transition在不同目标下本来就具有不同的价值所以这种重标记不仅不产生偏差反而能盘活海量失败数据。就我个人的实际使用习惯而言有几个小技巧很值得分享。第一个是k值不用死守4如果任务轨迹很长、状态维度高可以适当增大k到8或16让HER经验在回放池里占更高比例反过来如果池子容量有限k太大容易挤掉原始真实经验要注意平衡。第二个是HER可以和世界模型类方法结合先在低维状态空间做后见重放再在表征空间做同样的操作能进一步提升样本效率。第三个是如果任务里有多个可并行的目标可以在一个episode里同时生成多个不同方向的HER目标配合多目标学习收敛速度比单目标更快。这个内容后续还能扩展的方向不少。比如分层HER把长程任务拆成多个子目标每个子目标内部再用HER再比如结合大模型自动生成候选目标让HER不局限于轨迹实际达到过的状态而是从语义层面构造更有指导性的虚拟目标。这些方向我都还在尝试后面有稳定结果了再单独写一篇文章分享。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →