☰
强化学习稀疏奖励难题:HER事后经验回放原理与实战
2026/10/1 22:26:48 网站建设 项目流程

如果你训练过一个机械臂抓取任务,大概率经历过这种绝望:智能体在环境里乱动了几万个step,reward始终是-1,没有任何一个动作能碰到目标物体。不是网络容量不够,也不是学习率调错,而是奖励函数给的有效信号太少——这就是强化学习里最让人头疼的稀疏奖励问题。Hindsight Experience Replay(HER,事后经验回放)是OpenAI提出的解决思路,核心思想就一句话:既然这次"没抓到"目标,那不妨把实际到达的状态当作目标,让智能体学到"如何到达那里",下一次才有希望真正抓到目标。这篇文章,我会从原理到代码把HER讲透,并分享我在复现过程中的完整踩坑记录。适合正在啃稀疏奖励问题、或者已经在跑DDPG/TD3但卡在收敛上的同学参考。

1. 稀疏奖励面前,普通的经验回放为什么不够用

1.1 一个让人崩溃的训练场景:机械臂抓取

先还原一个典型场景。你用的是OpenAI Gym里的FetchReach环境,任务是一台七自由度机械臂,需要把末端执行器移动到某个随机目标位置。环境给了一个稀疏奖励:如果末端到目标的距离小于阈值,reward=0,否则reward=-1。你觉得自己配置得很合理:网络是三层MLP,优化器是Adam,探索噪声是高斯噪声,replay buffer能存一百万条transition。结果呢?训练跑了几万个episode,success rate依然是0。

很多新手这时候会怀疑是不是网络写错了,或者是reward计算有bug。我最初也这么想,把环境里的reward打出来看了半天,发现确实是-1——但问题恰恰就出在"确实是-1"上。智能体在没有任何中间奖励引导的情况下,纯靠随机探索去逼近一个三维空间里的随机点,概率低得可以忽略。你换个思路想想:一个盲人在一个巨大的体育馆里要找一颗特定位置的乒乓球,没有任何声音和触觉提示,他凭什么能找得到?随机探索在连续高维动作空间面前,就是这么无能为力。

1.2 奖励稀疏到底"稀疏"在哪

稀疏奖励的本质是:在你完成任务之前,所有transition给出的reward都完全相同。这意味着replay buffer里存了海量的"(状态,动作,-1,下一状态)",这些样本对价值函数的梯度几乎不提供任何有效指引。Q-learning收到负奖励后只会把这条轨迹的Q值压低,但压低之后呢?它不知道往哪个方向才是对的,因为所有的采样方向都同样"不坏也不对"。

更麻烦的是,如果你尝试用reward shaping来解决,比如把奖励改成末端位置和目标之间的负距离,马上会遇到新的问题:怎么设计这个距离函数才算合理?用欧氏距离还是每个关节的加权距离?要不要对接近目标的行为单独加分?这些人工设计在仿真环境里还能凑合,一旦换到真实机器人或者更复杂的操作任务,你根本没法写出一个既能引导探索、又不至于让智能体钻漏洞的奖励函数。HER的思路就是绕开这个设计难题:不去改奖励,而是改训练样本本身。

1.3 随机探索的低效,光靠加大探索噪声也救不回来

也有人说,那我加大探索噪声σ,让智能体多乱跑一跑,总有一半概率能碰上目标吧?实测并没有用。原因有两个:第一,动作空间的维度和状态空间的维度往往不一样,噪声在高维动作空间里叠加之后,终端状态在目标空间里的分布依然是弥散的;第二,你加大噪声的同时也会破坏已经学到的策略,success rate即使偶尔上升一点,很快又会掉回来。我试过把σ从0.1调到0.5,FetchReach的success rate从0变成了偶尔冒一次0.1,但训练曲线震荡得像心电图,最后也没突破0.3。

这其实暴露了一个深层问题:在稀疏奖励下,你需要的是"如何从失败中学到东西",而不是"如何把失败变成成功"。从失败中学习,这件事人类天生就会——我们打篮球没投进,至少知道了"这个力度偏大还是偏小";但强化学习智能体拿到失败样本后,只会把它当作一次纯粹的负样本丢掉。HER做的,正是把失败的样本改造成有用的正样本。

2. HER的核心思想:把"没做到的目标"当作"做到了的目标"来学

2.1 事后重新标注目标

HER的全名已经说得很直白:Hindsight Experience Replay,经验回放时带着"事后眼光"去看。具体做法并不复杂。在goal-based RL框架里,一条transition长这样:

状态s_t、动作a_t、奖励r_t、下一状态s_{t+1}、目标g

按照常规做法,这条样本会被存进buffer,然后被用来更新Q函数。HER在存样本时额外做了一步操作:以一定概率把这条transition里的目标g换成另一个目标g',并且g'取自这条轨迹未来某个时刻真实到达的状态对应的目标分量。因为新的目标已经达成了,r_t就不再是-1,而是0。于是你得到了一条新样本:

状态s_t、动作a_t、奖励0、下一状态s_{t+1}、新目标g'

这条经过"篡改"的样本,本质上在告诉智能体:你现在这个动作,在达成另一个目标时是有用的。虽然它没帮助你达成原来的目标,但它帮助理解"什么样的状态转移能够产生回报"。日积月累,buffer里就会充满大量奖励为0的有效样本,价值函数的梯度终于有了前进方向。

2.2 为什么"事后诸葛亮"反而能提升数据效率

这里最容易让人绕不过弯的是:你改出来的新目标并不是智能体最初真正要完成的目标,这算什么学习?拿推箱子举例就更清楚了。一开始你想把箱子推到位置A,结果箱子被推到了位置B,任务失败,reward = -1。如果没有HER,这条轨迹直接扔掉,智能体只得到一个"没推到位"的模糊反馈。有了HER,你把"推到位置A"这个目标改成"推到位置B",把这条轨迹变成一条成功轨迹,reward = 0,存储下来。下次让智能体推箱子到B位置的时候,它发现之前有一条经验恰好成功过,这条经验就能用来更新策略。

所以HER做的事情不是让智能体忘记原目标,而是让智能体理解"状态空间里有很多目标,某些我已经接近过、达成过"。一个能够把箱子推到B的智能体,至少知道如何操作机械臂、如何施加力、如何调整抓取姿态——这些都和推到A共享大部分底层技能。用课程学习的视角理解:HER相当于在每个episode都创造了一门"降低难度的课程",你永远在学一个"已经不小心完成的任务",再逐步逼近真正的目标。

2.3 一个在论文里容易被忽略的关键:状态和目标的分量映射

HER能成立有一个前提:目标g必须可以从状态s_t里提取出来,而且大多数情况下,目标的维度只占状态的一部分。比如FetchReach环境里,状态是一个25维向量,包含机械臂各关节角度、末端位置、物体位置等,而目标g只是一个3维向量,对应期望的末端位置。重标记目标时,你从s_{t+1}的对应分量里取3个数字出来,当作新的g',这没问题。

但如果你手里的环境状态和目标是割裂的,比如目标是"红色物体的位置",而状态向量里根本没有红色物体的坐标,那HER就没法直接使用。我见过有人拿到自定义环境后直接把整个状态s_{t+1}当新目标存进去,结果训练爆炸,因为目标维度和状态结构根本不匹配。正确的做法是建立一份"状态分量索引表",明确每一块维度对应的物理含义,然后只替换目标相关的分量。

3. 用OpenAI Baselines把HER跑起来的完整实操

3.1 环境准备与依赖安装

虽然OpenAI后来维护了很多新工具,但baselines仓库里的her实验依然是最容易跑通的参考实现。老仓库是用TensorFlow 1.x写的,安装的时候有几个坑需要注意。我建议用conda单独建环境,不要直接往你的主力环境里装,因为tensorflow1和现在的很多包会冲突。

conda create -n her_env python=3.6 conda activate her_env pip install tensorflow-gpu==1.14.0 pip install gym==0.15.4 matplotlib git clone https://github.com/openai/baselines.git cd baselines pip install -e .

baselines依赖包版本锁定做得一般,装上mujoco会比较麻烦。如果你没有MuJoCo授权,可以先跑FetchReach这类需要mujoco_py的环境之外的任务做测试;如果你有gym的robosuite或者其他自定义gym环境,其实也可以直接把HER的算法类单独拖出来,不一定非要完整编译baselines。

3.2 一份能复现结果的超参数配置

baselines的her默认入口是run.py,但直接跑会使用默认参数。HER论文里对FetchReach给了一组可以复现的配置,我把它整理在下面,同时标注了每个参数的作用。

参数数值作用与我的理解
n_epochs50训练轮数,每轮包含BC个episode
num_cpu19并行环境数,和num_envs配合使用
n_envs19同时采样环境的数量,采样量不够时success率会很飘
buffer_size1000000replay buffer大小,太大影响采样速度,太小存不下充分样本
batch_size256每次梯度更新的样本量
gamma0.95折扣因子,HER论文里用的是0.95而不是0.99
reward_typesparse这一项对应环境内部的稀疏奖励开关
n_cycles50每个epoch内的cycle数
rollout_batch_size2每个cycle采样几个transition

跑的时候命令大概是:

python -m baselines.her.experiment.train --env FetchReach-v1 \ --num_cpu 19 \ --num_envs 19 \ --n_epochs 50 \ --reward_type sparse

如果机器核心数没这么多,可以把num_cpu降下来,但一定要保证num_envs == num_cpu并开启多个并行worker,否则采样速度会拖垮整个训练流程。baselines在启动时会给每个worker分配独立的随机种子,这也是我后来才发现的一个细节:如果不设置seed,多次跑的success率曲线差异可能很大。

3.3 训练过程中的关键观察指标

HER训练过程中最重要的指标不是loss净值,而是success rate和平均回报。baselines会周期性evaluate当前策略并打印出来,你需要关注的是这两行:

Epoch: 20, success_rate: 0.43, mean_episode_reward: -0.12 Epoch: 35, success_rate: 0.71, mean_episode_reward: -0.05

我的经验是,FetchReach这种单目标点任务在20个epoch以内应该能冲到0.4以上,50个epoch左右稳定在0.8-0.9是正常的。如果你的success rate在20个epoch后仍然为0,参考下一节的排查思路。critic loss本身也会下降,但下降不能代表策略好,很多时候critic都学会了预测奖励而actor还是乱的,所以别盯着loss看。

4. 我在复现HER实验时踩过的坑

4.1 稀疏奖励下网络不收敛:从loss到数据的完整排查链路

有一段时间我换到自己的自定义环境,success rate持续为零。当时第一反应是"算法实现有问题",于是去翻baselines源码,几乎把整个her策略类读了一遍,也没找到问题。后来我改成逐步排查,这条链路对我后来的排错特别有用。

第一步先看replay buffer里的数据。HER不过是一个数据改造方法,如果存进去的样本本身不合理,后面全都白搭。我把每条transition打出来后,发现一个问题:我定义的目标是一整块"目标物体最终位置",但我的state分量里同时包含物体当前位置和物体速度。重标记时我直接把s_{t+1}里"物体当前位置"做成新目标,这没问题;但奖励函数仍然在判断"当前物体位置是否等于原目标",互相错位了。也就是说,新目标g'的设计要考虑奖励的判定口径。

第二步看actor和critic的loss比例。如果critic的loss下降很快,但actor的loss没有变化,往往说明actor的梯度被"目标状态和值函数之间的梯度"断层挡住了。这时候优先检查输入归一化。HER的DDPG实现里,状态和目标都经历了归一化,但如果你自己加了一个额外输入,忘做归一化,训练就会不稳。

第三步看探索噪声的尺度。DDPG在稀疏奖励下对噪声方差极其敏感。噪声太小,几乎不探索;噪声太大,策略震荡,success rate上来又掉下去。FetchReach里默认的噪声方差表可以参考baselines,但换环境时必须重新标定,不是拿来就能用。

4.2 目标重标记的策略选择:future比final强在哪

HER论文里对比了四种目标重标记方式:final、episode、random、future。我一开始用的是final,只把每条episode最后到达的状态当新目标,结果success率比future低了将近一半。future的策略是:在这条轨迹当前时刻往后的状态中,随机挑一个作为新目标。为什么future更好?因为final只提供"最终状态的信息",而future提供"沿途状态的信息",对学习中间阶段的技能更有帮助。

具体到实现里,baselines的her有专门的sample_goals函数,你需要关注两个参数:future_k和概率p。默认配置里future_k=4,表示在未来4个step中随机挑一个作为目标。我在实际测试中发现,future_k=4对这个任务比较合适;如果设得太大,会造成新目标严重偏离当前状态,反而变成一种"伪成功",不利于学习。

4.3 on-policy算法和off-policy算法的选择误区

网上有不少人说"HER可以配合PPO一起用",理论上没有错,但实操中是自找麻烦。HER的核心价值在于可以反复使用旧的失败经验,这需要一个大容量的replay buffer去存储这些历史样本;而PPO这类on-policy算法,每一轮更新后都会把旧数据丢掉,学到的"失败经验"根本没有累积的机会。你硬把它接上去,等于一边往桶里倒水,一边把桶漏水,最终数据效率还不如原版PPO。

我明确推荐你把HER和off-policy算法一起用:DDPG是baselines里默认的,TD3和SAC也可以。TD3对超参数更鲁棒,适合你不想反复调参的时候;SAC本身对探索有额外机制,配合HER在部分环境里效果也不错。但如果只是为了复现论文和做对比实验,先用DDPG跑通再说,不要一上来就上更复杂的算法。

5. HER之外:这项思想还能往哪里延伸

5.1 从机械臂到更多任务

HER虽然是在机器人操作任务上提出的,但"重新标注目标"的思想完全可以迁移。只要任务能写成"状态-目标对"的形式,HER就有一席之地。比如自动驾驶中的"到达某个位姿"、游戏里的"走到某个房间"、推荐系统里的"命中某个候选集",本质上都是goal-based回合任务。我后来在一个物流分拣模拟任务里也用过类似思路:把"没分拣到目标组"的样本改成"分拣到实际组"的奖励为0的样本,训练效率提升非常明显。

不过要提醒一句:HER适合那些目标可达性较强的任务。如果目标空间里存在大量不可达区域,重标记出来的"新目标"往往也是不可达的,这种假成功样本反而会误导策略。我遇到过一个任务是让智能体推动一个铰链结构到特定角度,有些目标角度在物理上根本不可能实现,HER在这种环境里几乎没有增益。

5.2 从算法层面看HER的实际工程取舍

回到工程视角,HER不是一个需要巨大算力才能跑的算法。它最大的成本在存储和采样:如果你用一个58维状态的目标空间,重标记后的样本数量会翻倍,buffer读取和采样压力随之上升。但相比reward shaping的人工调参成本,这点算力开销非常划算。

另外一个容易被忽视的点是:HER并不会自动解决所有样本效率问题,它解决的是"目标设定困难"这个环节。如果你的任务连目标都定义不清楚,或者动作空间本身探索难度极高,HER的效果依然有限。我在实际操作中更愿意把HER当作一个"训练加速器"而不是"银弹":先用HER把整个训练流程跑顺,再逐步引入更细致的奖励设计。

5.3 一个小技巧:用自定义环境快速验证HER是否适合你的任务

如果你有一个新的goal-based任务,不确定HER能不能帮上忙,我建议你先做一个快速验证:把环境的初始状态随机化,让目标也随机化,然后用baselines的默认DDPG+HER跑10个epoch。如果这10个epoch里success rate从0开始出现明显上涨,说明任务本身是HER友好的;如果纹丝不动,再去找任务定义里的问题。跑一次只需要两三个小时,远比你去猜"到底哪里不收敛"高效得多。

我在几个任务里反复验证下来,HER在机械臂类、导航类任务上几乎是最省心的基线方案。至少在未来的很长一段时间里,我个人的项目默认配置都会带上HER,只有当实验明确要求on-policy对比时,才会把它关掉。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询