资讯动态

HER算法实战:稀疏奖励下机械臂目标达成的强化学习解法

发布时间:2026/10/15 9:45:04 来源:尧图企业网站定制
做技术这几年我最大的感受是很多当时把我们卡死的问题事后回头看都特别明显可真正处在那个时间点就是看不见。英文里有个词专门形容这种感觉hindsight后见之明。这个词在认知心理学里常被当成一种需要警惕的偏差但在强化学习里有一个叫 Hindsight Experience ReplayHER的算法把“事后视角”变成了训练智能体最趁手的工具。我今年用 HER 解决了一个很典型的问题一个二维机械臂模型在稀疏奖励下怎么都学不会到达目标点。训练日志拉出来成功率从第 1 轮到第 15 万步都牢牢贴在 0 附近。当时团队一度想放弃后来引入 HER 重写了经验回放逻辑同样的环境、差不多的网络结构5 万步左右成功率直接爬到 85% 以上。这篇文章就从这个项目说起把 HER 的设计思路、实现细节、踩坑经验完整拆开讲一遍。如果你也在做机器人控制、路径规划、或者任何带“目标达成”性质的强化学习任务这篇文章大概率能帮你省两周以上的时间。1. 从“事后视角”到强化学习的核心思路拆解1.1 “hindsight”这个词放在技术语境下指什么先把这个词捋清楚。心理学里的 hindsight bias 说的是事后回看时人们会倾向于认为“我早就知道会这样”这种认知偏差会扭曲我们对真实因果的判断。项目管理里的 postmortem 复盘则是一种主动利用 hindsight 的方式事故已经出了回到时间线上重新理解每一步为什么会走到那个结果目的是找出根因避免下次再犯。HER 的火花也来自同一个观察既然事后视角能看到更多信息能不能把它用在强化学习的训练阶段而不是等项目结束之后具体到实现上它的办法非常朴素——一条轨迹跑完智能体确实没有到达预设目标但“事后”我们重新审视这段轨迹发现它其实到达了另一个位置。如果我们把那个实际到达的位置当作这次尝试真正想要完成的目标那么这条原本被判为“失败”的轨迹就变成了一个“成功”的样本。强化学习里最不缺的就是失败样本把失败样本改造成带正信号的经验这就是“后见之明”最有工程价值的落点。这个想法看着简单真正落地后效果好到惊人尤其在稀疏奖励场景下它几乎是把“经验数据利用率”这个指标翻着倍地往上抬。1.2 稀疏奖励为什么能直接把训练卡死要理解 HER 的价值先得知道稀疏奖励环境有多难。很多控制类任务设计奖励时会选稀疏形式到达目标区域给一个正奖励或者给 0全程其他状态给 -1 或者 0。好处是奖励设计近乎零成本也不会因为 reward shaping 引入误导坏处是学习这件事几乎成了“大海捞针”。举我项目里的实例。环境是一个 2D 平面空间末端点要移动到一个半径 0.02 的圆形目标区域里整个工作区是边长 1 的正方形。目标区域占整个状态空间的比例是 π×0.02²/1×1约等于千分之 1.256。如果智能体完全随机运动单步踩进目标区域的概率低得可怕更别说轨迹是几十步的序列所有动作叠加后最终命中目标区域的概率随步数指数级下降。这就是稀疏奖励的核心困境在未命中目标之前每一步收到的奖励完全一样智能体根本没法从奖励信号中分辨“这一步比上一步更接近目标”于是价值函数学不到任何梯度上的引导策略更新就退化成盲目探索。传统解法无非几种做 reward shaping用人工先验把“距离减小”折算成中间奖励或者用一个离线的演示数据集做模仿学习预热再或者干脆加大量随机采样暴力尝试。但前两种都依赖额外的设计和数据最后一种在维度稍微高一点的任务上直接失效。HER 选择了一条更省事的路径不改变奖励函数只改变经验缓冲区的“解释方式”。1.3 HER 的核心机制把失败的结局重写成新目标HER 的正式名称是 Hindsight Experience Replay它是 OpenAI 在 2018 年被提出的一个算法严格来说不是一个独立的学习算法而是对经验回放机制的改造。常规的强化学习过程是这样每个时间步产生一条 transition记为 (s_t, a_t, r_t, s_{t1})。这条 transition 里蕴含的目标信息是采样时智能体被指令去争取的那个目标 g。如果没达成目标r_t 就是 -1 或者 0代表“失败”。HER 做了一件非常不起眼但影响深远的事在存经验的时候除了保留原始目标 g 的 transition还额外生成几份“事后重标记”的 transition。那份重标记的目标 g 来自这条轨迹上未来某个时刻实际到达的状态 s_{tk}。用 g 来看这条 transition 就不再是失败——智能体确实从 s_t 走到了 s_{tk}如果目标就是 s_{tk}那这就是一笔“成功的经验”。举个例子机械臂三个半小时都没抓到目标物但在 14 分钟的位置它的末端恰好经过了目标点附近。原始视角下这段数据毫无价值惩罚信号是 -1。HER 视角下把“抓目标物”替换成“到达轨迹第 14 分钟时末端所在的位置”同一段控制数据立刻变成了一次漂亮的精准到达。算法真正学到的是“从当前状态到那个状态的这组动作是对的”这个理解对未来到达其他目标也有通用性。1.4 为什么必须要配 off-policy 算法一起用很多第一次接触 HER 的朋友会问为什么不是把 PPO 的 rollout 数据里重标一下目标就行答案是HER 的收益完全建立在“来自旧策略甚至随机策略的数据仍然可以用于当前阶段训练”这个前提上也就是 off-policy 学习的假设。HER 重标记后的经验会给同一个 transition 套上不同的目标相当于在 replay buffer 里插入了多份结构相似但目标不同的样本。只有 off-policy 算法典型如 DDPG、TD3、SAC才允许模型反复从历史 buffer 中采样学习因为这些算法的 Q 函数拟合不依赖数据是否来自当前策略。而 on-policy 的 PPO 或 A2C 要求每次更新时数据都必须来自当前策略分布用 HER 重标记一批“伪造目标”的历史数据去更新旧策略在理论上很难站稳实际跑起来需要额外做重要性采样修正工程复杂度没有意义。所以我现在做的 HER 项目基本都锁死在 off-policy 的框架里最常用的是 TD3 和 SAC。这也是整个设计链条上比较容易被新手忽略的第一环先有 off-policy 的“地基”HER 才能盖起高楼。2. 核心细节解析目标重标记的四种路线与网络设计2.1 final、episode、future、random 四条重标记路线怎么选HER 论文里给了四种目标重标记策略含义和实践效果差别挺大我把它们拆开说。第一种 final每个 episode 结束后只把整段轨迹最终的最后一个状态作为重标记目标。好处是简单坏处是轨迹长的时候早期状态到最终状态的距离可能很远重建这条“成功路径”的学习负担太大。第二种 episode从当前这段轨迹里随机抽一个状态当作新目标。这个做法更均衡但有可能抽到很早以前的状态距离当前时刻的动作序列跨度也比较大。第三种 future在当前时刻 t 的后续轨迹段里随机抽取一个状态标准做法是从 t 到 tk 的范围内取。k 通常设成 4。它既能保证重标记目标离当前状态不远、动作序列与“达成目标”相关性强又保留了一定的时间跨度不至于变成 trivial 地从原位到原位。第四种 random完全随机抽取状态几乎不保证时间关系在实际项目里我很少用。我的经验是无脑优先选 futurek 设 4大部分任务都能获得稳定收益。如果你的任务里目标状态本身就分布很散可以考虑用 future 叠加少量 episode增加全局目标的覆盖度但要注意别喧宾夺主。策略重标记目标来源优势劣势适用范围final整段轨迹终点简单、稳定跨度大学习负担重轨迹很短episode当前轨迹随机状态均衡目标状态太随机中等轨迹future当前时刻后 k 步内状态时序关联强学习效率高需试 k 值大多数任务random全域随机状态覆盖广目标与动作关联弱特殊场景留用2.2 Goal 的表示与注入方式Concate 前先想清楚归一化HER 里需要把“状态”和“目标”分开描述。OpenAI Gym 的 GoalEnv 接口定义了三种观测字段observation 是智能体自身状态achieved_goal 是当前实际达成位置desired_goal 是希望达成的目标位置。训练时 policy 的输入不一定是原始向量而是把状态和期望目标拼在一起。拼法拍脑袋就能想到但归一化这个环节经常被忽略。状态和目标经常是物理量比如末端坐标在 0 到 1 之间角度在 0 到 6.28 之间关节角速度可能到几十。直接 concat 输入网络数值范围大的维度会主导梯度导致训练极其不稳定。我在第一个 HER 项目里就吃过大亏当时目标和状态没分别归一化前 300 轮 loss 曲线像锯齿一样完全降不下去。正确的做法是给每个维度独立做归一化把训练中接触到的目标分布提前采样统计一遍用固定的 mean/std 变换不要用 batch norm 动态跑均值。你可以在环境初始化时单独跑几百条随机轨迹收集 achieved_goal 的边界然后除以常数把输入压缩到相对一致的尺度。这个细节往往决定 HER 能不能收敛比调学习率的优先级高得多。2.3 判定器与奖励阈值太小学不动太大失真HER 的目标重标记需要判定“目标是否达成”。稀疏奖励环境下这个判定一般是一个距离阈值if distance(s_t, g) δ则 r0否则 r-1。δ 的取值是整个项目里另一个容易“拍脑袋”的参量。太小比如给的容差只有 0.005那即便是重标记后的“成功样本”判定器也可能认为根本没成功奖励信号依然稀疏太大比如 0.1那智能体离目标还有十万八千里也会收到成功信号学出来的策略粗手粗脚真机部署时末端精度完全没法看。我的做法是分阶段先定一个比较宽松的 δ比如工作区边长的 5%保证训练前期能频繁吃到成功信号让价值函数先“活起来”等成功率稳定后把 δ 缩一半继续训练逐步逼近真实精度需求。这算是一种轻量的 curriculum 策略不需要额外改奖励函数只在环境配置里调阈值即可。2.4 经验缓冲区的混合比例与存储方式HER 不是把原始经验扔掉而是在存储时同时保留“原始目标版本”和“重标记版本”。如果原始目标概率为 p重标记为目标概率为 1-p一般建议 p 在 0.2 到 0.4 之间至少保留两到三成原始目标经验否则价值函数会过度乐观。因为它学到了“只要我走过就一定能成功”而事实是很多状态下智能体并不一定能再次成功。保持一部分“目标确实没达到”的负样本让 Q 函数别被成功经验冲昏头脑。存储方式的细节同样不可忽视。不要在每个时间步把重标记后的 transition 单独复制一份扔进 buffer而应该先把完整 episode 缓存到临时结构中等 episode 结束时统一生成重标记版本。这样同一个逻辑可以同时采到多个不同的重标记目标而且未来窗口的选择更加灵活。一次采样时一条 (s_t, a_t, s_{t1}) 可以挂三四个不同的目标成批存入 buffer数据利用率一下子就上去了。3. 实操过程从环境搭建到训练结果复现3.1 自建一个二维机械臂的稀疏奖励环境为了复现这个项目我建议你也从自建环境开始不要一开始就上 FetchReach 那种现成环境因为自定义环境能让你清楚看见 HER 每一步到底改了哪些变量。环境定义为一个二维平面上的点状机械臂动作空间是末端点横纵坐标的增量范围 [-0.05, 0.05]状态空间是末端点坐标 (x, y)目标空间也是二维坐标。每个 episode 开始目标点随机出现在工作区里初始末端位置随机。奖励如果末端与目标点的欧氏距离小于 0.02奖励 0否则 -1。这个环境用 Gym 的 GoalEnv 子类实现关键点是 observation_space 要定义成 Dict包括 observation、achieved_goal、desired_goal 三个字段。很多人在这个地方跳过直接用普通 Box 状态导致后面 HER 的接口要自己造轮子浪费不少时间。3.2 HER Replay Buffer 的核心代码实现下面这段代码是我在项目中实际用的 buffer 核心逻辑我把细节压缩到了关键部分。class HindsightReplayBuffer: def __init__(self, capacity, future_k4): self.capacity capacity self.future_k future_k self.buffer [] def store_episode(self, episode): # episode: 包含 obs 序列、actions、achieved_goals、desired_goal 的列表 # 这里简化为按整段轨迹存储 self.buffer.append(episode) if len(self.buffer) self.capacity: self.buffer.pop(0) def sample_transition(self): episode np.random.choice(self.buffer) t np.random.randint(len(episode[obs]) - 1) s_t episode[obs][t] a_t episode[actions][t] s_next episode[obs][t 1] # 以一定概率保留原始目标 if np.random.random() 0.3: goal episode[desired_goal] else: # HER 重标记从未来 k 步内随机选一个状态 horizon np.random.randint(t 1, min(t self.future_k 1, len(episode[obs]))) goal episode[achieved_goals][horizon] reward compute_sparse_reward(s_next, goal) return s_t, a_t, s_next, goal, reward这里有两个细节要注意第一重标记目标用的是未来 k 步内“实际到达状态”而不是当前状态本身。如果很短视地直接选 s_next学出来的模型只能做诸如“往前挪 0.000001”这种原地踏步的“成功”毫无意义。第二compute_sparse_reward 要根据新的 goal 重新计算 reward——因为重标后原本的失败数据很可能变成“成功”奖励值是实时算出来的不是从环境返回的原始值。这个逻辑是整个 HER 最容易出错的地方但反过来说也是它最精彩的地方环境给的是死的奖励buffer 用动态的解释把它变“活”。当然上面的代码只演示了重标记逻辑。实际训练中的 DDPG 或 SAC 的网络更新、软更新、探索噪声等都可以直接复用通用框架的实现不需要改任何代码。HER 的侵入性比想象中小得多。3.3 训练参数、曲线对比与阶段结果分析我用一个小型 DDPG 网络跑这个环境。actor 和 critic 都是两层 256 的 MLP学习率 1e-3target 网络 soft updatetau0.05replay buffer 容量 20 万条batch size 256每 1 个 step 更新一次OU 噪声用于探索。没有 HER 的对照组15 万步成功率稳定在 0.3% 附近基本等于随机动作偶尔蒙中的概率训练曲线完全是一条贴着底部的横线。加上 HER同样的超参数5 万步时成功率已经到 60%8 万步后可维持在 85% 到 90% 之间。收敛速度的差距完全不是一个量级。更有意思的是 HER 组前 1 万步的表现。成功率不是缓慢爬坡而是先急剧冲到 70% 左右然后掉回 30%再慢慢爬上去。这个“先冲高、再回落”的模式非常典型那是因为前期重标记的目标都是近未来状态网络很快就学会了“局部修正”能力判定器也容易满足但中后期目标变成远距离的真实目标任务变难成功率会被暂时打下来。如果看到这种曲线别着急不是训练崩了而是任务难度爬坡后训练进入深水区继续观察就行。3.4 从仿真到真机的落地边界HER 在仿真里表现得很好但真机上需要格外注意几个边界条件。第一真机状态估计一定有噪声。HER 重标记出来的目标来自传感器读数如果状态估计有偏差重标记的目标本身可能就是歪的训练得越久累计误差越大。对策是在仿真阶段就给 observed achieved_goal 加少量高斯噪声提前让策略适应。第二判定器在真机上需要重新标定。仿真里距离就是欧氏距离但真机上的“距离”往往要从视觉定位或者力传感器里反推精度不稳时阈值只能人为放宽。这会导致 HER 训练出的策略在仿真里精度很高真机上显得有点“粗糙”需要配合真机数据微调。第三动作频率。仿真里动作频率可以很低比如每秒 10 帧已经够用但真机控制频率往往要到 50Hz 以上否则末端位置会出现剧烈抖动。HER 本身不关心帧率但在把仿真经验迁移到真机时时间尺度变了未来窗口 k4 对应的物理时长也变了需要重新换算。4. 实操中的四个典型坑与问题排查实录4.1 训练不收敛先查目标归一化和奖励口径这是最常见的“HER 不 work”现场。表现是 loss 曲线不下降成功率也不上涨。多数情况不是算法问题而是两个实现层面的小坑。第一个是归一化。我在 2.2 里反复强调过再提一次网络输入里同时混着 0 到 1 的坐标和 0 到 6 的弧度梯度几乎全被数值大的维度吸走价值函数学了跟没学一样。排查方法很简单把 actor 输入翻出来打印一下看不同维度方差差了几个量级如果超过 10 倍先处理归一化。第二个是奖励口径不一致。在前面那个 buffer 实现里reward 是采样时重算的但在一些现成框架里transition 的 reward 是环境返回的原始值HER 重标记后没有重新计算导致“目标换了但奖励还是失败的 -1”HER 完全失效。核对方法把一条重标记样本找出来打印新的 goal 和新 reward如果 reward 为 -1 但距离明明小于阈值那一定是口径没有统一。4.2 Q 值虚高、策略退化重标记比例需要节制重标记比例过高时buffer 里绝大多数都是“成功”样本TD 误差里的 target 长期偏低Q 函数容易出现过估计。表现是训练前期成功率冲得很高之后反而掉下来或者策略动作变得很“飘”前一步向左后一步强行向右像是在尝试掩盖错误。缓解方案有两条路。一是降低重标记比例把原始目标保留概率从 0.3 提到 0.5让价值函数继续吃到足够的失败信号。二是换用 TD3 的 clipped double-Q 机制用两个 Q 网络取最小值算 target对过估计的抑制很有效。我一旦发现 Q 值虚高优先换 TD3比单纯调比例来得更干脆。4.3 看着在逼近目标就是达不到判定阈值这种“距离在递减但从未真正成功”的情况多半是奖励判定阈值和 HER 学习目标没对齐。比如阈值设成 0.005但智能体经过重标记学习后能稳定停在距离目标 0.02 的位置。0.005 的要求对当前网络表达能力来说太苛刻价值函数在 0.02 到 0.005 这一段基本没有平滑梯度。解决办法就是我在 2.3 里提过的分阶段缩小阈值。先 0.05 练到成功率稳定再改成 0.02 训练两万步最后 0.005 微调让每次“成功难度”的变化都不要太陡。4.4 智能体原地打转探索策略和 future 窗口要一起调训练前期智能体动作幅度极小一直在一个小圈子里打转看起来是陷入局部最优。排查时先看探索噪声DDPG 里 OU 噪声方差设太小动作几乎恒定自然走不远把噪声标准差从 0.1 提 0.3 就能看到明显改善。如果噪声没问题再看 future 窗口。k1 时重标记目标基本就是一步之后的状态学出来的策略本质上只做“小步修正”探索范围非常受限等于在一个很小的漏斗里学习。把 k 提到 4 或 6让重标记目标覆盖未来几个时间步动作选择空间变大原地打转的现象会大幅缓解。4.5 常用问题速查表现象可能原因快速排查解决办法成功率一直为零归一化不一致 / 奖励未重算打印输入维度方差和重标记 reward统一归一化、统一奖励口径前期冲高后回落任务难度爬坡 / 重标记比例过高看不同训练阶段的成功目标分布保留原始目标比例 0.3-0.5距离在减但不成功判定阈值过严检查距离分布与阈值差值分阶段缩小阈值在原地打转探索噪声太小 / future 窗口太短观察动作方差加大噪声k 提到 4-6Q 值虚高过估计对比 double Q target换 TD3 或降低重标记比例我个人做完这个项目之后最大的体会是hindsight 的价值其实比 HER 算法本身更值得多琢磨。我们总觉得训练数据不够但很多时候问题不在数据量而在怎么定义目标。一次没有到达预设目标的操作换一个视角看它精准地到达了另一个位置那就是一次成功能到达的经验。这个思路放到很多相关任务里也通比如多目标路径规划、层次化任务拆解、甚至离线强化学习里都可以通过“事后重解释”把冷冰冰的失败数据变成燃料。最后再分享一个小技巧是后来在另一个排障任务里验证过的重标记目标的时候可以在 achieved_goal 上追加一个很小的球面扰动让重标记出来的目标不是像素级精确的唯一状态而是一个邻域内的随机点。这么做的理由是真机上传感器读数本身有误差训练时引入一点目标噪声学出来的策略才有鲁棒性。代价是前期收敛会慢一点但换来的是落地时换传感器、换安装位置都不用重新训练这笔买卖非常划算。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑