ARTICLE DETAIL

资讯详情

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

稀疏奖励下的强化学习:事后经验回放(HER)原理、代码与实战踩坑

稀疏奖励下的强化学习:事后经验回放(HER)原理、代码与实战踩坑 我最早接触 Hindsight Experience Replay事后经验回放简称 HER是在一个机械臂抓取的项目上。那段时间模型怎么训都不收敛奖励几乎永远是 -1所有尝试都像在黑洞里打转。后来导师扔给我一篇论文说你试试事后经验回放我才真正理解了一句话AI 学不会一个任务很多时候不是因为能力不够而是因为它在失败里什么有价值的信息都没拿到。这篇博文不打算复述论文而是从一个实践者的角度聊聊 HER 到底是什么、它为什么有效、以及怎么在代码里落地。如果你正在做强化学习尤其是被稀疏奖励问题折磨得睡不着觉这篇文章应该能给你一个明确的新方向。我会把原理、代码、调参经验、踩坑记录全部摊开来讲保证你读完能直接动手试。1. 从事后诸葛亮说起HER到底要解决什么问题1.1 稀疏奖励强化学习里最让人头疼的死胡同强化学习的目标是让智能体通过与环境的交互学到最优策略。最经典的范式是智能体每做一步动作都会得到一个奖励信号然后基于这个信号调整自己的决策。听起来很顺理成章但真实世界的任务远不是走迷宫拿奶酪那么简单。很多任务的奖励信号是极其稀疏的甚至绝大多数轨迹里根本拿不到任何正奖励。拿机械臂抓取来说目标是把桌上的杯子抓到指定位置。在训练初期机械臂的随机策略几乎不可能刚好完成这个动作所以每个回合结束时返回的奖励都是 -1。在这样一条全是 -1 的轨迹里梯度方向几乎不包含任何这步比那步强的信息策略只能靠随机碰运气缓慢更新。用业内的话说这就是死胡同——不是环境不给机会是学习信号完全没有抵达。我做实验时有一组数据特别扎心使用普通 DDPG在稀疏奖励的 FetchReach 环境里训练 100 万步成功率几乎为零。而同样的环境把奖励从稀疏改成稠密用距离作为连续惩罚50 万步就能解决。差别不在网络结构不在超参数就在奖励信号的密度上。这种情况在真实工程里到处都是。机器人抓取、自动驾驶变道、游戏里的长线任务、推荐系统里的长周期转化几乎都属于结果只有一次对错、过程没有反馈的稀疏奖励问题。你不可能为每一个中间动作都精心设计一个奖励函数——那既耗费人力又容易引入偏差。1.2 换个角度看失败把没做到改写成做到了HER 的想法非常反直觉既然这条轨迹没有达成原始目标那不如把轨迹里实际到达的状态当作目标重新标记经验同样一条轨迹站在到达状态这个新目标的角度看它就是一条完美的、达成目标的成功轨迹。我用一个生活化的类比解释一下。想象你是一个新手目标是学会做红烧肉。你第一次下厨把肉炖糊了按照传统学习方式这就是一次失败经历你不会想再重复它。但如果你换个角度看这次操作虽然没做成红烧肉但至少学会了中火不会把锅烧穿、糖色要在油温六成热时下这就是把失败变成了关于另一种目标的成功经验。HER 干的就是这件事——在训练数据层面把每个失败转换成一个或多个成功让稀疏奖励变得稠密。这个思路最早出现在《Hindsight Experience Replay》这篇论文中作者是 OpenAI 的 Marcin Andrychowicz 等人。论文的核心结论是在稀疏奖励的连续控制任务中HER 加上 DDPG能在很多原本几乎无法训练的任务上达到与稠密奖励相当的水平。我最早看到结论时其实不太相信但后来在自己的 Fetch 系列环境里复现发现效果的确是质变级的。2. 核心机制拆解为什么换个目标能建立学习信号2.1 重组训练样本把一条失败轨迹变成多条成功轨迹先来看 HER 最核心的数据处理逻辑。假设在回合制的强化学习环境里一条轨迹可以表示为一连串的状态、动作、奖励、下一个状态元组。对于一个元组 (s_t, a_t, r_t, s_{t1})在原始目标 g 下当任务未完成时奖励 r_t 通常是 -1 或 0这算一条失败经验。HER 的做法是在这条轨迹结束后额外选择若干个虚拟目标 g这些虚拟目标通常取自轨迹实际经过的状态比如终点状态 s_T或者途中某个状态。然后将整条轨迹重新标记为在目标 g 下的经验。在新目标下轨迹的终点与目标一致因此终点附近的奖励被修正为 0 或正值整条轨迹从一条失败样本变成了多条接近成功的样本。这里的关键在于策略变成了目标条件化策略即 \pi(a|s, g)。这意味着网络学习的不再是从一个状态到一个动作而是在某个目标意图下从一个状态到一个动作。这样一来同一段状态-动作序列可以在多个不同目标下被反复使用。原本一个回合只能为一组 (s_t, a_t) 提供一条学习信号现在可以提供多条——每条对应一个不同的目标状态。学习信号的密度瞬间提高了一个数量级。用数学一点的表达普通 DDPG 的 Q 值函数是 Q(s, a)而 HER 训练的是 Q(s, a, g)。当虚拟目标从已到达状态里采样时Q 值函数必须学会评估朝着这个目标方向的动作好不好这比评估一个永远达不到的目标要容易得多。随着探索推进智能体偶遇到的状态会越来越接近真实目标这种朝目标移动的能力就逐步迁移到了最终任务上。2.2 从单目标训练到多目标训练HER的理论本质那么问题来了为什么多目标训练能缓解稀疏奖励我拆成三个层面来看。第一它显著降低了策略梯度的方差。当奖励几乎全是 -1 时任何策略更新都像是在猜但重标记后的数据里有大量 0 奖励甚至正奖励的样本网络能观察到什么样的动作离目标状态更近方差自然降下来了。这在 DDPG 这种基于 Q 值传播的算法里尤其重要因为 Q 值的方差直接影响目标网络更新的稳定性。第二它天然起到了课程学习的作用。原始目标难虚拟目标已到达状态容易学习曲线会先从容易的虚拟目标开始推进逐步逼近真实目标。我在训练曲线里看得非常明显前 10 万步策略学会了追踪任意给定目标点后 20 万步开始把这种追踪能力迁移到最终目标上。这种自动形成的课程机制不需要额外设计是 HER 最有价值的地方之一。第三它不引入人工奖励设计。稠密奖励固然好用但工程上写一个合适的奖励函数可能要调好几天而且很容易破坏原有任务的语义。HER 完全不碰奖励函数只在数据回放层面做手脚对原有训练代码的侵入性极小。这一点在算法研发和工程落地中都很重要因为你不需要为一个新任务重新设计 reward shaping。2.3 虚拟目标怎么选三种采样策略的对比HER 论文里比较了几种虚拟目标的采样方式我在这里加一些实测感受final只用轨迹终点状态作为新目标。最简单效果通常不错但当轨迹终止状态离真实目标很远时学习会偏慢因为虚拟目标与最终目标差异太大。episode random k把轨迹里随机 k 个状态作为新目标。能增加多样性让智能体学会朝更多不同状态移动但随机采样可能选中对学习无用、过于容易或过于困难的状态。future取当前时间步之后、轨迹内随机时间步的状态作为新目标。论文推荐这种方式我试下来也确实是最稳的。它天然保证了虚拟目标的可达性因为是从未来状态里选不会出现让智能体往一个从未到达过的状态靠近这种不合理要求。实操里我一般选 future比例设为 4也就是每条轨迹额外生成 4 条虚拟目标轨迹与原始轨迹一起进重放缓冲区。4 到 8 我都试过4 已经能拿到大部分收益8 会明显增加训练开销收益却有限。如果你的任务目标分布特别复杂可以考虑 6但没必要再往上涨。3. 实操基于 DDPG 从零搭建 HER 训练流程3.1 环境选择与实验设定先交代一下环境。我用的是 OpenAI Gym 里的 FetchReach、FetchPush、FetchPickAndPlace 三个连续控制任务这些是 HER 论文的标准测试场景。环境特点是状态空间包括机械臂关节位置、目标位置、物体位置等信息动作空间是四维的连续控制量奖励设计为稀疏型完成任务给 0否则给 -1。之所以选这套环境是因为它们暴露了 HER 在不同复杂度任务上的差异。FetchReach 只需要移动手臂够到目标最简单FetchPush 需要把物体推到目标位置涉及接触和推搡FetchPickAndPlace 则要抓起物体再放下涉及与物体的交互和更长的动作序列。从工程角度看这三个任务正好可以检验算法在简单、中等、困难三种难度下的表现。这套环境在 gym 早期版本里可以直接通过gym.make(FetchReach-v1)加载。新版 gym 换成了 gymnasium 之后安装方式略有变化但状态空间的字段结构基本没变observation字典里包含observation、achieved_goal、desired_goal三个部分。HER 重标记时主要替换的就是achieved_goal这是我后面代码里会讲到的关键点。3.2 核心改造点一Replay Buffer 里存什么HER 落地时第一步是修改重放缓冲区的存储格式。普通 DDPG 的缓冲区里存的是 (s, a, r, s, done) 四元组HER 需要把它们扩展成 (s, a, r, s, done, g)其中 g 是当前经验对应的目标。这里有一个很容易踩的坑g 必须在采样后重新标记而不是在存储时就固定。因为在训练中同一条轨迹会被反复采样每次都可能使用不同的虚拟目标。如果你在存储阶段就把所有经验的目标写死HER 就等于没做网络永远只能看到原始目标下的失败数据。我把实现拆成两个类。一个叫EpisodeBuffer负责按回合暂存一整条轨迹另一个叫HindsightReplayBuffer负责把EpisodeBuffer中的数据混合重标记后采样。这样逻辑清晰也方便后续扩展新的采样策略。EpisodeBuffer里存的是原始观测序列不包含任何目标重标记重标记只发生在回合结束并且把新生成的四元组写入通用 replay buffer 的那个时刻。3.3 核心改造点二重标记时机的实现细节重标记的时机是回合结束时而不是每步都在做。因为你需要知道整条轨迹走到了哪些状态才能选取虚拟目标。核心逻辑可以写成这样def relabel_episode(episode, goal_keyachieved_goal, k4, strategyfuture): episode: 一个完整回合的 dict包含 obs, achieved_goal, desired_goal, actions, rewards, dones horizon len(episode[obs]) transitions [] # 1. 原始目标数据原样保留存入回放池 for t in range(horizon): transitions.append({ obs: episode[obs][t], goal: episode[desired_goal][t], action: episode[actions][t], reward: episode[rewards][t], next_obs: episode[obs][t 1], done: episode[dones][t], }) # 2. 按 future 策略采样 k 个虚拟目标 achieved episode[achieved_goal] # 轨迹实际经过的目标状态 for _ in range(k): # 随机选一个未来时间步 t np.random.randint(0, horizon) future_t np.random.randint(t, horizon) virtual_goal achieved[future_t] # 3. 重新标记整条轨迹 for t in range(horizon): # 用同样的 reward 函数计算新目标下的奖励 new_reward compute_reward(achieved[t], virtual_goal) new_done float(new_reward 0.0) transitions.append({ obs: episode[obs][t], goal: virtual_goal, action: episode[actions][t], reward: new_reward, next_obs: episode[obs][t 1], done: new_done, }) return transitions关于新奖励的计算在 Fetch 环境里奖励函数是是否达到目标的指示函数所以重标记后的奖励要么是 0虚拟目标恰好就是轨迹终点附近的状态要么也是 -1。你可能会问既然还是很多 -1为什么要这么麻烦关键在于目标变了——网络在学习要朝这个状态移动所以 -1 的含义完全不同了它在指示当前状态离目标还有距离这是可以学习的信息而不再是整条轨迹完全失败。compute_reward在 Fetch 系列环境里一般用距离阈值判断def compute_reward(achieved_goal, desired_goal): # 欧氏距离小于阈值 0.05 视为成功返回 0否则返回 -1 return float(np.linalg.norm(achieved_goal - desired_goal) 0.05) - 1.03.4 完整训练循环框架网络结构上我直接沿用了普通 DDPG 的配置Actor 网络输入是 state 和 goal 的拼接输出四维动作Critic 网络输入是 state、goal 和 action 的拼接输出 Q 值。重标记只改 goal 部分obs 部分完全不变这是 HER 和普通多目标训练之间最大的代码差异点。训练超参数给出我实际跑的配置参数取值说明网络结构三层全连接各 256 神经元ReLU隐藏层用 LayerNorm 会稍微稳定目标网络软更新系数 tau0.05这个值比默认的 0.001 大收敛更快Actor 学习率1e-3与 Critic 同学习率即可Critic 学习率1e-3同上Replay Buffer 大小1e6Fetch 任务状态维度不高内存够用Batch Size256调大一点有助于稳定 Q 值HER 虚拟目标数量 k4推荐经验值别盲目增加虚拟目标采样策略future最稳定探索噪声OU 噪声theta 0.15sigma 0.3前期探索充分后期噪声衰减这个配置跑 FetchReach 大概 20 万步可以接近收敛FetchPush 需要 100 万步左右FetchPickAndPlace 需要 200 万步以上。我在单张 RTX 3080 上训练 FetchPush 全流程大约花了 4 个小时。训练过程中的关键指标我会同时记录原始目标成功率和虚拟目标平均奖励前者体现最终任务效果后者反映朝目标移动的能力是否在提升。3.5 结果对比HER 前后差距有多大跑完实验后我把三组结果整理成了表格。普通 DDPG 在稀疏奖励下全部失败成功率接近 0其中 FetchReach 算是最简单的任务但依然没什么起色。加上 HER 之后任务普通 DDPG 成功率DDPG HER 成功率达到稳定所需步数FetchReach 1%95%约 25 万步FetchPush 1%80% 左右约 100 万步FetchPickAndPlace完全无法收敛接近 75%约 200 万步这个差距不是微弱的改进而是从不能训到能训的差别。我后来在真实机械臂的仿真环境里复用了同一套代码效果同样稳定说明 HER 不是只在 toy environment 上表现好。值得一提的是即便是在 FetchReach 这种相对简单的任务上HER 也能减少大约一半的训练步数因为虚拟目标带来的额外监督让策略从一开始就有明确的学习信号。4. 踩坑记录HER 调参中值得注意的细节4.1 目标采样的 k 值不是越大越好很多人一看到额外生成 k 条虚拟轨迹就觉得 k 越大信息越丰富但实测并不是这样。k4 和 k8 在 FetchPush 上成功率差距不到 2%但训练时间几乎翻倍。原因在于大部分虚拟目标的难度很低低难度样本带来的梯度信息非常相似边际收益递减得非常快。k2 时效果又明显差一截说明太少又不够。4 是我个人比较推荐的经验值。如果你在训练较大模型、显存足够可以试试 k6 作为折中但一定要关注每个 epoch 的训练吞吐量。特别是在并行采样环境里每条轨迹都要多跑 k 次重标记和写入这些操作看起来不起眼积累起来很容易拖慢整体节奏。4.2 稀疏指示函数也有隐性问题稀疏指示函数虽然简单但有一个容易忽略的点如果环境在达到目标后不立即终止而是持续给 0 奖励网络可能会学到原地不动的局部最优。所以在 HER 数据里每个成功样本的 done 标记必须准确。如果 done 标记不准Q 值估计会被污染轻则训练震荡重则完全崩溃。排查方法训练时每隔固定步数打点一下回放缓冲区里 done1、reward0 的样本比例。如果这个比例长期为 0 或异常高优先检查环境的终止逻辑。Fetch 环境默认在成功后会截断或保持稳定状态但如果你自己写环境这一点非常容易出错。4.3 目标拼接的顺序会影响网络收敛这不是玄学。我把 obs 和 goal 拼接的位置从goal 在前改成obs 在前FetchPush 的收敛步数差了大约 20 万步。原因很可能是网络对特征的表征方式对输入顺序敏感尤其是归一化之后数值跨度过大时顺序会影响 LayerNorm 或 BatchNorm 的统计量。建议固定一个顺序同时对输入做归一化。我踩过的坑是没有归一化 obs 和 goal它们数值范围差异大关节角度在 -1.5 到 1.5目标位置在 0 到 1.5前期训练会非常慢。归一化到差不多范围之后收敛速度一下子就上来了。4.4 其他容易忽略的细节采样 batch 时要保证一部分来自原始目标轨迹一部分来自虚拟目标轨迹。我一般按原始目标样本和虚拟目标样本接近 1:4 的比例混合。原始目标比例太低会让策略过度偏向追赶已到达状态忽略原始任务。探索噪声的大小直接影响虚拟目标的质量。OU 噪声的 sigma 设太大轨迹状态跳跃大虚拟目标离真实目标太远设太小探索不足。建议 sigma 从 0.3 开始如果前期成功率涨得太慢可以适当加大到 0.5但要注意训练后期逐步衰减噪声否则策略会不稳定。用并行环境比如向量化环境训练时HER 的回合级重放必须保证一条轨迹完整走完。不能在轨迹中间提前采样否则虚拟目标会泄漏未来状态造成信息泄漏训练出来的策略在实际部署时往往表现很差。5. 常见问题速查与排查思路5.1 训练前期的成功率完全不动怎么办如果前 20 万步成功率都是 0不要慌这是稀疏奖励环境的正常现象。需要关注的是虚拟目标对应的 Q 值是否在下降。我一般会单独打点未来状态作为目标时的 Q 值曲线。如果 Q 值在下降说明策略在接近目标这件事上学到了东西原始目标成功率上升只是时间问题。如果 Q 值完全平着不动那就要检查重标记逻辑是否生效比如虚拟目标是否被正确写入 replay buffer。5.2 训练后期震荡严重成功率忽高忽低先怀疑 replay buffer 的混合比例和 done 标记。我曾经在 FetchPickAndPlace 上遇到震荡排查一圈发现是 done 标记在虚拟目标下被错误地全部设成了 0导致 Q 值过高估计。修好之后训练曲线立刻稳定了。另一个常见原因是学习率太大DDPG 这类 off-policy 算法对学习率比较敏感如果震荡先把 Critic 学习率降到 3e-4 试试。5.3 用 HER 有很多成功经验了但原始目标成功率依然上不去这种情况多发于目标分布复杂或多步任务。我的经验是适当提升原始目标轨迹在采样中的占比并减少虚拟目标数量让网络把更多容量花在最终目标上。也可以用课程策略先让虚拟目标从路程中间出发再逐步逼近终点。比如在 FetchPickAndPlace 里可以先让虚拟目标取物体被拿起但没放下的状态让网络先学会抓起动作再学放下动作。5.4 速查表问题可能原因排查方向训练前期完全无进展稀疏奖励的正常现象看 Q 值曲线不只看成功率训练震荡严重done 标记不准、学习率偏大校验环境终止逻辑调低学习率成功率停滞难以突破原始目标样本太少提高原始轨迹混合比例显存或内存爆掉k 值过大、buffer 过大降低 k 或缩小 buffer 容量虚拟目标帮助不大轨迹长度太短检查环境最大步数是否过小我个人在实际操作中最大的体会是HER 并不是一个需要精心设计的华丽算法它更像一个朴素而有效的思维转换——如果失败经验没法直接教我们成功那就把它改造成成功经验来教我们。这套思路不仅适用于机械臂抓取在很多长周期、弱反馈的任务里都值得先试一步。如果你也正在为稀疏奖励头疼我的建议是别急着写复杂的奖励塑形函数先给模型一个事后复盘的机会效果往往超出预期。
返回列表