ARTICLE DETAIL

资讯详情

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

稀疏奖励下的救星:HER事后经验回放原理与实战调优

稀疏奖励下的救星:HER事后经验回放原理与实战调优 做机器人抓取任务那阵子我差点被稀疏奖励问题折磨到怀疑人生。状态空间大得离谱奖励函数稀疏得像沙漠里的绿洲智能体在环境里瞎转几百个回合策略纹丝不动。后来在一次组会讨论里同事提到了 Hindsight Experience Replay 这个名字——事后经验回放。当时第一反应是这不就是给失败找借口吗但仔细读完原论文Andrychowicz 等人NeurIPS 2017之后我意识到这个思路解决的是一个被严重低估的问题在目标条件化强化学习里失败的尝试本身蕴含了巨大的学习信号只是我们长期没有去利用它。这篇博文就围绕我复现和调优 HERhindsight experience replay的全过程展开从核心思路、算法拆解、代码实现到踩坑记录把值得注意的细节一次性讲清楚。适合正在做目标条件化强化学习、机械臂操作控制或者被稀疏奖励困住的同学参考。别急着跳过原理部分HER 之所以漂亮恰恰在于重新审视失败这件事本身——理解了这个思想后面所有实现选择都顺理成章。1. 项目缘起一个机械臂任务把问题逼出来了先讲讲我遇到的实际问题。项目目标是让一个仿真机械臂学会把物体推到指定位置FetchPush 类型任务输出是连续的动作控制关节力矩或者末端位移增量。环境反馈的奖励是稀疏二元值——物体到达目标位置就给 1否则每一步都是 0。听起来简单但实际训练起来根本不是那么回事。智能体初始策略几乎是随机的在 6 维连续动作空间里它能碰到目标位置的概率低到可以忽略不计。更糟的是奖励全是 0梯度信号为空策略网络根本没有优化方向。我试着加 shaped reward也就是距离的负值作为奖励但这样会把智能体引向局部最优它学会了靠近目标却学不会抓住目标的精确位置变化因为距离近和最终的二元判定之间没有平滑的桥梁。稀疏奖励问题本质上是一个信用分配的问题在一个长达 50 步的轨迹里哪一步动作是关键的如果只有最后一步能拿到 1前面 49 步的动作如何被评价标准策略梯度方法在这种情况下方差极大Q 学习的自举也帮不上忙因为绝大多数状态-动作对的 Q 值都是 0。1.1 为什么随机探索救不了场很多人第一反应是加探索噪声。确实ε-greedy 或者 OU 噪声在连续控制里是标配但问题在于探索的方向是盲目的。一个机械臂要完成推物体到目标位置需要的位置精度往往是厘米级动作空间又是连续的。随机探索想要恰好踩中目标概率差不多是在一大片黑暗里找一根针。我试过大幅提高探索噪声方差结果策略变得更不稳定有时候机械臂直接进入震荡状态甚至在环境边界卡死。原因很简单大噪声会放大动作的不确定性在接触力敏感的仿真环境里这会导致物体被推飞、机械臂姿态失控等连锁反应。理论上说只要有无限时间随机探索总能覆盖状态空间但那意味着训练时间从几小时变成几周甚至几个月这在工程上是不可接受的。我们需要的是利用已有数据——哪怕是失败的数据——来加速学习这正是 HER 要做的事情。1.2 事后经验回放的核心直觉HER 的核心思想用一个生活化的例子就能讲清楚。想象你往墙上的飞镖盘扔飞镖目标是靶心结果扎在了左上角 8 分的位置。在标准强化学习框架下这一掷是纯粹的失败毫无学习价值。但换个角度思考如果目标一开始就定在左上角那个点这一掷就是满分命中。HER 做的事情就是把事后的信息用起来一次失败的轨迹在追溯时可以被重新解释为一次成功的轨迹——只要把目标改成实际达到的状态。这个想法看似简单但它改变了经验回放的底层逻辑我们不再只存储原始经验而是给经验重新赋予目标让失败的数据也能产生正面的学习信号。这个直觉在数学上是成立的因为目标条件化策略goal-conditioned policy本身就把目标作为输入。同一个状态转移 (s, a, s)在目标 g 下可能是失败的但在另一个目标 g 下就是成功。Q 函数 Q(s, g, a) 的训练不再局限于原始目标下的稀疏奖励而是被大量人工标注的假成功经验填充价值函数的估计密度大幅提升。2. Hindsight 的设计哲学重新定义学习素材HER 不是一套全新的学习算法它是一个经验重标注机制必须搭配 off-policy 算法使用。原论文用的是 DDPG现在主流的搭配是 SAC、TD3 这类基于 Q 学习的连续控制算法。它解决的问题是 off-policy 算法在稀疏奖励下的退化现象当回放缓冲区里绝大部分样本的奖励为 0 时价值函数的梯度更新基本没有信息量。理解了这一点你就会发现HER 的价值不在于发明新学习规则而在于改变训练样本的分布特性。它把一张几乎全是零奖励的数据集变成一张正负样本合理混合的数据集让 Q 函数有东西可学。2.1 重标注流程与计算公式具体来说HER 在每条 episode 结束后除了把原始轨迹存入回放缓冲区还会额外生成若干条逆向重标注的轨迹。流程如下智能体用当前策略与环境交互产生一条轨迹 τ {s_0, a_0, r_0, s_1, ..., s_T}目标是 g。从这条轨迹中按照某种策略选取一个事后目标 g。对轨迹中的每一步用 g 重新计算奖励 r_t R(s_t, a_t, g)。将重标注后的转移样本 (s_t, g, a_t, rt, s{t1}, g) 与原样本一起存入回放缓冲区。这里最关键的公式是奖励重算。在机械臂任务里原始奖励函数通常定义为R(s, a, g) 1, 如果 ||achieved_goal - g||_2 ε 0, 否则重标注后achieved_goal 是轨迹中实际到达的状态新目标是 g所以r_t 1, 如果 ||achieved_goal_{t1} - g||_2 ε 0, 否则你看由于 g 是根据轨迹中真实发生的状态选出来的这个奖励在轨迹末端几乎必然为 1整条轨迹就带上了密集的正信号。2.2 与普通 Experience Replay 的本质区别普通经验回放Experience Replay, ER做的事情很简单把交互产生的转移样本存进缓冲区训练时随机抽样打破时间相关性。ER 本身不改变样本的语义它只是管理样本去重和重放。HER 在 ER 之上叠加了样本生成逻辑。被重标注的样本不是环境真实产生的而是算法事后编造的。这听起来有点危险——用假数据训练靠谱吗答案是在目标条件化设定下这些不是假数据而是在不同目标下的真实数据。状态转移 (s, a, s) 是真实的只是我们改变了提问方式在目标 g 下这个动作好不好这个问题的答案是真的因为环境动力学没有变。一个容易忽略的前提是重标注的有效性依赖于目标与奖励函数的结构。HER 对目标的定义必须是状态的一部分achieved goal 可从观测中提取并且奖励必须是目标的函数。如果你的任务奖励依赖于不可观测的意图或者复杂逻辑HER 就不适用了。3. 算法细节拆解目标采样策略与网络设计HER 论文里最容易被忽视、但对效果影响最大的部分是目标采样策略。原论文提出了四种策略我用一个实验表格直接对比它们的思路和适用场景策略采样方式优点缺点典型场景final使用轨迹最终状态作为新目标实现最简单、正信号最密集目标可能过于容易或不可达短轨迹、确定性环境future从当前时刻之后的 k 个状态中随机选一个每个时刻的信号密度适中覆盖更多可达目标需要调 k 值长轨迹通用场景episode从整条轨迹中随机选一个状态状态覆盖最广与当前状态关联弱学习稳定性稍差探索初期random从环境状态分布中随机采样增加目标多样性可能采样到物理上不可达的目标配合其他策略使用我实际使用下来推荐以 future 为主k 取 4 或 8。原因是你希望重标注目标在时间上离当前状态不太远也不太近太远比如 early 状态配最后状态会导致单步回报的信号与真实可达性脱节太近则起不到重新解释困难尝试的效果。原论文在 Fetch 系列环境上的实验也验证了 future 策略通常最优。3.1 重标注概率与超参数取舍HER 还有一个重要超参数重标注概率 P表示每条原始轨迹中有多少比例的转移样本会被额外生成一份重标注版本。常见的取值是 0.8也就是说原始经验 20% 概率只保留原版80% 概率额外生成重标注版本。为什么不是 100%因为我试过 P 1.0效果反而变差。原因在于原始目标的重要信息不能被完全淹没——如果你把所有经验都重标注成事后目标智能体就永远学不会追求原始目标这件事策略会偏向于随便动动反正事后会被重新解释。保留一部分原始经验相当于在exploit hindsight 信号和保留 original goal 意图之间做了平衡。另一个容易被忽略的细节是HER 的重标注应该作用于整个 episode 的转移样本而不是只在 episode 末尾处理。也就是说一条长度为 T 的轨迹你会生成 T 条重标注版本的转移每个时间步都有对应的事后目标回推。这样才能让 Q 函数在任意时间步都学习到朝某个可达目标逼近的价值信号。3.2 网络结构与输入编码HER 要求策略和 Q 网络都接收状态 目标作为输入。这里有个工程上的细节在原始实现里通常把观测拆分成两部分——desired goal期望目标和 achieved goal实际达到的目标然后将当前状态与 desired goal 拼接成一个向量输入网络。以 FetchReach 环境为例状态维度约 25 维其中 3 维是 achieved goal指尖位置3 维是 desired goal。这里一个常见的坑是没有做输入归一化或目标归一化。状态和目标的量纲可能完全不同位置是米速度是米/秒力是牛顿如果不做归一化Q 网络很容易被某些维度支配导致训练不稳定。我通常在拼接前先对状态和目标分别做标准化统计量来自回放缓冲区。网络结构方面我用的架构是import torch.nn as nn class GoalConditionedCritic(nn.Module): def __init__(self, state_dim, goal_dim, action_dim, hidden_dim256): super().__init__() self.input_dim state_dim goal_dim action_dim self.net nn.Sequential( nn.Linear(self.input_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, 1) ) def forward(self, state, goal, action): x torch.cat([state, goal, action], dim-1) return self.net(x)三层 256 维隐层的 MLP 在 Fetch 系列环境上是够用的不需要更复杂的网络。Q 函数的输入是 [state, goal, action]输出是标量价值估计。策略网络输出确定性的动作DDPG或者高斯分布的均值方差SAC输入同样是 [state, goal]。3.3 与离线算法的搭配选择原论文用 DDPG HER 取得了不错的效果但我实际对比后发现 **TD3 HER ** 在稳定性上更好。原因不复杂TD3 的 clipped double-Q 和 delayed policy update 机制能缓解 Q 值高估问题而 HER 产生的重标注样本天然带有乐观倾向因为目标总是可达的Q 值高估会被进一步放大TD3 正好对症。SAC HER 也成立但 SAC 的 entropy coefficient 需要重新调整。因为 HER 让经验分布变得更密集熵项对探索的贡献变小了如果把 temperature 调太高训练后期策略会过于随机。我一般把 SAC 的初始 temperature 降到默认值的一半左右。4. 实操记录从零到收敛的关键实现直接说结论DDPG HERfuture, k4, P0.8在 FetchReach 上 20 个 epoch 内收敛FetchPush 上 50 epoch 左右就能看到成功率过 0.8。作为对比纯 DDPG 在相同 epoch 数下成功率基本为 0。这个对比不是夸张而是我在 mj_envs 和 gymnasium-robotics 环境里的实测结果。4.1 环境与基线配置我用的环境是 gymnasium-robotics 的 FetchReach-v3 和 FetchPush-v3新版本 API。每个 episode 长度是 50 步优化器用 Adam学习率 1e-3actor 和 critic 一致batch size 256回放缓冲区容量 1e6。每 10 个 episode 做一次策略评估连续 20 次评估的成功率超过 0.95 则提前终止训练。这里要提一个重要细节回放缓冲区里原始样本和重标注样本的比例会自然变化。因为每条轨迹会生成 T 条重标注样本所以缓冲区里重标注样本的总数往往多于原始样本。这是 HER 的正常状态不需要刻意做任何平衡。但要注意如果你用一个固定的循环缓冲区旧的 HER 样本被挤出后训练早期的乐观信号会消失后期可能出现成功率停滞。我建议缓冲区容量设大一些或者带优先级地把重标注样本保留更久。4.2 核心代码重标注逻辑HER 的核心实现并不复杂完全可以自己实现而不是依赖现成框架。下面是我项目里用的核心逻辑def her_relabel(batch_size, episode_transitions, strategyfuture, k4, relabel_prob0.8): 对一条 episode 做 HER 重标注。 返回重标注后的 transition 列表 her_transitions [] horizon len(episode_transitions) for t, trans in enumerate(episode_transitions): # 原始经验不丢 if np.random.uniform() relabel_prob: if strategy future: # 从 t1 到 T 范围内随机选 k 个候选随机取一个 if t 1 horizon: future_indices np.random.randint(t 1, horizon, sizek) sample_idx np.random.choice(future_indices) new_goal episode_transitions[sample_idx][next_obs][achieved_goal] else: continue elif strategy final: new_goal episode_transitions[-1][next_obs][achieved_goal] else: raise ValueError(fUnknown strategy: {strategy}) # 基于新目标重算 reward achieved trans[next_obs][achieved_goal] reward compute_sparse_reward(new_goal, achieved) her_trans { obs: trans[obs], goal: new_goal, action: trans[action], reward: reward, next_obs: trans[next_obs], done: trans[done], # 注意done 通常保持原值 } her_transitions.append(her_trans) return her_transitions这里 min 部分是 apply_her 类实现。我把 HER 逻辑独立成一个模块训练循环、策略网络都通过统一接口访问这样后面切换环境会很方便。4.3 集成到训练主循环HER 必须和 off-policy 算法的训练循环配合。下面是我的训练主循环的简化版本已经跑通 FetchPushfor epoch in range(total_epochs): for _ in range(n_cycles): episode_transitions [] obs env.reset() goal obs[desired_goal] obs_flat flatten_obs(obs) for t in range(horizon): action actor.get_action(obs_flat, goal, noise_scale0.1) next_obs, reward, terminated, info env.step(action) reward compute_sparse_reward(goal, info.get(is_success, 0)) transition { obs: obs_flat, goal: goal, action: action, reward: reward, next_obs: flatten_obs(next_obs), done: terminated, } episode_transitions.append(transition) obs next_obs if terminated: break # 核心HER 重标注 her_transitions her_relabel(episode_transitions, strategyfuture, k4) all_transitions episode_transitions her_transitions # 存入回放缓冲区 for trans in all_transitions: replay_buffer.add(trans) # 每次交互后训练若干步 for _ in range(n_train_steps): batch replay_buffer.sample(batch_size) critic.update(batch) actor.update(batch)这个流程里有一个我踩过坑的地方flatten_obs必须把状态和目标保持一致的处理方式。Fetch 系列环境的 observation dict 里有observation、desired_goal、achieved_goal三部分你不能只拼observation和desired_goal而忽略了achieved_goal在重标注时也要对应变化。我一开始在重标注时只改了goal字段没同步修改next_obs里的achieved_goal对应关系导致 reward 重算和目标错位训练直接崩了。4.4 真实训练曲线与调参经验跑通之后我记录了训练曲线。FetchReach到达任务是最简单的收敛快且稳定。FetchPush推物体因为物体与机械臂有接触动力学训练波动较大但 HER TD3 组合在 50 epoch 内成功率稳定超过 0.8。FetchPickAndPlace抓取放置是最难的单纯 HER 只能学到一部分动作需要叠加 curiosity 或 curriculum learning 才能达到高成功率。成功率 0.8 这个阈值背后有个经验判断在做实际部署前成功率至少高于 0.9 且方差在小范围否则策略在真实物理环境上的泛化会很不可靠。仿真里看似 0.8 够用但转移到真实机械臂时sim-to-real gap 会吃掉至少 20% 的性能。5. 常见问题与排查技巧实录HER 看着简单实际调参时坑很多。我把自己踩过的和帮同事排查过的问题整理成一张速查表现象常见原因解决方案训练 loss 为 NaN状态/目标未归一化数值溢出对 state 和 goal 做标准化clip 梯度成功率一直为 0重标注概率太低或 k 太小检查 P 是否 0.5k 至少 4Q 值爆炸TD 目标过大双 Q 缺失换 TD3 或给 reward clip 到 [-1, 1]训练后期停滞缓冲区里重标注样本占比失衡用 prioritized replay 或加大容量重标注奖励全为 1目标采样策略用了 final 且轨迹确定性太高换 future 策略增加目标多样性策略震荡抖动探索噪声过大降低噪声方差或换成确定性策略评估5.1 三个必须盯紧的调试信号第一个信号是回放缓冲区里正样本比例。HER 的核心价值就是提高正样本比例如果训练了一段时间这个比例还徘徊在 0.1 以下说明重标注逻辑有问题。你需要检查new_goal的采样范围是否越界或者achieved_goal是否始终是同一个值比如环境输出的目标没有正确更新。第二个信号是原始目标成功率与重标注目标成功率的分离度。理想情况下原始目标成功率应该在训练中逐步上升而重标注目标成功率应该始终较高。如果两者都很低说明 Q 函数没有学会目标条件化的语义——此时应检查网络输入中 goal 和 state 是否拼接正确目标编码是否被高维状态淹没。第三个信号是训练曲线上的周期性尖刺。这通常是因为环境 reset 分布不均匀或者 eval 时使用的 policy noise 没有关闭。eval 时一定记得把 exploration noise 置 0否则成功率曲线会呈现大幅波动真伪难辨。5.2 我常用的调试手段调试 HER 和调普通 RL 不太一样我建议从最小可运行入手先在最短 episode比如 T10的简单环境上跑通再泛化。如果 T10 都跑不通先检查 reward 函数在重标注后是否为常数——这是最常见的 bug。另外把重标注前后的样本打印出来逐一对比是一个被低估的排查方法。我写了一个简单的日志函数在调试模式下打印每个重标注样本的(old_goal, new_goal, old_reward, new_reward)。如果发现 new_reward 计算出错往往一眼就能定位是目标索引错了还是 reward 函数阈值问题。最后如果你用的是 SAC 或者 TD3记得检查 target network 的 soft update 参数。HER 产生的重标注样本分布比较乐观target 网络的滞后更新可以缓解这种乐观带来的自举偏差。5.3 什么时候不应该用 HERHER 不是万金油有几个场景我明确不建议使用。第一奖励函数本身是稠密且信息丰富的任务比如连续距离奖励 速度惩罚直接用标准算法往往比 HER 更好因为 HER 的重标注会引入额外的目标维度反而干扰策略学习。第二目标不是状态的一部分、无法从观测中提取的任务HER 没有定义实际达到的目标这个量无从重标注。第三环境动力学非常复杂且随机性强的场景事后重标注的成功可能并不代表策略真的学到了有价值的行为因为同一状态转移在相同目标下结果本身就是高度不确定的。根据个人经验HER 在机械臂操作、简单导航、多目标格子世界这类目标明确、动力学可控的任务里效果最好。在粒子物理模拟、高维流体控制这类任务上它的收益会显著下降。结尾一点实在的体会HER 这个项目做下来我最深的体会是强化学习里最稀缺的不是算法复杂度而是对什么数据值得学习的理解。我们习惯性认为失败的经验没有价值但 HER 把这个假设彻底推翻了——只要你能重新定义问题失败本身就是通往成功的阶梯。这种思想不仅适用于 RL在生活中的复盘、项目管理里也是一样的道理。最后分享一个实用技巧如果你想快速验证 HER 是否适合你的任务不要一上来就搭完整训练流程。先写一个几十行的脚本在最短 episode 的简化环境里跑 100 个 episode统计重标注后正样本比例和 Q loss 的下降趋势。如果这两个指标都健康再投入时间做完整实验。这样做可以帮你节省大量无效调参的时间。实测环境gymnasium-robotics FetchReach / FetchPush / FetchPickAndPlace 算法框架DDPG HERTD3 HERSAC HER 硬件条件单张 RTX 3090每个实验约 2~6 小时后续如果想继续扩展可以把 HER 和 curriculum learning 结合或者和模型预测控制MPC做混合——这些方向都有不少论文可挖但先把 HER 本身吃透是性价比最高的一步。
返回列表