拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

强化学习中的稀疏奖励难题:HER如何让智能体从失败中学习

强化学习中的稀疏奖励难题:HER如何让智能体从失败中学习

hindsight这个词,直译是“后见之明”,说人话就是事情做完之后回头看,才意识到当时其实应该怎么做。我第一次见到它出现在强化学习领域,是OpenAI那篇Hindsight Experience Replay论文里——这篇文章解决的问题非常朴实却很致命:稀疏奖励任务中学不动的智能体,以及明明探索了半天却什么梯度信号都没留下的失败轨迹。它的核心操作也简单得不像学术成果:既然你没达成我给你的目标,那我干脆把“你实际到达的地方”重新当成目标,让这条轨迹变成一段经验有效的数据喂给算法。HER不改变奖励函数,不增加额外监督,不设计课程难度,就是从“事后”这个角度给经验重放加了一次重新解释。对做机器人控制、目标导向型任务、以及研究样本效率的人来说,HER几乎是绕不开的基础算法;对只想理解强化学习核心思想的人来说,它也是理解“从失败中学习”如何落地的极佳切片。

1. 稀疏奖励难题:智能体为什么学不动

1.1 稀疏奖励到底是什么

大多数强化学习算法靠奖励信号来学习。像cartpole这种经典测试环境,每个时刻都有反馈,智能体很容易判断“这个动作让杆子更稳还是更歪”。但现实任务里,奖励往往不是这种连续的、聪明的信号,而是“要么0要么1”的硬性判定。比如机械臂要把一个方块推到桌上某个位置,整个过程里如果没有到达目标位置,奖励就是-1,到了才是0。一整条轨迹跑下来全是-1,这还算好的,更狠的是某些导航任务,中途完全没有任何奖励,只有终点亮那一下。

稀疏奖励带来的问题不是“学习慢”,而是“根本学不动”。策略梯度算法或者Q学习,更新依赖误差信号。如果随机探索一整条轨迹都拿不到任何正奖励,误差全等于零,网络参数几乎纹丝不动。打个比方:你让一个人在黑房间里找一盏开关,但这个开关只有在他站到特定地砖上才会亮。他瞎转了半天,灯从来没亮过,那他就连“往哪个方向多走一步”这种判断都做不出来。时间久了,他只会原地打转——这就是稀疏奖励场景下智能体的真实状态。

1.2 典型任务长什么样

容易被稀疏奖励卡住的任务其实很常见,我列几个典型的:

  • 机械臂抓取和推动(Fetch类环境):只有把物体推到目标位置才给奖励,推歪了啥都没有。这类任务动作空间是连续的,随机探索碰巧推准的概率极低。
  • 二进制翻牌(bit-flip):目标是一个十几二十维的01向量,只有把所有位都翻到正确的值才给奖励,错一位都不行。状态空间是离散的,但维度足够高时,随机凑对全部位的概率指数级下降。
  • 长程导航和迷宫任务:中间没有检查点,奖励只在终点出现。智能体跑了很远发现没奖励,区别只在于“跑远了”和“跑更远”,对学习来说都是同样无效的零信号。

这些任务的共同点是:必须做出一连串“恰好正确”的动作才能碰到奖励,而随机策略碰到奖励的概率随序列长度指数级下降。动作空间维度越高,探索越绝望。我见过不少刚上手强化学习的人在这种环境里跑十几个小时,训练曲线跟心电图一样平,最后怀疑是代码写错了——很多时候代码没错,是算法本身就没拿到足够的学习信号。

1.3 常规解法为什么不够用

在HER出现之前,面对稀疏奖励任务,大家通常会做这些事情:

  • 奖励塑形(reward shaping):把“离目标还有多远”装进奖励信号里,比如距离每缩小一点就给一点正向激励。这个方法很依赖设计者的领域知识,而且特别容易引入局部最优——智能体可能学会一个“看起在靠近”但实际永远到不了目标的动作。
  • 课程学习(curriculum learning):人工把任务分成从易到难的多个阶段,先学简单的,再学难的。问题是课程设计成本极高,而且很多时候“简单版本”和“困难版本”之间的迁移并不自然。
  • 探索奖励(ICM、随机网络蒸馏等):通过内在好奇心让智能体多走走没去过的地方。这类方法解决的是“探索不够”的问题,但没解决“探索到的轨迹有没有学习价值”的问题——在稀疏奖励环境里,探索了一大圈没碰到奖励,这些数据对目标函数仍然是零贡献。

HER的视角和上面这些都不一样。它不是想办法让智能体更容易碰到奖励,而是把奖励的定义从“固定的目标”换成“实际发生的结果”。这样就直接绕开了稀疏奖励的核心难点——既然原生奖励信号稀缺,那就自己从已经发生的事实里生成新信号。

2. HER的工作原理:把失败轨迹改写成成功经验

2.1 核心操作:目标重标记

HER一句话版本:对每条轨迹,除了把原始目标存进经验池,再额外生成几个替代目标,把同一条轨迹改写为几次“成功经验”存进去。

具体拆开来看是这样:

  1. 原始目标为g,智能体从初始状态出发,跑完一条轨迹。假设这条轨迹并没有达成g,所以每一步奖励都是-1。
  2. 轨迹末尾附近,实际到达的状态是某个s_T(比如机械臂最终推到物体的位置)。
  3. 把新的目标g'定义为“实际到达的状态”本身——在Fetch类环境里,通常就是物体最终位置。
  4. 用这个新目标g'重新计算这条轨迹每一步的奖励。如果某一步到达了g'附近(距离小于阈值),奖励就变成0(或+1),于是这条轨迹里至少有一部分时间步变成了正样本。
  5. 把改写后的(state, action, new_reward, next_state, new_goal)作为新经验放进回放缓冲池,和原始轨迹的“失败经验”混在一起。
  6. 训练时随机抽取,算法每次更新都能同时看到失败样本和“成功样本”。

这里的关键点是:同一段物理过程,在原始目标视角下是一文不值的失败,在替代目标视角下却是实打实的成功。重标记不改变物理世界,只改变看待这段经历的角度。

2.2 为什么“假目标”能让模型真正进步

逻辑链条是这样的:策略网络会被“成功经验”推动,朝能够达成某种目标的方向前进。这些替代目标并不是从天上掉下来的,而是智能体自己实际走过的状态,所以它们是可达的。于是模型学到的东西就变成了“如何到达更多可达状态”,而不是“如何到达一个几乎不可能够到的状态”。

当模型能够到达越来越多的“近处”状态之后,再配合原始目标经验穿插训练,它就有更大的概率接近真正的目标。这就像一个学投篮的人,一开始对准三分线怎么都投不进,如果只看成功经验,他永远得不到反馈;但如果把“投到篮筐附近”也定义成一种成功,他至少能学会发力方向,然后逐渐扩大命中范围,最终摸到三分线。

更深一步看,HER相当于把每一条轨迹都变成了一个微型多目标数据集。原始目标视角下这条轨迹是负数样本,替代目标视角下它是正数样本。训练时buffer里正负样本都有,Q函数的估计会稳定很多。这个过程比单纯调奖励函数更灵活,因为它不改变环境给奖赏的逻辑,只是在训练数据层面增加了信号的密度。

2.3 HER能用的前提条件

HER不是万能的,上手之前建议先确认以下几条:

  • 任务必须是目标条件化的(goal-conditioned):策略网络的输入里要有目标g,而且目标要能映射到状态空间或状态空间的一部分。典型做法是观测向量里同时包含agent状态和goal状态,例如Fetch环境中观测是state和desired_goal拼接而成。
  • 奖励函数必须是“基于状态是否接近目标”的判定形式,通常是距离阈值判断。如果你的奖励是“完成一个翻转动作”或者“得到某个抽象结果”,压根无法用状态位置作为目标,HER就无从下手。
  • 算法必须是off-policy的。HER的根基是经验重放,需要算法能从历史buffer里反复抽取数据来更新策略。DQN、DDPG、SAC这类天然带replay buffer的算法都合适,PPO、A2C这类on-policy算法直接硬配HER基本是灾难。
  • 目标维度不要太高。bit-flip到20维还能跑,但如果目标是一个100维的向量,重标记后要学习的“达到目标”关系也会变得更难拟合,HER的效果会明显下降。

3. 实操落地:目标重标记策略与实现细节

3.1 四种替代目标选择策略

HER论文里给出了几种从轨迹中选择替代目标的方法,实践中用得最多的是下面四种,我用一个表格快速对比:

策略采样范围核心优点主要缺点推荐程度
final只取轨迹最后一个状态实现最简单,计算量最小只提供终点的目标信息,中间过程被浪费一般,适合快速验证
future当前时间步k之后随机取一个状态能给时间上靠前的状态提供丰富的后继目标,Q函数信号更连续实现稍微复杂一点强烈推荐,论文默认选项
episode从整条轨迹任意位置取一个状态全局视角,不依赖时间顺序会让“过去的状态”和“未来的达成”关联,有时逻辑上不够自然看情况用
random从环境中随机采一个状态理论上覆盖最广采到的状态大概率不可达,学习信号失真基本不用,实战效果差

final策略适合先跑通代码逻辑再考虑效果;真正调模型的时候,我默认都是future策略。原因是它保留了时间上的因果性——轨迹执行到第k步,后面发生的状态是“从这个位置出发可以到达的”,这种数据对策略学习最友好。

3.2 关键实现逻辑与伪代码

假设我们已经有了一个能输出的轨迹,每条经验包含状态、动作、奖励、下一状态、目标。HER的核心逻辑在往replay buffer里写数据这一层。下面这个伪代码可以直接照着改:

def add_her_transitions(episode, replay_buffer, her_replay_num, threshold): # episode是完整的轨迹数据 # 每条transition: (state, action, reward, next_state, achieved_goal, desired_goal) for t, transition in enumerate(episode): state, action, reward, next_state, achieved_goal = transition # 原始目标下的经验,必须保留 replay_buffer.add(state, action, reward, next_state, transition.desired_goal) # 生成her_replay_num条替代目标经验 for _ in range(her_replay_num): # future策略:从当前时间步之后随机选一个状态作为替代目标 future_idx = np.random.randint(t, len(episode)) new_goal = episode[future_idx].achieved_goal # 用新的目标重新计算这条transition的奖励 new_reward = compute_reward(achieved_goal, new_goal, threshold) # 0或-1 replay_buffer.add(state, action, new_reward, next_state, new_goal)

compute_reward是环境里给的判定函数,通常长这样:

def compute_reward(achieved_goal, desired_goal, threshold): dist = np.linalg.norm(achieved_goal - desired_goal) return 0.0 if dist < threshold else -1.0

这里有个非常容易写错的地方:用新目标重新计算奖励时,一定要用transition里记录的achieved_goal(当前实际到达位置)去和新目标做距离判断,而不是拿desired_goal去算。如果弄混了,生成的经验目标和新奖励对不上号,整个算法就废了。

3.3 超参数推荐与训练配置

我跑HER实验时常用的配置如下,你可以作为起点,然后根据任务调整:

参数名称推荐值说明
her_replay_num(K值)4每条轨迹额外生成的替代目标经验数量,太多会增加方差
replay buffer大小100万条transition环境复杂时越大越好
batch size256混合原始经验和HER经验采样
策略更新频率每个episode结束时更新若干次要和环境交互频率配合
探索噪声epsilon或高斯噪声,初始大一点HER本身不负责探索,还是需要噪声撑场
阈值根据环境调整,一般0.05判定“到达目标”的距离

retrain的时候,K值不是越大越好。我见过有人把K设到16,结果训练后期策略很怪,总往一些奇怪的中间状态跑。K=4是论文里常用的值,也是实测中比较稳的。buffer大小和batch size按你显卡内存和状态维度来,但这些是我做实验的基准。

4. 配合算法选型与调参经验

4.1 为什么HER必须搭配off-policy算法

这一点值得多说两句,因为它涉及对强化学习基础的理解。HER做的事情,本质上是对历史轨迹做二次加工。加工完的数据,需要被算法反复利用。DQN、DDPG、SAC这套体系天然维护一个很大的experience replay buffer,训练可以随时从buffer里抽样,所以HER经验能很自然地混进去。

on-policy算法的情况完全相反。PPO/A2C每次更新用的是当前策略采样的那批数据,要求数据分布和当前策略尽量一致。如果往PPO的数据里混入大量经过目标重标记的旧轨迹,策略更新方向会被这些“伪造目标”的经验带着走,而数据里的策略分布却不是当前分布——这会直接破坏on-policy算法的理论基础,效果大概率不升反降。

所以看到有人问“HER能不能配PPO”,答案通常是不建议。如果你真的需要,在on-policy框架下做her,本质是把问题转化成一个importance sampling问题,复杂度和收益不成正比。

4.2 常用算法组合与效果对比

我自己实际跑下来,最常用的组合是DDPG+HER和SAC+HER。DDPG+HER是论文原配,在MuJoCo的Fetch环境上跑起来效果非常稳定。SAC+HER的探索性更好,因为SAC有熵正则,配合稀疏奖励能多多少少缓解探索不足的问题,不过SAC的超参数比DDPG多,调起来更费劲。

离散环境我一般用DQN+HER。比如bit-flip任务,状态是比特向量,目标是另一个比特向量,动作就是翻转某个位。DQN+HER在这个任务上能见识到从“完全学不会”到“几十步内解决”的过程,而且代码比DDPG简单很多,非常适合做HER的入门实验。

下面这个是我在几个常见测试环境上跑的印象总结:

算法组合适用环境类型效果调参复杂度
DQN + HER离散状态、离散动作表现扎实低
DDPG + HER连续状态、连续动作论文标配,稳定性好中
SAC + HER连续状态、连续动作探索更强,但熵系数敏感高

4.3 如何判断HER是否真的起了作用

初学者容易犯一个错误:看到训练loss下降就觉得算法练好了。在HER里这不一定对。因为replay buffer里混了大量替代目标经验,Q值在替代目标上的预测会很快变小,但真实目标下的表现不一定变好。

我一般开两个监视指标:

  • 真实目标成功率:隔若干个环境交互周期,把当前策略跑几条真实目标的评估轨迹,看有多少成功的。这是唯一靠谱的“实力指标”。
  • buffer里正样本比例:正常情况下,加入HER后buffer里被标记为0奖励的正样本比例应该上升,说明重标记机制在产生有效学习信号。如果这个比例极低,说明替代目标生成策略有问题,或者阈值太严格。

训练时tensorboard记录这两个数,比盯着loss靠谱得多。如果真实目标成功率在稳步上升,说明HER的数据加工方向是对的;如果成功率长周期不动,就得回过去检查重标记逻辑和K值。

5. 踩坑实录与排查清单

5.1 我实际踩过的几个坑

第一个坑是用错目标来算新奖励。刚开始在FetchReach上跑,先写了HER的逻辑,但重标记奖励那行代码里不小心用了“原始目标”,导致新经验里的goal和新奖励不匹配。跑出来的现象特别诡异——Q值在下降,但真实目标成功率完全不动。排查了很久才发现,transition里保存的achieved_goal和desired_goal串了。建议在保存transition时就同时存这两个量,分别写清楚。

第二个坑是忘了保存原始经验。为了省内存,我一开始只往buffer里加替代目标经验。结果训练到后期,策略确实能到很多中间状态,但对原始目标的完成度很差。原因很简单——算法压根没见过几条“真正的成功经验”,它只知道去接近可达状态,不知道什么才是任务真正想要的。后来每条原始transition都保留,同时按K值生成替代经验,效果立刻正常。

第三个坑是K值开太大导致震荡。这也是我观察到的比较隐性的问题。K=4和K=8差别不大,但K=16之后训练曲线明显震荡。因为每一条原始经验被复制出16条不同目标的经验后,buffer里不同目标的经验分布极不均匀,Q函数的估计方差被放大。我自己现在一般固定在K=4,问题少很多。

5.2 问题排查速查表

现象可能原因排查方法解决办法
真实目标成功率一直为0替代目标生成逻辑没生效检查buffer里正样本占比确认compute_reward用的是achieved_goal算距离
Q值波动剧烈K值过大或batch size偏小记录Q值分布减小K到2~4,加大batch size
策略总去某些固定状态替代目标选的太集中打印替代目标分布future策略取多个不同位置,K=4
训练曲线和没加HER一样平奖励判定阈值过严打印距离分布放宽阈值,比如从0.05调到0.1
离线评估很差但训练指标很好指标选错了别用buffer里的奖励做评估独立跑真实目标评估轨迹

5.3 从小任务迁移到大环境的经验

如果你从来没跑过HER,建议不要直接往机械臂仿真环境上冲。先在bit-flip这种离散环境上把代码跑通,观察HER带来的成功率曲线变化。bit-flip环境里,你能非常直观地看到“加了HER之后,成功率从近乎零开始往上走”的过程,而且调试非常简单,一个纯Python循环就能写完整个环境。

然后切换到FetchReach这种连续动作仿真环境。此时要特别注意目标向量和观测向量的拼接方式,以及物理引擎的阈值设定。跑通之后,再考虑更复杂的环境,比如固定方块堆叠、双机械臂协同这类。

迁移一个环境到另一个环境时,最常改的就是阈值和动作空间范围。这两个参数直接影响compute_reward的判定,也是最容易出问题的地方。我的习惯是每换一个环境,先把环境自带的compute_reward打印出一组真实距离分布,看看目标附近距离大概在什么量级,再定阈值。

我个人在实际操作中的体会是,HER是一种“思路简单但是细节决定成败”的算法。它的思想本质上是把人类复盘学习的习惯翻译成了机器可执行的数据加工过程——失败的经历并没有被浪费,而是换了个目标之后继续有效。每次调参时看到真实目标成功率从0开始往上爬,我都觉得这个机制特别有意思:它并没有让环境变得更容易,只是让智能体学会从自己的每一次尝试里都拿走一点东西。

最后给你留一个小技巧:做HER实验时,把观测里的achieved_goal和desired_goal拆开来记录,分别存成两个字段。调试Q网络、分析奖励异常时,这个拆分能帮你省下一大堆翻代码的时间。这个习惯我从第一次跑HER一直保留到现在,每次重新捡起这类实验都靠它快速定位问题。

返回列表