ARTICLE DETAIL

资讯详情

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

稀疏奖励难倒强化学习?HER后见经验回放算法从失败中学习

稀疏奖励难倒强化学习?HER后见经验回放算法从失败中学习 “hindsight”这个词我初次见到是在一篇强化学习论文的标题里。当时第一反应是这大概讲的是“事后复盘”之类的经验总结等到读完正文才发现这是 OpenAI 在 2017 年提出的一个相当有名的算法——Hindsight Experience Replay后见经验回放简称 HER。乍一听名字有点抽象但它的思想特别朴素让智能体从失败中学到东西而不是只从成功中学习。这篇博文我会从算法动机讲到数学细节再带你用一个可落地的例子跑通 HER中间穿插我实际调参踩过的坑。如果你是做强化学习、机器人控制或者对“稀疏奖励”问题头疼这篇应该对你有用。1.1 核心问题“稀疏奖励”为什么难倒了一批算法在强化学习的常规设定里智能体通过最大化累积奖励来学习策略。如果你的奖励函数设计得很密比如每走一步都有反馈那问题相对友好但真实任务往往不是这样。机器人要抓起一个物体、机械臂要推到指定位置、自动驾驶要完成一次变道——这些任务的特点是大部分尝试都得不到任何正反馈直到最后一刻成功才给1分其余时间全是0。这个现象在学术圈叫“稀疏奖励”sparse reward。稀疏奖励的直接后果是算法几乎学不动。原因很简单在没有奖励信号的地方策略梯度算出来要么是0要么是纯噪声。智能体在原地随机探索了半天唯一得到的结论是“没好处”它既不知道该保留哪些动作也不知道该改进哪些动作。这就像你让一个小孩拼一千块拼图不给他参考图也不告诉他拼得对不对只在他完整拼好的那一瞬间表扬一句——正常孩子早就放弃了。我最早踩到这个坑是在用 DDPG 做机械臂推动任务的时候。环境是 OpenAI Gym 的 FetchPickAndPlace目标是把桌子上的方块推到某个随机位置。我满心以为 DDPG 这种知名算法总该自己搞定吧结果训练了两百万步成功率始终是0%。那时候我意识到论文里那些漂亮的收敛曲线背后几乎都藏着一个精细设计过的奖励函数或者某种聪明的探索机制而稀疏奖励本身是一个相当硬的坎。1.2 硬凑奖励函数治标不治本面对稀疏奖励很多工程师的第一反应是手动设计“中间奖励”。比如机械臂靠近目标给0.1分、触碰方块给0.3分、推到位给1分。这种做法看起来合理实际上问题很多。第一你得对任务有极强的先验理解才知道中间哪些状态是值得奖励的第二设计出来的奖励函数很容易被智能体钻空子它可能学会反复触碰方块骗分而不是真的把方块推到位第三换一个任务场景所有中间奖励都要重新设计这套思路毫无泛化能力。还有一种常见做法是“课程学习”curriculum learning先让智能体学简单版任务再逐步加大难度。这个思路本身没问题但难点在于“怎么定义难度梯度”而且要保证智能体不会“忘掉”旧技能。HER 走了一条完全不同的路不改奖励函数也不设计课程而是把失败经历重新解释成成功经历。这个想法本质上是一个脑筋急转弯——你不需要改变已经发生过的事实只需要改变“目标”这个标签。2. HER 的核心思路换个角度看失败2.1 一个生活化的类比盲人摸象式的“重新定向”假设你要教一个人射箭十次里有九次脱靶。普通算法只会告诉他“你失败了九次继续蒙吧。”HER 的做法是把脱靶的落点当作新的靶心然后宣布你这次完美命中。听起来像自欺欺人对吧但仔细一想这里面有真实的信息量——智能体的每一步动作其实都在试图接近某个“状态”只是不是它最初想要的那个目标状态。既然这串动作能成功抵达 A 点说明这串动作对目标 A 来说就是一次成功轨迹。放到强化学习的框架里我们把这个狡猾的思路翻译成算法术语对于每个 episode除了原本要完成的目标 g我们另外构造几个“虚拟目标” g这些虚拟目标就是该 episode 中实际到达过的状态。然后把这条轨迹的奖励函数从“是否到达 g”替换成“是否到达 g”。替换完之后原本全是0的奖励序列里出现了1原本没有学习信号的轨迹变成了有监督信号智能体就能从中学到“原来这样做能靠近这个状态”。这里最妙的一点是它不需要任何额外的环境交互不增加采样成本只改 replay buffer 里的数据格式。这也是 HER 能被广泛应用的根本原因——它几乎不改变训练流程只是把经验数据“处理”了一下。2.2 从“目标”到“状态”为什么重标注是成立的有的朋友可能会疑惑把目标换成终点状态这真的能让智能体学会最初的任务吗答案是能原因需要从“目标条件化强化学习”Goal-conditioned RL的角度理解。在 Goal-conditioned RL 里价值函数 V(s, g) 和策略 π(a|s, g) 都是以“当前状态 s 和目标 g”为输入的。也就是说智能体学到的不是一个单一任务的策略而是一族策略给定任意目标它都知道该往哪走。理论依据是Universal Value Function ApproximatorsUVFA只要用一个足够强大的函数近似器同时拟合多个目标的 V 函数这些目标之间的价值函数就能互相迁移。所以当 HER 用虚拟目标 g 去训练时其实是在丰富 V 函数对“不同目标”的覆盖范围。等到真正需要达成 g 的时候智能体虽然没见过 g 对应的成功轨迹但它见过很多与 g 很相似的 gV(s, g) 可以通过泛化得到合理的估计。这个逻辑说起来有点绕当年我看论文时也琢磨了很久。后来我给自己编了个口诀“HER 等于把一条失败轨迹在目标维度上平移到它本来就成功的位置。”一旦想通这一层再看后面的公式和代码就顺多了。2.3 伪代码HER 到底改了训练循环里的哪一行用伪代码来看 HER 的插入位置非常清楚。以下是在 DDPG 基础上加入 HER 的典型训练流程# 一次环境的完整交互 for episode in range(num_episodes): # 采样一个目标比如 Fetch 环境里的“目标位置” g sample_goal() # 执行 rollout记录每一步的 state、action、reward、next_state episode_buffer [] state env.reset() for t in range(max_steps): action actor(state, g) noise next_state, _, done, info env.step(action) reward compute_reward(next_state, g) # 稀疏奖励达到 g 为 0否则 -1 episode_buffer.append((state, action, next_state, g)) state next_state if done: break # HER 核心把 episode 里的经验用虚拟目标重新标注一遍 for transition in episode_buffer: # transition 原本的目标是 g此刻我们选择一个新的目标 g g_prime sample_her_goal(episode_buffer, transition) transition_her (transition.state, transition.action, transition.next_state, g_prime) replay_buffer.add(transition_her) # 同时保留原始经验防止完全丢掉原始目标的信号 replay_buffer.add(transition) # 常规 off-policy 更新 for _ in range(update_steps): batch replay_buffer.sample(batch_size) critic_loss, actor_loss update_ddpg(batch)这段伪代码里最关键的是sample_her_goal()函数。它决定了一个 episode 里的哪些状态可以当作虚拟目标。最常见的实现是从当前时间步之后的所有状态里随机挑一个这样可以保证虚拟目标对应的轨迹是“因果正确”的——你不可能用未来的状态去解释过去的动作未来的状态用于重标注没问题但迭代到过去时目标必须是当时之后的状态。我在实际项目里就是这么实现的效果非常稳定。大批次训练时HER 版本比原始 DDPG 提前了至少三倍收敛有时候甚至从“完全不收敛”变成“稳定收敛”。3. 四种重标注策略选对才是关键3.1 一张表看清四种策略HER 论文里给出了四种从 episode 中选取虚拟目标的方式实际效果差别很大。我用一个表格整理出来方便你对比策略采样范围特点适用场景final只取 episode 的最终状态最稳定偏向于“能走到哪就算哪”训练前期效果好适用于目标空间连续、状态维度低的任务future取当前时间步之后某时刻的状态额外采样 k 个最常用兼顾了短时可达与长时迁移论文默认也是它推荐默认选择episode从同一个 episode 内随机采样任意状态样本来源丰富但可能引入不合适的远期目标目标空间简单、episode 较长时可用random从所有历史状态中随机采样可能会破坏因果性一般不单独用与其它策略混合时才会用我个人的经验是future 策略 k4 是最稳的起点。k 表示每个 transition 额外生成几个虚拟目标。论文里 k4 是经过网格搜索的结论我复现的时候发现 k4 比 k1 大概提升了 5~8% 的最终成功率而 k 增加到 8 之后收益就明显边际递减了反而增加存储和计算开销。原因可能是k 太大意味着同一批 transition 被复制太多份导致训练分布过于偏向虚拟目标真实的原始目标信号被稀释。3.2 为什么 future 比 final 更有潜力你可以把 final 理解成“只看结局”而 future 是“沿途的每一步都在伸手够前面的状态”。final 的优势是因果明确、噪声少但它有一个明显的天花板——如果这个 episode 的表现很差比如机器人一开始就卡在角落里那么把最终状态当成目标学到的策略也只是“往角落里凑”对真正的目标任务贡献非常有限。future 不要求虚拟目标必须是最后的落点它更激进一点随机挑选 episode 后面时刻的状态当作目标这样同一段轨迹可以“分拆”出多个不同难度的小目标。比如机械臂从 A 点运动到 B 点B 点可以是最终目标 g 附近的一个中间状态。智能体在这一条轨迹里就能同时学到“去 B 点”和“从 B 点再往前一点”两个层次的控制信号。这在复杂任务里相当于隐式地构建了一条课程路径这就是为什么 future 在绝大多数任务上的表现都优于 final。3.3 重标注不该丢原始经验这里有个很容易忽略的细节重标注后的经验不能完全替代原始经验。如果你把所有 transition 的目标都换成虚拟目标原始目标的信号就彻底消失了相当于放弃了对真正目标的直接学习。正确的做法是“两碗都端”——原始经验放一份重标注经验放一份。这也是我上面伪代码里同时add(transition_her)和add(transition)的原因。放两份之后replay buffer 里虚拟目标的样本在比例上会显著超过原始目标样本。我统计过当 k4 时buffer 里大约有 5/6 的经验是虚拟目标的原始目标只占 1/6。这个比例看起来失衡但论文和实验都表明这实际上对训练有利——因为原始目标样本几乎全是“失败的负样本”它们的价值就在于“不要靠近错误方向”而虚拟目标样本才能提供丰富的正梯度。两者形成互补。4. 实操复现用 HER 解决 FetchPush 任务光说理论没意思我带着你完整跑一个实验。环境用 OpenAI Gym 里的FetchPush-v1或者新版 API 下的FetchPush任务是把方块推到目标位置。这个任务在稀疏奖励设定下DDPG 几乎从头到尾都是0而加上 HER 之后能在几百万步内达到 70%~90% 成功率特别适合做对比实验。4.1 环境配置与依赖我是用 Python 3.8 PyTorch 1.12 复现的。MuJoCo 环境需要处理 gym 环境注册问题我用的是 gymnasium 兼容库建议直接装gymnasium-robotics版本它内部对 Fetch 系列做了迁移适配省了很多麻烦。这里需要注意FetchPush-v1的观测空间是 dict 结构包含observation机械臂自身状态、desired_goal目标位置、achieved_goal当前方块位置。HER 重标注时主要用achieved_goal来构造虚拟目标。4.2 网络结构一个简单的 MLP 就够在 DDPG HER 的框架里actor 和 critic 就是两个全连接网络不需要花哨的结构。我用的配置如下actor MLP(state_dim goal_dim, action_dim, hidden[256, 256], output_activationtanh) critic MLP(state_dim goal_dim action_dim, 1, hidden[256, 256], output_activationNone)注意 critic 的输入是“拼接后的状态目标动作”不是分开塔式结构。这一步虽然简单但直接影响训练稳定性。很多复现教程忽略这一点用两个编码器分别编码状态和目标再特征拼接在我的实验中效果比直接拼接差不少。直接拼接虽然看起来暴力但能让保证梯度的信息流更畅通而不是经过编码器“压缩变形”。4.3 一组稳定复现的超参数我把我自己复现时经过调参验证的一组稳定参数放在下面可以直接抄作业。但要注意这组参数针对的是FetchPush任务换成别的任务可能略有出入。参数取值说明replay buffer size1_000_000虚拟目标会增加样本量buffer 尽量大actor 学习率1e-3太高容易崩太低收敛慢critic 学习率1e-3与 actor 保持一致gamma0.98稀疏奖励环境下衰减要设得保守一点tau (软更新率)0.05DDPG 目标网络更新系数稍大一点更稳batch size256虚拟目标样本多batch 太小梯度不稳定noise 标准差0.2探索噪声训练最后阶段可以降到 0.05HER 重标注策略futurek4训练步数2_500_000FetchPush 大概需要这个量级才能看到明显收敛关于 gamma 这里多说一句。稀疏奖励下目标是否达到的判定通常只有 0/1 两种如果 gamma 设成 0.99遥远未来的成功信号会被指数衰减掉反而体现不出长期规划的价值。我试过把 gamma 从 0.98 改成 0.95最终成功率下降明显而 0.98 和 0.99 之间差距不大所以我推荐 0.98 作为通用起点。4.4 训练中我最关注的三条曲线训练过程中我基本只看三条曲线epoch 平均成功率、critic loss、Q 值估计。前两条在 tensorboard 里直接看第三条需要自己记录原始 Q 值。成功率曲线的形状能直接反映 HER 是否生效。一个健康的训练过程应该类似“阶梯上升”每过一段时间突然跳升一个台阶停留一段时间再跳升。这是因为 HER 把目标泛化到了不同难度智能体先学会简单的“短距离目标”再逐步攻克更难的“远距离目标”。如果成功率始终是一条平线多半是重标注目标选择策略出了问题或者 noise 太大导致探索方向过于随机。critic loss 反而是我用来判断是否过拟合的依据。如果 loss 一直降不下来且 Q 值飙升大概率是 critic 对虚拟目标产生了过于乐观的估计而 actor 顺着错误的 Q 去更新形成正反馈崩溃。这种崩溃在稀疏奖励里特别常见后面我会讲具体的应对方法。5. 训练中踩过的坑与排查速查5.1 问题一Q 值爆炸训练早期就崩我在第一次跑 FetchPush 时就遇到了这个现象前 50 个 epoch 的 Q 估计从 -0.5 一路飙到 100 多紧接着成功率断崖式下降。排查时发现问题出在 buffer 里虚拟目标比例太高导致 critic 看到了大量“成功”样本。这些样本都是机械臂偶然到达某个状态后被人为打上“目标达成”的标签但对应动作和状态之间其实没有可靠的因果关系。critic 被这些虚假正例带偏产生了严重的高估。解决方法有几种。第一种是降低 k 值把 k 从 4 降到 1高估问题会缓解不少但学习速度也会变慢。第二种是为奖励函数增加一个“距离阈值”不在完全相等时给奖励而是距离小于某个阈值就判定为成功这样可以增强重标注样本的可信度。第三种是限制 actor 的更新频率让 critic 先多学一阵子稳定 Q 值actor 再跟上。我最终用的是第二种第三种组合效果不错成功率从崩盘前的 30% 提升到 75% 以上。5.2 问题二future 策略里的 k 到底怎么调k 这个超参数对训练效果的影响非常大但它不是一个“越大越好”的参数。我做过一组对比实验k1 的时候每条 trajectory 只生成一个虚拟目标样本多样性不足智能体很难学到不同难度的目标转换k16 的时候样本量爆炸而且大量相近目标互相干扰效果也没显著提升。k4 恰好平衡了两者。如果你遇到训练曲线震荡不定建议先检查是不是 k 偏大。一个判断技巧是观察 buffer 中虚拟目标样本和原始目标样本的比例如果超过 8:1就说明 k 需要往下调。我在工程项目中一般是 k4 起步如果发现震荡就降到 2如果发现学习过慢再回到 4 或升到 8。这个调节方法很朴素但比盲调学习率高效得多。5.3 问题三旧的 buffer 数据与新策略脱节HER 本身是个 off-policy 算法replay buffer 里会积累大量早期探索产生的经验。当策略逐步进化之后早期那些低质量动作的重标注样本反而会拖累新策略。这种情况在连续控制任务里比较典型尤其是 actor 输出变化的幅度很大时。我的处理办法是给 buffer 加一个简单的“新鲜度衰减”在训练中期开始有概率淘汰掉超过一定步数的旧样本。具体实现时我给每条经验打上时间戳每次采样时以一定权重优先选择较新的样本。这个方法不复杂但在实践中明显提高了后期训练的稳定性。另一个更轻量级的策略是周期性清空一部分 buffer给新经验腾空间实际效果也还可以。5.4 常见问题排查速查表现象可能原因快速解决训练早期 Q 值飞涨虚拟目标比例过高critic 高估降低 k增加距离阈值判定训练初期限制 actor 更新成功率长期为 0探索噪声太小智能体找不到任何可行轨迹调大 noise 标准差或者在动作空间加更激进的探索成功率曲线阶梯式停滞buffer 里旧经验过多引入样本新鲜度淘汰机制critic loss 始终不下降目标判定太严格几乎无正样本扩大成功判定阈值检查 achieved_goal 与目标距离的分布训练到中期开始发散学习率过高或目标网络更新过快降低 actor/critic 学习率调小 tau这些坑我是一步步踩过来的。有些问题看起来很奇怪但背后的原理都是同一根线重标注改变了数据分布凡是引起分布剧烈波动的操作最后都会影响稳定性。掌握到这个层面你会发现自己处理这类问题的速度会快很多。6. hindsight 的另一种读法把“重新定向”用到工程复盘里研究 HER 的时间越久我越觉得这个算法的名字值得琢磨。hindsight 不只是“事后聪明”它其实给了我们一个做工程复盘的好模型不要只盯着最初的目标把已经发生的结果当作可学习的样本重新定义问题再从中抽取经验。这个思路不仅仅适用于强化学习。我在实际做机器人项目时发现很多失败的实验数据其实非常宝贵。比如机械臂抓取失败的轨迹虽然没能把物体放到目标点但那一整串运动学数据记录了机械臂在哪些角度会打滑、在哪些位置会碰撞。如果只用“失败”这个标签把它们丢掉下一轮实验大概率还会犯同样的错但如果像 HER 一样把这些轨迹的目标改成“躲避特定障碍”它们立刻就变成了宝贵的训练数据。6.1 把“成功”的定义扩大一点很多传统复盘的误区在于只把成功经验拿来总结失败经验只是看一眼原因然后记在备忘录里。但 HER 的思想强调的是即便结果与初衷不符也存在某种在其他目标下成立的规律。比如一次产品迭代没达到预期增长目标但如果把目标换成“验证用户偏好”那这次迭代其实是成功的实验。HER 不是让你自欺欺人地否认失败而是让你从失败中提取“对某些潜在目标依然有效”的成分。这种思路搬到团队管理里也有价值。项目复盘时用“事后目标重标注”的视角问一句我们这条路虽然没到原定目标但顺手达到了哪些此前没想到的目标这个提问方式比单纯追究“为什么没做到”更容易打开讨论空间。6.2 给代码之外的经验留个位置我之所以推荐大家在学 HER 时不要只盯着公式也是因为这种算法属于“思路的价值大于实现细节”的典型代表。你可以把 HER 缩写成一行字“与其纠结如何到达目标不如先学会如何到达你已经到过的地方再一步步挪到想去的地方。”这个逻辑放在生活里也相当适用比如学一门新语言总盯着“流利对话”的大目标会很容易气馁但如果把目标拆成“能顺利点完一杯咖啡”每完成一次就给自己一个正反馈学习动力会持续得多。当然这种类比不能无限外推毕竟算法的“虚拟目标”是数学上严格定义的而生活中的目标是模糊的。但它确实为一个古老的道理提供了有意思的注解有时候调整目标本身就是一种进展。最后说一点实操层面的个人体会。从 DDPG 换成 DDPGHER 的那天我第一次体会到“算法思想领先于代码十倍”的感觉。代码改动不过 40 行训练效果却天翻地覆。但这个效果只展现给愿意观察曲线的耐心人——不只是看有没有收敛还要看怎么收敛的、什么时候崩的、崩之前发生了什么。如果你正在复现或者准备在项目里用 HER我建议你多花点时间做对比实验同一组超参分别跑一遍有无 HER 的版本然后盯着那些中途崩掉的曲线好好看它们教给你的东西可能比论文正文还多。
返回列表