ARTICLE DETAIL

资讯详情

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

强化学习稀疏奖励困境:HER事后经验回放原理与实战指南

强化学习稀疏奖励困境:HER事后经验回放原理与实战指南 hindsight英文直译是“事后聪明”。这个词放到人工智能领域有一个非常经典的技术含义——Hindsight Experience Replay后见之明经验回放简称HER。我第一次接触到它是在一篇讲机器人机械臂推小球、抓方块的论文里当时心里还嘀咕失败的经验怎么能拿来当成功用这不是自欺欺人吗后来完整跑通实验才发现这个思路不仅不玄反而极其实用直接解决了一类让人头疼的强化学习任务——稀疏奖励问题。这篇博文我打算从“为什么稀疏奖励会让人绝望”讲起拆解HER的核心机制然后给出一套可以在Fetch系列环境上直接跑的代码级实操方案最后把我踩过的坑和参数调优经验整理成一张速查表。适合正在做强化学习研究、准备复现经典论文或者想拿HER解决实际控制问题的朋友参考。如果你只是听说过HER但一直没搞清楚它为什么有效这篇应该能帮你把疑问一个个消掉。1. 为什么要从 hindsight 出发稀疏奖励问题的破局思路1.1 强化学习里最折磨人的问题奖励几乎为零想象一个机器人控制任务桌面上放着一个滑块机械臂要把它推到某个目标位置。奖励函数怎么设计通常有两种极端思路。一种是稠密奖励比如“滑块离目标点越近奖励越大”。听起来很合理但实际做过的都知道设计这个函数本身就是在人为介入。距离怎么度量是欧氏距离还是曼哈顿距离要不要把机械臂本身的位置或速度也惩罚进去奖励尺度定多大合适同一套距离奖励换到另一个环境效果可能天差地别。更麻烦的是稠密奖励很容易被策略钻空子。拿真实机器人来说如果奖励函数没有精巧设计机械臂完全可能学会靠“小幅抖动手臂”来反复刷分动作看起来毫无意义但奖励曲线倒是很好看。另一种就是稀疏奖励如果滑块最终位置和目标位置的距离小于某个阈值比如5厘米给奖励1否则给奖励0。没有人为诱导目标清晰不会钻空子可问题也来了——机械臂在一个连续动作空间里靠随机策略探索想碰到一次“刚好把滑块推到位”的概率低得可怜。奖励长期为零梯度也就长期为零智能体完全感受不到自己到底是“更接近目标了”还是“更远了”。它只能像无头苍蝇一样乱撞。我用一个生活类比解释这有多绝望教一个完全不会投篮的孩子每次投不中你什么都不说只有空心入筐时你才大力鼓掌。这个孩子几乎学不会投篮因为“差一点”和“完全离谱”两种投法对他来说完全没区别没有任何反馈能告诉他“你刚才其实有进步”。最后他只能靠撞大运而那需要极长时间甚至根本等不到。1.2 后见之明的本质用“what if”主动制造学习信号HER的想法朴素到什么程度呢既然失败的轨迹对“当前目标”提供不了梯度那就别死盯着这个目标了。换个角度——这条轨迹最终到达的位置是A那我就把目标临时改成A这一整条轨迹不就变成了一条“完美示范”吗回到投篮的例子上。孩子没投中篮筐但球打到了篮板右上角。于是我们心想如果刚才的成功标准不是“空心入筐”而是“击中篮板右上角”那这次投篮其实投得非常准甚至可以画出一条手臂应该如何发力的完美示范轨迹。虽然这不是教练本来想要的但这条轨迹确实包含信息量比“毫无反馈地失败”有价值得多。放到强化学习的技术语境里这个想法在目标条件强化学习Goal-Conditioned RL中尤其顺理成章。目标条件策略的输入是“状态目标”输出是动作它回答的问题是“给定这个目标我该怎么做”。那么把目标从“原来的目标”换成“轨迹实际到达的状态”这个问题的答案仍然是成立的——往回看这些动作在给定新目标的前提下就是一组能最终达到目标状态的合理动作序列。失败不再是垃圾数据而是伪成功经验。这就是后见之明的精髓不是否认失败而是重新解释失败把它变成对自己有用的学习信号。当年OpenAI那篇论文能拿到几千引用核心卖点就是这个看似简单、却极其反直觉的设定每次尝试都算数只要事后换一套评价标准。1.3 原论文给出的四种目标重标记策略原论文Andrychowicz et al., 2017设计了四种目标选择策略也是到现在为止各类复现里最常见的配置final直接取轨迹最后一个状态作为新目标。future从轨迹中当前时刻之后的某个时刻采一个状态作为新目标。episode从同一个episode里随机抽一个状态作为新目标。random从整个回放缓冲区里随机抽一个状态作为新目标。四个策略里实际效果最好的是future。原因不难理解final策略要求整条轨迹从头到尾都“属于”同一个目标噪声很大episode和random选出来的状态与当前transition里的动作因果关联太弱价值信号偏分散而future取的是当前时刻之后不久的真实状态与当前动作有很强的因果联系——就是这个动作把状态推进到了那个地方所以目标更有针对性策略更容易从中学到“什么样的动作会通往什么样的状态”。先记住一个结论HER不是万灵丹但对“目标可定义、状态与目标空间一致”的任务它几乎是一种零成本的样本效率提升方案。它改的不是网络结构不是探索策略而是数据本身——把“没用的失败记录”变成“有用的教学案例”。2. HER 的核心机制逐一拆解2.1 目标状态重标记一个简单却反直觉的操作我们把HER的具体操作说得再细一点。在Fetch系列环境里一个transition长这样观测obs通常分成三块——原始状态observation、期望目标desired_goal、已到达目标achieved_goal。你执行一个动作act得到下一个观测以及一个奖励rew这个奖励的计算方式一般是“achieved_goal到desired_goal的距离小于0.05米就返回1否则返回0”。普通RL会把这个transition直接丢进回放缓冲区原样学习。HER多做了一个动作把transition里的desired_goal替换成一个新的目标g然后重新计算奖励。如果这个g恰好来自轨迹后面某个时刻的achieved_goal那重算出来的奖励就是1。一条“失败样本”瞬间变成“成功示范”。但这里有一个反直觉的点很多人第一次读论文时都没注意到这个transition里的动作真的“配得上”这个成功奖励吗严格来说不一定这个动作并不是针对g精心设计的最优动作。它只是“在通往g的路径上实际发生过的动作”。但没关系它至少是一条有引导性的次优示范。智能体反复学习这些“够得着的伪成功”就会被一步步引向那种“确实能达到某个目标”的状态分布然后再从这些状态出发往真正的目标推进。这就是HER有效的核心也是它本质上的偏差来源——我们有意向数据里注入了“事后视角”。这个偏差是算法成功的关键但也意味着你不能完全指望它解决所有问题这一点放到后面聊。2.2 future、final、episode、random 四种策略对照我把这四种目标选择策略的优缺点整理成一张表方便你直接对照使用策略目标来源优点缺点final轨迹最后一个状态实现最简单制造完整成功轨迹目标单一目标与中途动作的因果链太长信号稀疏future当前时刻之后的随机状态因果关联强伪成功率高信号丰富需要额外采样逻辑实现略复杂episode本episode内随机状态实现简单覆盖episode内分布可能选到与动作几乎无关的早期状态random从回放buffer随机采样覆盖全局目标分布目标与当前transition基本无关价值信号弱实践中我基本只用future偶尔mix一点final。原论文的默认策略就是future超参future_k4意思是每一条真实transition额外生成4条future重标记transition。这样缓冲区里真实目标和伪成功经验的比例大约是1:4。这个比例很关键后面在参数调优部分会单独展开。2.3 哪些 transition 可以重标记不是所有transition都适合重标记这里有三个容易踩的点我一开始都踩过一遍。第一目标空间必须和状态空间同源。HER能成立的前提是“目标可以从状态里提取出来”比如机械臂末端坐标、滑块坐标、物体位置这类可达状态。如果目标是“图片里某个物体”或者“某个语义概念”而状态是机械臂的关节角向量那重标记出来的新目标g会无法对应到原始状态整条经验就废了。第二重新选出来的新目标必须在环境允许的范围内。机械臂末端位置不能超出关节限位滑块位置必须在桌子范围内。否则你造出来的transition本身就是非法样本策略学到的是“往桌子外面推也能拿到奖励”这种错误关联。我见过有人调试半天最后发现伪成功经验里一堆目标点在天花板上策略自然学得乱七八糟。第三重标记比例不能失控。如果用future从当前时刻之后采样伪成功率通常不低但如果全部transition都改成伪成功原本目标对应的“当前任务”上下文就太少了策略会偏斜。一般推荐每条真实transition保留一份额外生成K份重标记版本K在4附近比较稳。2.4 为什么一定要配 off-policy 算法读到这里你可能会问既然只是改写数据那任何RL算法都能套上HER吗答案是不能。HER本质上是在改写回放缓冲区里的数据分布改写之后再用这些数据训练智能体要求算法必须能反复使用离线数据并且能容忍“数据分布和当前策略不一致”。这类算法就是off-policy算法——DQN、DDPG、TD3、SAC都可以而PPO这类on-policy算法就不适合。原因在于on-policy算法假设每一轮数据都来自当前策略策略更新完这些数据就该扔了。HER改写的伪成功经验如果只被用一次那它的价值就被浪费了一大半。而且HER引入的伪成功数据明显偏离当前策略的采样分布on-policy算法在做重要性采样时比值会抖动得很厉害最终训练不稳定。所以原论文里HER配的是DDPG不是没道理的。DDPG对超参虽然有点敏感但结构简单责任边界清楚做HER的载体正合适。我自己实操的体会是入门先跑通DDPGHER后面想追求稳定再换SAC。一上来就上SAC遇到问题时你很难判断是SAC的问题还是HER的问题调试成本反而更高。3. 动手实现在 FetchSlide 环境中跑通 HERDDPG3.1 环境准备OpenAI Gym 的 Fetch 系列实操环节环境选OpenAI Gym里的Fetch系列这是HER原论文的标准测试床也是后来无数复现实验的“默认赛道”。现在环境库是gymnasium-robotics常用环境名有FetchReach-v1机械臂末端直接到达一个目标点最简单适合验证管线。FetchPush-v1把桌面上的方块推到目标位置。FetchSlide-v1用机械臂击打滑块让它滑到远处的目标点滑块有惯性比较难。FetchPickAndPlace-v1抓取方块并放到目标位置涉及夹爪闭合控制。安装命令是pip install gymnasium-robotics还需要MuJoCo物理引擎。注意新版gymnasium接口里env.reset()会返回(obs, info)两个值和旧版gym不一样。另外Fetch环境的obs是一个dict里面包含observation、desired_goal、achieved_goal三个部分HER实现里这三个字段全都要用千万别只取observation那一块否则后面重标记和奖励重算都做不了。我个人建议先跑FetchReach或FetchPush验证代码再挑战FetchSlide。FetchSlide难在滑块有滑行惯性策略输出一个小力可能让滑块滑过头“指哪打哪”式控制在这里会失灵很多初学者在这个环境里怀疑人生其实这是正常的。3.2 网络结构选择小网络为什么够用HER当年在真实机器人抓取场景里也是用小型MLP完成的。网络结构我参考原论文actor和critic都是两层或三层全连接隐藏单元数256。有人会想现在都是大模型时代了网络是不是该加宽加深我劝你别。RL问题里盲目增大网络容量带来的收益远不如调奖励和探索来得快而且大网络在稀疏奖励下更容易过拟合噪声训练会更慢不会更快。我常用的actor-critic精简实现长这样import torch import torch.nn as nn class Actor(nn.Module): def __init__(self, state_dim, action_dim, max_action1.0): super().__init__() self.net nn.Sequential( nn.Linear(state_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, action_dim), nn.Tanh() ) self.max_action max_action def forward(self, state): return self.net(state) * self.max_action class Critic(nn.Module): def __init__(self, state_dim, action_dim): super().__init__() self.net nn.Sequential( nn.Linear(state_dim action_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, 1) ) def forward(self, state, action): return self.net(torch.cat([state, action], dim-1))注意actor输出层必须用Tanh把动作限制在[-1,1]之间Fetch系列的动作范围恰好就是[-1,1]的连续向量乘以max_action1就完事。这样后续step的时候不用手动clip省心。3.3 核心伪代码与关键超参数HERDDPG的训练主体并不复杂核心就在那个“重标记并存入buffer”的步骤。用Python风格的伪代码写是这样的for episode in range(total_episodes): obs env.reset() episode_transitions [] for t in range(max_episode_steps): action select_action(obs, noise_std0.2) next_obs, reward, done, info env.step(action) episode_transitions.append((obs, action, reward, next_obs, done)) obs next_obs # 关键HER 重标记 for i, trans in enumerate(episode_transitions): buffer.add(trans) # 保留原始 transition # 用 future 策略额外生成 K 条伪成功经验 for _ in range(future_k): future_idx random.randint(i 1, len(episode_transitions) - 1) new_goal episode_transitions[future_idx].next_obs[achieved_goal] new_reward compute_reward(new_goal) # 距离判断0.05米内算成功 buffer.add(rewrite_transition(trans, new_goal, new_reward)) # 然后照常从 buffer 采样更新 actor/critic for _ in range(update_steps): batch buffer.sample(batch_size) update_actor_critic(batch)这套代码里几个超参直接决定成败。我把实测能用的组合整理成表参数我常用的值说明future_k4每条transition额外重标记条数别太小也别太大buffer大小1_000_000HER依赖大量伪成功经验buffer要够大batch_size256太小训练不稳定actor/critic lr1e-3原论文常用值gamma0.98Fetch任务中比较常见tau0.05目标网络软更新系数新手别调太小探索噪声N(0, 0.2)连续动作空间的高斯噪声每episode更新次数40采完一个episode后再批量更新多步这些参数为什么这么定gamma取0.98而不是0.99是因为Fetch的任务时域较短更大的gamma会让价值估计在稀疏奖励下过于乐观。tau取0.05是原论文的常见设定目标网络跟随太快会放大伪成功经验的偏差所以新手上手用0.05稳稳当当。3.4 训练实操记录我看到的曲线现象我记得自己第一次在FetchSlide上跑通HERDDPG时前20万个时间步几乎看不到任何成功样本eval成功率一直是0。那段时间最容易让人怀疑代码写错了。于是我在训练里加了日志打印buffer里真实成功率和伪成功率发现伪成功率已经爬到了30%以上——说明HER的“伪成功”信号其实一直在累积只是策略还没学会如何利用这些信号。大概40万步之后成功率开始突然向上跳几乎是直接从不到5%跳到60%。这个“悬崖式上升”在稀疏奖励HER里相当常见。原因是一旦策略学会了某一种能抵达目标的动作模式成功经验就会闭环堆叠进度条就从龟速变成跳表。你如果看到HER训练曲线是平滑上升的反而要怀疑是不是不小心用了稠密奖励或者哪里偷改了条件。同一时间我跑了无HER的对照组结果惨烈训练100万步eval成功率基本贴着0不动偶尔出现几个1%的尖峰但方差极大均值没有任何上升趋势。这也正是HER论文能拿到那么高引用的原因——它真的把一类以前“学不动”的任务变成了“学得会”。4. 训练过程中我踩过的坑与排查清单4.1 稀疏奖励的“偶尔成功”假象我踩过最坑的一个情况是buffer里明明已经积累了一些真实成功样本但训练曲线还是纹丝不动。后来我把成功样本打印出来才发现这些样本集中在少数的初始位置上完全靠运气而不是策略能力。稀疏奖励任务里偶尔会有几个“天选episode”让eval成功率出现一个尖峰特别容易给人希望但一旦看rolling平均就原形毕露。建议你在训练时多打印几类指标episode平均长度、buffer中真实成功比例、伪成功比例、eval的100次平均成功率。如果eval成功率虽然偶尔冒尖但长期均值为0先不要急着调网络去检查目标距离有没有算对、探索噪声范围合不合理。单看最大成功率是最典型的误判方式。4.2 重标记的边界非法目标怎么毁掉策略第二个大坑是重标记后的transition“看起来对、实际非法”。比如FetchSlide里滑块滑到桌沿附近时future策略很容易采到一个桌面以外的未来状态。以这个目标去重标记策略会学到一种“把滑块往世界外面推也能成功”的错误逻辑。再比如机械臂到达极限位置时超界的末端坐标作为新目标同样会污染数据。所以一定要加合法目标检查最简单的做法就是判断新目标向量是否落在环境允许的坐标范围内。另外future_k也不是越大越好。调太大会稀释真实目标策略会变得越来越“只会在事后找目标”对原始任务反而无感调太小则伪成功经验不够学习缓慢。从默认的4开始用实验对比2和8的差异不要无脑照搬默认值。4.3 随机种子不是玄学RL实验对随机种子极其敏感这是真实现象不是玄学。同一份代码、同一套超参换个随机种子成功率的跨度可能从90%掉到40%。所以我强烈建议至少跑3到5个随机种子最终报告结果时取中位数而不是挑最高的一次截图发出来。很多开源实现和你的复现结果有差距一半原因出在随机种子的统计幸运度上。实操时固定三处随机numpy.random、torch.manual_seed、env.seed。尤其是env.seedFetch系列环境的初始状态由统一随机源控制不固定seed你连复现bug都做不到更别谈调试了。4.4 超参敏感度tau、future_k 与回放比例DDPG的tau我用0.05时稳定但变慢换成0.005前期会抖后期上限可能更高。道理不难理解tau控制目标网络跟随当前网络的快慢相当于“你对当前评估的信任程度”。HER环境下伪成功经验本身就有偏差目标网络更新太激进等于把偏差放得更大训练自然不平稳。回放比例同样关键。前面说过buffer里真实transition和伪成功transition的比例维持1:4比较合适。我见过有人为了省事把原始transition全扔了只存重标记数据结果策略开始“左右横跳”——它把“任何状态都有可能是目标”这件事过度内化了对原始任务反倒没了概念。保留真实数据不只是为了数量更是为了让策略记得“当前任务的目标到底应该是什么”。4.5 一张问题排查速查表现象可能原因排查建议eval成功率始终为0动作范围不对/噪声过小检查actor是否用了Tanh是否乘了max_action训练后期掉点目标网络更新过快tau调小或回放比例调回1:4伪成功比例过高future_k太大或采样窗口太短减小future_k检查采样范围曲线突然全部归零reward被改过或数值NaN打印loss检查actor/critic是否有NaN不同seed差异巨大随机种子未统一或统计口径不对固定seed报告时取中位数学到“往错误方向推也成功”重标记目标越界对g做合法范围检查无HER也能学但ER下不涨奖励不是真的稀疏确认不是用了伪装的距离奖励5. 从Fetch到真实场景HER的适用范围与局限5.1 什么样的任务适合HER经过这么多实验我总结HER最适用的场景有三个共同点目标可以被表达成一个状态、目标与状态共享底层特征、任务里存在“没能达到预期目标但达到了另一个可达目标”的失败情况。机械臂操作、移动机器人导航、多目标规划这类任务几乎全中。在这些场景里HER是一种几乎零成本的样本增效手段。你不改网络、不动环境只需要在数据采集后多做一个重标记步骤样本效率就能翻一个量级。配合回放缓冲区每一次失败都能变成有用的“教学案例”。所以我现在一看到目标条件RL第一反应永远是HER能不能直接套上去。5.2 哪些场景会失效但也要给HER泼点冷水。假设任务是“走出房间并找到钥匙开门”钥匙位置随机目标状态空间在整个环境里几乎不可达。轨迹实际到达的状态与钥匙位置毫无关系重标记出来的伪成功经验基本全是垃圾HER帮不上忙。第二个失效场景是奖励函数不仅取决于目标距离还包含不可忽略的约束。比如避开障碍物、节省能量、不能碰撞。HER只会重标记“目标状态”不会重标记“约束状态”伪成功经验可能违反真实任务的约束条件。在真实机器人上尤其要小心——你让机械臂“把失败推成成功”但现实中的失败可能意味着碰撞或坠落这些代价没法被一个事后重标记抹掉。第三个局限是工程成本。HER实现起来虽然简单但它对buffer容量和训练时长有要求。如果你做的是在线实时学习而不是离线训练后部署HER带来的额外存储和计算开销不一定划算。先想清楚应用场景再决定要不要上HER。5.3 值得继续扩展的三个方向如果你读完这篇博文想继续深入我推荐三个方向。第一结合课程学习Curriculum Learning。HER已经在“重标记目标”了你可以更进一步根据智能体当前能力动态调整目标难度比如CHER、Course-based Hindsight这类变体。它们在复杂任务上的表现通常比原始HER更好。第二替换策略算法。DDPG只是载体你可以把HER的数据重标记机制接到SAC或TD3上。我个人用SACHER跑正常机器人任务时感觉稳定性比DDPG好不少新手学起来也更省心代价是多了自动熵温度的调试。第三目标表示学习。HER假设目标能用状态向量表示但在图像输入或语义目标场景下你可以先学一个目标空间编码器在编码空间里做重标记再映射回原始状态。这块目前还有不少研究空间适合有论文想法的朋友尝试。最后再说一点我个人的体会。我最早接触HER的时候总觉得它像是在作弊——把失败说成成功不是自欺欺人吗后来自己做实验才发现这恰恰是智能体学习过程中最需要的一种能力在稀疏反馈的世界里学会重新定义问题、从失败中提取有用的信息。这种思路不光在RL里有用在设计实验、调模型的时候也一样。一个失败的实验往往不是失败只是它原本想回答的目标有问题。每次跑HER我都会提醒自己多想想“这个目标是不是我强加给任务的”。换个视角数据可能比你想象的更有价值。希望这篇博文能帮你少踩几个我踩过的坑也希望你能在hindsight这个思路里找到属于你自己的解读和乐趣。
返回列表