资讯详情

资讯详情

HER事后经验回放:破解稀疏奖励困境的强化学习实战指南

1. 这个叫Hindsight的项目到底在做什么先交代背景。我之前在搞一个机械臂抓取的任务模型怎么训都不收敛奖励明明给了策略就是学不会最后发现问题是出在稀疏奖励上——任务只有抓到了和没抓到两种结果绝大多数episode里根本拿不到正奖励梯度信号稀疏得跟沙漠里的绿洲一样。后来接触到Hindsight这个概念也就是事后经验回放Hindsight Experience Replay, HER整个思路一下打通了。hindsight这个词本身就是后见之明、事后聪明的意思。放到强化学习里思路很朴素开局没抓到目标就别当失败样本丢掉直接把目标改成实际上到达的位置把这个episode包装成一次成功经验存进回放池。也就是说让智能体从失败里事后发现意外收获。这篇文章我会完整拆解HER从原理到落地的全过程包括核心机制怎么设计、目标重标注的四种采样策略怎么选、Pytorch训练pipeline怎么写以及我自己实际调参踩过的坑。适合正在做机器人控制、机械臂抓取、导航类任务的强化学习玩家也适合看过RL教程但没真正动手训过稀疏奖励任务的读者。我复现HER的项目就叫hindsight后面说的所有代码和经验都是这个项目里真实跑过的。为了复现方便我不直接用仿真平台而是先在一个我自制的二维网格环境里把算法跑通再迁移到机械臂任务上。网格环境的好处是能可视化、能快速调参一个episode几十步就结束非常适合验证算法逻辑是否正确。2. 为什么稀疏奖励能把智能体训成原地转圈2.1 稀疏奖励的本质问题绝大多数决策任务最自然的奖励设计都是稀疏的下棋只有赢/输寻物只有找到/没找到机械臂抓取只有抓住物体/没抓住。这种设计符合人类直觉但对强化学习的优化过程非常不友好。原因在于策略梯度和TD误差都依赖奖励信号来传播。拿DQN举例Q值的更新公式是[ Q(s,a) \leftarrow Q(s,a) \alpha \left( r \gamma \max_{a} Q(s,a) - Q(s,a) \right) ]如果r大部分时间都是0整个回放池里几乎没有正向反馈Q网络只能学到所有动作都没区别策略随机游走。环境探索得越多、步数越长这个问题越严重。传统解法无非几种奖励塑形reward shaping、课程学习curriculum learning、内在好奇心驱动curiosity-driven exploration。但每一种都有代价。奖励塑形最大的坑是工程实现极其容易引入钻空子行为——智能体找到一条奖励路径但完全没完成任务课程学习需要人工设计任务难度序列成本高好奇心机制则可能让智能体沉迷于环境中不可控的随机事件。2.2 HER看问题的方式不一样HER干脆不碰奖励函数设计转而从数据下手。它的核心观察是一个episode虽然没达成目标 (g)但它达成了某个实际状态 (s_{achieved})。如果把这个实际状态当成目标这个episode就是一次成功的示范。举个例子。机械臂要从起点抓起红色方块放进蓝色筐里一次尝试中红色方块没被抓起但机械臂在运动过程中意外碰到了一个绿色方块。按传统写法这个episode没有任何价值按HER的写法先把目标临时改成碰绿色方块再看这个episode里某个时刻确实碰到了绿色方块——恭喜生成了一条成功经验。这就是事后聪明起作用的地方失败样本不是垃圾只是对当前目标失败而已换一个目标它就是宝贝。2.3 HER适用的任务类型不是所有任务都适合用HER。它能解决的问题必须满足一个条件目标是可以从状态里直接观测的且存在多个可能的达成目标。典型适合的场景机械臂抓取/摆放目标 物体最终位置点导航/地图寻物目标 智能体到达的目标坐标推箱子/推物体目标 物体最终被推到的位置多目标合成任务如把A放到B再把C放到D不适合的场景单目标二值任务比如二元分类因为没有多样化的其他目标可供重标注——成功就是成功失败就是失败你想怎么伪造成功所以拿到一个新任务先问自己三个问题目标能用连续值或离散值表达吗状态里能观测到实际达成的目标吗存在不止一个合理目标吗三个都满足HER立刻可以上。3. 核心机制拆解目标重标注是在干什么3.1 一个episode的数据结构先定义清楚问题。设每次尝试有一个目标 (g)策略 (\pi(a|s,g)) 与环境交互 (T) 步记录一条轨迹[ (s_0, a_0, s_1, a_1, \ldots, s_{T-1}, a_{T-1}, s_T) ]每一步还有一个当步实际达成的目标状态 (s_{t}^{achieved})。比如在网格寻物环境里(s_{t}^{achieved}) 就是当前时刻智能体所在的坐标。经典经验回放池存的是 ((s_t, a_t, r_t, s_{t1}, g))其中奖励 (r_t f(s_{t1}, g))。当没有达到目标时所有r都是0回放池里全是平的信号。3.2 重标注的完整过程HER对每个episode做两件事第一用原始目标正常生成一条轨迹存进回放池该是0就是0该是1就是1。第二从轨迹中选取一个伪目标 (g)把它替换掉原始目标重新计算每一步的奖励并存入回放池。伪目标通常直接选用轨迹里某个时间点实际达到的状态[ g s_{k}^{achieved}, \quad k \in [t1, T] ]这样对于第 (t) 步的状态动作对 ((s_t, a_t, s_{t1})) 来说如果 (k t1)那么伪目标就是它下一步实际到达的状态奖励直接变为1——一条原本没奖励的转移摇身一变成了成功转移。伪目标选择的时间点不同生成的训练数据质量也不同。按照原论文的对比常见四种策略策略伪目标来源特点final取episode最后一步实际达到的状态最常推荐稳定性好适用面广future从当前时间步之后的任意状态里随机选更丰富常见选择覆盖率好random从整条轨迹任意状态里随机选时序关系弱效果一般episode从回放池其它episode里随机选变化大容易引入噪声我实际测试下来最容易稳定出效果的是future并且采样时只从当前时间步之后的若干个状态里选。原因也很好理解未来的状态是当前动作之后会去的地方用它们做伪目标天然构造出了做完这个动作就达成了那个目标的因果关系学习信号最真实。final只取了终止状态数据多样性差一点但在很多环境里也够用。3.3 重标注后的奖励怎么算设原始奖励函数 (f(s, g)) 对目标进行二值判断。重标注后新的奖励为[ rt f(s{t1}, g) ]在二值稀疏环境下这意味着很多原本为0的(r_t)变成了1。回放池里的正样本比例会显著提高Q网络不再是一潭死水TD误差开始有明显的高低起伏梯度更新就有了方向。但必须要提醒一句重标注之后网络学到的Q值是针对伪目标的条件Q值不是(s,a)-原始目标价值的直接映射。那这个合法吗合法。因为网络输入本身就包含目标(g)目标变了任务定义也随之改变状态动作对的语义是闭环的——网络实际上学到的是给定任意目标当前状态动作对达成该目标的能力这正是通用策略universal policy的思想。原论文里专门有实验证明用HER训出来的策略可以直接泛化到训练分布外的目标上。这就是HER能在机械臂任务上大放异彩的根本原因它把一次尝试同时当成了对多个目标的学习样本样本效率成倍提升。4. 实战从零搭一个HER训练pipeline4.1 网格环境设计为了不引入额外的仿真复杂度我直接在二维网格上验证HER。环境是一个10x10网格智能体每秒可以向上/下/左/右移动一格目标位置每次episode随机生成也是网格上的一个坐标。观测是拼接后的向量[agent_x, agent_y, goal_x, goal_y]。4个离散动作。每步奖励到达目标则为1否则为0。episode最大步数50。目标条件策略用一个小型MLP PPO做aciton输出。定义环境import numpy as np import torch import torch.nn as nn import random from collections import deque GRID_SIZE 10 MAX_STEPS 50 class SimpleGridEnv: def __init__(self): self.grid_size GRID_SIZE self.reset() def reset(self): self.agent_pos [random.randint(0, self.grid_size-1), random.randint(0, self.grid_size-1)] self.goal [random.randint(0, self.grid_size-1), random.randint(0, self.grid_size-1)] # 避免初始就达成目标 while self.agent_pos self.goal: self.goal [random.randint(0, self.grid_size-1), random.randint(0, self.grid_size-1)] self.steps 0 return self._obs() def step(self, action): # 0: up, 1: down, 2: left, 3: right if action 0: self.agent_pos[1] min(self.grid_size-1, self.agent_pos[1] 1) elif action 1: self.agent_pos[1] max(0, self.agent_pos[1] - 1) elif action 2: self.agent_pos[0] max(0, self.agent_pos[0] - 1) elif action 3: self.agent_pos[0] min(self.grid_size-1, self.agent_pos[0] 1) self.steps 1 achieved self.agent_pos.copy() done (achieved self.goal) or (self.steps MAX_STEPS) reward 1.0 if (achieved self.goal) else 0.0 return self._obs(), reward, done, achieved def _obs(self): return np.array([self.agent_pos[0], self.agent_pos[1], self.goal[0], self.goal[1]], dtypenp.float32)这里每一步step返回的实际达成状态achieved就是后面HER重标注要用到的关键数据——没有它HER就是无米之炊。4.2 HER采样的实现细节具体实现HER时我建议单独写一个HERBuffer类不要在训练循环里临时拼逻辑否则后面调参很痛苦。核心就是两步正常存入一个episode的完整轨迹然后做重标注再存一遍。class HERBuffer: def __init__(self, capacity20000, future_k4): self.buffer deque(maxlencapacity) self.future_k future_k def add_episode(self, episode_transitions, achieved_states): episode_transitions: 原始episode的转移每项为 (obs, action, reward, next_obs, done)其中obs包含goal信息 achieved_states: 每一步的实际达成状态list for t, trans in enumerate(episode_transitions): self.buffer.append(trans) # HER: 为每条转移额外生成 future_k 个伪目标样本 future_idx t 1 random.randint(0, len(achieved_states) - t - 1) for _ in range(self.future_k): if future_idx len(achieved_states): future_idx t 1 goal achieved_states[future_idx] obs_t trans[0] # 重构成带伪目标的obs her_obs np.array([obs_t[0], obs_t[1], goal[0], goal[1]], dtypenp.float32) # 重算奖励 her_reward 1.0 if (trans[3][0:2] goal).all() else 0.0 her_next_obs np.array([trans[3][0], trans[3][1], goal[0], goal[1]], dtypenp.float32) self.buffer.append((her_obs, trans[1], her_reward, her_next_obs, trans[4]))这段代码里有几个值得注意的点第一future_k是每一条原始转移额外生成的伪目标样本数。设为4意味着回放池的容量增长速度是原来的5倍。样本量多了训练步数不变的话每个epoch看到的batch更多收敛速度自然更快。但future_k不是越大越好太大会让回放池里原始目标样本占比很低策略会偏向优化伪目标而忽略真实目标。我测过4到84就已经够用。第二伪目标的重标注要和obs的拼接格式严格对齐。我踩过一次坑obs里前两维是智能体坐标后两维是目标坐标重标注时坐标顺序错了模型看到的目标是乱的跑了一晚上loss降了但环境测试成功率是0。排查了两天才发现是维度顺序问题。4.3 策略网络和PPO训练循环策略网络我用了最简单的一层编码器加两层MLPclass GoalConditionedPolicy(nn.Module): def __init__(self, input_dim4, action_dim4): super().__init__() self.net nn.Sequential( nn.Linear(input_dim, 128), nn.ReLU(), nn.Linear(128, 128), nn.ReLU(), nn.Linear(128, action_dim) ) def forward(self, obs): logits self.net(obs) return torch.softmax(logits, dim-1) def act(self, obs, greedyFalse): obs_t torch.FloatTensor(obs).unsqueeze(0) probs self.forward(obs_t) if greedy: return torch.argmax(probs[0]).item() dist torch.distributions.Categorical(probs[0]) return dist.sample().item()训练循环用PPO更新每次从HERBuffer里采一个batch计算损失并更新。关键超参数如下每次采样batch大小1024PPO剪裁系数0.2每个episode后用当前策略重新采样马上洗进buffer每collect 10个episode做一次PPO内循环更新10个epoch每个epoch采4个batch学习率3e-4最大训练步数50万这里有一个跟普通RL训练不一样的习惯HER的replay buffer需要持续包含新鲜的episode数据不要做成offline模式一次性填满否则伪目标分布和当前策略分布会发生严重漂移。我采用的是边采样边训练模式每新采集一批episode就立刻做更新这样策略、环境和回放池三者的分布始终是接近的。4.4 训练效果对比同样的PPO参数下我分别跑了普通回放池不加HER和HER回放池各训练30万步。普通回放池前10万步成功率基本是020万步之后才开始有点懵懂的上升30万步时成功率大概在15%左右。训练曲线非常震荡稍微调一下学习率就可能直接崩掉。HER回放池5万步成功率就到40%15万步到80%20万步稳定在90%以上。最直观的差异是训练初期不再冷启动——因为回放池里有很多伪目标成功样本即使原始目标一个都没达成策略也已经能掌握移动到目标附近然后走过去的基本技能。这个对比不是我在吹原论文公开的实验数据里HER在多个机械臂环境下对比PPO、DDPG的样本效率优势也是这样量级的。5. 实际调参踩过的坑和排查实录5.1 HER和普通策略梯度结合时的样本倾斜问题最容易踩的坑就是HER样本和原始样本的比例失衡。如果你直接把所有episode都做了future重标注回放池里原始数据的占比会降得非常严重策略可能出现一个诡异的现象在伪目标下表现很好换到真实目标测试成功率却不高。我的解决方式很简单在存回放池时原始转移永远保留一份重标注样本另外每一条生成一份然后训练时从buffer里采样确保原始样本占比不低于20%。如果不做这层保护目标分布会被伪目标污染。用网格环境试过纯HER只存重标注样本50万步能达到70%的成功率但比混合模式慢很多而且后期波动大。5.2 连续动作空间的奖励应该怎么算上面的网格环境是离散动作、二值奖励。换到连续控制任务比如模拟机械臂HER同样适用但奖励函数的计算要稍微改一下。二值奖励在连续状态上有一个问题伪目标恰好等于实际状态的概率几乎为0奖励永远是0重标注就失效了。正确做法是引入一个距离阈值。比如二值化函数写成def sparse_reward(achieved, goal, threshold0.05): return 1.0 if np.linalg.norm(achieved - goal) threshold else 0.0或者干脆用带距离的稠密奖励但加上单位缩放def dense_reward(achieved, goal): return -np.linalg.norm(achieved - goal)我实际测试下来连续控制场景里阈值二值奖励比距离稠密奖励更好训。原因是里程计误差和动作噪声会让距离奖励产生大量抖动Q值的方差上升而HER的伪目标已经保证了正样本覆盖不需要再靠稠密奖励提升信号密度。每组阈值要结合任务的尺度调整。机械臂任务里关节空间的距离阈值和笛卡尔空间的距离阈值完全不是一个量级直接用固定阈值会出问题。5.3 future采样指数选错导致训练数据全是未来有个细节我必须强调future策略中从当前时间之后的节点里均匀采样和只从最后一个节点采样效果天差地别。我在一个推方块任务上试过只从final节点采样。直觉上觉得取最终位置最安全但实际发现训练中期开始出现严重的过拟合倾向策略记住了几个固定终点遇到新的目标位置就在原地转。原因在于final伪目标只能提供最终状态附近的奖励信号对中途各阶段的状态覆盖大量缺失Q函数在尚未探索到的区域没有梯度。改成future采样限制在t1到t15的窗口内随机选之后问题立刻缓解。这也侧面说明HER不适合简单粗暴地把每条轨迹终点的state当成goal重标注的粒度要匹配环境的时间尺度。5.4 网络不收敛排查清单如果你也遇到HER训练loss乱蹦或者成功率长期不涨按这个顺序排查现象大概率原因解决办法loss一直降但成功率接近0obs里目标维度顺序错误打印几条obs核对坐标值训练前期有提升后期骤降buffer里旧数据占比过高清理过旧episode限制buffer中样本来源周期成功率达到瓶颈如85%future_k过大伪目标太杂调小future_k到4或2动作全都一样、策略退化PPO的entropy系数过大或过小0.005到0.01之间调整网格任务我最终用0.01训练初期成功率不为0但提升极慢伪目标采样窗口太小把窗口从[t1, T]放宽为[t1, min(T, t50)]5.5 一个容易被忽略的调试技巧强烈建议在训练过程中记录一个额外指标HER样本中奖励为正的比例。这个数字能直观反映重标注的效果。随机策略下伪目标正样本比例通常在10%-20%之间如果明显偏低多半是状态空间太大、轨迹太少、伪目标太稀疏。把比例可视化之后你可以清楚地看到训练过程中这个数怎么变化——它应该和成功率呈正相关关系。如果正样本比例一直很低但成功率还在涨说明策略在靠非HER样本硬学这时候需要增加future_k了。6. 实战体会和扩展方向到这里整个HER机制就讲完了。最后说一点我对这个方法的整体感觉HER最大的价值不是提高几个百分点的成功率而是彻底改变了RL训练中数据的使用方式——之前是成功才是资源现在是任何尝试都能变成资源。这种思路放到真实机器人场景里特别重要因为真实系统的每一个动作轨迹都昂贵且不可再造多一位价值是一位的逻辑才是落地时真正需要的。下一步我打算把HER从网格环境迁移到更高维度的视觉输入场景比如用目标物体的图像特征替代坐标向量作为goal。那种情况下伪目标不再是一个三维坐标而是一张图像数据量会大得多但HER的基本逻辑完全不变。另外也可以尝试把HER和contrastive representation learning结合起来用目标表征自动提取s和g的相似度省掉手工设计距离阈值的过程。如果你手头也有一个稀疏奖励训不动的任务先把目标形式理清楚再按上面的pipeline跑通大概率第一版就能看到成效。踩过我踩过的坑之后基本上HER在你的工具箱里就是常备武器了。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →