1. 这到底是个什么问题:稀疏奖励下的强化学习困境
1.1 一个让智能体“无从学起”的典型场景
做强化学习的人都绕不开一个让人抓狂的局面:你搭好了环境,写好了网络,调好了超参数,智能体跑了一百万步,结果它的累计回报纹丝不动,跟死了一样。这时候大概率不是网络坏了,而是你撞上了稀疏奖励问题。
说一个我在机器人操作任务里最常见的例子。机械臂要抓取一个物体,放到目标位置。你给它的奖励很简单——只有当物体最终位置与目标位置的距离小于某个阈值(比如5厘米)时,奖励为1;其余所有时刻奖励都是0。这个设定非常符合直觉:任务成功就奖励,失败就不奖励。但对智能体来说,这几乎等于没有信号。因为动作空间是连续的,手臂姿态有几十维,物体初始位置是随机的,在这么高维的空间里瞎碰,碰到一次成功奖励的概率低到可以忽略。训练好几万回合,智能体可能连一次正样本都没见过,策略梯度算出来全是零,参数原地踏步。
换个角度来看,这就像让一个人在没有地图、没有指南针、也没有任何中途反馈的情况下,从一片沙漠里徒步找到一枚硬币。他可以一直走,但完全不知道自己在往哪儿走,甚至不知道自己是不是已经远离了目标。绝大多数人会在原地打转,最后饿死。传统强化学习在这种环境下的表现,跟这个绝望的旅人差不多。
1.2 为什么稀疏奖励会让普通经验回放失效
很多人以为稀疏奖励只是“难学”,但实际上它暴露了经验回放机制的深层缺陷。以DQN为例,它从过往转移样本(state, action, reward, next_state)中随机采样来打破时间相关性。每一段样本都携带一个即时奖励,如果这个奖励在绝大多数情况下都是0,那么采样得到的样本就有将近100%是“零奖励样本”。算法靠这些零奖励样本能学到什么呢?只能学到“做啥都一样没有回报”,价值函数对所有动作的估计都会趋向于均匀,无法区分好动作和差动作。
有人会问:那用成功轨迹来引导不就行了吗?问题在于稀疏奖励环境里,成功轨迹本身就极为稀缺,甚至根本不存在。你没法回放那些不存在的成功经验。所以普通经验回放需要的是“足够的正反馈密度”,而稀疏奖励恰恰切断了这个前提。于是我们经常看到算法在训练初期完全停滞,只有等到某个随机偶然事件撞上成功条件后,才开始慢慢利用那条轨迹学习,但这种运气出现的概率实在太低了。
这就是Hindsight(后见之明)要解决的核心矛盾:为什么非要让智能体靠“撞运气”才能获得训练信号?既然事后都知道目标没达到,那为什么不把“实际达成的状态”临时当作目标,让智能体从失败中也能学到怎么接近目标?这个思路很大胆,也直接改变了稀疏奖励问题的游戏规则。
2. Hindsight Experience Replay 核心思想拆解
2.1 “后见之明”怎么变成训练信号
Hindsight Experience Replay,简称HER,最早出现在OpenAI的一篇论文《Hindsight Experience Replay》中。这个标题本身就是个玩笑式的哲学命题:人总是事后聪明,失败之后才明白当初应该怎么做。那么,能不能把这种“事后聪明”形式化,变成强化学习的训练数据?
问题出在奖励函数只奖励“通向指定目标”的转移上。但在一个回合结束后,智能体虽然没能把物体放到原定目标位置,它却把物体放到了另一个位置——那个位置是它真正到达的。这句话乍一听像废话,但在强化学习里却价值连城:如果我们把那个实际到达的位置重新定义为本回合的目标,那么这个“失败轨迹”里的每一步转移,都变成了“朝着目标成功移动”的正样本。
比如,初始目标是把物体放到桌面坐标 (0.3, 0.5, 0.2),结果智能体把物体推到了 (0.4, 0.6, 0.1),距离原目标差了老远。按原目标算,整个回合奖励全是0。但如果我们暂时把目标替换成 (0.4, 0.6, 0.1),那么回看这一整条轨迹:每一步的状态、动作、新状态,是不是正在让物体越来越接近或通向最终那个位置?当然,由于轨迹是固定的,其中某些过渡可能不是严格单调接近,但大部分动作至少是合理动作序列的一部分,而且最终确实到达了那个位置。于是我们就获得了大量“看起来成功”的转移样本,奖励可以设置为正数或至少是带距离信息的稠密信号。
这样做的物理意义在于:一个失败回合实际上隐含着无数个成功的子问题。机械臂虽然没把物体放到目标A,但它成功地把物体带到了位置B。如果我们把这个“碰巧达成”的结果当作一个更简单的目标,那么这条轨迹就是一条完美的演示。这种思想类似于我们在人生中的经验总结:虽然没考上那所理想大学,但回头看,正是那段备考经历让自己掌握了高效学习方法,那么“掌握高效学习方法”这个目标其实是达成了。
2.2 从目标到假设目标:HER的关键一步
实现HER有个核心问题:怎么把“实际到达状态”替换成“假设目标”?这里要定义清楚状态与目标的表示。
在机器人操作这类带目标的任务中,环境通常提供一个观测状态 s,同时包含当前物体位置与目标位置。为了清晰,很多实现会把“要达到的目标”单独剥离开,比如状态 s 是物体当前的位置和速度,目标 g 是物体应处的位置。奖励函数定义为 r(s, a, g) = -[distance(s, g) < threshold ? 0 : 1](或者直接用负距离)。这属于“通用目标表示”的标准做法。
HER的巧妙之处在于:一次正常的采样回合中,我们会记录整条轨迹 {(s_0, a_0, g, r_0), (s_1, a_1, g, r_1), ..., (s_T, a_T, g, r_T)},其中 g 是原始目标。如果这个回合没有成功,我们不会直接丢弃,而是另选一个“替代目标” g',这个替代目标取自轨迹中某个时刻真实达到过的状态(比如最终状态)。然后我们重新计算轨迹中每一步的奖励:r'_t = R(s_t, a_t, g'),再把整个轨迹作为新的一批转移样本存入经验池。这样,一条失败轨迹就转化成了针对替代目标的完整成功或部分成功轨迹。
这个“重新标记”过程看起来只是改了奖励,但背后隐藏着数据分布的巨大改变。原本经验池里的样本基本都是“零奖励”的无效样本,现在却被大量带有不同目标的正负样本填充。而且这些目标是从实际可行状态中采样的,所以它们全部是“可达目标”。这比人为设置一些理论上可达但实际很难碰到的虚拟目标要高效得多。毕竟,如果假设目标不可达,那么基于它的样本依然全是失败的,又回到老路上了。
2.3 为什么HER能白捡这么多“成功经验”
直观上,HER把每一个失败回合都变成了“在某些替代目标上成功”的回合。如果每个回合长度为50步,那么原本50条零奖励的转移样本,被重新标记后,可能有很大一部分变成正奖励样本,因为有多种目标替换策略。更重要的是,它让智能体学会了一个泛化能力很强的策略:给定任意目标,它能够把物体推过去,尽管原目标还没学会,但“把物体推向某个位置”这种技能本身,在替代目标训练下会被不断强化。
这就像让一个学习成绩很差的学生做一套非常难的考卷,他全军覆没,考试分数是0。但如果把每道错题的目标改成“会做这道题”,然后让他反复练习这些错题,虽然他在原考试中依然会挂科,但他确实学会了做错题的能力。下一次遇到稍微容易一点的题,他可能就做对了。HER就是这种“以错题当教材”的机制。
从理论上看,HER本质上相当于一种隐式的奖励塑形(reward shaping)。它从轨迹中提取实际达成的状态,赋予其作为目标的高奖励,使得价值函数能够学习到“朝向广泛可达状态”的方向性梯度。与手工设计中间里程碑的奖励塑形相比,HER不需要人工指定子目标,而是自动挖掘可达的、真实的中间状态。正因如此,它能在完全没有领域知识的情况下,破解很多稀疏奖励的连续控制难题。
3. 上手实现HER:目标生成策略与伪代码
3.1 HER的完整算法流程(伪代码)
如果你已经跑过OpenAI Gym里的FetchReach-v1或者FetchPush-v1一类环境,你会立刻明白我在说什么。这些环境的奖励就是稀疏的:成功得0分(或1分),失败得-1。直接用DDPG训练,几乎不收敛。加上HER之后,通常几十万步就能看到明显进展。
我给出一个简化但可运行的HER训练循环伪代码,以离策略的DDPG为例,假设环境中每个回合长度为T:
# 伪代码:HER+DDPG训练循环 for epoch in range(num_epochs): episode_experiences = [] # 暂存整个回合 original_goal = env.sample_goal() obs = env.reset(goal=original_goal) for t in range(T): action = policy(obs) obs_next, reward, done, info = env.step(action) # 注意:原始奖励基于original_goal计算 episode_experiences.append((obs, action, reward, obs_next, original_goal, done)) obs = obs_next if done: break # HER重标记:把整个回合变成多条“假设目标”轨迹 her_episode = [] for transition in episode_experiences: # 根据选择的策略,生成替代目标 g' (见3.2) her_goal = sample_alternative_goal(transition, episode_experiences, strategy='future') obs, action, _, obs_next, _, done = transition # 用替代目标重算奖励 new_reward = compute_reward(obs_next, her_goal) her_episode.append((obs, action, new_reward, obs_next, her_goal, done)) # 把原始回合和HER回合都存入经验池 replay_buffer.add(episode_experiences) replay_buffer.add(her_episode) # 从经验池采样更新策略与价值网络 for _ in range(num_updates): batch = replay_buffer.sample(batch_size) update_ddpg(batch)这段伪代码省略了网络结构、噪声和优化器细节,但核心流程已经足够清楚。关键在于那个sample_alternative_goal函数,它的策略选择直接决定了HER是否能发挥威力。
3.2 四种目标替换策略的选择:final vs future vs episode vs random
OpenAI论文中对比了四种目标替换策略:
| 策略 | 做法 | 特点 |
|---|---|---|
| final | 用回合最终状态作为替代目标 | 最简单,整条轨迹共用一个替代目标 |
| future | 从当前时间步之后的状态中随机采一个作为替代目标 | 每个转移可对应不同目标,最常用 |
| episode | 从整个回合的所有状态中随机采一个 | 相对随机,可能包含过去的不可达状态 |
| random | 随机采样一个其他能力范围内的状态 | 不依赖当前轨迹,较少用 |
我的经验是,future策略在绝大多数任务上表现最稳定。原因有两层:从时间逻辑上说,当前转移之后达成的状态,才是“未来才知道”的后见之明;而从转移结构上说,用未来状态作为替代目标时,当前动作与目标达成之间的因果关系更强。假设t时刻物体在A点,动作把它推向B点,而在t+5时刻确实到达了B点,那么用B点作为目标,这个动作就被标记为“直接有用”。如果用episode策略采到一个过去状态作为目标,那么当前动作与那个过去状态之间可能毫无因果关系,会引入噪声。
具体实现future策略时,对于轨迹中第i步转移,我们从下标k∈[i+1, T]中均匀采样一个k,然后取出第k步的真实观测状态obs_k,从其中提取目标向量。这么做有一个细节:episode里每一步的观测都同时包含机械臂状态和物体位置,但我们通常只用物体位置部分作为目标。如果你的状态向量和目标是同一个东西,可能还需要做掩码处理。很多人在实现时忘了这一步,导致替代目标里包含了机械臂自身的状态,训练出来的策略非常奇怪。
3.3 与主流离策略算法结合(DQN、DDPG、TD3、SAC)
HER不是一个完整的学习算法,它更像一个经验预处理插件。它能搭配的算法必须满足一个条件:离策略(off-policy)。也就是说,算法需要能够从经验池里随机回放旧样本,而HER提供的正是大量“被修改过目标”的样本。如果是在策略算法(如PPO),则无法直接用,因为数据分布和当前策略不一致,强行使用会引入巨大偏差。
在连续控制场景,我推荐按以下组合:
- DDPG + HER:最经典的组合,实现简单,但DDPG对超参数敏感,容易发散。
- TD3 + HER:目前我最常用的组合。TD3在DDPG基础上加了双Q网络、延迟更新和目标策略平滑,稳定性好很多,配合HER能解决绝大多数稀疏奖励连续控制任务。
- SAC + HER:如果任务对探索要求高,SAC的熵正则能让智能体初期更“折腾”,但计算开销略大。SAC的自动温度调节也需要仔细调。
离散动作空间呢?如果环境状态是离散的(比如GridWorld),HER也能用,但意义会打折扣,因为离散动作空间本身可以通过大量随机探索来碰触成功,稀疏奖励问题没有连续空间那么严重。不过如果你有一个离散动作的机械臂环境想用HER,也不是不行,把目标替换逻辑照搬即可。
实操中,我还建议给HER搭配一个“混合奖励”技巧:如果替代目标与原目标非常接近,可以把原始目标也保留一部分样本。比如每条轨迹重标记8次,其中1次保留原始目标,其余7次用future策略替换。这能防止智能体彻底忘记最初的任务目标,毕竟我们真正要解决的是原目标,替代目标只是辅助。
4. 实操中的坑与调参经验
4.1 一个我看到过的失败案例:HER在FetchReach上不收敛
先讲一个真实翻车案例。有个朋友照着论文用HER训练FetchReach,这个任务的目标就是让机械臂末端到达某一点,相对简单。他跑了50万步,成功率仍然是0。检查代码后发现两个致命问题:
第一,他重标记后把原样本丢了,只保存HER样本。这导致目标分布过度集中在“高频到达状态”,策略逐渐忘记了原始任务的目标分布。在FetchReach里,原始目标采样范围比实际到达范围广很多,一旦只学替代目标,智能体只会往几个它常去的地方凑,再也学不会去其他目标点。
第二,他计算rewards时用了原来的目标变量。重标记后,new_reward = env.compute_reward(obs_next, original_goal),而不是obs_next和her_goal。这个bug非常隐蔽,因为代码看起来“差不多”,跑起来也能正常保存样本,但所有样本的奖励依然都是稀疏的,等于HER完全没生效。这个错误值得记住:重标记之后,奖励必须基于替代目标重新计算,不能复用原奖励。
解决方式很简单:保存样本时把original_goal和her_goal都存进buffer项,更新时分别取出;或者每条转移单独封装成一个包含目标字段的对象。归根结底是保持“状态-动作-目标”三元组的一致性。
4.2 超参数选择与实验心得
HER引入了几个关键超参数:每回合重标记的替代目标数量k,future策略的采样范围,以及经验池大小。
关于k,OpenAI论文中用了k=4(即每条轨迹额外生成4个HER样本)。我试过从1到8不同的值,发现k太小(1)效果提升有限,k太大(8)会让训练变慢且目标分布过于集中。k=4是一个性价比很好的起点。如果任务非常难,我会先加大到8看看梯度是否更稳,如果效果不明显再调回4,而不是盲目增加k。
经验池容量也很关键。HER会产生大量样本,如果池子太小,老样本被踢出得太快,新样本又偏向某些替代目标,策略就会震荡。我通常把经验池容量设为100万到200万条转移,对于Fetch类任务已经足够。如果你的显存和内存紧张,可以适当减少,但不要低于50万。
训练回合的长度也要注意。HER的前提是轨迹中包含了“可达的真实状态”。如果回合太短,机械臂还没来得及移动到任何有意思的地方,替代目标几乎都在起点附近,采样到的目标多样性就差了。通常我会把最大回合步数设置得比任务实际完成所需步数稍长,比如FetchPickAndPlace原版设置是50步,这个长度经过验证比较合理。
4.3 排查技巧速查表
在调HER时,我整理过一张速查表,每当训练效果不对,就按表逐项检查:
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 训练前50万步成功率始终为0 | 重标记后没有重算奖励 | 打印几个样本,核对奖励值是否出现非稀疏值 |
| 策略学会去“原地刷分” | 替代目标全是轨迹终点,智能体只需维持不动 | 改用future策略,并保证目标分布覆盖起始附近状态 |
| 训练后期性能下降 | 经验池中原始目标样本不足 | 每条轨迹保留1条原始目标样本,或单独提高原样本采样权重 |
| 多个随机种子表现差异巨大 | 探索噪声过大或过小 | 把高斯噪声标准差调到0.2,结合目标策略平滑调参 |
| HER收益不明显 | 奖励阈值设置太苛刻 | 把阈值调松一些(如距离<0.1),先验证HER有效再收紧 |
| 与SAC搭配时熵系数崩溃 | 自动温度调节与HER目标重标记不兼容 | 手动固定熵系数为0.2,观察后再调 |
除此之外,还有一个容易忽略的点:HER的替代目标不要选“还没被探索到的真空状态”。从轨迹中选目标是安全的,因为那些状态是真实访问过的。如果自己用生成器伪造一些“可能的目标”塞进经验池,可能引入大量不可达样本,导致价值函数高估异常。所以,千万别自作聪明去扩展目标分布,老老实实用轨迹内状态。
最后说一句我对HER的理解。它本质上是一种“事后辩解”式学习——不是让智能体学会如果失败就假装成功,而是让智能体从一个已经发生的结果中,重新构建出真实可达的、更有用的训练目标。这种思路在很多领域都有共鸣:复盘一次事故、回顾一段错过的机会,都能提炼出比当时成功路径更丰富的信息。HER把这个思想落到了强化学习的经验池里,让机器不仅仅从成功中学习,更从失败和偏差中挖掘价值。如果你正在被稀疏奖励折磨,我建议不要急着设计一堆复杂的奖励函数,先给现有的离策略算法加上HER试试,它往往比想象的更管用。