强化学习稀疏奖励难题:HER后见经验回放原理与实战
hindsight英文直译过来是“后见之明”更接地气的说法是“事后诸葛亮”。可你要是跟我一样做强化学习看到这个词脑子里蹦出来的多半不是日常调侃而是那篇让我重新理解“稀疏奖励”的论文——Hindsight Experience Replay后见经验回放。这个算法最绝的地方就是给机器人装了一颗“会后悔”的脑子。以前我们训练智能体全靠奖励信号喂饭做对了给糖吃做错了挨一棍子。现实任务里奖励信号往往稀疏得可怜机械臂抓十个杯子九个空算法啥也学不到一脸茫然地原地打转。HER的思路很朴素没抓住杯子但你碰到了杯子的位置那就把“碰到那个位置”当成新目标先学会再说。这一招看着像自我安慰实际上是把“死局”变成了“源源不断的成功经验”。这篇博文就围绕hindsight这个话题展开我会从问题本质、算法原理、代码实现到训练踩坑一步步拆给你看。适合正在啃强化学习的入门者、被稀疏奖励折磨的工程师以及所有想知道“失败经验到底怎么复用”的人。1. 先说清楚hindsight到底在解决什么问题1.1 后见之明人类天生就会的“作弊”能力你回想一下自己是怎么学会投篮的。新手上场十投九不中篮筐在那里纹丝不动。但你不会因为没投进就一无所得——你至少知道了“刚才那一球偏右了”“力气使大了”甚至你会调整姿势让球的落点一点点靠近篮筐。这个过程里篮筐原始目标从来没变过但你的每一次失败都提供了“下一步怎么调整”的信息。强化学习里的普通算法没这个本事。智能体投偏了一球得到的奖励是-1它只知道这次不行却不知道“偏右了”更不知道“如果以篮板右上角为目标我刚才那一投是可以命中的”。人类在失败后能自动完成一层“目标替换”把实际达成的状态拎出来当成临时目标然后倒推刚才的动作到底对不对。这就是后见之明也叫“事后归因”。HER把这件事做成了算法。它不改变网络结构不改变策略梯度公式只是在你失败之后帮你把“目标”这个词重新定义了。本质上它利用的是人类最自然的学习方式为一个已经发生的结果寻找解释而不是为一个没达成的目标懊恼。1.2 稀疏奖励强化学习最头疼的经典场景先说说什么叫稀疏奖励。假设你训练一个机械臂开门奖励函数写的是门开了给1没开给0。整个训练过程里机械臂在茫茫动作空间里随机试探99.9%的尝试都打不开门那它拿到的奖励几乎全是0。没有梯度信号反向传播无从谈起策略网络就只能靠瞎猜猜一万年也猜不出“先伸手、再转腕、最后拉”这个动作序列。这种场景在真实任务里太常见了。机器人抓取、导航到指定点、拼积木、倒水全都是“要么成、要么败”的二值判断。你说我可以设计密集奖励啊比如离目标越近奖励越大——理论上没问题但工程上你得手写奖励函数这个函数稍有不合理智能体就会学会钻空子。比如你奖励它靠近目标它就原地绕圈不动因为“不动就不会扣分”你奖励它前进它就在目标周围画八字。HER走出了一条截然不同的路不优化奖励函数而是优化经验的使用方式。它承认失败是常态然后想办法让失败变得“有营养”。这就是hindsight这个词在算法语境下的真正分量——不是让你事后补救而是让你事后学习。2. HER的核心思想拆解目标重标记为什么这么能打2.1 一句话理解目标重标记假设智能体在一个episode里尝试完成目标g但最终没做成实际到达了状态s_T。普通经验回放把这个episode的所有transition存进经验池但因为奖励全是稀疏的0或-1这些数据对学习几乎没有正面贡献。HER不一样。它在episode结束后把“原始目标g”换成“实际到达的某个状态g”然后重新计算每条transition的奖励。因为g是真实到达过的所以相对于g这个episode里的最后几步天然就是“成功”的——奖励不再是0而是1。于是原本一整个episode的废数据瞬间变成了高质量的成功示范。我常用一个生活类比来解释你学做菜菜谱目标是“红烧肉软糯入味”你做出来一锅有点焦但香味不错的肉。普通算法会直接判定失败把这次操作扔掉HER会把它重新标记成“目标做一锅焦香风格的红烧肉”然后告诉你你这次操作是成功的请记住这些步骤。下次你再做别的菜至少“炒糖色”“收汁”这些动作已经有了正反馈基础。这个trick的关键在于强化学习关心的是“从状态到动作的映射”而映射本身并不在乎目标叫什么。你把目标从g换成g动作序列还是那个动作序列但奖励变了梯度信号就活了。2.2 四种目标采样策略与我的选型建议目标重标记不是随便挑个状态当新目标就行。原始论文比较了四种策略final、episode、future、random。我用大白话翻译一下final把episode终止时到达的最终状态当成新目标。简单粗暴每个episode只需多存一个目标。episode从当前episode里随机抽一个状态当新目标。随机性大可能抽到跟前半段动作毫无逻辑关系的状态。future从当前时刻之后的时间步里随机抽一个状态当新目标。这是我最推荐的一种也是论文里效果最好的。random从其他episode或全局状态里随机抽。基本没人用信息量太低。future为什么最强因为它保证了时间和因果的一致性。你在第t步做了一个动作第t5步到达了某个状态用这个状态当目标那这个目标与你第t步的动作之间隔了5步逻辑链路是通的网络更容易学出“动作导致状态变化”的因果关联。而episode策略可能抽到第t步之前的状态那就成了“用过去解释未来”逻辑上就拧巴了。实际操作时我的默认配置是future策略加k4。k的含义是每条原始transition额外生成几个虚拟目标经验。k越大经验池里“重标记经验”占比越高学习信号越密但也会挤压原始经验导致策略对真实目标的环境感知变弱。k4是论文验证过的平衡点我自己从k1到k8都试过4左右最稳。2.3 HER本质改了数据分布不是改网络很多人初学HER有个误解以为要改网络结构、要加新的loss项。其实HER完全不动模型它只动经验池里的数据。你可以把HER理解成一个“数据增强层”在写入经验池之前先把失败的经验用虚拟目标重写一遍让训练数据的分布从“稀疏奖励的荒漠”变成“密度适中的绿洲”。这跟普通经验回放Experience Replay有本质区别。普通回放只解决了“数据相关性”——打乱时间顺序让小批量样本独立同分布。它不改变奖励信号本身。HER解决的是“信号密度”——把0奖励替换成有正有负的奖励让每个batch里都有网络能学的信息。这个定位很重要。因为HER不挑算法DQN、DDPG、TD3、SAC都能嵌进去它只是给经验池加了一个“预处理步骤”。我在项目里最常用的是HERDDPG的组合处理连续控制任务非常顺也有人拿HERSAC做更复杂的操作任务一样work。只要你的算法是off-policy的、有经验池、能从历史数据里反复采样就能嫁接HER。3. 实操记录从代码到手把手复现HER3.1 训练主循环的完整流程纸上谈兵够了直接上实操。我用OpenAI Gym的FetchReach环境举例这是验证HER效果最经典的“Hello World”。任务很简单机械臂要把末端执行器移动到目标点。看似简单但在稀疏奖励设定下没有HER的DQN几乎不可能学会。整体训练流程分四步采样一个episode、episode结束后做目标重标记、把原始经验和重标记经验一起存入经验池、从经验池采样更新网络。我把核心的重标记代码单独拆出来说明这一步是整个项目的灵魂。def hindsight_relabel(episode_transitions, k4): # episode_transitions: 一个episode内所有transition的列表 # 每条transition包含 (obs, action, reward, next_obs, done, achieved_goal) new_transitions [] episode_len len(episode_transitions) for t, trans in enumerate(episode_transitions): # 原始经验原样保留 new_transitions.append(trans) # 额外生成k个虚拟目标经验 for _ in range(k): # future策略从当前时刻之后的时间步随机抽一个状态 future_idx np.random.randint(t, episode_len) new_goal episode_transitions[future_idx][achieved_goal] # 用新目标重新计算奖励稀疏二值奖励达成给0否则-1 new_reward compute_sparse_reward( new_goal, trans[achieved_goal] ) # 替换obs / next_obs中的goal字段实际环境里通常拼接在状态向量末尾 new_obs replace_goal_in_obs(trans[obs], new_goal) new_next_obs replace_goal_in_obs(trans[next_obs], new_goal) new_transitions.append({ obs: new_obs, action: trans[action], reward: new_reward, next_obs: new_next_obs, done: False, achieved_goal: new_goal, }) return new_transitions函数逻辑不长但有四个细节必须拎出来强调第一future_idx要大于等于t保证采样的是“未来状态”。如果你手滑写成了np.random.randint(0, episode_len)那就退化成episode策略效果会明显变差。第二new_goal取的是achieved_goal字段也就是机械臂末端实际到达的三维坐标不是原始状态向量里那一大串位置、速度、关节角。第三重新计算的奖励必须基于“新目标是否达成”我见过有人偷懒直接复制原始reward那整个重标记就白做了。第四done标志千万别沿用原始值否则环境明明没结束你告诉网络“这个transition终结了”价值函数会被教坏。3.2 必须盯紧的三个训练参数第一个参数是重放比例k。我前面说了默认k4但k不是越大越好。k8时经验池里重标记经验占比太高智能体容易“活在虚拟目标里”真实目标偶尔出现反而像异常值训练后期成功率上不去。我的经验是任务越简单k可以越小任务越复杂k适当调大但封顶8。第二个参数是观测与目标的拼接方式。Fetch环境返回的observation是25维向量其中前10维是机械臂状态后15维里混合了当前目标、实际到达位置等字段。HER替换目标时必须把obs和next_obs里的“当前目标”字段整体换掉不能只换一半。这个坑非常隐蔽因为维度没变程序不会报错但训练就是时好时坏。我排查过整整两天才发现是拼接逻辑写错了。第三个参数是奖励尺度。原始her论文用的稀疏奖励是“达成给0未达成给-1”目的是让智能体尽量缩短轨迹、用更少步骤达成目标。但很多复现代码会顺手把奖励写成1和0这两种写法对DQN这类算法影响不大对DDPG这类连续控制算法影响非常大——因为critic网络输出的Q值尺度会完全不同学习率、目标网络更新参数都得跟着调。我建议你统一用“0和-1”不仅方便对比论文数据也更容易稳定。3.3 训练效果什么样的曲线说明真跑起来了我在FetchReach上跑过一套基准实验环境用MuJoCo物理引擎算法用DDPG网络是两个256×256的全连接层学习率1e-3batch size 128经验池大小1e6每个epoch跑50个episode。无HER版本跑了200个epoch成功率长期在5%以下——基本就是瞎蒙。加了HER之后大约50到60个epoch成功率开始爬升到120个epoch左右稳定在95%以上。这个对比极其直观。两者的网络结构完全一样采样方式相同唯一的区别就是经验池里有没有重标记后的虚拟目标经验。有人问为什么不用更复杂的任务举例我的回答是FetchReach简单才能暴露最本质的对比。你连这个环境的收敛曲线都拉不直就别急着上FetchPickAndPlace。更复杂的任务上比如FetchPushHER同样有效但收敛速度会慢很多往往需要300到500个epoch。而且你会看到成功率曲线呈“阶梯式”上涨——某个epoch突然学会一个子技能比如推箱子靠左一点成功率跳一截然后平台期再过几十个epoch再跳一截。这是稀疏奖励任务的常态看到阶梯别慌说明智能体在积累子技能不是卡死了。4. 踩坑实录与排查思路4.1 从环境到网络我遇到过的四个典型问题先讲最邪门的训练曲线全程贴地跟没训练一样。我起初怀疑是网络结构问题换了DDPG、TD3都一样。后来打印经验池里的reward分布发现90%以上的transition奖励是-1几乎没有正样本。再一查重标记函数竟然在原始transition上原地修改把经验池里已经存好的数据也改了。train循环里每次episode结束调一次relabel但我不小心把内存里的原始列表也覆盖了。正确的做法是relabel返回一个新的list原始transition先存进经验池再处理副本。第二个问题出在future采样的实现。我当时图省事直接用np.random.choice(achieved_goals)从整个episode的所有goal里抽看起来和future差不多但训练总是不稳定。原因在于future策略要求抽样下标不能小于当前时间步否则会出现“用过去的目标去教当前动作”的时序倒置。这是理论上的含义实践里体现在loss上就很不稳定。第三个坑是评估阶段没有还原原始目标。HER训练时网络见过大量“虚拟目标”如果你评估时直接把验证环境的目标塞进去网络可能产生一种错觉这个真实目标没见过我该怎么动作其实不会那么夸张但确实会影响成功率。正确做法是评估时关闭重标记只用原始目标多跑几个episode取平均成功率。第四个坑更隐蔽不同环境的achieved_goal和obs里的目标字段可能不是同一份数据。Fetch环境里obs后15维够装三个坐标但PickAndPlace这类任务的目标是六维可能横跨多个字段。你要是用了统一的状态拼接工具函数必须逐一确认环境文档里的索引范围别想当然。4.2 常见问题排查速查表我整理了一张表基本覆盖新手复现HER时90%的问题你可以直接当排查手册用。现象最常见原因排查方向训练曲线全程贴地重标记后reward没重新计算打印经验池reward分布统计正样本比例曲线震荡剧烈future采样时序错了或k值过大检查future_idx是否大于等于t调低k成功率有上限上不去真实目标经验占比太低降低k或重标记时额外保留原始目标样本obs维度对不上goal替换索引范围写错打印替换前后obs的具体数值人工比对前期涨得快后期崩经验池被虚拟目标污染确保原始transition优先写入重标记样本后置评估比训练差很多评估时仍走了重标记分支评估代码单独写不调用relabel函数连续控制动作失控奖励尺度不统一统一用0和-1的稀疏奖励别混用密集奖励4.3 事后诸葛亮的边界HER不能解决什么写到最后还是要泼点冷水。HER不是万能的它有三条边界你要心里有数。第一HER不能替代探索。重标记只改变目标的定义不改变底层的动作探索策略。如果机械臂的动作空间大到离谱而它连“碰到物体”这件事都做不到那全程都没有任何状态可用来重标记HER照样白搭。这类场景需要配合curiosity、NoisyNet或者更好的探索策略HER只能在“有失败轨迹可挖”的前提下锦上添花。第二目标和状态必须同构。这是重标记成立的前提条件。你的替代目标必须是在状态空间里有物理意义的东西比如坐标、速度、位姿。如果目标是抽象离散值比如“打开冰箱”“拿起苹果”你没法从一个连续状态里抽出“目标”来重标记。解决办法是把离散目标嵌入成向量但嵌入空间的语义要对齐否则重标记出来的虚拟目标毫无意义。第三HER对“多目标多阶段”任务加成有限。比如倒水这个任务光把水杯挪到水壶边没用还得对准壶口、倾斜角度每个阶段是串联关系。HER能帮你学会第一阶段挪到水壶边但第二阶段的失败水洒了重标记后并不能直接贡献给第一阶段的学习。这类任务要拆层级或者配合选项框架Options这类层级强化学习思路光靠HER是不够的。我在实际项目里用HER调通的最有成就感的一个场景是让机械臂学会了把散落在桌面上的三个积木逐个推到指定区域。每个积木单独看都是一个FetchPush的变体但HER让我不用为每个积木手写奖励函数只要定义好“目标达成”的判定条件剩下的全部交给重标记机制。那种“失败自动变教材”的感觉确实让我重新理解了hindsight这个词的分量。如果你现在正卡在稀疏奖励的算法调不通我建议你别急着改奖励函数先往经验池里加一层HER试试。选future策略k设4从FetchReach跑起。等你看到那根成功率曲线从零开始爬升你就明白“后悔”对机器来说是多么强大的学习信号了。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →