ARTICLE DETAIL

资讯详情

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

稀疏奖励下的强化学习救星:HER事后经验回放原理解析与实战

稀疏奖励下的强化学习救星:HER事后经验回放原理解析与实战 hindsight这个词英文直译是“后见之明”放在强化学习这个圈子里它基本就指代一个绕不开的技术名词Hindsight Experience Replay中文通常译作“事后经验回放”。我第一次接触这个概念是在处理机械臂推滑块的任务里——环境只在末端执行器进入目标区域时给一个1的奖励其余时刻全是0。训练跑了几十万步策略纹丝不动连目标区域的边都摸不到。当时一度怀疑是算法实现写错了后来把HER加上去成功率才渐渐从0爬到0.7以上。这篇内容我想把它从原理到实现、从参数调到坑位全部拆开讲透希望能帮到正在被稀疏奖励折磨的人。适合看这篇的大概有两种一是刚入门强化学习跑过几个demo但一遇到“奖励几乎全是0”就不知道怎么办的新手二是已经能跑通DQN、PPO之类算法想提升样本效率、减少手工设计奖励函数的工程人员。前者的收获是理解HER为什么有效、怎么落地后者可以跳过原理直接去抄“实操”和“踩坑”部分的配置思路。我不打算堆公式吓人尽量用大白话把逻辑讲明白毕竟这些东西我在跑通之前也是反复绕了很久的弯子。1. 先从问题说起稀疏奖励为什么难倒强化学习1.1 机器人学不会卡在信号太少先还原一个典型的场景。假设我们要让一个两轮小机器人走到某个坐标点这个坐标点每次开局随机生成。奖励设计成如果机器人当前位置和目标点的距离小于某个阈值给1否则什么都不给。这个环境的问题在于一个episode里机器人可能随机走几百步绝大多数时间它都不知道自己离目标还有多远神经网络看到的状态和目标、动作的组合和“最后能不能拿到1”几乎没有任何可学习的相关性。因为成功轨迹出现的概率太低了低到可以忽略不计。我在实际项目里测过一个更极端的版本在一个连续动作空间里随机策略要走通一个长度为200步的目标抵达任务概率大概是十万分之一量级。也就是说指望靠随机探索碰出一次正奖励然后让价值函数慢慢扩散那在有限训练步数内基本就是绝望的。就算改用off-policy算法把历史经验反复利用经验池里依然连一条像样的“成功示范”都没有Q网络怎么学都是瞎猜。这种场景在真实机器人任务里非常普遍拿盒子、推瓶子、插销钉这些任务的成功判定都是离散的“成”或“不成”。如果我们非要等成功样本出现才给信号那绝大多数项目根本跑不起来。很多人的第一反应是给奖励函数加“势场”——比如用欧氏距离的负值做稠密奖励。这方向没有错但问题也很现实每个任务都要重新设计距离度量而且势场设计得不好agent会学出绕路、震荡甚至故意卡在中间状态刷分的行为。1.2 “事后诸葛亮”的核心直觉HER的思路其实特别像日常生活中的复盘思维。你练投篮的时候如果只有“空心入框”才算成功那么一次投歪了、砸到篮筐边缘的球有没有信息量当然有。你至少知道了“我把球的弧线压到这个程度时落点是偏左的”这就是一条有效经验。问题是强化学习只认“目标是否达成”所以这条经验会被当成无效数据丢掉。HER做的事情就是把目标换掉。既然这次没推进原定的目标点但我实际到达了一个新位置那我干脆把“到达这个新位置”定义为一次成功。原来的那串轨迹从“失败尝试”重新标记成“成功路径”。这样一来每条失败的episode里也能挖出大量带正奖励的transition。这个“事后换目标”的动作对应在代码层面就是改一下transition里的goal字段再按新目标重新计算奖励仅此而已。我当年第一次看到这个方法时觉得这简直是在玩文字游戏。但仔细想想你会发现它有个非常隐蔽的前提我们不是在欺骗算法而是在还原一个事实——这条轨迹确实“成功完成了某个目标”只是那个目标不是我们最初指定的那个而已。策略学习的是从状态到动作的映射它不关心目标是在执行前定的还是事后补的。只要能学到“当目标在这些位置时动作该朝哪个方向给”它对原任务同样产生迁移能力。1.3 对比几种常规解法看HER的定位在HER出现以前业界处理稀疏奖励常见几类思路。一个是前面提的reward shaping手工把连续距离、进度之类信息折算成奖励问题是要为每个新任务单独设计且容易引入局部最优。一个是课程学习从简单目标开始逐步加大难度思路很好但你需要先设计出“难度递增”的序列这在很多任务是另一个难题。还有一个是给探索加bonus比如鼓励agent访问未见过状态潜力不错但常常在连续控制问题里参数敏感、效果不稳定。HER的定位恰好是另一个维度它不改变探索方式也不改造奖励函数而是从现有失败数据里二次榨取价值。它不需要额外的环境交互只需要在经验回放里做一次数据变换计算开销极低几乎可以说是白捡的样本效率提升。当然它也不是万能药后面我会重点讲讲它只适用于哪一类环境以及哪些场景用了反而坏事。2. HER的原理拆解怎么把失败重写成经验2.1 目标条件化把任务描述塞进状态里很多初学者理解HER时卡在第一步反复看到“goal-conditioned”这个词却不明白和普通RL有什么区别。在传统RL里状态s就是环境给的全部信息比如小车的位置、速度而在目标条件化设定里观测变成状态加目标的拼接也就是 o (s, g)。策略写出来就是 π(a | s, g)意思是“给我一个状态和一个目标我输出动作”。这个设定是HER的前提。因为只有目标明确地出现在观测里你才能事后修改它。如果你的网络输入压根没有goal那你想把失败轨迹重新标记成“实现了另一个目标”模型根本不认识这个输入维度当然无从谈起。所以第一步检查你的环境目标是否可采样、是否可编码进观测、奖励是否能由(s, g, a)重新计算出来。这三个条件缺一不可。举一个具体的例子。我想做一个二维点机器人到达任务状态是(x, y)目标是(gx, gy)动作是(x方向速度, y方向速度)。那输入给网络的向量就是四维 [x, y, gx, gy]。奖励函数定义为如果 sqrt((x-gx)^2 (y-gy)^2) 0.05给0否则给-1。注意我用的是负奖励而不是1很多实现里用-1/0比用0/1更顺手因为可以和“未成功”的惩罚对应起来训练出来的Q值更稳。这个细节后面还会提。2.2 事后目标重标记公式化表达直接看伪代码可能比看长段落更容易建立直觉。先看一个episode里原始transition的标准形式(s_t || g, a_t, r_t, s_{t1} || g)。这里的||表示拼接。原始经验里r_t几乎全是-1因为agent几乎从不在原目标上成功。HER做的就是在这个episode结束之后把其中的一部分transition重新写成另一份(s_t || g, a_t, r, s_{t1} || g)。关键在于g怎么选。最朴素的取法是把episode最后一个状态当作新目标也就是g s_T或者取s_T里和目标相关的维度。有的环境状态里还包含机械臂关节角度那你得把目标投影到目标空间里。另一种取法是在当前transition的时间步t之后再随机抽一个后续状态作为g也就是从未来状态里采样。最后重新计算奖励r reward(s_{t1}, g)计算方式和环境原本的奖励函数完全一样。为什么说这个新transition是“干净”的因为g确实是在这条轨迹后续被到达过的位置从s_t出发、执行a_t之后物理上真实地接近了g。那么把a_t当作“在目标为g时的好动作”是完全符合因果的。这不是伪造数据而是在重新利用真实发生过的因果链条。下面是核心伪代码我用Python风格写出来方便你对照实现。# 假设环境返回obs为状态goal是目标act是动作 for episode in range(total_episodes): goal env.sample_goal() obs env.reset(goalgoal) episode_buffer [] # 临时存放本episode的transition for t in range(max_steps): act policy.sample_action(obs, goal) next_obs, _, done, info env.step(act) reward reward_fn(obs, goal, act, next_obs) # 原始奖励 # 原始transition先存下来 episode_buffer.append((obs, goal, act, reward, next_obs)) obs next_obs if done: break # 每个transition都重标记K次 for idx, (obs, goal, act, reward, next_obs) in enumerate(episode_buffer): replay_buffer.add(obs, goal, act, reward, next_obs) for _ in range(K): # 从idx之后的时间步中抽一个状态作为新目标 future_idx random.randint(idx 1, len(episode_buffer) - 1) new_goal episode_buffer[future_idx].obs new_goal project_goal_space(new_goal) # 投影到目标空间 new_reward reward_fn(obs, new_goal, act, next_obs) replay_buffer.add(obs, new_goal, act, new_reward, next_obs)这段代码核心逻辑非常短但注意几个容易写错的地方。一是future_idx必须大于当前idx不能抽一个“已经发生过”的状态当新目标那就破坏了因果关系。二是new_goal最好是从状态里拆出来的目标分量。三是K次重标记不一定每次都用future策略也可以一部分用final策略混合起来效果更稳。2.3 四种重标记策略与我的实测感受原论文提出了四种从episode里选新目标的策略final也就是直接用最后一个状态当新目标random从整个episode的所有状态里随机抽一个episode从当前transition后面的状态里随机抽一个future每次随机延迟一个固定窗口再从后面抽。很多人看论文时容易把episode和future搞混我实际写代码时也很纠结后来干脆直接去看OpenAI开源代码发现它们基本用的是future类实现只是具体窗口参数有差异。四种策略各有短长。final最稳定但也最保守因为一个episode的最终状态往往不是“有意义”的目标尤其当episode很长、最后几步在乱走时用它重标记可能导致目标分布和真实任务分布差很远。random问题更明显它可能抽到一个和当前动作毫无因果关系的状态新目标看似成功实际上是“天上掉下来的”这种伪样本会让Q值学习产生偏差。episode和future本质一样只是future更强调时间顺序上的因果性。我自己的实测体会是future是最省心的默认选择。它既保证了因果性——新目标出现在动作之后又不至于像final那样限制目标多样性。如果你正在搭环境建议直接用futureK先设4后续想优化再考虑混合。2.4 与off-policy算法的结合原理HER有一个硬性前提必须配off-policy算法。为什么因为经验回放里的transition经过重标记后已经不再是“当前策略在当前目标下采集的数据”了。一个transition被重新打上“目标g成功”的标签后它对应的实际行为策略和价值函数都变了。这就是off-policy场景——用别人的经验来更新自己的策略。反过来说像PPO这类on-policy算法每一步都要用当前策略采到的数据做重要度采样不允许拿旧数据直接更新所以HER在它身上完全不成立。实际项目里和HER搭配最常见的是DDPG、TD3、SAC这类连续控制算法离散动作环境也可以用DQN系列。但有一个点容易翻车如果你用DQN动作空间必须离散化粒度粗了会导致即使重标记出了正样本策略也很难输出足够精细的动作去逼近目标。我在一个二维点机器人任务里试过把动作离散成9档结果成功率最高只到30%左右换成连续动作的DDPG同样的HER配置直接冲到80%以上。所以我的建议是能上连续动作算法就上连续动作算法。3. 实操搭一个HER训练流程3.1 环境选型与代码骨架跑HER最快的方式是根据你的实际环境类型选工具。如果你只是想快速验证算法有没有写对强烈推荐先拿一个轻量级环境跑通而不是直接上真实机械臂。Gymnasium里有现成的Fetch系列环境比如FetchReach、FetchPush都自带目标采样和稀疏奖励设置配合MuJoCo物理引擎是目前社区里验证HER的准标准环境。我自己早期也是拿FetchReach起步的环境复杂度适中训练时间可控很推荐。如果连MuJoCo都懒得装也可以自己写一个几十行的点机器人环境就是我前面说的那个二维到达任务。别小看这个简化环境它麻雀虽小五脏俱全有连续状态、连续动作、稀疏奖励、随机目标全部具备测试HER的条件。先在它上面把训练曲线跑出来再迁移到FetchReach能省掉大量调bug的时间。我个人的建议顺序是先跑通简化环境确认成功率曲线能往上走再上FetchReach感受真实高维状态下的训练节奏最后才考虑真实机器人。因为HER真正能帮你省的是“设计奖励函数”的时间而不是环境仿真调通的时间前两步走扎实了后面才有意义。3.2 核心代码HER重标记逻辑逐行讲上一节已经给了简化伪代码这里我再补充一点工程细节。实际项目里我通常会把HER封装成一个独立的回放缓冲区类和算法本体解耦。它对外暴露两个核心方法add_episode接收一整个episode的原始transition列表负责内部做重标记sample从缓冲里随机抽batch供训练。这样写的好处是算法部分完全不用改动你只需要在每次episode结束后调用add_episode万事大吉。有一个细节值得特别强调重标记的目标并不总是“完整状态”。在FetchReach里状态包含了机械臂每个关节的角度、角速度、末端执行器的三维坐标等等但目标只关心末端执行器的三维位置。如果你直接把整个状态向量当新目标塞进去维度对不上代码直接崩。所以必须定义一个goal_projection函数从状态里抽出和目标空间对齐的分量。这个小函数我在前三个项目里每次都忘记写然后被维度不匹配的报错狠狠教育一顿。另外一个工程细节是K次重标记时不要全部使用future策略。我的做法是对每个transition以50%的概率保留原目标再生成4个新目标其中3个来自future窗口1个来自final。这样重放缓冲区里既有“原任务”的真实经验也有“事后任务”的伪成功经验Q值估计会平衡得多。如果K全部拉满且都用future训练前期缓冲区里几乎全是“伪成功”Q值容易虚高后面再校正就慢了。3.3 关键超参与调参经验HER本身引入的超参数不多核心就两个K和重标记比例。K代表每个原始transition额外生成几个重标记版本OpenAI论文里用的4我也建议从4起步。重标记比例在实现里通常体现为“每个episode里随机挑一半transition做重标记”也就是0.5左右。如果K太大伪成功样本过多正负样本比例失衡如果太小HER的优势发挥不出来。我分别试过K1和K16K1几乎看不到提升K16早期Q值确实高但成功率爬升反而更慢训练曲线像是卡在一个虚高的平台上。K4到K8是安全区间。奖励的形状也要配合算法选。我在前面用了-1/0方案也就是未成功给-1成功给0这样智能体在优化过程中会倾向于累积更大的奖励值目标函数是“最大化累积回报”。有些教程写成0/1也就是成功给1未成功为0那目标函数就变成“最大化成功次数”。两种写法理论上等价但在Q网络的数值稳定性和学习率的选择上会有微妙差别。我个人更习惯-1/0因为初值都是负的梯度方向更明确不容易因为Q值全为正导致过估计。学习率这类算法参数按原算法的推荐值来就行一般不需要因为HER单独调整。但有一点要注意目标网络软更新率τHER场景下建议设得比默认略小一点比如从0.005降到0.001。原因在于重标记后的目标分布是非平稳的如果Q网络追踪太紧会把“伪成功”样本的错误信号也牢牢记住造成后续灾难性遗忘。追得慢一点反而更稳。3.4 训练监控与结果判读跑HER训练时最关键的一个判读指标是“按原始目标评估的成功率”而不是缓冲区里的伪成功比例。很多人看到loss下降就以为训练正常实际上HER的loss下降非常快因为伪成功样本让TD误差变小了但真实任务成功率可能纹丝不动。我习惯每5000步就暂停训练重新用原始目标跑10个episode统计真实成功率画一条曲线。只有这条曲线在抬升才说明HER真的在起作用。还有一个辅助指标可以看重放缓冲区里正奖励样本的占比。在纯稀疏奖励环境里正常回放这个比例几乎是0加了HER之后这个比例会快速上升到10%到30%之间具体取决于K和任务难度。如果你的缓冲区正样本占比迟迟上不去多半是重标记逻辑写错了或者新目标投影函数出了问题。这个信号很有用能帮你早发现问题。在我跑FetchReach的经验里训练初期的真实成功率会在0附近摇摆很长一段时间看起来像是完全没动静。但如果你观察缓冲区正样本比例会发现它在稳步上升。这是正常现象策略必须先学会“去够那些事后目标”然后才会慢慢迁移到原始目标上。熬过这段平台期曲线往往会突然向上拐这种“顿悟”现象在HER里非常典型别在平台期就以为代码写错了而放弃调参。4. 踩坑记录HER容易翻车的五个地方4.1 目标编码不一致导致训练崩坏我最早跑HER踩的第一个坑就是目标维度处理不一致。状态里包含目标的拼接顺序在训练时是[x, y, gx, gy]但在重标记时我写成了把整个状态向量塞进目标位置结果目标维度从2维变成了4维网络输入size都变了训练直接报错。这类问题还算好发现的。更难查的是目标空间和状态空间的量纲不一致。比如状态里x的单位是米取值范围0到1目标如果是从另一个采样器里来的取值范围变成了0到10俩拼接起来等于给网络喂了一个分布极其扭曲的向量训练起来非常折磨。解法也很粗暴但有效所有进网络的向量先做归一化。目标空间和状态空间分开归一化再拼接。不要相信你的采样器“恰好”和状态空间一致尤其是从真实环境转到仿真环境时坐标系原点一变量纲全变。我后来在任何HER项目里第一步都是先打印几个样本的目标向量和状态向量看一眼分布范围再决定要不要加归一化层。4.2 重标记比例不是越大越好新手最容易陷入的误区是“反正重标记是白赚的那我每个transition都重标记10次样本量不就大了10倍”这话前半句对后半句错。重标记确实不增加环境交互成本但过度重标记会让经验回放缓冲区里的数据出现严重的分布偏移。想象一下一个缓冲区里90%的样本都是“伪目标下的成功经验”那Q网络学到的价值函数几乎完全是在拟合“事后目标”分布真实目标下的价值估计反而被挤占了。训练初期看起来Q值又大又稳等到中期需要真实目标信息时网络却已经把真实目标对应的输入模式遗忘得差不多了。我自己做过一组对照实验K1、K4、K16三档。K4最终成功率最高K16前期学习速度快但后期出现明显的成功率回撤最终比K4低了一大截。这个现象很好解释伪成功样本在Q里灌了太多乐观偏差而真实成功样本太少无法把偏差掰回来。所以我的默认建议是K4如果想冲一冲样本效率最多到K8不要无脑堆。4.3 非平稳目标分布导致灾难性遗忘这是HER一个很少被讲的深层问题。因为重标记的goal是跟着episode实际轨迹走的所以随着策略进化轨迹状态分布本身在变导致重放缓冲区里的目标分布也在不断漂移。早训练阶段agent经常乱逛重标记目标多集中在某些随机区域后期策略收缩了新产生的目标又集中到另一片区域。如果整个回放缓冲区用的是一个固定长FIFO结构旧目标的经验被逐渐挤掉网络就会把旧目标对应的能力忘掉。对付这个问题我用的一个土办法是经验回放缓冲区里天然保留一部分“原始目标transition”不做任何重标记这样至少保证真实任务分布一直在训练数据里。这个比例不需要太高20%到30%就够。另外就是选一个容量比较大的缓冲区让旧经验的退场慢一些。OpenAI论文里的对数缓冲区大小配到10^6量级是有道理的不一定全是追求样本量可能也隐含着缓解非平稳分布的作用。4.4 与off-policy算法搭配的版本陷阱虽然HER理论上能和所有off-policy算法结合但不同算法对重标记样本的敏感度差别很大。我在实际跑DDPG和TD3时感受很明显TD3因为有了裁剪式Q学习对伪成功样本的过估计问题不那么敏感稳定性比DDPG好很多。如果让我现在选默认就是TD3或者SACDDPG只适合快速验证想法的场景不适合做长期训练。还有一个容易被忽略的陷阱是动作bound。HER重标记出来的成功样本其动作是“真实执行过的”这个没问题。但Q网络在更新时输出的是当前策略的目标动作如果策略网络的输出没有限制在环境动作边界内就会出现“用一个非法动作去评估Q值”的问题导致价值估计完全失真。这个锅不完全算HER的但加了HER后正样本密度高了非法动作带来的偏差会被放大。我建议所有连续动作环境中策略输出层后一定要接tanh缩放或者clip并且确保Q更新时使用的目标动作也经过同样的缩放处理。4.5 常见问题速查表把我在项目里遇到的高频问题整理成一张表方便你排查。这些问题的现象通常很相似但原因各不相同对照着查效率最高。现象可能原因排查与处理训练loss震荡发散目标维度拼接错误、奖励值域异常打印状态/目标维度与取值范围真实成功率曲线长期为0重标记目标投影函数错误单独写单测验证new_goal的维度与范围缓冲区正样本占比过低K值太小、future窗口实现错误检查future_idx是否严格大于当前idx成功率前半段上升后半段回撤K过大、目标分布漂移降低K、保留部分原始目标transitionQ值虚高但成功率低伪成功样本过多导致过估计换TD3/SAC、降低重标记比例动作输出越界策略输出未加bound输出层加tanh或clip处理5. 这个思路还能往哪里延伸5.1 HER的边界什么时候不能用HER这么好用但也不是包治百病。我最想提醒的是如果你的任务不满足“目标可描述”这个前提HER就无从谈起。比如你要训练一个机器人倒水让人工定义一个“倒水成功”的目标描述可能还能做到但如果是“把桌子收拾整齐”这种开放任务目标空间难以定义HER就完全用不上。另一个边界是那些需要多步因果链的任务。HER能从失败轨迹里学到“最后一步操作接近了某个目标”但如果任务要求“先拿起螺丝刀再拧螺丝”重标记只会学到“拧螺丝这个动作接近了目标”但“拿螺丝刀”这步没有任何事后目标可以填补。这就得靠分层HER的思路高层规划子目标低层用HER执行。此外目标奖励函数必须是可计算或可查询的。如果你的奖励逻辑藏在仿真器内部、外部不可见那就没法重标记。做真实机器人项目时尤其要注意很多真实环境的奖励是靠人工判断打分的HER没法实时重打分也就不适用。这也是为什么目前HER的成熟应用还是集中在仿真环境里。5.2 后续发展与我的个人建议HER提出之后社区在它基础上做了一堆扩展。比如把分层思想加进去的HIGHer用更高层的策略生成子目标再让底层策略用HER去实现又比如Goat在重标记时额外构造辅助目标改善表示学习和探索效率。如果从实用角度出发我不建议一上来就追这些花活。先把基础HER用熟练理解了目标分布漂移和过估计这两个核心问题再看这些扩展会事半功倍因为它们本质上都是在缓解这两类问题。工程层面的建议只有一条先小后大先简后真。任何新环境都先做一个简化版本验证HER在生产环境里是否生效再投入算力调大模型。我在真实机器人项目上栽过的跟头绝大多数不是算法不行而是环境建模的细节和算法假设不匹配。HER是个便宜又高效的方法但它的有效性建立在环境符合目标条件化设定之上。把这个前提搞扎实了它能帮你在稀疏奖励任务里少走好几个月的弯路。最后再分享一个小技巧在写这个项目的过程中我养成一个习惯训练的任何阶段都定期保存一份“只含原始目标transition”的少量样本用它们单独评估Q值。这个评估不参与训练只看Q值是否随真实成功率同步变化。如果有一天你发现真实成功率在涨但原始目标上的Q值却在掉说明网络正在被重标记目标带偏这时候就该考虑降低K值或者在经验回放里增加原始目标样本的比例。这个小技巧听着简单但在我后来几个HER项目里几乎每次都帮我提前发现了训练跑偏的迹象比盯着成功率曲线等结果要靠谱得多。也希望你跑HER的时候不用再经历我当初那种“明明加了方法却完全没效果”的茫然期。
返回列表