稀疏奖励难题与HER算法:从失败轨迹中学习成功的强化学习实战解析
发布时间:2026/10/2 14:51:16来源:尧图网络
“事后看来一切都很清晰”——这个词本身就像一记释义只有在拿到结果之后我们才会恍然大悟。它同时也是一把钥匙打开了我今天想聊的项目。作为强化学习工程师我这些年被稀疏奖励问题折磨过太多次智能体在迷宫里乱撞跌跌撞撞到超时也拿不到一次正向奖励训练曲线横在那里就是不动。直到读到了hindsight相关的思路——也就是把“事后才看清”这句话翻译成算法语言用「hindsight」把失败的轨迹重新解释成学习的养料。人工智能里那个著名的HERHindsight Experience Replay事后经验回放技术就用了一个很朴素但极其优雅的假设这个回合没拿到你想要的奖励没关系把这个回合里真实到达的地方当成目标反过来再学一遍。这篇文章我打算把这条算法的来龙去脉、推导逻辑、PyTorch落地细节和踩坑实录一次性讲透给正在做机器人控制、游戏AI、推荐决策等方向的朋友一份能直接拿去用的参考。1. 整体设计与思路拆解1.1 稀疏奖励为什么是个难啃的骨头先聊点背景。强化学习的本质是让智能体通过试错学会“在不同状态下做什么动作能让累计奖励最大”。但如果任务的目标特别难达成比如机械臂要抓一个远端的杯子初始状态离目标太远智能体执行几十上百步动作之后依然没触碰过成功状态那它得到的所有奖励都是同一个值——零。零奖励意味着什么意味着梯度信号为零策略更新时不知道哪个方向是对的、哪个方向是错的只能继续瞎试。我早年在做仿真机械臂抓取任务时遇到过一个非常经典的场景状态空间是7维关节角目标是6维末端位姿我设置了奖励函数为“距离目标小于5厘米即1否则为0”训练跑了整整一夜智能体依然只会站在原地转悠。后来我意识到这不是网络容量不够是样本的信噪比太低。整个训练过程中有价值的“正样本”占比不到千分之一绝大多数transition都是“随便动了一下没成功”的无效经验。为了应对稀疏奖励业界常见的方案有几类一是奖励塑形Reward Shaping给中间状态人工设计逐步逼近目标的奖励比如按距离给一个连续分数。这个方案最直接但需要很强的任务知识而且设计得不好很容易让智能体学会钻规则的空子——比如绕着目标走圈圈。二是课程学习Curriculum Learning从简单任务逐步过渡到难任务让智能体先学会“近处抓取”再慢慢拉远距离。这思路也有效但需要人为划分难度阶梯每次换环境都要重新调。三是好奇度/内在奖励Intrinsic Reward鼓励智能体探索未知状态但这类方法在环境随机性大的时候经常会跑偏去探索那些和任务毫不相干的角落。1.2 HER的核心想法把失败也变成经验HER的思路和上面几条都不在同一个维度上。它不改变奖励函数不改变环境难度的分布而是改变我们怎么把经验写进记忆库。设想一下智能体这局的目标是“把方块推到桌子右侧的红色区域”结果它一通操作把方块推到了左侧墙角。这一整段轨迹按原目标来看是真真切切的失败样本奖励全部为0。但HER会问一个问题如果这局的目标其实是“把方块推到左侧墙角”那这段轨迹是不是就完美达成了目标于是它的处理方式是把原始的状态-动作-奖励序列复制一份把目标替换成“它实际到达的那个位置”再根据这个新目标重新计算每个时间步的奖励。你会发现原本密密麻麻全是0的奖励序列在新目标下到了某个时间点之后的奖励变成了1。这一份原本被废弃的轨迹变成了货真价实的成功样本进入了经验回放池Replay Buffer。这个思想的底层逻辑其实是人类学习的常态。我们学投篮不可能每一投都命中但每一次投偏之后你会琢磨“刚才这一球如果目标是篮筐右边一点的那个位置的话我这姿势好像还挺对的。”这种“用实际结果重新定义目标”的转化给了智能体在海量失败中依然能提取正向经验的能力。这也是“hindsight”这个英文词最直观对应的中文意涵——后见之明。从模型层面来看HER最典型的落地对象是以目标为条件Goal-Conditioned的强化学习算法。也就是策略网络的输入除了当前状态之外还会额外拼接一个目标向量输出动作。这样同一个网络可以泛化到不同目标上。HER本质上是改变了目标分布和经验采样分布它要求算法本身是off-policy的因为只有离线经验回放才能对转化后的样本做重复利用。比如DDPG、SAC、TD3这样的actor-critic架构天生就适合和HER搭配。2. 核心细节解析与实操要点2.1 目标的设定与重标注策略理解了整体思路接下来就要拆细节。HER里最关键的一个模块是**重新标注目标Goal Re-labeling**的策略。我用白话解释它的四步流程一个训练回合episode跑完拿到一条长度为T的原始轨迹状态序列 s1,s2,...,sT动作序列 a1,a2,...,aT以及按原目标g生成的即时奖励序列 r1,r2,...,rT。从这个轨迹里随机抽取一个“未来状态”sf一般是从某个时间步k到回合结束之间抽取。把sf里蕴含的目标提取出来记为g。比如目标就是“到达某坐标”那g就等于sf的位置坐标。按新目标g重新计算这条轨迹中每一步的奖励ri然后把 (si, ai, ri, si1, g) 这组五元组送进经验池。你可能会问为什么抽取未来状态时要限定在“时间步k之后”而不是任意一个状态这里有个讲究。如果抽取的是轨迹很早阶段的状态比如episode刚开始时的位置那个位置往往和起始状态太接近作为目标的难度太低起不到提升泛化能力的作用。如果抽取的是很晚的状态比如最后一步那就变成了“复盘整条轨迹中最成功的一刻”样本信噪比最高。实践中通常的做法是均匀随机从【当前时间步之后的某个时间段】抽取保证目标不全集中在一个点上。开源的源码里有一个参数叫做future_k或者sample_future指的就是在原始轨迹中每个时间步后面最多取多少个未来状态来重组目标。我用过的配置一般是每一个原始样本额外生成4到8个重标注样本。这个数字不是越大越好因为这意味着经验池里成功样本比例急剧升高模型会偏向“只学已经做到的事”反而会削弱它朝更远目标尝试的动力。2.2 奖励函数的重新计算HER要求奖励函数必须能被“目标化”。这也是整套方法论能不能成立的前提。如果任务目标是“到达三维坐标点”那么奖励函数可以写成关于状态和目标距离的函数比如负距离或者阈值型稀疏奖励。这类函数的好处是只要替换目标向量奖励就能直接重新算出来。我在实际项目中遇到过一类反例奖励函数里掺杂了和任务过程强相关的项比如“用了超过500毫秒的动作时间就扣分”。一旦重标定目标这个扣分项就会和新目标产生语义上的错位。比如这局虽然到达了某个新位置但整个过程耗时过长按新目标算出来的扣分完全不合理于是经验池里就混进了大量奖励信号互相矛盾的样本。这种情况我的处理方式是把“时间惩罚”从奖励函数里拆出来放到终止条件上或者让HER只重算与目标相关的奖励分量其他分量保持原值。你可以把HER对奖励函数的要求理解成一个“可插拔”约束新的目标值必须能被代入同一个目标奖励函数。所以设计算法之前先问自己一个问题如果把目标从“A点”换成“B点”当前这行奖励代码跑一遍结果是否还自洽如果不能就得先重构奖励函数结构。2.3 与off-policy算法的结合方式HER不是一套独立的强化学习算法而是一个样本增加机制。它需要寄生在某个擅长处理经验回放的算法上。我用过的组合里最顺手的是SAC HER和TD3 HER。说下原因。SAC的核心优势在于最大化熵正则化让策略在探索和利用之间保持一个相对平衡这对于稀疏奖励环境里前期的大范围探索很有帮助。TD3则是双Q网络加延迟更新对Q值的过估计控制得更好训练波动更小适合奖励信号本身噪声大的场景。在仿真环境FetchReach、FetchPush这类标准抓取任务里纯SAC和纯TD3在稀疏奖励下几乎是学不起来的而加上了HER之后成功率可以稳定跑到90%以上。有个容易踩坑的地方是HER一般只重放一条轨迹中送进经验池的部分而不是把所有原始transition直接替换掉。在实现上通常的做法是原始轨迹的样本按照100%概率存入replay buffer另外对每个时间步按概率额外生成重标注版本。这个“重标注占比”是超参数常见的是1比1或1比4。如果占比太高比如每一个原始样本都配套生成8个重标注样本经验池就会严重偏向“新目标下的成功经验”导致原始目标的样本被稀释最终模型学会的是“如何在一堆随便定的目标上表现良好”而不是“如何完成真正要做的那个任务”。我自己的经验值是重标注样本数保持在原始轨迹的4倍以内一旦超过训练初期的loss会出现明显振荡。3. 实操过程与核心环节实现3.1 环境与网络结构选择为了让你能直接复现我以一个经典的稀疏奖励任务“二维平面点位到达”作为例子。环境是一个连续动作空间的小车状态包括小车的二维坐标目标是一个从单位正方形均匀采样的二维点。奖励函数是小车的当前位置与目标距离小于0.05时给1否则给0。这个任务看着简单但如果没有HER训练成功率大概率在10%上下挣扎几个月。网络结构上我采用的Actor网络输入是“状态拼接目标”也就是 concat(s, g)输出是动作均值和对数方差SAC风格。Critic网络的输入同样是“状态拼接目标再拼接动作”。如果你用MLP一般两层256个神经元的隐藏层就够了不需要太深。我在实践中发现HER对网络宽度比深度更敏感宽一点的全连接层比加层数更容易提升效果。原因可能在于重标注引入的样本分布多变宽网络能更好地拟合多模态的目标分布。3.2 HER核心代码实现这是我的仓库里实际跑过的核心实现精简之后放在下面各位可以直接改造到自己的流程里。import numpy as np import torch def hindsight_relabel(episode, future_k4, goal_extractorNone): episode: dict, 包含 states, actions, goals, rewards, dones future_k: 每个原始transition额外生成的重标注样本数 goal_extractor: 从状态中提取goal向量的函数 返回新生成的transition列表 states episode[states] # shape: [T, state_dim] actions episode[actions] # shape: [T, action_dim] goals episode[goals] # shape: [T, goal_dim] 或 [goal_dim] rewards episode[rewards] # shape: [T] dones episode[dones] # shape: [T] T len(states) extra_transitions [] for t in range(T): # 从 t1 到 T 中随机抽取 future_k 个未来时刻 future_indices np.random.randint(t 1, T 1, sizefuture_k) for f_idx in future_indices: # 防止越界 if f_idx T: continue # 新的目标未来某个状态的goal表示 new_goal goal_extractor(states[f_idx]) # 例如取位置坐标 # 按新目标重新计算奖励 # 注意这里使用了向量化的距离判定 new_reward compute_sparse_reward(states, new_goal) # shape: [T] # 生成重标注后的transition # (s_t, a_t, new_reward[t], s_{t1}, new_goal, done_t) new_transition { state: states[t], action: actions[t], reward: new_reward[t], next_state: states[t 1] if t 1 T else states[t], goal: new_goal, done: 1.0 if t 1 T else dones[t], } extra_transitions.append(new_transition) return extra_transitions这个实现里有三个核心点值得展开。第一goal_extractor的设计。对于二维点到达任务直接取状态里位置分量即可。但如果你做的是机械臂控制目标可能不是状态本身而是某个变换后的量比如末端执行器的位置。我的习惯是把这个函数独立出来因为一旦目标空间定义错了整个HER的效果会直接归零。第二done的处理。在原任务中如果智能体到达目标环境会提前终止doneTrue。但在重标定的轨迹里由于新目标可能从未被真正达成过原来的done标志可能不再适用。稳妥的做法是只有当新目标和该时刻的真实状态真正匹配时才给doneTrue否则给0。有些开源实现偷懒直接沿用原轨迹的done结果模型学会了“虽然没到达目标但也提前结束”的错觉成功率虚高但实际控制效果极差。这个坑我在自建环境里踩过教训深刻。第三奖励函数向量化。如果你逐时间步循环计算奖励T很大时会拖慢整个训练循环。建议把所有状态堆叠成矩阵一次性算距离、一次性判定是否在阈值内能省下不少时间。训练百万步时这一点能省出好几个小时的等待。3.3 训练循环中的调度与更新策略光把重标注样本塞进buffer还不够训练循环的写法也有讲究。我常用的策略是一份原始轨迹产生两份数据来源——原始样本和重标注样本这两部分按一定比例参与训练。在PyTorch的伪代码里训练循环大概是这样智能体在环境中跑完一个episode记录原始transition。用上面的函数生成额外transition把两者合并后一起存入replay buffer。从buffer中随机采样一个batchbatch里默认按9:1的比例混合原始样本和重标注样本。为什么要留一部分原始样本因为重标注样本虽然能提升成功经验密度但完全丢掉原始目标会让模型忘记自己真正该干什么。保留10%到20%的原始样本可以保证优化信号里始终有“原任务”的监督。每个batch用SAC或TD3的更新公式更新actor和critic。这里再提醒一个细节Batch大小。因为HER引入了重标注样本状态-目标对的分布变得更广所以batch size不能太小。我测试过在256以内时效果随着batch增大而提升明显再往上就趋于稳定。推荐至少128起步最好256。我见过不少人用默认的64导致训练全程震荡一度以为是算法问题后来把batch调到128就顺了。另外一个容易被忽略的训练稳定性策略是learning rate调度。HER重标注会让经验非平稳性加剧——样本分布在中途变化比较大所以学习率如果太大critic很容易剧烈波动。我习惯给actor 3e-4、critic 1e-3这种经典配置再配合梯度裁剪grad clip可以大大缓解震荡。在FetchPush环境里我试过不裁剪梯度训练到20万步时loss直接冲到了原来的10倍量级裁剪之后就平稳了。4. 常见问题与排查技巧实录4.1 加了HER之后训练反而更慢你可能会奇怪HER明明提升了样本利用率为什么训练速度还变慢这通常是因为目标替换之后经验池里不同目标之间的样本过于多样相互冲突的梯度变多每一步更新的信息量反而下降。我在仿真任务里就碰到过HER把每个episode的样本量扩大了4倍但每轮梯度更新提升的幅度只有原来的1/2整体训练时间长了近两倍。排查方法很简单观察critic loss是否出现周期性振荡。如果是大概率是重标注样本的比例过高。先把future_k从4降到2或者把重标注样本占比从50%降到25%训练会稳健很多。记住一个原则HER给模型提供的是“更多可学习的成功样本”但不是“越多样越好”。另外一个可能的原因是目标空间过大。如果你的目标是在一个很大的范围内连续采样重标注出来的目标之间距离很远策略网络需要拟合的函数形态就非常复杂。这时候的解决方案是缩小目标采样范围或者把目标空间做归一化。比如在二维平面任务里把目标从[0,10]线性缩放到[0,1]训练速度能提升不少。4.2 reward shaping和HER到底怎么搭配这是我在技术群里被问得最多的问题。很多朋友的做法是先用密集奖励训练到一定水平再加HER结果发现两者叠加的效果反而不如单独用HER。原因在于密集奖励本身已经给模型提供了足够的梯度信号HER重标注的样本反而会和密集奖励信号产生冲突。举个例子。原始奖励设计为“负距离”这种连续奖励那么智能体在轨迹中段获得的奖励已经是负的小数值不需要事后重塑。HER却把这个轨迹的目标替换掉重新算一个负距离奖励由于新目标往往离轨迹起点比较近负距离会比较大相当于人为引入了大量“这条路很差”的错误信号。我的建议是分阶段使用任务初期用HER来支撑稀疏奖励探索等成功率超过30%后逐渐减少重标注比例最后切换成纯原始样本训练。或者更简单一点——如果你能用密集奖励顺利训练那就别用HERHER的价值恰恰在于“除了稀疏奖励别无选择”的场景。4.3 智能体学会了“假成功”这个现象很隐蔽需要特别小心。我遇到过一种情况训练曲线显示成功率到了95%看起来非常漂亮但我把智能体放到真实环境里一跑发现它只是学会了原地颤抖以极其微小的幅度抖动误打误撞“碰”到了目标点。为什么因为奖励函数用的是“瞬时距离小于阈值”而HER重标注的时候把这个阈值判定同样应用在了未来状态对应的目标上模型发现只要高频小幅抖动就能大概率碰中那个小圆域。解决这类问题通常有三种手段。第一奖励函数里加持续性判定比如“连续5步都落在目标区域才给奖励”这样原地抖动无法刷分。第二动作输出加惩罚比如动作幅度过大或过小都扣分抑制高速颤抖式的策略。第三HER重标注时不采用“目标阈值”判定而采用更严格的距离奖励比如重标注样本的奖励设为“负距离”的密集形式让模型知道越接近越好而不是只满足于碰到边界。这让我想起一个特别有意思的对照人类学骑自行车的时候如果只盯着“过终点线就给奖励”很可能练半天只会歪歪扭扭冲到终点技术完全不过关但如果你告诉自己“每个时间点都要尽量保持车子竖直、方向正”学出来的技术就扎实得多。HER也是这个道理——它用后见之明给了你更多模拟的“目标碰触”机会但最终的泛化能力还需要奖励函数本身足够有区分度。4.4 经验与技巧速查表我把这几年的实操经验汇总成一张表方便各位直接对照排查。症状可能原因建议调整训练曲线完全不涨原始样本与重标注样本比例失衡降低future_k或降低重标注占比critic loss持续上升学习率太大或梯度爆炸引入梯度裁剪critic lr降到1e-3成功率虚高但实跑不行奖励阈值过松颤抖刷分加持续性判定或动作惩罚泛化到新目标效果差目标空间分布过广目标归一化或增加重标注样本多样性训练后期不稳定重标注比例持续过高按阶段衰减重标注比例与密集奖励冲突HER和reward shaping目标不一致阶段性使用不混用4.5 一个容易被忽略的细节目标维度的千万不可错位最后再说一个我在实际工程里栽过跟头的细节——目标维度和状态的拼接顺序。神经网络本身对输入顺序完全无感但你的训练代码必须保证从经验池里采样出来的元组里目标的位置永远是同一个索引范围。HER重标注时如果存buffer时写的键是goal采样时误读了下一个状态的goal位置整个训练就会变成一个看似在收敛实则乱学的黑箱。我建议把transition封装成命名元组而不是简单的数组拼接。我在一个项目里偷懒用列表存储结果调试了两天最后发现是读取时索引错位了一位。这种错误不会报任何异常只是训练效果诡异极其折磨人。用dataclass或者字典结构虽然代码多几行但排错效率会高一个数量级。对于刚上手的朋友这点尤其重要。5. 扩展思路从仿真到现实HER还能走多远聊完了细节和踩坑我想再说点更宏观的观察。接触hindsight这个思想越久我越觉得它不仅仅是一个算法技巧更像是一种对“失败怎么看”的重新定义。在传统监督学习里失败样本的作用是修正边界——告诉模型“这不是正类”。但在HER的世界里每一个失败的轨迹都可以被转化成某个新目标下的成功轨迹。这种思路对真实场景尤其有吸引力。比如工业机器人的装配任务你要它把螺丝对准孔位它经常对不准但每一次“对不准”其实都产生了有价值的信息如果目标不是孔位中心而是它当前实际指向的位置那这条轨迹就构成了“精确到达该位置”的成功样本。多次积累之后模型对“到达任意指定位置”的能力就越来越强那么真正对孔位时它已经是一个熟练的“点位到达专家”稍加微调就能完成精确插装。这和人类练习书法是一个道理——你临摹了很多字帖到最后即使没写过某个字的组合也能根据笔画的规律写出来。从工程落地的角度HER比较适合的任务特征是目标可以量化、可以用状态来反推目标、奖励信号在目标达成处有突变。比如游戏里“到达某个坐标”、“击败某个BOSS”、“完成某个建筑形态”——这些目标都天然可以用状态来定义。而一些目标难以量化、需要大量隐式判断的任务比如对话机器人让用户满意HER就还不好直接套用因为“让用户满意”这个目标无法从状态直接提取。这也是未来我比较关注的方向——如何把HER从“可量化目标”推向“抽象目标”。另外还有一条线是基于模型的HER。传统HER是在真实轨迹上做后见之明重标注但那需要真实环境跑大量rollout。如果叠加一个世界模型World Model那么智能体可以在预测的虚拟轨迹上做同样的事情相当于在后见之明的维度之外再加了一个“想象”的维度。这个方向上已经有一些研究在做核心是想解决重标注样本只来自于真实执行过的状态、分布受限的问题。如果虚拟轨迹的质量足够高理论上能进一步扩大HER重标注的覆盖面。我给新手朋友的建议是别一上来就直奔SOTA算法先把HER在一维二维的玩具任务里跑通感受一下重标注比例、future_k这些参数对训练曲线的影响再去挑战机械臂、爬行机器人这些更复杂的场景。把基础机制吃透了后面任何变了形的hindsight思想——HIRL、HTM、甚至多任务学习里的目标重解释——你都能很快上手。最后再分享一个小经验。我每次调试HER相关的训练都会在代码里加一个可视化工具实时展示episode轨迹、原始目标和新生成的重标注目标。这样你能非常直观地看到智能体在哪些地方“重新规划了目标”也更容易判断参数调优的方向。有一次我因为重标注比例太高可视化里出现了一堆几乎重合的目标点这才发现问题出在future_k设置得过大。hindsight这个词放在生活的语境里叫做“事后诸葛亮”放在算法的语境里是让机器从每一次错过中学到正确的步伐。机器能学会我们作为工程师也应该学会。
网站建设高端定制企业官网