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

资讯详情

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

HER算法解析:用“后见之明”解决强化学习稀疏奖励问题

HER算法解析:用“后见之明”解决强化学习稀疏奖励问题 “hindsight”这个词字面意思是“后见之明”“事后聪明”。做强化学习的人听到这个词的反应通常都会落到那篇经典论文《Hindsight Experience Replay》上也就是HER。我第一次读这篇论文时印象很深它并没有多复杂的数学却把一个人类复盘失败的思路搬进了算法一条轨迹没达到预设目标先别急着扔掉把目标换成实际达到的状态这条轨迹立刻就变成了成功样本。我后来在机器人控制任务上用它解决稀疏奖励问题实测下来效果非常明显今天这篇就从原理、复现到踩坑完整拆一遍HER适合正在上手goal-conditioned RL、被稀疏奖励折磨的工程师和学生参考。1. 为什么要研究“后见之明”稀疏奖励到底难在哪强化学习里有个非常反直觉的困境很多真实任务的奖励函数是稀疏的。比如机械臂抓取积木放到指定位置最常见的设定就是成功给1失败给0。这个设定看起来简单干净但训练时几乎要命——策略输出一百万个动作回报全是0梯度算出来不是0就是NaN智能体根本不知道自己在瞎试。很多人第一反应是“把奖励改成距离的负值不就行了”这就是dense reward但亲手调过的人都知道人工设计稠密奖励很容易引入局部最优比如机械臂发现“把手靠近积木”能增加奖励就一直贴着积木蹭反而学不会真正的抓取动作。于是有了课程学习、模仿学习这些思路。课程学习需要人工设计任务难度阶梯哪个阶段该过渡到哪个阶段敲定这些本身就是高成本工作模仿学习依赖专家演示数据很多任务根本没有专家。我们需要的是一种不修改奖励函数、不引入外部数据、纯靠算法机制来提升样本利用率的方法。HER的核心观察很朴素一条失败的轨迹里其实藏着成功的经验。假设设定了目标A智能体一通操作最后到了B从目标A的角度看是失败但从目标B的角度看它刚刚完成了一次“到达B”的完整轨迹。如果能把这层事后视角利用起来不就等于把失败轨迹也变成了可学习的正样本吗这正是hindsight这个词的含义——用事后诸葛亮的方式重新解释历史数据。这个思路放到人类身上特别好理解。你做菜想做红烧肉最后端出来一盘炒肉片从红烧肉的角度这顿饭是失败的但从炒肉片的角度你已经完整走了一遍切肉、腌肉、下锅、调味、收汁的流程。下次让你做炒肉片你大概率能复现。HER做的就是这件事让智能体在“我本来想完成A但没完成”的轨迹里学会“如果目标是实际到达的B应该怎么做”。长期下来策略会变成一个goal-conditioned策略给它任意目标它都尽力去达成而不只是死磕一开始设定的那个目标。多目标强化学习还有一个额外的好处行为策略在探索时trial-and-error积累的数据是分散在不同目标下的。HER把这些数据按目标重新归类、打标签相当于把数据利用率放大了好几倍。这是它在Fetch机械臂任务上能把成功率从接近0拉到接近100的根本原因。2. HER核心机制拆解目标重标注是如何让失败变废为宝的先形式化一下HER工作的问题设定。这里有一个goal-conditioned MDP状态空间S、动作空间A、目标空间G策略是π(a|s, g)奖励函数是r_g(s, a)。以FetchReach为例状态s包括机械臂关节角度、末端执行器位置等目标g是想要末端到达的三维坐标所谓achieved goal φ(s)是当前末端实际位置。奖励一般是0/1判断如果‖φ(s) - g‖小于阈值奖励1否则0。这种设定下绝大多数transition的奖励都是0所以critic很难学到东西。HER的操作就是在采样一条完整轨迹之后对轨迹里的transition重新选择目标用新目标重新计算奖励再存进replay buffer。论文给出了四种重标注策略final、future、episode、random。它们的区别只在于“用哪个状态去替代原目标”。我做了个对比表方便看清差异策略目标来源优点缺点final整条轨迹最终状态逻辑最简单和真实最终结果一致每条轨迹只产生一种重标目标信息量太小future同一轨迹当前时刻之后的随机状态时序自洽目标在当前状态之后一定可达效果最好实现稍复杂需要随机采样未来索引episode同一轨迹任意时刻的随机状态目标覆盖范围广可能选中“已经发生但回不去”的状态时序逻辑混乱random整个replay buffer的随机状态目标多样性最大目标与当前状态之间未必存在可行路径学习效率低理论分析和实验都指向future策略最优。原因其实很好理解当前时刻是tfuture采样出来的目标g来自t之后的某个状态那么从st到g这条路径在真实物理过程中已经发生过了说明它是可达的策略要学的是“如何从当前状态逼近这个可达目标”。final虽然也符合时序但只有一个目标点浪费了轨迹中段蕴含的大量目标信息。episode策略选了过去的某个状态做目标这就有个逻辑矛盾目标在t之前已经路过现在要重新到达物理上不是不可以但优化方向会被拉扯。random跨轨迹采样就更乱了可能采到一个当前状态根本够不着的地方给critic制造大量难样本。论文里还有一个重要的实现细节叫bit flip。每条transition有一定概率保留原始目标gbit为0另一部分概率换成重标后的目标gbit为1。论文默认是各50%。保留一部分原始transition是为了防止策略在重标过程中完全忘了用户指定的原目标毕竟我们的最终任务是完成给定目标而不是随便到达一个状态。两种样本混合训练既保持了任务意图又利用事后视角补充了成功样本这是HER能够稳定训练的关键之一。伪代码层面HER的实现并不复杂。这里给一个future策略的简化版本import random def her_rollout_and_store(env, policy, buffer, k4): # 采样一条完整轨迹 episode [] obs, _ env.reset() done False while not done: action policy(obs) next_obs, reward, done, info env.step(action) episode.append((obs, action, reward, next_obs, info)) obs next_obs T len(episode) for t, (s_t, a_t, _, s_t1, info_t) in enumerate(episode): g_true s_t[desired_goal] # 原始轨迹也存一份 buffer.add((s_t, a_t, compute_reward(s_t1, g_true), s_t1, g_true)) # 额外重标注 k 份 for _ in range(k): # future 策略从 t1 到 T-1 之间随机选一个时刻 future_idx random.randint(t 1, T - 1) g_new episode[future_idx][3][achieved_goal] r_new compute_reward(s_t1, g_new) buffer.add((s_t, a_t, r_new, s_t1, g_new))这里的compute_reward就是判断achieved goal和desired goal距离是否小于阈值。注意重标注只改变目标和奖励动作、状态转移都不动物理动态是真实的这正是HER能做对的原因——它不生成虚假的状态转移只生成对同一段物理过程的合理“解读”。k值对应论文里的n_sampled_goal4意思是每一条原始transition在采样时额外产生4条重标样本。k太小重标效果不明显k太大buffer里原始目标样本被大幅稀释策略容易忽略用户指定目标。3. 实操过程用SB3从零搭一套HER训练管线这一节我直接给可以跑的方案。环境方面最经典的是OpenAI Gym Robotics的Fetch系列目前已经迁移到gymnasium-robotics。建议按FetchReach、FetchPush、FetchPickAndPlace、FetchSlide的顺序练手因为它们的难度递增接触动力学、滑动摩擦这些坑在后面。环境观测是dict格式包含三个关键键observation是机械臂自身的状态向量desired_goal是任务目标achieved_goal是从状态中提取出的“当前实际到达位置”。这个结构正好匹配SB3的MultiInputPolicyHER replay buffer依赖achieved_goal做重标注所以环境选型上这是一条顺路。依赖安装建议用新版组合pip install gymnasium pip install gymnasium-robotics pip install stable-baselines3如果遇到版本兼容问题稳妥的做法是固定老组合gym0.21加gym-robotics搭配stable-baselines31.7因为网上大量教程基于这套。新老版本的主要差异在环境id后缀比如FetchReach-v2还是FetchReach-v3和env类型的判断逻辑遇到报错时优先检查这里。训练代码主体如下import gymnasium as gym import gymnasium_robotics from stable_baselines3 import DDPG from stable_baselines3.her import HerReplayBuffer, GoalSelectionStrategy from stable_baselines3.common.callbacks import EvalCallback env gym.make(FetchReach-v3, reward_typesparse) eval_env gym.make(FetchReach-v3, reward_typesparse) model DDPG( policyMultiInputPolicy, envenv, replay_buffer_classHerReplayBuffer, replay_buffer_kwargs{ n_sampled_goal: 4, goal_selection_strategy: GoalSelectionStrategy.FUTURE, }, gamma0.98, learning_rate1e-3, batch_size256, buffer_size1_000_000, tau0.05, policy_kwargs{net_arch: [256, 256, 256]}, verbose1, ) eval_callback EvalCallback( eval_env, best_model_save_path./logs/best_model, log_path./logs/results, eval_freq5000, n_eval_episodes10, deterministicTrue, ) model.learn(total_timesteps2_000_000, callbackeval_callback) model.save(her_fetchreach)为什么选DDPG而不是PPO或者SACHER论文原版实验用的是DDPG因为DDPG是off-policy算法天然支持replay buffer中的随机采样。PPO是on-policy算法它的优化目标是当前策略采样得到的数据分布一旦把buffer里跨策略期的旧样本混进来重要性采样比值会剧烈波动训练很容易崩溃。SAC理论上也能配HER但SAC自带的熵调节在高维连续控制里调参更麻烦DDPG对于这种任务通常更稳定、更省心。先把DDPG跑通再折腾SAC是比较务实的路径。超参数我直接给出推荐值并说明理由参数推荐值说明n_sampled_goal4论文默认值每个transition额外重标4次样本利用率与分布平衡的折中goal_selection_strategyfuture效果最好绝大多数任务选它gamma0.98Fetch类任务常用不设太接近1是为了降低长远奖励方差learning_rate1e-3DDPG默认常用值过大Q值容易爆过小收敛慢batch_size256批量够大才能同时覆盖原始目标和多个重标目标buffer_size1_000_000足够大的经验池保证重标样本多样性内存不够可降到300_000tau0.05比常规0.005大一些target网络更新更快配合稀疏任务更早出学习信号训练过程中的指标要重点盯eval成功率而不是train reward。原因在下一节细讲但这里先强调一句HER的train reward在训练早期经常是0因为eval回调里统计的是真实目标下的成功率而train reward混入大量重标后的伪成功样本两者意义完全不同。如果看到eval里的mean_success_rate从0开始逐步往上爬基本就说明HER生效了。在FetchReach上DDPG加HER通常几万步就能看到成功率变化百万步内可以接近100%。FetchPush要慢一些FetchPickAndPlace最慢瓶颈主要是接触动力学和物体姿态多样性的探索难度。如果想加载模型继续跑注意DDPG.load之后要重新绑定envmodel DDPG.load(her_fetchreach, envenv) obs, _ env.reset() done False while not done: action, _ model.predict(obs, deterministicTrue) obs, _, done, _, _ env.step(action)4. 实战中常见的坑与排查方法HER原理听起来简单真正跑起来坑不少。我把自己实际踩过且帮别人排查过的问题整理成一张速查表现象常见原因排查与解决办法成功率始终为0loss也不动实际没启用HER用了普通replay buffer或探索噪声过大检查代码里replay_buffer_classHerReplayBuffer是否真的传进去了噪声std降到0.1~0.2PPO配HER后训练崩溃on-policy算法与跨期buffer不匹配换DDPG/TD3/SACHER只建议搭off-policy算法gym环境报EnvNotSubclassError新旧gym/gymnasium API不兼容dict obs解析失败固定版本组合或升级到最新gymnasium-robotics并调整环境idtrain reward在涨但eval成功率很低训练回调记录的是重标后的伪成功不是真实目标成功以eval callback的success rate为准不要看train rewardQ值在训练中期爆炸成NaNreward scale太大、学习率太高、obs量纲差异大调低lr和tau检查obs范围必要时候做归一化后再喂网络训练慢一千万步都没起色k值太大buffer被重标样本淹没或未来采样时间跨度过长把n_sampled_goal调回4检查future采样是否限制在t1到T-1第一个坑最容易被忽略。很多人以为把DDPG的replay_buffer_kwargs写上就等于启用HER实际上只要replay_buffer_class没设成HerReplayBuffer整个训练就是纯DDPG稀疏奖励下结果自然惨不忍睹。我在调试时习惯在训练循环里打印buffer采样的transition个数和重标目标的来源确认样本里确实存在“原目标失败但重标目标成功”的transition。没有这种样本说明重标注逻辑压根没跑起来。第二点想多说几句。我见过不少人在社区问“为什么我参照论文把HER加到PPO上效果反而更差”。HER论文本身没限定算法但它整个机制建立在一个前提上你有一个可以反复从历史数据中均匀采样的off-policy缓冲区。PPO每次更新必须基于当前策略的在线采样它一旦从缓冲区里抽到很久以前的样本重要性权重就会严重失真策略更新方向会变得非常混乱。这跟HER本身无关而是算法匹配问题。实践中如果想尝试policy-gradient类算法可以考虑用TD3或SAC这类off-policy方法它们才是HER的真正搭档。第三个高频问题是obs归一化。FetchReach的观测数值大多在个位数范围归一化影响不大但FetchPickAndPlace里物体位置、关节角、线速度混在一个向量里尺度差异明显DDPG的Q网络输入层容易发生数值病态。使用VecNormalize时要格外小心因为HER会对replay buffer中的样本做动态重标normalize统计量如果基于这些重标样本计算会导致训练信号失真。我个人的做法是先不过度依赖标准化在FetchReach和FetchPush这类小型任务里直接跑只有在FetchPickAndPlace及更难的任务上才对观测做min-max clip且不使用依赖在线统计更新的normalizer。这个取舍也许不是最优的但实测更稳定。还有一个容易出错的是is_success字段。gym robotics不同版本中info里的is_success判断阈值可能与奖励函数不一致比如切换reward_typesparse或dense时环境内部逻辑会有差异。我建议eval时绕过info字段自己用np.linalg.norm(achieved_goal - desired_goal) 0.05来判定成功这样最可控也不受版本迁移影响。5. 扩展思路与个人体会HER的思想已经远超“一个经验回放技巧”的范围。它本质上是在做“反事实经验增强”——用可达目标替代预设目标重新解释过去的轨迹。这个思路后来催生了HGG、CHER、GCSL等一系列工作。比如HGG不再随机采样future目标而是主动生成有信息增量的目标用来引导更难的任务GCSL更是把“目标重标”的思想直接搬到了监督学习框架里不再依赖critic和actor的异步更新。如果你的项目卡在稀疏奖励上HER通常是性价比最高的第一刀。我自己跑了两年多HER相关实验最深的一个体会是HER不是银弹。它适合“目标明确、状态可测量、奖励完全稀疏”的任务在这种条件下它效果拔群但如果任务本身已经能设计出比较可靠的稠密奖励强行上HER反而可能因为重标目标干扰原始意图导致训练变慢。另外不要把n_sampled_goal盲目开大k4这个经典值是论文里反复试出来的我试过k8样本占用和训练耗时都上去了成功率却没有明显提升。最后分享一个小的实操习惯每次跑新环境我都先把buffer_size调小到能让训练快速走完一轮同时固定随机种子先确认整条管线能跑通、能看到成功率上升的趋势再放开规模去长训。这样能省掉大量“训练了十几个小时才发现环境包装错了”的冤枉时间。HER这类算法调参空间不小先跑通再跑好是我最想叮嘱后来者的一件事。
返回列表