ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Hindsight Experience Replay:用事后经验解决强化学习稀疏奖励问题

2026/10/3 15:30:56 拓冰建站 浏览量
Hindsight Experience Replay:用事后经验解决强化学习稀疏奖励问题 提到 hindsight 这个词搞机器学习的同行第一反应多半是 Hindsight Experience Replay也就是常说的 HER。我第一次读 Andrychowicz 那篇论文的时候觉得这个名字起得实在太妙——把“事后聪明”这个人类认知里非常普遍的毛病直接变成一种提升强化学习采样效率的算法。说到底就是让智能体从失败的轨迹里翻旧账这次没抓到目标不要紧把目标换成“实际到达的位置”这条轨迹反而成了一条成功轨迹。如果孩子每次投篮没进都只得到“没进”的惩罚他很难学会投篮但如果他每次都记住“球最后飞到哪”他会慢慢建立起“朝那个方向用这个力度”的手感。HER 干的就是这件事。这篇文章我会从概念讲起把 HER 为什么能解决稀疏奖励问题、网络和代码怎么搭、参数怎么调、以及实际跑训练会踩哪些坑都摊开讲一遍。适合正在做机器人控制、抓取、导航这类 goal-conditioned 强化学习任务的朋友也适合刚接触稀疏奖励问题、想知道除了奖励塑形和课程学习还有哪些思路的入门读者。我会尽量写得像操作笔记一样你照着做基本能复现出可用的训练曲线。1. 项目解读这个“hindsight”到底在做什么1.1 为什么用“事后聪明”来命名一个算法项目先扯一下这个词本身。hindsight 的字面意思是“后见之明”日常语境里常带点自嘲事情发生之后谁都能看清当时该怎么做。放在强化学习里这个词精准得让人拍大腿——因为 HER 的核心动作就是在 trajectory 结束之后回头把“当初设定的目标”替换成“后来实际达到的状态”然后用这个新的目标重新解释整段经历。我见过不少第一次接触 HER 的人都会卡在这个直觉上替换目标之后这条轨迹还是原来那条轨迹吗答案是物理轨迹没变但学习信号完全变了。原来每一步 reward 都是 -1因为最终没达成目标替换目标之后轨迹终点恰好就是新目标所在位置所以最后一步 reward 变成 0前面的步骤也因为“离目标越来越近”而有了明确的改进方向。原本一条毫无信息量的失败轨迹经过这次“事后重写”就变成了一条教会智能体“如何逼近一个目标”的正样本。这正是 HER 这个名字的妙处它不是让智能体变聪明而是让智能体学会像人一样“事后复盘”。人犯了错会反思“要是当时这么做就好了”HER 直接把这个反思过程自动化而且不需要任何额外的人工标注。1.2 HER 解决的核心痛点稀疏奖励为什么需要这种“事后反思”因为强化学习里最难啃的骨头之一就是稀疏奖励问题。很多真实任务比如机械臂抓取、移动机器人导航、游戏里找钥匙开门环境只在任务真正完成的时候给一个非零奖励其他所有时刻都是 0甚至是 -1。在这种设定下智能体随机探索一百步九十九步都得不到任何正反馈它根本不知道往哪个方向动才能离奖励更近。传统解法有三板斧。第一板斧是奖励塑形也就是人为设计一个中间奖励函数比如“距离目标越近奖励越高”。但奖励塑形最大的坑是容易学歪智能体会找到让你给的函数数值最大、但任务根本没完成的投机行为而且设计奖励函数本身非常耗时换一个任务就要重新设计。第二板斧是课程学习先让智能体学简单任务再慢慢变难但课程难度的自动调整也是个难题设计不好反而拖慢训练。第三板斧是模仿学习需要大量专家演示数据很多场景根本拿不到。HER 绕开了这三条路。它不修改环境奖励不设计课程也不需要专家数据仅仅靠修改 replay buffer 里数据的“解释方式”就把稀疏奖励变成了密集且有效的学习信号。这个思路听起来简单但它对采样效率的提升是实打实的在 Fetch 系列机器人操作环境上HER 配合 DDPG 可以把成功率从几乎为零拉到 80% 以上。1.3 适用边界什么任务真正需要 HER说句实在话HER 不是万金油。它适用的任务有一个关键前提必须能定义一个“goal”而且这个 goal 必须是可观测、可判定的。比如机械臂抓取goal 就是“物体位置到达目标点”这个可以用物体当前位置和 desired goal 的欧氏距离来判断又比如迷宫导航goal 就是“智能体到达终点坐标”。反过来如果你手里的任务根本没有清晰的目标定义——比如“让对话更自然”“让画面风格更高级”——那 HER 就很难直接套用因为你说不清楚“实际到达的状态”到底算不算完成了目标。还有一个隐性的适用条件任务最好允许 agent 在 episode 结束前“无限接近目标”。如果任务是二值的比如“按按钮成功或失败”失败之后智能体什么都没做到那 hindsight 重写出来的目标也接近随机噪声帮助有限。另外提一句HER 和 off-policy 算法是天然搭档因为它依赖 replay buffer 做目标重标注on-policy 算法比如 PPO 用起来就比较别扭虽然可以硬套但收益远不如 DDPG、SAC 这类算法明显。后面我讲实操也默认是 HER DDPG 的组合。2. HER 的核心原理让智能体从失败里“翻旧账”2.1 目标重标注把“没做到”改成“我做到了”先看数学上的操作。标准的 goal-conditioned 强化学习里一条轨迹由一系列状态、动作、目标、奖励组成。智能体在当前状态 s_t 下面对目标 g执行动作 a_t得到奖励 r_t R(s_t, a_t, g)然后转移到 s_{t1}。当目标没有达成时奖励恒定是 -1 或者 0学习信号非常虚。HER 在 trajectory 结束之后额外采样一个新目标 g这个 g 从轨迹中某个实际到达的状态里取比如轨迹终点 s_T 的位置。然后整条轨迹的 reward 全部用 g 重新计算一遍def recompute_reward_with_goal(state, goal): # 以机械臂抓取为例距离小于阈值算成功 distance np.linalg.norm(state - goal) return 0.0 if distance 0.05 else -1.0 # 原始轨迹: goalg, 在 s_T 处失败 # 重标定轨迹: goalg, s_T 距离 g 为 0, 所以奖励不再是全 -1 for t in range(len(states)): rewards_new[t] recompute_reward_with_goal(states[t], g_prime)注意一个细节替换 g 之后轨迹里的 s_t 和 a_t 一个都不变变的只是“目标”这个条件。也就是说同一个物理过程被“解释”成了另一个过程本来是从起点乱撞一通最后失败重标定之后变成了“不断逼近一个目标、最后一刻正好抵达”。这就是 HER 最优雅的地方。它没有改变环境没有改奖励函数只改变了经验池里数据的语义。用大白话说它让智能体从“失败”中提炼出了“阶段性成功”——你虽然没有达成原始目标但你实际到的那个位置本身就是你此刻能做到的最好结果把它当作目标来学就是在学“如何稳定复现当前水平”这是能力提升的第一步。这里有一个很容易踩的误区重标定目标时不能随便从轨迹里挑一个状态。如果选的目标在轨迹起点就已经满足了那这条“成功轨迹”里几乎所有步的奖励都改成 0同样没有学习信号。所以目标通常要从轨迹中段或末段的位置采样才能在“开始没达到、后来达到了”的过程中形成清晰的梯度。2.2 四类虚拟目标采样策略论文里给出了四种从轨迹中采样 g 的方式分别是 final、episode、random、future。我用实际效果排序说明一下你可以直接抄作业。final 策略最简单拿轨迹最后一个状态作为 g整个 episode 只重写一次。我的实际体验是它适合很早期或者轨迹特别短的任务但一旦智能体学得好、大部分 episode 都成功时hindsight 的增益就消失了因为“最后状态”和“原始目标”往往已经重合。episode 策略是从整条轨迹的所有状态里均匀采样一个作为 g。这样能产生更多样化的虚拟目标但有个风险如果采样到的位置出现太早可能出现“轨迹刚开始就已经达到目标”的假成功需要额外过滤。random 策略是从 replay buffer 里其他 episode 的状态中随机抽一个。这种策略的理论含义是让智能体知道“任何状态都能成为潜在目标”但使用时要小心它可能生成一些不太合理的组合导致 critic 学习难度加大。future 策略是我个人最推荐的它要求采样位置在当前时刻 t 之后也就是“从未来状态中取目标”。这样能天然保证轨迹在某个时间点之后达到目标时间上的因果顺序也是合理的。baselines 的默认配置里normal 模式就是用 future 的某个变体。实际项目里我通常先用 final 跑通流程再切到 future 配合一个合适的 k 值来提升最终成功率。2.3 为什么 HER 能提升采样效率一条轨迹的多次复用聊采样效率之前先做个简单对比。普通 DDPG 在稀疏奖励环境里一条失败轨迹提供给 critic 的都是负样本actor 的梯度更新方向几乎是随机的。HER 把一条轨迹额外改写 k 次意味着同一条物理轨迹可以产生 k1 条带不同目标的训练数据。一条本来要扔掉的失败记录变成了 k1 条对“逼近目标能力”的训练样本。这个 k 值直接决定数据量的放大倍数。k4 是比较常规的起点论文实验里多数任务用 k4 到 k8 就能看到明显效果。我试过把 k 调到 16成功率没有继续显著上升训练时间倒是线性涨上去了所以不建议盲目加大。从信息论角度说一条轨迹里能提取出的“阶段性成功”信号是有上限的超过这个上限重复的目标只会让数据冗余。另外要说清楚HER 并不改变算法的 bias-variance 性质。它引入的“事后目标”会让价值函数学习到的是“在所有可能目标上的泛化能力”而不是“仅仅在原始目标上的能力”。这就是为什么 HER 学出来的策略往往比普通 DDPG 更鲁棒它在训练中见过各种各样的目标和对应的到达状态对环境的动态特性理解得更充分。3. 实操从零训练一个会抓取的目标条件智能体3.1 环境准备与依赖安装实操环节我们直接用 OpenAI baselines 仓库里的 HER 实现虽然这个仓库有点年头了但它的实现干净、逻辑清晰特别适合用来理解算法细节比很多包装精美的现代框架更适合学习。你需要准备 Python 3.6 左右的解释器环境以及一个能跑 MuJoCo 的机器。# 建议用 conda 建一个干净环境 conda create -n her python3.6 conda activate her # 安装 baselines 和配套依赖 git clone https://github.com/openai/baselines.git cd baselines pip install -e .Fetch 系列环境依赖 MuJoCo 和 mujoco-py。MuJoCo 现在已经对个人免费安装流程比前几年简单多了但 baselines 旧代码对新版 mujoco 的兼容性一般。如果你遇到 import mujoco_py 报错通常需要装一个特定版本的 mujoco-pypip install mujoco-py2.1.2.14装完之后先跑一个最小验证确认环境能正常加载import gym import baselines.her env gym.make(FetchReach-v1) obs env.reset() print(env.observation_space) print(env.action_space)如果这一步不报错说明环境链路是通的。FetchReach 是 HER 入门的最佳环境动作空间只有 4 维目标就是移动机械臂末端到一个 3D 坐标点训练速度快非常适合用来验证你的代码和参数是否正常。3.2 理解观测与动作空间Fetch 系列环境的 observation_space 不是简单的 Box而是一个 Dict里面包含三个字段observation、achieved_goal、desired_goal。这是 HER 能工作的基础因为算法必须能从 obs 里读出“当前实际到达的 goal”才能进行目标重标注。以 FetchReach 为例observation 是机械臂末端的位姿和速度信息achieved_goal 是末端的 3D 坐标desired_goal 是目标点的 3D 坐标。对于 FetchPush 这类带物体的任务achieved_goal 就是木块的位置desired_goal 是木块的目标位置。baselines 的 HER 实现会把这些字段拼成一个长向量作为网络的输入。具体拼接方式在代码里体现为将 observation 和 goal 拼在一起再用一个全连接网络处理。有一个细节我要重点提醒如果你自己写网络而不是用 baselines 封装一定要保证输入里同时包含 achieved_goal 和 desired_goal 两个信息。critic 需要知道“当前实际在哪儿”以及“要求去哪儿”才能判断“距离目标有多近”。很多人复现 HER 效果差就是网络输入里漏了 achieved_goal导致价值函数根本没法正确估计“到目标的距离”。3.3 用 baselines 跑起一个 HERDDPG 训练baselines 里 HER 的实现和 DDPG 强耦合入口可以直接通过 train 脚本启动。老仓库的用法是这样python -m baselines.her.experiment.train \ --env_nameFetchReach-v1 \ --n_epochs50 \ --n_cycles50 \ --num_cpu1如果你不想直接用命令行脚本也可以自己写一段 Python 调用逻辑方便在 Jupyter 里调试。核心配置项在 experiment/config.py 里比如# 以下是简化后的配置示意 config { env_name: FetchReach-v1, batch_size: 256, # 每次更新采样的经验条数 buffer_size: int(1e6), # replay buffer 容量 gamma: 0.98, # 折扣因子 tau: 0.05, # target 网络软更新系数 actor_lr: 1e-3, critic_lr: 1e-3, k: 4, # 每条轨迹额外重标定的次数 goal_selection_strategy: future, n_epochs: 50, n_cycles: 50, }我自己习惯把训练封装成一段循环每一轮做四件事采集 rollout、把轨迹存入 buffer、对轨迹做 HER 重标定、从 buffer 采样更新 actor 和 critic。baselines 的实现里重标定发生在存入 buffer 之前由sample_her_transitions函数完成它会对每条轨迹额外生成 k 条“虚拟目标版本”的 transition 一起放进 replay buffer。DDPG 部分的网络结构默认是 256-256 的两层全连接。actor 输出 4 维动作用 tanh 限制在 [-1, 1] 区间对应机械臂各关节的增量控制。critic 输入是拼接后的状态和目标输出一个 Q 值。这些结构不需要改训练前跑一两个 epoch 确保 loss 没有爆掉就好。3.4 核心参数设置与表格我把几个直接影响训练效果的核心参数整理成一张表都是跑 Fetch 系列环境的经验值参数推荐值含义调整心得k4每条轨迹额外重写的目标数4 起步够用上 8 收益不大16 纯浪费算力goal_selection_strategyfuture虚拟目标采样策略future 最稳final 适合快速验证管道batch_size256每次更新的样本数小任务 128 也行但 256 更稳buffer_size1e6经验池容量不要设太小否则目标重标定的多样性不够gamma0.98折扣因子机器人任务一般 0.95~0.99tau0.05软更新系数0.05 是 baselines 默认别动太猛actor_lr / critic_lr1e-3学习率两个网络都 1e-3 起步loss 震荡时降到 3e-4n_cycles50每个 epoch 的采集轮数控制训练时长FetchReach 50 个 epoch 足够clip_range5奖励裁剪范围baselines 默认防止异常奖励干扰这里最值得花心思调的是 k 和 goal_selection_strategy其他参数基本沿用默认就能出结果。我见过太多人在 actor_lr 上纠结半天结果发现 k 设成 1 才是成功率上不去的根本原因。4. 训练曲线解读与调参心得4.1 怎么看成功率的收敛曲线训练跑起来之后最重要的观测指标不是 loss而是 evaluate 阶段算出来的成功率。baselines 的 train 脚本会在每个 epoch 结束后跑若干次确定性策略的 rollout统计成功比例。FetchReach 上大概 10 个 epoch 左右成功率就会爬到 0.5 以上40 个 epoch 冲到 0.9 算是正常水平。看曲线有几个要点。第一成功率曲线初期可能有较长的“平台期”看上去一点不动这通常是 critic 还在学习距离函数策略没有明确改进方向不用慌。第二中期曲线可能突然跳升这是因为 critic 对“距离-价值”的建模突然变得准确actor 的梯度从混沌中稳定下来。第三成功率到 0.8 之后可能长期在 0.7~0.9 之间抖动这时候优先检查是不是动作噪声太大或者 explore 阶段的随机扰动没有衰减。我踩过一次很典型的坑训练到 30 个 epoch 成功率卡在 0.6 不动我把 k 从 4 调到 6再把 tau 从 0.05 调到 0.02两个 epoch 内成功率就突破了 0.85。这两个参数一个增加有效样本量一个让 target 网络更新更平滑往往能解决“后期不涨”的问题。4.2 参数优先级先调哪个后调哪个面对一个不收敛的 HER 训练任务我的排查顺序是这样的先确认环境配置和 reward 函数没写错再看网络输入是否包含 achieved_goal然后调 k 和未来目标采样策略最后才碰学习率和网络结构。为什么把学习率放在后面因为在这个项目里成功率上不去的首要原因通常是“有效学习样本不足”而不是“梯度更新步长不对”。你把学习率调得再精细样本里全是“失败且无信息”的轨迹actor 还是学不到东西。反而是把 k 加大、把目标重标定做好直接解决样本质量的问题效果立竿见影。网络结构也不是主要瓶颈。256-256 的两层 MLP 在 Fetch 系列上足够用了。我有一次把网络加到 512-512 还加了 BatchNorm结果训练不仅没变快反而因为网络容量过大开始过拟合经验池里的噪声轨迹。记住HER 的价值在于高效利用数据不是靠大网络硬啃。4.3 算力有限时的实用策略很多朋友没有多 GPU 集群只能用一台台式机或者云端 CPU 实例。这种情况下我的建议是不要一上来就碰 FetchPickAndPlace那玩意儿训练量很大先跑 FetchReach 把整个流程走通。FetchReach 在单核 CPU 上大概一两个小时就能看到成功率上涨完全可以用来验证代码正确性。如果目标确实需要跑更复杂的任务有几个省钱省时的做法。一个是降低 n_cycles比如从 50 降到 20先跑 10 个 epoch 看成功率有没有上涨的趋势有趋势再拉长训练另一个是调小 buffer_size 到 5e5虽然理论上会影响性能但能减少内存占用让单机训练不那么吃力还有一个是用已经训好的 FetchReach 模型做初始化再迁移到 FetchPush 上比从头训快不少。这里我必须提醒一句迁移初始化不是简单地把权重加载进去就完事FetchReach 的动作空间和 FetchPush 一样是 4 维但观测维度不同输入层要改。更实用的做法是只加载 critic 的底层特征提取层或者干脆把 FetchReach 作为“预训练经验”的来源把它的 replay buffer 导出来灌进 FetchPush 的初始经验池这个操作在 baselines 里没有现成接口需要自己写几百行代码但效果确实好。5. 常见问题与排查技巧实录5.1 训练很久成功率不上涨这是被问得最多的问题。我的排查步骤很固定先打印一条原始轨迹和一条重新标定后的轨迹对比它们的 reward 序列。如果重标定后的 trajectory 的 reward 还是全 -1说明目标重写环节出了问题——最常见的原因是从轨迹里采样的 g 不在 agent 实际到达的位置上或者 g 的坐标没有正确对齐状态里的坐标单位。另一个隐蔽的原因是 reward 阈值写错。Fetch 环境默认 0.05 的判定距离如果你把环境自己包了一层可能使用的是 0.01 或 0.1导致重标定后“假成功”或“真失败”的数据分布和算法预期不一致。我建议新手先用原始环境跑通再考虑自定义 reward。还有一个场景是成功率卡在某个值长期不动比如 FetchPush 卡在 0.4。这种往往是任务本身难度导致的价值函数局部最优或者探索噪声不够。试着把 exploration 的高斯噪声标准差从 0.1 提到 0.2让智能体多尝试不同的策略轨迹有时候就能跳出平台期。5.2 训练早期 NaN 或崩溃NaN 问题在强化学习里十有八九是数值溢出。Fetch 环境的奖励是 -1 或 0理论上不会爆 reward所以 NaN 基本来自梯度爆炸。检查三个地方网络是否用了不合适的初始化、critic 输入的 goal 和 observation 是否尺度差异过大、学习率是不是太高。baselines 默认的输入没有做归一化所以动作范围是 [-1, 1]observation 里某些维度可能是物体坐标范围在 0~1 之间这还好说但如果你自己拼接了额外特征比如机械臂角度、速度数值一定要先做归一化。我的习惯是把所有输入压缩到 [-1, 1] 或 [0, 1] 再喂给网络能省掉很多 NaN 烦恼。另外注意 explorer 的噪声实现。baselines 用的是自适应参数噪声有时候种子设置不当会导致噪声标准差一路涨到非常大的数值action 超出物理约束环境重置时崩溃。如果你在训练日志里看到 action 序列突然变成极端值直接把噪声初始标准差调小一个量级。5.3 复现已开源项目时的环境兼容问题复现 baselines 老代码最大的成本往往不是算法本身而是环境版本兼容。gym 0.26 之后改了 reset 接口老代码里env.reset()返回的还带 info直接拿旧写法会报错numpy 1.24 之后移除了np.bool老代码里如果引用就会被绊住。我踩这些坑的时候解决方案很粗暴直接进入到 conda 环境里固定关键依赖版本不要追求最新。pip install numpy1.19.5 gym0.21.0 mujoco-py2.1.2.14 tensorflow1.15没错这套组合拳打下来基本能稳。tensorflow 1.15 在 Python 3.6 环境下运行很流畅baselines 的原始实现就是基于 TF 1.x 的。如果你实在不想用老版本 tensorflow也可以用 PyTorch 重写 HER 的 researcher 逻辑但那就不是“复现”而是“重写”了工作量完全不一样。有一个实用的小技巧跑训练之前先跑通一个极短的 smoke test比如 n_epochs1、n_cycles1确保从环境采集、重标定、更新网络的整条链路是通的再放心去跑长时间训练。这个习惯能帮你省下大量排查时间。5.4 常见问题速查表现象直接原因处理方案重标定后奖励仍全为 -1目标采样策略/位置不对检查 g 是否对应轨迹中实际状态成功率长期为 0网络输入缺少 achieved_goal确认输入拼接包含 achieved_goal 与 desired_goal训练中断/NaN梯度爆炸或噪声过大降低学习率缩小噪声 std归一化输入老代码 import 报错环境版本新、接口变了固定 numpy/gym/tensorflow 版本FetchPush 卡 0.4探索不够或 k 偏小提高噪声 stdk 从 4 调 6检查采样策略最后再分享一个小技巧。很多人在博客或论文里只看成功率最终值却忽略了一个关键指标确定性策略和带探索噪声的策略之间的差距。如果确定性策略成功率已经很高但训练时因为噪声影响导致 reward 曲线一直波动可以先调低噪声而不是急着改网络结构。HER 的整个设计思路就是“从过往经历中提取改进信号”只要你把这个信号源的闭环打通后面所有调参都是在锦上添花。我个人在实际操作中最深的体会是HER 不是银弹但它是稀疏奖励工具箱里最值得先试的一把锤子。面对一个 goal-conditioned 任务先别急着设计复杂的奖励函数先跑一版 HER看看它能把采样效率提到什么程度。很多时候你以为需要更多智能、更复杂网络其实只需要换个方式“看待过去”。