拓冰建站拓冰建站
首页 / 资讯中心 / 正文

破解稀疏奖励难题:HER后见经验回放机制详解

第一次在技术群里看到hindsight这个词时我以为是某个复盘工具或者日志回溯产品。真正点进论文才发现它是强化学习领域绕不开的一个算法——Hindsight Experience Replay后见经验回放简称 HER。今天这篇文章就把它彻底拆开从它解决的稀疏奖励问题到目标替换的核心机制再到我从零复现和调参过程中踩过的坑。如果你正在做机器人控制、策略优化或者单纯被奖励永远给不出来折磨过这篇应该能省下你不少时间。1. hindsight在AI圈指什么——从单词到稀疏奖励难题1.1 一个单词多副面孔hindsight直译过来是后见之明日常语境里就是事后复盘。但在机器学习圈子里这个名字被好几个项目用过有做数据库时间旅行的有做日志回溯的而最出圈的是强化学习里的 HER 算法全称 Hindsight Experience Replay。OpenAI 在 2017 年发表了那篇论文讲的就是怎么让智能体从失败的经历里学到东西。这篇文章核心讲 HER后面所有内容都围绕它展开。很多人第一次看到后见经验回放这个中文翻译会有点懵。其实拆开看并不复杂hindsight是指你回头看某条轨迹的时候会忍不住想如果当时目标是另一个就好了experience replay是强化学习里经典的经验回放技术也就是把智能体经历过的状态转换存到缓冲区里训练时随机抽样。HER 就是在这个回放机制上做了一个关键改动让原本毫无学习信号的失败轨迹也能变成有价值的训练数据。1.2 稀疏奖励问题强化学习的高压锅时刻强化学习的基本套路是智能体在环境里不断试错靠奖励信号调整策略。问题是大多数教学 Demo 里的奖励都很密每做一步就有反馈算法当然跑得欢。真实场景根本不是这样机械臂抓取物体只有最后把物体放到目标位置才给一个正奖励下棋只有终局胜负才有回报外卖调度要等整个配送链路跑完才能知道收益。这种情况下绝大多数尝试的奖励都是零甚至全程是负一。没有奖励信号算法就没有梯度策略就一直在原地打转。我把这种状态比作高压锅时刻环境一直在运行训练日志一直在输出但 loss 曲线死水一潭成功率永远为零。你用随机策略去推一万步能碰上几次正向奖励都算烧了高香。这也是为什么稀疏奖励问题长期被视为强化学习落地的一道硬门槛。1.3 为什么普通经验回放救不了场有人可能会说那用经验回放复用样本不就行了这里要澄清一个关键点经验回放解决的是样本相关性不是样本有效性。DQN 时代提出的 replay buffer 是把历史 transition 存起来训练时随机抽一批避免连续样本之间的强相关性导致网络震荡。但 buffer 里存的全是没到目标的失败样本时抽样一万次奖励还是负一算法依然学不到任何东西。有个生活化的类比很贴切稀疏奖励下的试错就像准备一场只有期末考的课程。密奖励等于随堂测验错了老师立刻指出稀疏奖励是你苦学一学期最后只看一眼成绩单连哪道题错了都不知道。经验回放是让你把平时做的练习反复做但练习题本身没有标注对错你做一万遍也提升不了期末成绩。问题的根源不在练习量不够而在有效反馈缺失。HER 的思路就是反过来把这些没有标注的练习题自己动手补上标注。2. HER核心机制拆解把失败变成经验2.1 一次目标替换的完整演示HER 最有创造性的地方是把人类后见之明这个认知偏差变成了算法。先看一个具体的二维导航例子。假设一个点从初始位置出发目标是地图右上角但跑了整整 50 步最后停在地图左下角。这显然是一次失败轨迹每个状态的奖励都是 -1。传统做法是整条轨迹丢进 buffer 当废料。HER 说且慢既然智能体最后停在了sT那sT本身为什么不能作为目标我们把这条轨迹里原始目标全部替换成g sT然后重新计算奖励。你会发现一个有意思的现象这条轨迹从头到尾正好是从起点成功走到 sT的完整演示。最后一步当然到达了目标奖励是 0中间几步虽然没到但路径本身是连贯的学习信号瞬间从全是负一变成了有梯度可寻。原始轨迹 s0, a0, s1, a1, ... , sT 原始目标 g全程未达奖励恒为 -1 重标定后 s0, a0, s1, a1, ... , sT 替代目标 g sT最后一步成功奖励为 0这里有个细节容易被新手忽略HER 并不是只把最后一步改成成功就完事而是将轨迹里所有的goal全部替换成g。因为目标条件策略的输入是(状态, 目标)你只改最后一步而前面不给新目标网络根本不知道我这 50 步到底在往哪走。必须整条轨迹都使用同一个替代目标这条数据才自洽。2.2 替代目标怎么选四种采样策略既然目标可以替换那替换成什么就很有讲究了。OpenAI 论文里给出了四种策略这里我逐一讲清楚并且会说明我实际实验后的感受。final直接取轨迹结束状态sT。这是最简单也最稳定的方式官方实践里经常默认推荐。episode从同一轨迹内随机抽一个状态作为目标。样本多样性比 final 好但和轨迹本身的走向耦合较深。random从整个回放缓冲区里随机抽一个状态。发散性最强但噪声也最大。future从当前时刻之后的状态里随机抽一个。也就是以后来的事实作为目标兼顾了因果性和多样性。论文里的超参k指的是每个 transition 额外采样多少个替代目标。默认k4意思是原始轨迹的一个转移(s, a, s_next)除了保留原始目标那份样本外还会再生成 4 份带不同替代目标的样本总共塞进 buffer 5 份。这个复制多份的操作很重要它让一条失败轨迹被反复利用而替代目标的不同又带来了样本多样性。2.3 为什么HER只改数据不改算法HER 最让我欣赏的一点是它没有引入任何新的策略优化公式而是纯粹把 replay buffer 的输出加工了一下。它的输入是 off-policy 算法采样出的 transition输出是替换过goal和reward的新 transition然后再喂给 DQN、DDPG、SAC、TD3 这类算法正常训练。外层算法代码一行都不用动。这种只改数据不改算法的设计带来了巨大的工程收益。首先它是可插拔的你可以把 HER 封装成一个 replay buffer 模块想用就用、想换就换。其次它容易和其他技巧叠加后面接 layer normalization、population-based training 都不会有冲突。最后调试也方便如果训练出问题可以先关掉 HER 看原始算法是否有 bug再单独排查重标定逻辑。要注意的是 HER 天然绑定 off-policy 算法。因为重标定后的样本来自行为策略当前策略更新后再去用这批数据分布已经偏移DQN 这类基于 Q 值的学习能容忍DDPG/SAC 这类 actor-critic 也能吸收但 PPO 这类 on-policy 算法直接吃重标定数据就会有偏差。如果你在 PPO 上套 HER先想清楚这个分布偏移问题。3. 落地实现手写HER与用现成库跑通3.1 最小实现思路与伪代码理解机制后自己动手写一个最小实现是彻底掌握 HER 最有效的方式。核心其实就两件事一个能存整条轨迹的 buffer一段在 episode 结束后执行的重标定循环。replay HERBuffer() for episode in range(episodes): g sample_goal() # 采样一个原始目标 s env.reset(goalg) traj [] while True: a policy.get_action(s, g) # 目标条件策略 s_next, r, done env.step(a) traj.append((s, a, s_next, g))# 先存原始目标 s s_next if done: break # 后见之明重标定 for i, (s, a, s_next, g_orig) in enumerate(traj): # 原始目标也保留一份防止策略漂移 replay.push(s, a, s_next, g_orig, compute_reward(s_next, g_orig)) # 每个 transition 额外生成 k 个替代目标 for _ in range(k): g_hat choose_goal(goal_selection_strategy, traj, replay, i) r_hat compute_reward(s_next, g_hat) replay.push(s, a, s_next, g_hat, r_hat)这段伪代码里有三个地方必须注意都是我实际写过才明白的。第一必须等整个 episode 结束之后再统一做重标定不能边跑边标。因为future策略需要索引当前时刻之后的状态流式处理拿不到未来信息。第二原始目标那份样本一定要保留否则训练过度偏向替代目标分布真实任务的成功率反而不升反降。第三k4意味着每个过渡会被复制成 5 份buffer 容量设置时要把这个放大系数算进去不然你以为存了 100 万步实际有效覆盖只有 20 万步的真实转移。3.2 用stable-baselines3开箱即用理解了原理真正做项目时我会直接用成熟实现。stable-baselines3的HerReplayBuffer已经做得相当完善下面给一套可以本地直接运行的配置。先写一个简单的二维导航环境目标是让一个点在[-5, 5]的方形区域内走到随机目标点到达距离 0.5 以内即算成功。import numpy as np import gymnasium as gym from gymnasium import spaces class SimpleNavigationEnv(gym.Env): def __init__(self): super().__init__() self.observation_space spaces.Dict({ observation: spaces.Box(-5, 5, shape(2,), dtypenp.float32), desired_goal: spaces.Box(-5, 5, shape(2,), dtypenp.float32), achieved_goal: spaces.Box(-5, 5, shape(2,), dtypenp.float32), }) self.action_space spaces.Box(-1.0, 1.0, shape(2,), dtypenp.float32) self.max_steps 50 self._step 0 def reset(self, seedNone, optionsNone): super().reset(seedseed) self._step 0 self.state self.np_random.uniform(-5, 5, size2).astype(np.float32) self.goal self.np_random.uniform(-5, 5, size2).astype(np.float32) return self._get_obs(), {} def step(self, action): self._step 1 self.state np.clip(self.state action, -5, 5).astype(np.float32) obs self._get_obs() reward self.compute_reward(self.state, self.goal, None) terminated bool(reward 0.0) truncated self._step self.max_steps return obs, float(reward), terminated, truncated, {} def _get_obs(self): return { observation: self.state.copy(), desired_goal: self.goal.copy(), achieved_goal: self.state.copy(), } def compute_reward(self, achieved_goal, desired_goal, info): return float(np.linalg.norm(achieved_goal - desired_goal) 0.5) - 1.0环境准备好了下面配置带 HER 的 SAC 模型。注意MultiInputPolicy是必须的因为 GoalEnv 的观测是一个包含observation、desired_goal、achieved_goal三个字段的字典。from stable_baselines3 import SAC from stable_baselines3.common.her import HerReplayBuffer model SAC( MultiInputPolicy, SimpleNavigationEnv(), replay_buffer_classHerReplayBuffer, replay_buffer_kwargsdict( n_sampled_goal4, goal_selection_strategyfuture, online_samplingTrue, max_episode_length50, ), learning_starts1000, buffer_size100_000, batch_size256, gamma0.95, tau0.05, train_freq1, gradient_steps1, policy_kwargsdict(net_arch[256, 256, 256]), verbose1, ) model.learn(total_timesteps300_000) model.save(sac_her_navigation)如果不想自己写环境也可以直接用gymnasium-robotics里的 Fetch 系列环境比如FetchReach-v3、FetchPush-v3这些是机械臂操作的标准测试场景。唯一要注意的是它们需要 MuJoCo 物理引擎虽然现在 MuJoCo 已经免费但安装配置还是要花点时间。新手我建议先用上面的自定义环境跑通逻辑再上复杂环境。3.3 训练配置推荐与超参数解析超参数这个东西不同任务差异很大但 HER 有几个参数是共通的我整理成了一张表方便你对照实验。参数推荐值说明n_sampled_goal4每个 transition 额外生成几个替代目标。太大增加计算开销太小样本多样性不足goal_selection_strategyfuture多数连续控制任务上 future 综合效果最好final 最省事online_samplingTrue在轨迹实时生成时做重标定等价于 batch 内用未来状态做目标设 False 则按 archive 模式回放max_episode_length与 env 一致必须显式传给 buffer否则它不知道 episode 边界future 索引会越界batch_size256较大 batch 对目标条件学习有帮助gamma0.95~0.99任务步数越少gamma 可以越低长任务建议接近 1net_arch[256, 256, 256]目标条件和普通状态拼接后输入维度变大三层全连接是稳妥起点buffer_size100_000 起记得这个数字会被重标定放大 k 倍有一点我在调参时印象很深online_sampling这个参数不是所有版本都有早期版本的 SB3 里没有你需要自己去源码里确认。它的含义是重标定时使用的未来状态是来自当前正在采集的轨迹还是来自已经存入 buffer 的旧轨迹。打开之后buffer 里的样本时效性更强效果通常会更好。4. 实测效果与调参心得4.1 稀疏奖励下的效果对比我在上面那个简单的二维导航任务上做过一组对照实验对比标准 SAC 和 SAC HER 的表现。任务本身很简单但因为奖励完全稀疏随机策略的成功率极低。标准 SAC 跑了 30 万步成功率曲线几乎是贴地的一条线个别跑动的 seed 能偶尔碰出几次成功整体学习不到稳定的策略。加上 HER 之后大约 15 万步开始出现连续成功30 万步时成功率稳定在 70% 以上。这不是 HER 有多神奇的玄学而是它把一条失败轨迹里的
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门