ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

什么是HER(后见之明经验回放)?破解稀疏奖励下的强化学习难题

什么是HER(后见之明经验回放)?破解稀疏奖励下的强化学习难题 1. 先搞清楚什么任务才需要 Hindsight1.1 目标条件强化学习的基本设定如果你的强化学习任务只是“左右平衡”“走迷宫到固定出口”那确实跟 Hindsight 没什么关系。Hindsight Experience Replay后见之明经验回放简称 HER解决的是另一类问题目标条件强化学习goal-conditioned RL。这类任务的典型架子是智能体每一步都拿到一个观测里面既包含当前状态也包含一个“目标”它要学的不是单一一套动作策略而是“看到任何目标都能执行对应动作”的通用策略。OpenAI Gym 里那组 Fetch 环境是最标准的测试床——FetchReach 是机械臂够到一个目标点FetchPush 是把桌面上的箱子推到指定位置FetchPickAndPlace 是抓起来再放下去FetchSlide 是把箱子滑到远处。这些环境的观测空间是 Dict 结构包含 observation、achieved_goal、desired_goal 三个部分。真正的难点在奖励Fetch 系列用的是最原始的稀疏奖励目标没达到就返回 -1达到了才返回 0。换句话说一整条轨迹里你几乎看不到任何中间信号。我当年第一次在 FetchPush 上跑普通 DDPG 时就体会到了什么叫“学不动”机械臂要么完全不动要么在原地乱晃奖励从头到尾都是 -1梯度就像打在一堵墙上。这不是网络结构的问题也不是超参数的问题而是信号本身不存在。条件反射需要刺激稀疏奖励环境里唯一的刺激只在“成功那一瞬间”出现而成功在随机策略下又几乎不可能发生于是整个学习过程陷入死循环。1.2 奖励塑形为什么只能救急不能救命很多人的第一反应是奖励太稀疏那我给它加一个稠密奖励不就行了比如用机械臂末端到目标点的欧氏距离作为每一步的负奖励离得越近奖励越大。这个思路在简单任务里确实见效快但它有很明显的副作用。第一距离函数本身要设计。距离用什么范数动作空间和状态空间的尺度怎么对齐目标点是三维坐标距离给 0.1 的权重还是 1.0 的权重训练结果能差出好几个数量级。第二局部最优。机械臂会发现“稍微往目标方向动一下”就能拿到一点奖励于是它死死停在某个中间位置因为往目标再挪一步的边际收益和原地不动差不了多少。第三最致命的是奖励塑形把人的先验写进了环境任务稍微换一下就全废了。你今天给 FetchReach 调好一套距离奖励明天换成 FetchPush箱子本身有摩擦、有质量同一个塑形函数直接失效。所以 HER 论文里那个核心观点我一直很认同与其辛辛苦苦去设计一个能引导智能体的中间奖励不如想办法让智能体从“已经发生过的事实”里自己挖掘学习信号。也就是本文标题说的 hindsight——事后看这条轨迹虽然没有达到原定目标但它确实达到了另一个目标那这个目标为什么不能拿来学习2. HER 核心思想把失败轨迹重新解释成成功轨迹2.1 目标重标注一句话版本与生活类比HER 的做法用一句话说就是一条 episode 跑完之后不把原始目标当作唯一目标把轨迹中实际到达过的位置achieved_goal当作新目标重新计算奖励然后连同原始数据一起放回回放缓冲区。原本那些“失败”的转移经过重标注之后就变成了“成功”的转移智能体就能从里面学到“如何达到某个实际可达到的目标”。这个概念听上去有点绕我一般会用投篮来打比方。教小朋友投篮投十次都没进。如果你永远只把“球进筐”定义为成功那他每一次出手拿到的反馈都是失败他什么都学不到。但如果你换一种思路他有一球投到了篮板上那我们就临时把目标改成“投中篮板”这一球就变成了成功案例。他至少能学到“从这个站位、用这个手势能把球送到篮板”。等这个技能稳定了再把目标往外挪一点最后挪到篮筐。HER 做的就是这件事自动把够得着的点重新定义成目标让智能体先学会那些它已经有能力达成的状态再顺着这个基础朝更高难度的目标爬。这里面有个容易误解的地方HER 不是简单地把“最终状态”当目标而是把轨迹里所有访问过的状态都纳入候选。因为最终状态只是一个点信息太少了一条轨迹中间经过了成千上万个状态每一个都对应一次“如果目标是它那我已经成功了”的重新解释。2.2 四种重标注策略对比为什么 future 最好HER 论文里给出了四种重标注策略final、episode、future、random。它们在开源实现里都是那一两行代码的差别但效果差距非常大。我整理了一张表策略新目标来源特点final这条轨迹最后一个状态对应的 achieved_goal实现最简单但把轨迹中所有转移都重标成同一个目标信息单一对于长轨迹前中期状态离最终目标很远重标注后的目标几乎不可达episode同一轨迹里随机抽一个状态作为目标比 final 多样一些但可能抽到当前时间步之前的状态时间上违反因果future当前时间步之后随机抽一个状态作为目标时间因果合理动作确实发生在目标状态之前学习信号最干净random在整个回放缓冲区里随机抽一个状态作为目标多样性最强但容易抽到其他轨迹的、当前轨迹根本不可能达到的目标噪声偏大实验结论和我在实践中的体会一致future 绝大多数情况下最优。开源的 baselines 实现里future 策略是先从当前时间步 t 到轨迹末尾之间均匀采样一个偏移量 k再取轨迹中 tk 那个时刻的 achieved_goal 当作新目标。这样每一步的转移都关联到一个“之后确实发生过的状态”因果上是自洽的。所谓“后见之明”恰恰就在这个时间窗口里我们借用事后才知道的未来信息给过去的动作一个更合理的解释。2.3 为什么 HER 只能配 off-policy 算法HER 天然依赖回放缓冲区。重标注不是实时做的而是先把整条轨迹存下来之后随机采样时再根据策略临时造出新目标。这就要求算法本身有一个能反复使用历史数据的结构。DDPG、SAC、TD3、DQN 这类 off-policy 算法都具备这个条件所以 HER 论文当年选 DDPG 不是偶然。另外还有一个深层原因HER 重标注后的数据严格来说并不完全“干净”——动作 a 当初是为了原始目标 g 选的现在却被解释成朝着新目标 g′ 迈进的样本。这种半真半假的数据用 Q-learning 这类基于值函数更新的方式去消化容错性很强但如果你用 PPO 这类 on-policy 算法去硬学它会对策略梯度里“这条样本来自当前策略”的前提非常敏感重标注数据一掺进去整个训练就乱了。还有一个常被忽略的点HER 要和通用值函数逼近UVFAUniversal Value Function Approximators配合。也就是说你学习的不再是 Q(s, a)而是 Q(s, a, g)——把目标也当成输入喂给网络。只有这样才能回答“如果目标是别的当前这个动作值多少”的问题。stables-baselines3 里对应的是 SAC 配 MultiInputPolicy就是因为它要把 desired_goal 也拼进观测。2.4 重标注为什么没有把策略带偏第一次接触 HER 的人几乎都会问同一个问题它把“为了 A 目标做的动作”硬说是“为了 B 目标做的动作”这不是在教智能体说谎吗策略不会变歪吗我的理解是这恰恰是 HER 最妙的地方。我们重新审视一条转移 (s_t, a_t, s_{t1})假设新的目标 g′ s_{t1} 时刻的 achieved_goal。这句话的真实含义是从状态 s_t 出发执行动作 a_t确实到达了 g′。这是一个客观事实不是伪造的。智能体从这个样本里学到的是“原来从 s_t 出发这个动作能把我带到 g′”这个结论没有任何错误。至于“这个动作当初不是为了 g′ 而选的”这一层意思在 UVFA 的框架下并不致命。值函数要做的是估计“给定一个目标这个状态动作对有多好”而不是追究“这个动作的意图是什么”。重标注相当于给每个转移额外增加了一次合法解释的机会它补的是数据覆盖度而不是替换原始目标的数据。我在实践中观察到的现象是原始目标的数据依然存在重标注只是让那些原本因为没有奖励而被丢弃的样本重新进入训练池策略反而学得更稳、探索范围更大。3. 从零上手代码实现与实验记录3.1 工具选型sb3 加 Gym FetchHER 有很多种实现方式openai 官方 baselines 里有一版但那个代码风格偏研究接口老装起来也麻烦。我的建议是直接用 stable-baselines3 2.0 以上版本它从 2.0 开始内置了 HerReplayBuffer 和 GoalSelectionStrategy配合 gym 的 Fetch 系列环境半小时就能跑起来。环境方面老的 gym 生态是gym.make(FetchReach-v1)新版 gymnasium 生态则通过gym_robotics包暴露环境。两个版本的 reset、step 返回格式有差异但你只要固定用一套环境管理包不要混装问题不大。我在下面的代码里按 gym 经典接口写如果用的是 gymnasium把 reset 的返回值拆成obs, infostep 的返回值拆成obs, reward, terminated, truncated, info就行。选择 sb3 还有另一个理由Fetch 环境的观测是 Dict 结构包含三个键普通 MLP policy 处理不了而 sb3 的 MultiInputPolicy 对这种结构支持得很完整。你要是自己从零写 HER光是把 Dict obs 拼成向量、再在重标注后正确更新里边的 goal 键就够你 debug 一个礼拜。3.2 最小可复现代码SAC 加 HER下面这段代码是我平时实验的起点直接能跑import gym import gym_robotics from stable_baselines3 import SAC from stable_baselines3.common.env_util import make_vec_env from stable_baselines3.her import GoalSelectionStrategy, HerReplayBuffer env_id FetchReach-v1 env make_vec_env(env_id, n_envs4) model SAC( policyMultiInputPolicy, envenv, replay_buffer_classHerReplayBuffer, replay_buffer_kwargsdict( n_sampled_goal4, goal_selection_strategyGoalSelectionStrategy.FUTURE, copy_info_dictTrue, ), buffer_size1_000_000, learning_rate1e-3, gamma0.95, batch_size256, verbose1, ) model.learn(total_timesteps200_000) model.save(sac_her_fetchreach)几个参数我解释一下这些都是踩过坑之后才知道的n_sampled_goal4表示缓冲池每采一条转移额外生成 4 个重标注版本。HER 论文里用的就是 4设太小重标注密度不够设太大计算量翻倍而收益递减。goal_selection_strategy对应前面说的四种策略这里选 FUTURE。copy_info_dictTrue是很多新手最容易漏的HerReplayBuffer 需要从 info 字典里读取 achieved_goal不把这个开关打开环境返回的 state 信息不会被复制进缓冲区训练直接出问题。FetchReach 这个环境其实很简单200k 步内基本能到 100% 成功率。想看点有难度的效果把 env_id 换成FetchPush-v1total_timesteps 拉到 1_000_000 以上你才能体会到 HER 相对普通 SAC 的明显优势。普通 SAC 在 FetchPush 上可能跑几百万步成功率依然是 0加了 HER 之后通常能涨到 80% 到 100%。3.3 核心函数拆解目标重标注与奖励重算用现成库跑通之后我强烈建议你亲手写一遍重标注函数。不为别的只有自己写过一遍你才能真正理解 HER 内部发生了什么也才能在出问题时知道去哪里排查。核心逻辑放在一起其实很短import numpy as np def relabel_transition(episode, t, strategyfuture): # episode 是整条轨迹每个元素包含 achieved_goal T len(episode) if strategy final: new_goal episode[-1][achieved_goal] elif strategy episode: idx np.random.randint(0, T) new_goal episode[idx][achieved_goal] elif strategy future: # 从 t1 到 T-1 之间均匀抽一个未来状态 idx np.random.randint(t 1, T) new_goal episode[idx][achieved_goal] return new_goal def compute_sparse_reward(achieved_goal, desired_goal, threshold0.05): if np.linalg.norm(achieved_goal - desired_goal) threshold: return 0.0 return -1.0实际接入训练循环时你需要对缓存的每条轨迹做这样的操作先用原始目标算原始奖励存入一组数据再对这条转移生成一个或多个新目标用新目标重新计算奖励作为额外的数据存入缓冲区。Fetch 系列环境的成功判定阈值是 0.05也就是机械臂末端或箱子位置与目标点的欧氏距离小于 5 厘米就算成功。有个细节必须注意重标注时用的 achieved_goal 应该是转移执行完之后的那个状态也就是 next_obs 里的 achieved_goal不是转移之前的状态。这一点写错整个学习的因果关系就反了。我见过不少教程代码在这个地方含糊最后训练出来的策略表现为“原地打转还自认为成功”。3.4 评价指标与典型结果训练完之后别只盯着 loss 曲线看HER 这类方法最终要看的是真实环境里的成功率。我通常写一个最简单的手动评测循环success 0 episodes 50 for _ in range(episodes): obs env.reset() done False while not done: action, _ model.predict(obs, deterministicTrue) obs, reward, done, info env.step(action) if info.get(is_success, 0) 1.0: success 1 print(fsuccess rate: {success / episodes:.2f})按你的 gym 版本调整接口细节但核心就是统计 50 或 100 个 episode 里有几个最终成功了。我在实际实验里见过比较典型的曲线是这样的FetchReach 加 HER十几万步就到 100%FetchPush 加 HER前几十万步成功率可能还在低位徘徊中期突然加速到一百五十万步左右稳定在 90% 上下FetchPickAndPlace 难度更高需要更长的训练时间和更大的缓冲区最终成功率通常在 70% 到 90%FetchSlide 则是最难的HER 单独上效果有限需要配合其他技巧。4. 踩坑实录与调参速查4.1 五个最常见的“不收敛”原因我帮别人调试 HER 代码的次数比我自己训练的次数还多总结下来高频问题就这几个。第一copy_info_dictFalse。这是 sb3 用户最容易中的招症状是训练一开始就报 KeyError 或者 achieved_goal 全是零向量模型学了半天完全没动静。处理办法很粗暴仔细读一遍 HerReplayBuffer 的构造参数把它设成 True。第二重标注了目标但没重新计算奖励。这个错很隐蔽因为你把新目标放进缓冲区之后如果 reward 字段还是旧值回放时 Q 函数会用新目标去对应一个旧的奖励相当于拿 A 题目的答案去填 B 题目的试卷。结果就是训练 loss 看起来在降但真实成功率纹丝不动。第三缓冲区太小。HER 的本质是数据增强它需要大量历史轨迹来反复重标注。buffer_size 如果你只设十万FetchPush 这种任务一条轨迹几百步缓冲区很快就写满了老数据被频繁覆盖重标注的价值大打折扣。我的经验是至少设到一百万并且尽量让缓冲区能覆盖几千条完整轨迹。第四无脑用 final 策略。final 在短轨迹、单目标任务里没问题但 FetchPush 这种轨迹很长的任务把最开始的转移重标成“离终点五米外的目标”等于教智能体瞬移。final 的信息量密度太低。对大多数任务直接上 future。第五观测空间没归一化。Fetch 环境的观测向量里混着位置、速度、相对位置不同维度尺度能差十几倍。MLP 对输入尺度非常敏感特别是 HER 这种高频重放的训练模式。sb3 里可以加 VecNormalize但要注意不要让它在 goal 维度上过度偏移否则重标注的目标分布会被扭曲。4.2 参数速查表调参这件事没有金科玉律但 HER 的常用参数区间相对集中。我把几个关键参数整理成了速查表参数推荐设置原因n_sampled_goal4论文默认值密度与开销的平衡点goal_selection_strategyfuture多数任务因果自洽信号最干净buffer_size1e6 以上HER 靠历史数据量取胜缓冲区越大越好gamma0.95 到 0.98太接近 1 会放大长尾方差batch_size256 到 512大批量更稳定代价是显存占用探索噪声动作标准差 0.1 到 0.3探索不够目标覆盖不充分n_envs4 到 8并行采样提高数据收集速度这里面最值得说的还是n_sampled_goal4。它意味着每一条真实转移在训练中被重复使用了 5 次原始目标 1 次重标注目标 4 次。相当于把数据密度放大了 5 倍。如果你任务特别难比如 FetchPickAndPlace可以临时调到 8 试试但要注意训练用时也会成倍上涨。4.3 HER 的边界什么时候它救不了你HER 不是银弹我在项目里遇到过几种它无能为力的情况提前知道能省很多时间。第一种是目标根本不可达。比如起点在一个封闭隔间里目标在墙外那么所有轨迹的 achieved_goal 都挤在隔间内重标注来重标注去智能体学会的还是“在隔间里到达各种位置”永远不会去撞墙。HER 只能利用已经发生过的状态不能凭空创造目标。第二种是长程多阶段任务。FetchStack 要求把箱子堆叠起来中间任何一个阶段失败后面的状态都谈不上“接近目标”。future 策略会把一个发生在箱子倒下之前的状态重标成目标但那个状态本身在完整任务里毫无意义相当于教智能体一个局部技能而这个局部技能和最终目标之间没有递进关系。这类任务需要更结构化的子目标划分或课程学习。第三种是环境随机性过大。如果同一个动作在不同时刻执行会得到完全不同的下一状态那么“这个动作导向了某个未来状态”这个经验本身就不可靠重标注反而会放大环境噪声。5. 顺着 HER 还能往哪走5.1 同门兄弟GCSL 与自监督后见之明HER 打开了一扇门既然可以事后给失败轨迹重新解释目标那能不能直接用监督学习的方式来做这就引出了 GCSLGoal-Conditioned Supervised Learning。GCSL 的思路更暴力把所有轨迹经过重标注后只保留那些“确实达到了新目标”的转移然后当作模仿学习的训练数据让策略网络去拟合“从状态 s 到目标 g′ 应该执行动作 a”。它不需要 Q 函数也不需要贝尔曼更新就是一个纯粹的行为克隆。GCSL 的优势是简单稳定不会出现 Q 值发散的问题劣势是它只能学到“达到过”的目标如果轨迹质量差它就跟着差。HER 则通过 Q 函数的泛化能力能对没见过的目标给出一个价值估计。我在实际项目里的选择标准很简单如果环境动力学简单、轨迹质量可控GCSL 更快更稳如果任务有较强的连续空间和泛化需求HER 的上限更高。两者不是替代关系而是互补关系。另外HER 也经常和好奇心机制搭配。重标注负责把“已经会的技能”固化下来好奇心或内在奖励负责把智能体推向“还没见过但可能有用”的新状态两者结合能让目标覆盖得更广。5.2 在实际项目中落地 HER 的几点建议如果要在真实项目里用 HER我有几条从实践中摸出来的建议。先用最简单的环境验证整套训练和评估流程是否可靠再加大难度。我见过太多人直接在 FetchPickAndPlace 上开始调参环境又慢问题又多最后连是 bug 还是超参问题都分不清楚。固定随机种子、记录成功率曲线、每次改一个变量这是铁律。仿真环境里训好的策略要迁移到真机时HER 本身不直接解决域差距问题但它能给你一个覆盖率很高的目标空间。你可以先用 HER 训出一个“能到达几乎所有可达状态”的底层策略再在上面加一个任务规划层把长程任务拆成一系列可达目标逐个用底层策略执行。这套“高层规划加底层 HER 策略”的组合比端到端硬训要稳得多。我个人在使用中的一个体会是把 HER 看成一种数据增强手段而不是一种新算法。它的核心贡献不是“让强化学习变强”而是“让强化学习在稀疏奖励下终于有数据可用”。想通这一点之后你就能自然地把它和其他机制组合——加课程、加演示、加内在奖励。今天它叫 HER明天可能换一个名字但“从失败里借后见之明”这个方法论的含金量会一直在。
返回列表