ARTICLE DETAIL

资讯详情

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

HER算法详解:后见经验回放如何破解稀疏奖励难题

HER算法详解:后见经验回放如何破解稀疏奖励难题 如果你常年在强化学习里打转就会遇到一个特别磨人的问题任务本身并不复杂但奖励信号稀疏得离谱智能体像个无头苍蝇在状态空间里瞎撞训练几百万步成功率还是零。我当年在调一个机械臂抓取任务时就卡在这种困境里试过奖励塑形、课程学习、手动设计辅助奖励效果都不能让我满意。直到后来把一个叫 Hindsight Experience Replay后见经验回放简称 HER的方法搬过来才真正感受到什么叫“换个视角看经验”。今天这篇博文就是把我复现和使用 HER 的完整过程、踩过的坑和经验教训一次性讲清楚。文章面向的读者是有一定强化学习基础、正在被稀疏奖励问题折磨的算法工程师和研究者。如果你刚接触强化学习也能跟得上因为我会把原理、公式和代码掰开揉碎讲。这个方案我已经在模拟环境和真实机械臂任务上验证过可以负责任地说只要你的任务满足“有可定义的目标”这一条件HER 就是性价比极高的稀疏奖励解法。1. 为什么大多数方法在稀疏奖励里翻车思路拆解1.1 稀疏奖励问题的本质先说说稀疏奖励为什么难。强化学习的核心是智能体通过最大化累积奖励来学会策略。当奖励在很多步骤里都是零、只有最终成功才给一个 1 时智能体面对的是一个几乎没有梯度信息的损失面。你想想看在状态空间动辄几十维、动作空间连续的任务里智能体随机探索一万步能恰好碰到成功状态的几率有多低低到基本等于买彩票中奖。我最早用的方法是奖励塑形Reward Shaping也就是人为给一些中间状态加上奖励比如“靠近目标就加分”。这个方法理论上没错但实际坑很多塑形奖励设计不好会改变任务的原始目标智能体可能学会转圈逼近目标却不做真正的抓取或者因为奖励幅度没调好整个训练过程震荡得厉害。我还试过课程学习Curriculum Learning把任务从简单到复杂排列起来训练。这个思路有效但问题在于课程的设计高度依赖人的直觉而且一旦任务切换不好前面学到的策略会迅速退化。这里要明确一个核心矛盾智能体不是学不会而是学不到。学不到的原因是成功的信号太稀薄它根本不知道自己的动作和结果之间有什么关系。要打破这种僵局要么想办法增加反馈要么想办法让反馈变得不那么稀疏。HER 走的是第二条路而且它的切入点极其巧妙。1.2 后见经验回放HER是什么HER 的核心思想概括成一句话就是如果没达到原始目标那就换个目标让当前经历变成一次成功经历然后再拿去训练。听起来有点“自欺欺人”实际上这是非常聪明的手段。假设一个机器人的任务是抓取桌子上的杯子它这次动作失败了杯子滚到了左边某个位置。在传统经验回放里这条经验就是一次失败经验奖励为 -1 或 0对训练的帮助极其有限。但 HER 会说既然你最后把杯子推到了(0.3, -0.2)这个位置那我把这个位置当作这次轨迹的目标重新计算奖励——咦物体最终到达目标位置这次抓取其实“成功”了奖励变成 1。这份被“改写”过的经验放入经验池后智能体学到的是从起始状态出发做出那串动作是可以让物体从原位置移动到(0.3, -0.2)这个位置的。这恰恰是它在稀疏奖励下最缺的信息动作与状态转移之间的因果联系。当经验池里积累了足够多的这种“虚拟成功”轨迹智能体就能学会向任意目标移动的技能最终泛化到原始的抓取目标上。这个思想背后的数学基础很简单一条经验元组从原本的 (s, a, r, s) 变为 (s, a, r, g)其中 g 是重新选择的目标r 是按 g 重新计算的奖励。其他什么都不变。正是这个看似微小的改动把一个无信号的失败轨迹变成了有信号的短程成功轨迹极大地提高了样本利用率。1.3 为什么是 HER 而不是别的方案对比下来HER 的优势是明显的。第一它几乎不需要手工设计额外奖励天然适用于目标是“到达特定状态”的任务比如导航、抓取、推箱子、点击游戏等。第二它对算法是通用的只要基础算法是 off-policy比如 DQN、DDPG、TD3、SAC都可以在上面套 HER不需要改动学习算法的内部结构。第三它的实现成本极低只需要修改经验回放时的目标选择逻辑代码量很小。对比一下选项奖励塑形需要调参且容易引入局部最优。课程学习需要人工设计课程序列任务间迁移不稳。HER只改经验重放逻辑无需额外奖励设计无需多阶段训练。当然 HER 不是没有局限。它要求任务目标必须能被形式化成某个可计算的量通常是状态中的一个子集比如物体最终位置并且环境需要提供 achieved goal实际达到的目标状态和 desired goal期望目标状态的区分。如果目标任务无法拆成这两个量HER 就很难用。这一点在后面的章节我会再展开。2. 重建经验目标重标记策略与网络设计细节2.1 四种重标记策略怎么选HER 论文Andrychowicz 等人NeurIPS 2017里提出了四种从当前 episode 中选取新目标 g 的策略我实际跑下来效果差异很大这里逐个说清楚。第一种是 final把 episode 最后一步的状态作为新目标。这个方法最简单每一个 episode 只能生成一条“成功经验”信息量偏少。第二种是 future从当前时间步 t 之后的状态中随机抽取 k 个状态作为新目标论文中推荐 k 取 50 或更大。future 策略可以保证新目标在当前轨迹中是可达的因为智能体确实在未来某个时刻到达过那个状态。第三种是 episode从当前 episode 的所有状态里随机抽取一个作为目标。这个策略不关心时间顺序目标可能出现在轨迹的任意位置可能会引入“智能体在到达目标前就已经过目标”的矛盾情况。第四种是 random不考虑当前轨迹从所有经验中随机抽取状态作为新目标。这个方法太散目标分布和实际轨迹的分布差异较大我在实验里效果明显不如 final 和 future。如果把四种策略拿来对比可以这样理解策略新目标取法优点缺点我的实测效果final本 episode 最后状态实现最简单每个 episode 只有一条目标效果一般能用future当前步之后 k 个状态中随机选目标可达性强信息丰富k 值需要调稍慢强推效果最好episode本 episode 所有状态中随机选代码简单目标可能与当前轨迹矛盾稳定性差random全局经验池随机状态不需要轨迹目标与动作关联弱基本不推荐我强烈建议优先使用 future如果资源紧张可以先上 final。论文里的对比实验也支持这个结论future 和 final 组合使用效果最稳定。2.2 为什么重标记时绝不改 action这是 HER 里最容易被误解的一个点。当你把目标从 g 换成 g 时智能体实际执行的原始动作为什么保持不变原因在于这条轨迹的动作序列已经是对原始目标 g 的“合理尝试”。虽然它对 g 来说不是最优策略但它仍然是非常有价值的探索数据——它展示了在某个状态下能做出一系列产生特定状态转移的动作。数据中隐含的正是动态特性信息。打个比方你原本想投篮得分结果投偏了篮球弹到了左边篮板。传统视角下这是一次失败投篮。但在 HER 视角里这条运动轨迹已经告诉了你“用力 角度组合会导致球飞向那个位置”这个因果关系。后续你在尝试“让球飞到任意位置”的时候这次经验就有了全新价值。所以重标记只改目标、奖励、是否结束这几个量动作和状态转移关系原封不动。2.3 输入网络的结构设计拼接目标与状态HER 在实现时有一个非常关键的工程点智能体的策略网络如何同时感知当前状态 s 和目标 g。通常的做法是把状态和目标拼接成一个向量再输入网络。比如原始状态 s 是一个 25 维向量机器人的关节角度、物体位置等目标 g 是 3 维向量期望物体终点坐标那么输入就是 28 维。Critic 网络则额外接收动作向量输入维度是 28 动作维度。这里要注意一个问题如果不加区分地把状态和目标拼在一起网络要自己学会区分哪些维度是“环境当前状态”、哪些维度是“期望目标”。实践下来网络的表达力通常足够但如果你想让训练更稳可以尝试给状态和目标各接一层独立的编码层再把编码结果拼接这种设计在某些高维任务里会有帮助。我在机械臂任务里用的就是独立编码方案整体收敛速度提高了大约 30%。除此之外HER 对 episode 轨迹的存储也有特殊要求。普通的经验回放只存单步 transition但在 HER 的 future 策略里你需要拿到一个 episode 完整的状态序列才能从中抽取未来的状态作为新目标。所以实际操作中需要临时用列表保存整个 episode 的状态、动作、奖励、done 等信息等到 episode 结束再统一重标记并拆成多条 experience 塞进回放缓冲区。这一段逻辑对刚上手的人来说是最容易写错的地方后面我会贴出完整代码。3. 从零复现DDPG 搭配 HER 的完整实操记录3.1 环境搭建与任务定义为了让讲解具体我以 OpenAI Gym 里的 FetchReach 为参照但这里用简化版本来演示核心逻辑。假设一个 2D 平面上的点机器人初始位置在 (0, 0)目标位置在 (1, 1)。动作空间是二维连续向量执行后机器人位置移动 dx、dy。当前状态 observation 包含三个部分observation自己的位置、achieved_goal实际到达的位置、desired_goal目标位置。所有分量的维度加起来我这里简化成 6 维位置 2 维 目标 2 维 实际目标 2 维。成功判定的标准是 achieved_goal 与 desired_goal 的欧氏距离小于某个阈值比如 0.05。奖励是稀疏的成功给 1不成功给 0或者 -1 也行效果差别不大我一般用 0 配 HER。下图是环境的基本逻辑描述observation { observation: robot_position, achieved_goal: robot_position, desired_goal: goal, }环境每 50 步截断也就是一个 episode 最长 50 步。如果 50 步内没到达目标这个 episode 在传统视角里就是全 0 奖励的失败轨迹。现在主角 HER 登场看看怎么把这样的轨迹变成有学习价值的样本。3.2 对经验回放缓冲区做关键改造普通 DQN 或 DDPG 的 replay buffer 里保存的是 (state, action, reward, next_state, done)。但 HER 的 buffer 有几个不同第一每个 transition 必须保存 achieved_goal因为重标记时要用它来产生新目标。第二state 需要拆分为 observation 部分和 desired_goal 部分别混在一起。建议用一个字典存储形如 {obs: ..., g: ...}。第三当使用 future 策略时需要临时保存整个轨迹列表episode 结束再统一做重标记和入库。代码层面我是这样写的class HerReplayBuffer: def __init__(self, capacity): self.capacity capacity self.buffer [] self.idx 0 def push_episode(self, episode_transitions): # episode_transitions 是 list每一元素是 # (obs, achieved_goal, action, reward, done, next_obs) T len(episode_transitions) for t, trans in enumerate(episode_transitions): obs, achieved_goal, action, reward, done, next_obs trans # 每次都额外存原始目标即 episode 的 desired_goal self._push_single(obs, achieved_goal, action, reward, done, next_obs, episode_goalachieved_goal) def _push_single(self, obs, achieved_goal, action, reward, done, next_obs, episode_goal): # 这里会生成四份不同的数据 # 1. 原始目标经验 # 2. 重标记目标经验用 future 策略选择 # 具体见下方 sample_goals 逻辑 pass这段只是骨架真正核心的是 sample_goals 函数我单独在下一节展开。3.3 HER 核心采样流程的代码实现HER 在实现里最关键的两个函数一个是如何从当前轨迹中选 future 目标另一个是如何按选定目标计算新的奖励和 done。先看第一个import random def sample_k_future_goals(episode_achieved_goals, t, k50): episode_achieved_goals: 整个 episode 每一步的 achieved_goal 列表 t: 当前步 k: 采样未来状态的候选窗口大小 future_idx list(range(t 1, len(episode_achieved_goals))) if not future_idx: return [] # 多采几个目标提高样本效率 k min(k, len(future_idx)) sampled_idx random.sample(future_idx, k) return [episode_achieved_goals[i] for i in sampled_idx]注意这里 k 是指从未来轨迹中随机采样的次数。每一条原始 transition 我通常会额外生成 8 条重标记经验每条对应一个随机 future 目标。你可以根据显存和训练速度调整8 是我试下来性价比不错的数字。然后是核心重标记逻辑def hindsight_relabel(episode, t, new_goal): episode 是完整轨迹数据 t 是当前时间步 new_goal 是新选的目标来自 achieved_goal 返回新的 transition 元组 obs episode[obs][t] achieved_goal episode[achieved_goal][t] action episode[action][t] next_obs episode[obs][t 1] next_achieved_goal episode[achieved_goal][t 1] # 判断新目标下是否成功 distance np.linalg.norm(next_achieved_goal - new_goal, axis-1) new_reward 1.0 if distance 0.05 else 0.0 new_done bool(new_reward 1.0) # 构造新的观测向量 new_obs np.concatenate([obs, new_goal], axis-1) new_next_obs np.concatenate([next_obs, new_goal], axis-1) return (new_obs, action, new_reward, new_next_obs, new_done)这里有几个容易出错的细节。第一判断成功所用的 achieved_goal 必须是 next_achieved_goal因为动作执行后到达的新位置才是真正的实际结果。第二新目标加入观测向量时要保持训练时观测空间的顺序一致千万别在某个分支里拼接顺序反了。第三new_done 不能用原始 done必须按新目标重新判断否则会把中途截断的 done 到处传播给训练带来混乱。我自己的经验是把这段重标记逻辑单独抽成一个函数后后续 debug 能省大量时间。如果你在实验里发现 HER 不生效百分之八十的问题都出在这个函数里比如距离阈值不对、拼接顺序错了、或者用了原始 done。3.4 网络结构与训练超参配置基础算法我用的是 DDPG因为它和 HER 的搭配最顺滑。Actor 网络把拼接后的观测向量状态 目标映射到动作Critic 网络把拼接后的观测向量 动作映射到 Q 值。我这里给出训练循环的核心片段你顺一遍就能看出 HER 是怎么嵌入到 DDPG 里的for episode in range(total_episodes): obs env.reset() episode_buffer [] achieved_goal obs[achieved_goal] desired_goal obs[desired_goal] done False for t in range(50): # 用当前策略采样动作 action actor.get_action(np.concatenate([obs[observation], desired_goal])) next_obs, reward, done, info env.step(action) episode_buffer.append({ obs: obs[observation], achieved_goal: obs[achieved_goal], action: action, next_obs: next_obs[observation], next_achieved_goal: next_obs[achieved_goal], }) obs next_obs if done: break # ---- HER 重标记阶段 ---- goals sample_k_future_goals( [e[achieved_goal] for e in episode_buffer], t0, k50 ) for t in range(len(episode_buffer)): # 原始经验也要存 replay_buffer.push(_to_transition(episode_buffer, t, desired_goal)) for new_goal in goals: new_trans hindsight_relabel(episode_buffer, t, new_goal) replay_buffer.push(new_trans) # ---- 更新 DDPG ---- if len(replay_buffer) batch_size: batch replay_buffer.sample(batch_size) critic_loss, actor_loss ddpg_update(batch)我常用的超参配置可以做一个参考表参数取值说明batch_size256实验里发现小 batch 不稳actor_lr0.001调大容易崩调小太慢critic_lr0.001同上tau0.05目标网络软更新系数稍大一点gamma0.98稀疏奖励场景下折扣因子别太接近 1her_ratio8每条原始经验生成 8 条重标记经验future_k50候选未来目标数buffer_size1000000足够大但要能装下重标记数据有两点我要特别说明。第一tau 我习惯用 0.05 而不是常见的 0.001因为 HER 重标记会让目标网络和在线网络之间的差距快速积累稍微大一点的软更新系数能让目标网络的追踪更及时我个人在 FetchReach 和 FetchPush 上测试都是这个结论。第二gamma 不建议取太接近 1因为 HER 会把“虚拟成功”的奖励传播得很远如果 gamma 太大远期虚拟奖励的累计会影响当前动作的评估导致训练后期震荡。4. 训练中的常见问题与排查实录4.1 Loss 下降但成功率纹丝不动问题出在哪这是我第一次跑 HER 时遇到的怪现象。训练几千步后 critic loss 在稳步下降看起来一切正常但评估时成功率长期为零。后来排查下来发现原因在于重标记经验占经验池比例太高几乎每一条都被打上了虚拟成功标签于是 critic 对任何状态都给出过高的 Q 值再也学不会区分“真正能够达成目标的策略”和“随机碰巧达成目标的策略”。解决办法是控制重标记比例。原始经验和重标记经验的比例很关键我试验下来her_ratio 在 4 到 8 之间比较合适。如果比例是 8 比 1意味着每 9 条经验里只有一条是真实的原始经验。某些任务里这个比例还是太高需要适当下调。另外如果经验池里原始目标是同一个目标重标记后的目标却各不相同那么 critic 学到的“对某固定目标的成功概率判断”会被稀释评估时就显得成功率很低。这种事后来我总结了一个经验HER 不是把所有轨迹都当成成功的而是在样本量和真实成功信号之间找平衡。4.2 future 策略里的 k 到底怎么选future 策略的 k 选太大会增加计算量每条 transition 都要采样 k 次然后生成 k 条经验显存和 CPU 都会吃紧。选太小又达不到效果因为 future 的目标是让经验池里覆盖足够多的“未来可达状态”如果 k 等于 1那新目标的可达性虽然强但多样性很弱。我做过一组对比实验k10、k50、k100。结果是 FetchReach 上三组都能收敛差异不大但 FetchPickAndPlace 上 k10 明显比 k50 慢很多k100 和 k50 效果接近。说明在高难度任务里k 的多样性决定了智能体能覆盖多少目标状态。实际使用中建议 k 从 50 起步如果任务特别复杂可以提高到 100但要注意总经验池膨胀带来的训练变慢。另外一个技巧是并非每条经验都要生成满 k 条重标记信息可以用一个概率 p 来决定当前 transition 是否被重标记比如 p0.8 就能省 20% 的计算量效果几乎不掉。4.3 把 HER 用到策略梯度算法上时踩的坑原则上 HER 只能配合 off-policy 算法使用因为 HER 需要从经验池里反复采样更新on-policy 算法每次更新完就丢弃数据HER 就没有用武之地。但很多人在 SAC 或者 TD3 上使用 HER 时仍然会遇到问题。我这里遇到的一个典型坑是SAC 的熵温度系数被 HER 的虚拟目标影响导致温度系数退到接近零最终策略变得过于贪婪一碰到失败就停止探索。解决方法是给 SAC 的熵温度系数设一个下限或者减小温度系数的学习率。HER 数据的分布变化比普通 off-policy 算法快得多因为重标记目标的分布随着智能体能力提升在不断改变所以那些对数据分布变化敏感的组件都需要重新调参。TD3 相对好一点因为它有延迟更新策略对分布漂移的容忍度更高。如果是从零开始我建议优先尝试 DDPG 加 HER因为组件最少问题排查起来也最直接。等熟悉了 HER 的脾性再迁移到 SAC 上。4.4 连续控制任务中的目标分布漂移问题HER 的重标记目标是从过往经验里来的但过往经验的 achieved_goal 分布会随着策略的改进而越来越接近真实目标。这个过程中经验池里早期数据的目标分布和后期数据的目标分布差异很大。如果不做任何处理训练初期 critic 可能饿死在“目标分布太广”的区域后期智能体又因为目标分布太集中而失去泛化能力。我常用的办法是给经验池设置一个采样权重近期数据的权重稍大比如采用优先经验回放Prioritized Experience Replay的思想。另一种更简单的方法是把缓冲区按时间分为两部分前期采样概率略低于后期。再有一种做法是在训练过程中逐步增大重标记的 k 值让目标分布自然地从广到窄过渡。这些技巧都不是论文里的标准操作但实际效果稳定分享出来供参考。4.5 避坑清单速查现象常见原因快速解决训练早期 critic loss 不降经验池为空重标记目标太少增大 her_ratio增大 kcritic loss 降但成功率零重标记比例过高降低 her_ratio 到 4~6评估时抖动剧烈gamma 太大或 tau 太小gamma 降到 0.98tau 升到 0.05目标复杂任务不收敛future 的 k 太小k 提至 100并提高重标记概率高维空间计算量大每条经验生成太多重标记样本用概率 p0.8 决定是否重标记策略过于激进不稳熵温度系数被虚拟目标拉低给熵系数设下限或降低其学习率这四类问题是我在实际项目中遇见频率最高的基本覆盖了我见过的大部分 HER 调参困境。结尾一点个人操作心得从第一次读到 HER 论文到把它真正跑进机械臂任务前后折腾了两周。我的体会是HER 不是什么万能灵药但它对付稀疏奖励问题的路子确实独辟蹊径。它的本质不是让智能体“变得更强”而是让经验池里的数据“变得更聪明”。同一条失败轨迹换个目标就是一次高质量的成功示范这种数据增广的思路值得任何一个做强化学习的人学习。最后分享一个我后来常做的小技巧在使用 HER 的同时把环境里 achieved_goal 这个量也一并交给数据记录模块训练完画图时把智能体在训练各阶段“实际到达的位置分布”画出来。你就能直观看到智能体从杂乱无章的探索到逐步覆盖整个可达空间再到集中在真实目标周围的过程。这个可视化在写报告或向团队解释算法效果时非常有用。后续如果大家有兴趣我可以再把 HER 配合 SAC 以及多目标任务扩展的细节整理出来。
返回列表