尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

事后经验回放HER:从失败的轨迹中学习,破解稀疏奖励难题

事后经验回放HER:从失败的轨迹中学习,破解稀疏奖励难题 1. 项目概述与核心思路拆解1.1 为什么几乎所有强化学习新手都会卡在稀疏奖励上接触强化学习快两年了要说哪个方法最让我觉得“原来还能这样”一定是 OpenAI 在 2017 年提出的Hindsight Experience ReplayHER事后经验回放。这个简称 hindsight 的项目方法解决的是强化学习里最让人头疼的一类问题完全拿不到奖励信号的时候智能体应该怎么学先举个大家都有体感的例子。你想训练一个机械臂去抓取桌面上的杯子每次尝试如果没抓住就给它一个-1的奖励抓住了给0。听起来挺合理但实际操作起来你会发现机械臂在成千上万次尝试里几乎没有一次能碰到杯子收到的反馈清一色是-1。这时候策略网络的梯度信号基本是废的——所有动作看起来一样差网络完全不知道该往哪个方向调整。这个问题就是所谓的稀疏奖励问题sparse reward problem。我最早在 Gym 的 Fetch 系列环境中做实验时开局一晚上跑下来奖励曲线像一条死人的心电图。后来换用 HER 之后情况立刻不一样了。这个方法的想法特别朴素既然这次没抓到杯子那就不要死盯着“抓杯子”这个目标不放——干脆把这次轨迹中实际到达的位置当作目标重新生成一条经验告诉智能体“你刚才虽然没有抓到杯子但你的运动轨迹成功到达了某个新位置这件事本身值得学习”。反复做这种目标重标记之后智能体从每一次失败里都能挤出一点有用的经验试到后来成功率自然就上来了。这个思路之所以叫 hindsight其实是借用了一个认知心理学的概念事后诸葛亮。人类总是觉得“我早知道会这样”HER 则是把这种“事后”的心态用在了算法训练中——既然已经知道这条轨迹最终走到了哪里我们就把这个实际发生的终点当作虚拟目标来学习哪怕这个终点从来不是最初的意图。这种“把失败当作成功来学”的哲学就是整个算法的灵魂。1.2 hindsight 要解决的核心问题清单HER 不是什么放之四海而皆准的银弹它的适用场景和边界条件需要先说清楚。从我的实践经验来看它主要解决以下几类问题稀疏的二元奖励环境只返回“成功/失败”两种信号没有中间过程的连续反馈。多目标任务训练时面对的不是单一固定目标而是一大堆可能的 goal比如“把物体推到任意一个指定位置”。目标达成判断可以自动计算我们可以从环境状态里提取出一个“实际已达成的目标”achieved goal并且能自动判断它是否等同于请求的目标observed goal。这个前提非常关键后面实操部分我会详细讲。探索本身能覆盖一部分目标状态空间即便随机探索失败率很高但轨迹中仍然会路过一些有趣的状态。HER 的价值在于把这些路过的状态捡回来当样本。反过来HER 不太适合的场景也很明显比如目标是击败一个开黑的对手对抗性任务或者目标是跑一条固定路径而中间没有任何可提取的中间状态这时候“事后”可取的替代目标并不自然硬套 HER 往往事倍功半。我在实际项目里发现HER 最适合的场景是机器人操控类任务抓取、推箱子、插拔、摆放这类任务天然满足“可以从状态中提取目标、目标空间连续、奖励稀疏”这几个条件。所以如果你也是做机器人相关方向的HER 值得优先学习。2. 算法核心目标重标记的完整原理2.1 目标条件化策略和“为什么失败里也有金子”要理解 HER先要理解它背后的策略模型。HER 不是凭空长出来的它建立在目标条件化策略goal-conditioned policy之上。普通强化学习里策略的输入是状态s输出是动作a而在目标条件化策略里输入变成状态和目标的组合(s, g)输出动作a。也就是说我们训练的不是“在某个状态做什么动作”而是“在某个状态下朝着某个目标应该做什么动作”。这一点是整个算法的地基。因为假设我们已经有一个目标条件策略那么训练数据的来源就不再局限于“当前这个任务里成功的那几条轨迹”。任何一个状态可以配上任何一个合理的目标来组成一条有效的训练样本。这么说有点抽象我打个比方。你雇了一个实习生第一次让他去市场买鱼结果他买回了猪肉。如果你只会盯着“买鱼”这个目标批评他那这次实习对他来说几乎没有任何学习价值因为他的行为在“买鱼”的目标下全错。但如果你换个角度把这次任务的目标临时改成“买猪肉”那他买回猪肉的整条路径瞬间就是完美的、成功的、值得学习的。下次如果再有人叫他买猪肉他知道该怎么做了。HER 干的事情就是给实习生不断发明新的“假目标”让每一条失败轨迹都能变成有学习价值的经验。从数学角度来看普通强化学习在稀疏奖励下学不动的原因很简单绝大多数 transition 的 reward 相同比如都是-1TD 误差接近零价值函数的梯度根本推不动。而经过目标重标记之后那些原本“失败”的 transition 被重新计算了 reward有一部分变成“成功”的0这让样本中出现了正例和负例的对比价值函数就具备了对不同目标进行区分的信号了。这个解释我在给团队做分享时反复强调HER 的核心不是“多生成成功样本”而是构造出正负样本的差异让损失函数重新可导。2.2 四种目标重标记策略与选择理由HER 原论文提出四种从一条 episode 里选择替代目标的方法我给它们起了方便记忆的名字策略做法特点与适用场景final直接把 episode 最终到达的状态当作虚拟目标最简单适合目标任务相对简单、轨迹不太容易漂移的场景future从当前时间步之后的某一步状态中随机选一个作为目标通常效果最佳因为目标与当前状态的时间距离适中提供了更丰富的学习信号episode从整个 episode 中随机选一个状态作为目标探索性强但可能出现目标与当前状态毫无关联的情况噪声较大random从整个重放缓冲区里随机选一个状态作为目标最不推荐信号太弱实践中很少用到从我跑过的几十组对比实验结果来看future策略在绝大多数情况下稳压其他三种这也和论文里的结论一致。原因在于future选的目标是“当前状态之后某个时间点会到达的状态”本质上是给策略提供了一个未来可达成的、有一定挑战性的目标。它比final更有挑战性因为不总是最终状态可能更难又比episode更可靠因为时间顺序保证了目标可达成性。我在实操中默认就选future只在调试特殊环境时才会换其他策略对比一下。future策略还有一个关键超参数K表示从后续状态中随机抽取目标的数量。论文推荐 K 在 4 到 8 之间我的经验是 K 取 4 最稳再大效果提升有限反而增加计算量。为什么不能无限增大 K因为 K 过大等于把重放缓冲里所有近邻状态都强行变成目标会让数据分布变得过于平滑价值函数反而学出“平均主义”失去对目标的敏感性。2.3 重标记的具体操作流程与奖励重构这里把 HER 的单步操作拆开讲确保你看完就能自己实现。假设我们采样了一条长度为T的 episode里面包含一系列状态s_0, s_1, ..., s_T和动作a_0, a_1, ..., a_{T-1}最初给定的目标是g。HER 的流程如下照常把原始轨迹存进重放缓冲区(s_t, a_t, r_t, s_{t1}, g)。额外再对每个时间步t按照选定的策略比如 future抽取一个新的虚拟目标g。这里抽取的目标通常是轨迹里某个s_jj t中提取出来的“已达成的目标”部分。用这个新目标重算奖励r reward_function(s_t, g, a_t, s_{t1})。由于g本身取自轨迹的某个未来状态这条 transition 很大概率会被奖励函数判为“成功”也就是正例。把这条新样本(s_t, a_t, r, s_{t1}, g)同样存入重放缓冲区。后续正常用重放缓冲区里的数据训练 Q 网络和策略网络。你可能注意到一个问题奖励函数如果只有二元值那重标记后的样本怎么算奖励我直接把奖励函数设计成基于“欧氏距离是否小于阈值”的判断比如def compute_reward(achieved_goal, desired_goal): d np.linalg.norm(achieved_goal - desired_goal, axis-1) return -(d 0.05).astype(np.float32)也就是说距离小于 5 厘米就认为是成功奖励为0否则奖励为-1。这样设计的好处是重标记时我只需要把desired_goal替换成轨迹中实际到达的位置就能立刻算出一个非负奖励而不用额外设计复杂的稠密奖励函数。这个奖励重构是整个 HER 中最容易出错却也最精髓的一步。我在做项目时遇到过一种误区有人担心二元奖励太粗糙改成连续距离奖励比如-d。结果训练反而不稳定。原因在于连续奖励里价值函数的目标是“最小化距离”但目标重标记让“距离”这个量本身就不断变化——同一个状态配上不同的虚拟目标距离值天差地别值函数被来回拉扯。反而是稀疏的二元奖励能明确区分“成/败”让梯度更干净。所以我建议HER 搭配稀疏二元奖励是最佳搭档别画蛇添足。3. 实操过程与核心环节实现3.1 环境选型与准备工作纸上谈兵容易真正动手跑 HER 时环境选错了会直接劝退。我最推荐从 Gymnasium 的 Fetch 系列开始特别是FetchReach-v2。这个环境的目标是把机械臂末端移动到某个目标点状态空间只有 10 维动作空间 4 维目标空间 3 维非常适合作为 HER 的第一个试验场。跑通之后再往FetchPush-v2、FetchPickAndPlace-v2进阶这两个环境目标维度低但动力学更复杂能看出 HER 的真正优势。这里有个小提醒使用新版 Gymnasium 时环境的 API 和旧版 Gym 有差异。我建议直接把观测结构打印出来确认一下。Fetch 环境的观测通常长这样旧版 API{ observation: array([...]), # 机械臂关节状态物体状态等 achieved_goal: array([...]), # 当前实际达到的目标比如末端位置 desired_goal: array([...]) # 当前请求的目标 }玩 HER 的人一定要养成一个条件反射时刻区分achieved_goal和desired_goal。前者是环境状态里提取出来的已完成情况后者是任务给定的目标。重标记时我们把desired_goal替换成轨迹中某个时刻的achieved_goal这是最常见的操作方式。另外一项准备工作是选一个合适的基线算法。HER 本身是一种“经验生成机制”它必须寄托在一个 off-policy 算法上。论文用的是 DDPG现在最常见的是 SAC 或者 TD3。考虑到实现复杂度我建议先用 OpenAI Baselines 或 rl-starter 里现成的 HER 示例代码做起点不要从零搭环境并行和向量化那一套否则很容易陷入工程细节。我自己第二次复现时直接基于 stable-baselines3 的 HER 接口配合一份现成的 SAC 实现半天就跑通了。3.2 核心代码实现与参数解读这里我写一份高度浓缩但可直接运行的 HER 核心逻辑重点不是完整工程而是让你清楚重标记环节怎么嵌入训练循环import numpy as np from collections import deque class HERBuffer: def __init__(self, buffer_size1_000_000, k_future4): self.buffer deque(maxlenbuffer_size) self.k_future k_future def add_episode(self, episode_transitions, achieved_goals): episode_transitions: 原始 transition 列表每个元素包含 (obs, achieved_goal, desired_goal, action, reward, next_obs, done) achieved_goals: 每个时间步的 achieved_goal 列表 T len(episode_transitions) for t, trans in enumerate(episode_transitions): # 原始样本直接入库 self.buffer.append(trans) # 从未来时间步里随机挑 K 个目标做重标记 future_indices np.random.randint(t 1, T 1, sizeself.k_future) for idx in future_indices: if idx T: idx T - 1 g_new achieved_goals[idx] # 重算奖励和 done r_new self.reward_fn(achieved_goals[t], g_new) done_new bool(r_new 0) new_trans (trans[0], achieved_goals[t], g_new, trans[3], r_new, trans[5], done_new) self.buffer.append(new_trans) def reward_fn(self, achieved, desired, threshold0.05): return 0.0 if np.linalg.norm(achieved - desired) threshold else -1.0核心逻辑就这么多把 episode 存储下来每条原始样本复制出 K 条重标记样本重标记的目标来自未来某个状态。剩下的采样和训练与普通 off-policy 算法完全一致——每次从缓冲区采样一个 batch交给 SAC 或 DDPG 的更新流程去算损失。注意这个k_future参数它不是越大越好。我做过一次消融实验K0 时也就是纯 DDPG 无 HERFetchReach 的成功率几乎为 0K1 时能到 20% 左右K4 时稳定在 95% 以上K8 时成功率略升但训练时间明显变长而且后期方差变大。在计算资源有限的情况下K4 是性价比最高的选择。如果你的任务目标空间更复杂可以尝试 K8但先别急着贪心。3.3 训练流程中的关键配置与调参心得我完整跑通 FetchPush 项目的配置大概长这样你可以直接抄去当基线配置项建议值备注基线算法SAC 或 DDPGSAC 更稳DDPG 更快但更容易发散重放缓冲区大小1e6至少要能容纳几十个完整 episode太小会影响多样性batch size256这个下面会说原因训练步数2e5 ~ 1e6FetchReach 约 2 万步就能看到苗头FetchPickAndPlace 要到 80 万步HER 重标记策略futureK4默认首选目标距离阈值0.05 米适合 Fetch 系列其他环境要重新标定每 episode 最大步数50Fetch 默认值别乱改探索噪声高斯噪声std0.2DDPG 常用SAC 则靠 entropy 自动调节网络结构两层 256 隐藏层备选 3 层但 2 层通常够用关于 batch size我要特别解释一句。HER 重标记会让缓冲区内目标分布比较杂batch 太小的话一个 batch 里可能碰巧全是虚拟目标样本或者全是原始目标样本梯度不稳定。256 是我试下来比较稳的下限再小效果会明显打折。训练过程中我会额外记录一个关键指标目标平均距离即轨迹终止时 actual goal 与 desired goal 的欧氏距离。这个指标比 reward 曲线更直观因为 Fetch 系列在很多实现里 reward 是稀疏的0/-1画出来全是跳变的线看多了眼晕。目标平均距离下降说明策略真的在往目标靠近比盯奖励曲线可靠得多。另外再提醒一点HER 不是不需要探索。如果你用 DDPG初始化时给动作加一些大噪声比如 std0.3前几千步让机械臂在空间里乱晃这对后续重标记特别重要——因为 HER 的“假目标”质量取决于轨迹是否覆盖了有意义的区域。如果前期动作太保守轨迹全堆在初始位置附近重标记出来的虚拟目标全是同一个地方样本多样性一样不足。4. 常见问题与排查技巧实录4.1 经典问题速查表为什么训练一直不收敛我在调试 HER 的过程中踩过很多坑也帮同事排查过不少问题。这些问题几乎每次复现 HER 的人都会撞上整理成表方便你定位现象可能原因排查与解决成功率一直为 0奖励曲线无任何波动重标记后样本没进入训练缓冲或者achieved_goal提取错误打印重标记样本的 reward看是否出现 0 值检查环境返回的achieved_goal维度是否和desired_goal一致早期能学后期不升反降缓冲区里重标记样本比例过高原始目标被淹没降低k_future或手动限制每 episode 重标记样本不超过一半训练崩溃损失值爆 NaN奖励函数返回了-inf或者距离计算出现 NaN检查achieved_goal里是否出现 NaN通常是环境 step 边界问题或归一化写错泛化到新目标时效果极差训练时目标分布过于单一重标记样本只覆盖局部区域训练时随机采样desired_goal的范围要足够大或者用 curriculum 方式逐步扩大目标范围同环境别人能复现你的代码跑不出来环境版本差异观测键名不同统一用 Gymnasium 0.21 以后的 API直接打印obs.keys()确认字段名称4.2 一次真实的调试记录与取舍逻辑最典型的一次调试经历是我在FetchPickAndPlace上尝试把 K 从 4 调大到 20结果全局成功率反而从 80% 掉到 55%。当时我很不解按说重标记越多样本越丰富怎么会变差后来我分析了缓冲区的数据分布发现K 太大会导致缓冲区内虚拟目标大量集中在这条 episode 自身走过的状态上而真实任务目标随机采样的点在 batch 里反而成了少数派。这就像一个班级里到处是“给差生改考试目标”出来的好成绩真正的月考反而没人关注了。价值函数过度拟合了“总是到达轨迹邻近点”的模式面对随机目标时泛化能力自然下降。所以我在团队里定了一条规矩K 值不超过 8默认 4如果任务难度低甚至可以降到 2。重标记的目的是让你从失败里挤出经验不是让你把整条轨迹全变成成功案例。另外一个经常被忽略的坑是done信号的设置。HER 重标记之后done必须根据新奖励重新计算。很多人的实现里直接照搬原始轨迹的done于是一条唯一“成功”的原始样本被复制成 K 条后全都带了doneTrue。这会让价值学习误以为大多数轨迹都是一步到位的严重扭曲了折扣回报的计算。正确做法就是我在代码里写的done_new (r_new 0)。还有一点HER 与优先经验回放PER的搭配问题。很多人觉得两个都是“提升样本效率”的方法叠起来应该更强。实际上 PER 在稀疏奖励场景下可能和 HER 产生冲突PER 会优先抽 TD 误差大的样本而 HER 重标记出来的虚拟目标样本里很多是“假成功”TD 误差可能异常大导致这些低质量样本被反复抽样。我调过一组对比HERPER 比纯 HER 在 FetchPush 上反而低了 5 个百分点。结论先别急着混合技巧把 HER 这个单一机制调到稳定再说。4.3 调参实验的稳妥路线与小样本冒烟测试给新接触 HER 的读者一个可复制的调参路线避免一上来就大改特改。第一步在FetchReach上用默认参数K4、SAC跑 2 万步确认目标平均距离有明确下降趋势。这一步相当于冒烟测试如果你的实现连这么简单的环境都学不动那问题大概率在代码不在环境。第二步换到FetchPush把 K 从 4 调到 2、4、8 各跑一轮观察成功率和稳定性的差异。第三步再回到你的具体业务任务按照“奖励函数 → 目标提取 → 重标记策略 → 超参数”的顺序逐项替换每换一次都要记录对比曲线不要同时改多个变量。这里我强烈要求自己践行一个习惯每次训练前把随机种子固定并且至少跑 3 个种子取平均。HER 本身方差不小同一个配置不同种子下成功率可能相差 20 个百分点。如果你只跑一个种子就下结论说某个改动有效很容易被噪声欺骗。4.4 从长远角度看 HER 的适用边界最后聊一点经验层面的东西。HER 出来已经好几年了但它至今仍是目标条件强化学习和机器人操控任务的重要基线。如果你要做跨任务泛化比如给机械臂训练一个能应对多物体的策略HER 的思想仍然可以融入现代 meta-RL 框架里作为一种“自动生成目标”的组件。甚至在一些模仿学习的场景中我们也用 HER 的思路给专家轨迹制造更多样的目标辅助逆强化学习。但我也必须说清楚它的边界。如果你的环境里目标空间本身就是离散的、不可从状态里自动提取的比如棋类游戏的目标是“将死对方”你没法直接从棋盘状态里拿到一个可量化的 achieved goalHER 就很难直接套用。这时候需要借助其他手段比如课程学习、内在奖励或者换一个完全不同的范式。所以看一个方法靠不靠谱先问一句这个任务是否允许我从“已经发生的结果”里定义替代目标能才用 HER。我个人在实际项目里的体会是HER 最迷人的地方不在于它的数学有多深而在于它教给研究者一个思维方式当数据不够好的时候不要急着换更大的网络或者堆更强的算力先想想能不能改变“数据的语义”。把失败重标记成成功不是自欺欺人而是在告诉算法这个世界上本没有绝对的目标你能到达的地方就是你的目标。这种想法在实操中帮我解决了不少看似“无解”的稀疏奖励场景也让我对自己做的每个机器学习项目多了一层反思——很多时候我们离成功只差一个“重新定义问题”的角度。
返回列表