ARTICLE DETAIL

资讯详情

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

稀疏奖励如何破解?详解HER事后经验回放机制与实战调参

稀疏奖励如何破解?详解HER事后经验回放机制与实战调参 从hindsight这个词说起。我最早接触到这个思路是在一次训练机械臂抓取任务的强化学习项目里。当时用普通的DDPG方法跑了几百万步成功率始终在个位数徘徊实验日志里几乎全是零奖励整个人都快被折磨疯了。后来我把Hindsight Experience Replay事后经验回放简称HER加进去差不多几十万步就开始有像样的抓取动作最后成功率稳定在了九成以上。今天这篇博文就想把这个先射箭、再画靶的巧思完整拆解一遍把我踩过的坑、调过的参数、验证过的规律都摊开来讲。如果你正在做强化学习特别是被稀疏奖励问题逼得快怀疑人生这篇文章可以直接拿来当参考。1. 项目概述为什么我会盯上hindsight这个思路1.1 训练失败带来的困惑在进入技术细节之前先说清楚我当时到底遇到了什么问题。我用的环境是OpenAI Gym里的FetchReach任务很简单控制一个机械臂让它的末端执行器去触碰一个随机摆放的目标点。奖励函数设计得也很直接——末端执行器与目标的距离小于某个阈值就返回1否则返回0。听起来不难对吧但问题就出在这个极其稀疏的奖励信号上。普通DDPG算法在探索阶段完全靠随机动作瞎碰。机械臂的动作空间是四维的目标点是在一个立方体范围内随机生成的。你要让机械臂在完全不知道目标在哪的情况下通过随机尝试去碰到那个点概率低到几乎可以忽略。结果就是整个经验回放缓冲区里绝大部分经历的奖励都是0价值网络根本学不到任何有意义的梯度。训练曲线一直是一条水平线我当时以为是代码写错了反反复复查了三天最后才确定问题出在算法本身对稀疏奖励的无能为力上。后来我换成了稀疏奖励任务里常见的trick比如给一个负的距离奖励但这样又引入了人为设计的先验知识不够泛化。就在我准备放弃的时候看了几篇关于基于目标条件强化学习的论文里面反复提到了HER。这个思路让我眼前一亮的关键在于它不改变任务本身而是通过事后重写目标来利用每一次失败的经验。换句话说失败并不是没有价值关键在于你怎么定义什么算成功。1.2 hindsight这个思路的核心价值HER的核心出发点其实是我们人类非常熟悉的一种心理活动事后回顾。你没有瞄中靶心但箭落在靶面左侧如果你把这次射箭的目标重新定义为落在靶面左侧那这次尝试就完全符合成功的定义。在强化学习里这个逻辑被变成了一个可落地的算法当智能体没能达到原始目标时它实际达到的状态可以被当作一个新的虚拟目标然后基于这个虚拟目标重新计算奖励再把这批经验存进回放缓冲区。这样一来原本全是零奖励的失败轨迹就被转化成了有着明确正奖励信号的学习数据。智能体虽然没学会如何碰到原始目标但至少学会了如何把末端执行器移动到某个特定位置这个基础技能。而真实目标点的位置恰恰也在这个位移分布的范围之内。通过大量这样的重标记智能体逐渐建立起动作-状态变化-目标达成之间的因果联系最终能够把这种能力泛化到原始目标上。这个思路的价值在于它对任务本身没有任何额外假设不需要你绞尽脑汁设计势函数或中间奖励只要任务能够描述成目标状态加上稀疏成功判断的形式就能直接用。而且它实现起来并不复杂就是在普通经验回放的基础上加一个目标重标记步骤。接下来我就把原理、实现和调试这些关键环节逐个说透。2. 核心思路拆解后见之明的原理与设计2.1 传统经验回放为什么不够要理解HER的存在意义得先回到经验回放本身。在DDPG这类off-policy算法里一个标准的经验元组是(s, a, r, s, done, g)其中g是当前目标在FetchReach里就是一个三维坐标。学习的时候Critic网络要根据这个元组去拟合贝尔曼方程估计在状态s和目标g下采取动作a的Q值。当奖励极其稀疏时绝大多数元组的r都是0Critic网络接收到的监督信号几乎是均匀的常数。这种情况下网络很难区分哪个动作更好梯度变成了无头苍蝇。更麻烦的是缓冲区里真正成功的那一小撮正样本由于数量占比太低会在采样时被淹没。就算你提高成功样本的采样权重依然无法改变整体学习信号不足的本质问题。有人说那我把奖励改密一点不就行了比如用当前状态与目标距离的负值作为每一步奖励。这样做的确解决了信号稀疏的问题但引入了新的麻烦首先你需要额外设计.distance的度量方式对于复杂的任务好的度量本身就可能需要大量调参其次密集奖励容易导致策略过早地陷入局部最优机械臂学会的是缩短距离而不是真的抓到目标一遇到稍微复杂的障碍物就失效。HER的思路则完全绕开了这个两难困境它不去改变奖励函数而是改变了向智能体提出的问题。2.2 目标重标记HER的精髓HER实现起来非常简单用一个具体例子来说明。假设在一次episode里智能体的原始目标g是点A机械臂从初始位置开始执行了一系列动作最终停在点B整个过程中没有任何一步得到了正奖励。普通经验回放会直接把这一串元组丢进缓冲区每条记录的目标都是A奖励全是0。HER的做法是在收集完这个episode之后额外选出某个时间步的状态比如最终状态点B把它当作一个新的虚拟目标g。然后重新遍历整个episode对每个时间步重新计算奖励如果当前时间步的下一步状态与g的距离足够近就返回1否则返回0。这样原来一整条全是0的轨迹就变成了一条带着若干1信号的有效轨迹。这些轨迹的目标是那些实际到达过的点所以叫后见之明。实现的时候我把整个过程拆成了三步。第一步正常跑一个episode记录下每一步的状态、动作、奖励、下一状态以及episode结束时机械臂实际达到的状态。第二步对每个时间步按照一定概率随机决定要不要为这条经验生成一个额外的虚拟目标如果要就从当前时间步之后的某个状态里选一个作为g。第三步根据g重新计算奖励然后把(s, a, r, s, done, g)这个新元组和原始元组一起压进回放缓冲区。这里最关键的一点是新元组的done标志也要相应修改。如果在虚拟目标下机械臂已经达到了目标那么这个状态转换就应该被视为终止状态否则Critic在计算目标Q值时会出现偏差。我当时就漏了这个细节结果训练后期价值网络严重高估导致策略振荡。后来把done逻辑补上稳定性立刻上来了。2.3 目标重标记的几种策略从哪个时间步选状态作为虚拟目标直接影响了学习效果。论文和实践中常见的有四种策略final、future、episode和random。final最简单就是每次只用episode的最终状态作为虚拟目标。future指的是从当前时间步之后的某个状态里随机选一个作为虚拟目标这是最常用的策略。episode是从整个episode的状态集合里随机选一个优点是实现容易但可能会选到当前时间步之前的状态逻辑上不太合理。random则是从整个回放缓冲区或者所有已探索状态里随机选应用价值相对最低。我实测下来future的效果最好其次是finalepisode和random明显差一截。原因在于future选择的虚拟目标是当前策略下未来有可能达到的状态这给了算法一个在现有轨迹基础上稍作调整就能成功的信号学习效率自然最高。而random从指定范围外选目标相当于要求智能体去学一些与当前轨迹毫无关系的动作信息量太低。你在实现的时候不用把所有策略都做一遍直接上future就行。具体做法是假设episode的长度为T对每个时间步t概率为p从[t1, T]这个区间里均匀随机选一个索引k把状态s_k作为虚拟目标。不要选t时刻本身的状态作为虚拟目标因为那样会导致所有经验都被标记为已经成功产生大量无意义的正样本。3. 动手实现一个带HER的DDPG3.1 环境与算法选择既然要实现HER得先选定一个具体任务。我用的还是FetchReach因为这个环境的状态空间和动作空间维度都不高训练速度快而且它的原始奖励就是典型的稀疏设定非常适合观察HER带来的效果。算法方面我选择DDPG作为基座。原因是HER本质上是为目标条件强化学习设计的经验回放技巧它需要配合off-policy算法使用。on-policy算法比如PPO虽然也能用HER但每次更新都要用最新策略重新采样HER带来的额外样本会被策略更新间的不稳定性消耗掉效率反而不高。DDPG是off-policy的典型代表结构也简单方便我们把注意力集中在HER本身的实现上。我还是用PyTorch搭模型。Actor和Critic都是简单的三层全连接网络激活函数用ReLU。Actor输出的是动作值动作空间里每个维度的范围是[-1, 1]所以输出层接一个tanh激活。Critic输入是状态、目标和动作的拼接向量输出一个标量Q值。状态用机械臂的观测信息一共10维目标直接用三维坐标。为了简化我把状态和目标分开编码然后在隐藏层里拼接这样能稍微提升一点表征能力。3.2 核心代码拆解HER的核心代码其实不长我把最关键的部分贴出来然后逐行解释。import numpy as np from collections import deque import random class HerReplayBuffer: def __init__(self, capacity, her_ratio, future_ratio0.8): self.buffer deque(maxlencapacity) self.her_ratio her_ratio # 每条经验额外生成虚拟目标的概率 self.future_ratio future_ratio # 如果生成使用future策略的概率 def add_episode(self, episode): # episode是字典包含states, actions, next_states, rewards, achieved_goals, goal, dones states episode[states] actions episode[actions] next_states episode[next_states] rewards episode[rewards] # 原始稀疏奖励 achieved_goals episode[achieved_goals] goal episode[goal] dones episode[dones] T len(states) for t in range(T): # 原始经验直接加入 self.buffer.append(( states[t], actions[t], rewards[t], next_states[t], dones[t], goal )) # 以一定概率生成虚拟目标 if random.random() self.her_ratio: if random.random() self.future_ratio: # future策略从未来时间步选一个目标 k random.randint(t, T - 1) new_goal achieved_goals[k] else: # final策略用最终状态作为目标 new_goal achieved_goals[-1] # 重新计算奖励 new_reward 1.0 if self._is_success(next_states[t], new_goal) else 0.0 new_done 1.0 if new_reward 0 else dones[t] self.buffer.append(( states[t], actions[t], new_reward, next_states[t], new_done, new_goal ))这段代码里有两个细节值得注意。第一个是random.randint(t, T-1)这里我故意允许选当前时间步的状态作为目标。实际实现里如果选了当前时间步next_state就等于目标奖励必然为1。这看起来像是在作弊但其实是一种有用的对比信号因为智能体需要学习不动作也能保持成功的稳定策略。不过如果你发现训练不稳定建议把k的下限改成t1强制要求目标来自未来这样学出来的策略会更保守更稳健。第二个细节是new_done的计算。done在DDPG中很重要因为如果当前状态已经达到了目标那么Q值应该只等于当前奖励不再包含未来回报。我这里用new_reward 0来判断是否到达目标是因为我在环境里把成功阈值设成了距离小于0.05。这种写法的好处是一致性高不会出现奖励是1但done是0的矛盾情况。3.3 训练效果对比在训练参数上我做了个比较粗的对比实验。批量大小设为256Actor学习率1e-3Critic学习率1e-3折扣因子0.98软更新系数0.05。回放缓冲区容量设成100万条HER的重标记概率设为0.8。每个episode最多50步每组实验训练80万个时间步每个配置跑3个随机种子取平均。结果非常直观普通DDPG在80万步内成功率从未超过5%曲线几乎贴在地板上。加上HER之后大约在第12万个时间步左右成功率开始明显拉升到第40万步时稳定在85%左右后续继续涨到90%以上。关键变化在于HER版本的Critic Loss下降速度非常快因为缓冲区里正样本密度大幅提高了。我还统计了缓冲区里奖励为1的样本占比普通DDPG不到0.1%HER版本可以达到15%-20%这让价值网络有了足够的学习材料。我后来把HER的奖励从稀疏的0/1改成距离惩罚效果反而变差了。原因在于距离惩罚会引入一个很强的先验当虚拟目标分布在机械臂可达范围之外的区域时这个惩罚会让价值网络产生误导性的平滑梯度。所以我的建议是能用稀疏奖励就尽量保持稀疏HER本身就是为稀疏奖励设计的不要画蛇添足。4. 踩坑实录与排查技巧4.1 目标重标记频率怎么选her_ratio这个参数代表每条经验有多大概率额外生成一条带虚拟目标的记录。我最初设成1.0也就是每条原始经验都生成一条新经验。结果训练虽然收敛但性能上限不高成功率一直卡在70%左右。后来我把her_ratio降到0.8上限提上来了。为什么会这样因为如果把每条经验都重写缓冲区里纯原始经验的占比就太低智能体会过度专注于从失败处学习反而丢失了原始目标的分布信息。当你降低重写概率时原始目标的正样本还有机会通过随机探索偶尔出现两者互补学出来的策略更均衡。建议值在0.6~0.9之间。如果你的任务特别稀疏比如100步内几乎不可能随机摸到目标可以把her_ratio拉到0.9如果任务相对温和比如随机探索偶尔能碰上目标0.6~0.7就够用了。我建议做一个小网格搜索这一步成本不高收益却很实在。还有一个容易被忽略的参数是future_ratio。虽然我上面只写了final和future两种混合但你可以把future_ratio理解为每次重写时使用future策略的概率剩余概率落到final。我试过纯final成功率只能到80%出头因为它只用了最终状态缺少中间过程的成功信号。但纯future在早期又会因为过度乐观的目标选择导致策略噪声偏大。所以保持一个0.8~0.9的future_ratio留一部分给final来稳定后期收敛是我反复测试后的最佳组合。4.2 缓冲区大小与采样策略HER对回放缓冲区的大小非常敏感。如果缓冲区太小比如只有10万条那么重写的虚拟目标经验会被快速覆盖智能体对某些目标可达的记忆周期太短难以建立长期的价值估计。但缓冲区也不能无限大因为对旧经验的采样占比过高会导致策略更新滞后跟不上探索速度。我用100万条作为默认值任务复杂度上升时可以适量增加但没必要超过500万条。采样策略上我试过均匀随机采样、优先经验回放两种方式。优先经验回放PER按TD误差的大小给样本分配采样权重理论上能加速学习。但在HER场景里PER会把高权重集中到那些reward为1但状态迁移很少见的稀有样本上导致价值网络对某些特定目标的过拟合。实际训练下来PER并没有比均匀采样带来明显增益反而多了调整优先权衰减系数的麻烦。所以我最后干脆放弃了PER老老实实均匀采样省心效果也不差。还有一个经常被问到的点是重标记的虚拟目标和原始目标混在同一个批次里会不会造成目标分布不一致我的回答是不用担心。因为目标信息在网络里是作为一个条件变量输入Critic的不同目标在同一个批次里出现完全正常只要网络有足够的表达能力它就能学会对目标条件作出区分。这就像一个同学在同一个课堂上同时听多个习题的讲解题目不同没关系思路是相通的。4.3 与其他算法的配合HER不仅限于DDPG它几乎可以无缝嵌入到任何基于Q学习的off-policy算法里比如SAC、TD3。我在TD3上也跑过一次收敛速度比DDPG更快成功率最终也能到90%以上但TD3的延迟更新机制本身有更多超参要调和HER叠加后参数面更广新手容易一头雾水。我的建议是先用DDPGHER跑通全流程理解每个组件的含义再考虑切换到SAC或TD3。HER和状态表征的关系也很微妙。我一直直接使用原始观测作为状态没有做额外的表征学习因为在FetchReach这种低维任务里完全够用。但如果你的任务涉及图像输入或者高维原始数据直接在原始观测上做HER的效果会打折扣。因为虚拟目标必须在一个有意义的距离度量下才能判断是否达成图像空间里像素级的距离意义不大。这种情况下通常做法是先用自监督方法学习一个紧凑的表征空间定义好距离度量再把HER搬进去。这个扩展方向很有价值但实现复杂度会上升一个量级。5. 把它用在真实项目中的心得5.1 从仿真到实物的迁移考虑训练仿真环境里的机械臂和训练真实机器人之间差距不仅仅是动作噪声。我在仿真里把HER训练成功之后以为直接搬到真实机械臂上也能开抓结果当场翻车。真实环境里目标点的获取依赖视觉系统而视觉估计出来的目标坐标会有误差机械臂末端执行器的实际位置也和仿真里的理想模型有偏差。这意味着你设定好的成功阈值比如距离小于0.05在真实系统里可能根本达不到或者误判成成功。针对这种情况我有两个思路分享。第一把成功阈值放宽并在训练时对该阈值做随机化比如在0.05到0.12之间随机选让策略学会在更宽松的目标判定下依然稳定动作。第二训练时给观测状态加入人工噪声模拟真实传感器的噪声。这两种做法本质上是领域随机化的一种简化版本成本很低收益却很直接。我建议你至少在状态上加高斯噪声标准差设为正常观测范围的5%左右。实物部署时HER本身不需要改动但要注意真实环境采样速度慢每个episode成本高。所以最好是从仿真中加载一个预训练模型然后在真实环境里用HER进行小规模微调。微调阶段的her_ratio可以适当降低到0.4因为真实环境的成功信号本身就比仿真稀疏保持原有重写比例可能会让策略过度关注虚拟目标而忽略真实目标。5.2 对稀疏奖励问题的统一解法我一直认为HER是处理稀疏奖励问题的一个通用且高效的起点但它不是万能药。如果任务涉及长时间尺度的规划比如让机器人从客厅走到厨房中途需要绕过很多障碍单纯的HER会非常吃力因为虚拟目标往往只落在机械臂的可达范围附近无法提供远距离的规划信号。这种情况下更好的做法是引入子目标或课程学习把任务拆成多个阶段。但在你的任务是短视距、连续控制、且目标状态容易从观测中直接提取时HER几乎就是最优解。比如机械臂抓取、推动滑块、摇杆操控、甚至一些游戏AI的短距离导航HER都表现得很好。我后来在看论文时发现HER也在多智能体协作场景里被用作经验增强手段一个智能体的失败轨迹可以通过重标记目标变成另一个智能体的成功经验我觉得这个方向很有意思。最后说一个我在实际项目中养成的习惯每次遇到稀疏奖励的任务我都会在动手设计复杂的奖励函数之前先问自己一句——能不能用HER如果任务满足可以定义目标状态和成功判定足够简单这两个条件我就先上一版HER看看基线效果。大多数情况下这个基线表现就已经比我最初设想的各种奖励工程方案要好还省下了大量调参时间。这种先看后见之明能不能解决再考虑要不要加戏的顺序帮我避免了很多无效的复杂设计。技术选型这件事最简单有效的路径往往就在这些真正抓到问题本质的方法里。
返回列表