ARTICLE DETAIL

资讯详情

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

HER算法全解:事后经验回放如何突破强化学习稀疏奖励困境

HER算法全解:事后经验回放如何突破强化学习稀疏奖励困境 hindsight 这个词翻译过来就是“事后诸葛亮”——事情结束之后回头看一切都清清楚楚可在过程里你就是抓不住那个关键点。我第一次认真对待这个词不是因为哲学讨论而是因为在强化学习里有一篇论文直接拿它当算法名Hindsight Experience Replay也就是常说的 HER。今天想写的东西就是围绕这个算法展开的。它做的事情可以用一句话概括让智能体从“失败”里照样学到东西把那些没达成的目标重新解释成“另一种成功”。这听起来有点绕但实际效果非常猛尤其适合奖励稀疏、目标明确、探索困难的场景。如果你正在做机器人控制、游戏 AI或者研究 goal-conditioned RL这篇文章能帮你省下不少自己踩坑的时间。1. hindsight 这个词在算法世界里是一套完整方法论1.1 先聊聊“事后”这件事本身“事后”在日常生活里通常是贬义谁都不喜欢马后炮。但做强化学习的人会发现这份“事后的信息”其实是无价之宝。你可以回想一下自己学骑自行车的经历你摔了无数次每次摔倒的目标都是“不摔”但从来没成功过。如果严格按照强化学习的逻辑这些尝试全都拿不到正奖励你根本没法学到任何东西。可实际上你学会了。原因很简单你每次摔倒后身体会记录下“刚才那个重心偏移的角度、车把扭转的幅度、速度的变化”然后这些信息被用来修正下一次的平衡动作。换句话说你虽然没有完成“不摔”这个目标但你把每一次摔倒都变成了“练习如何调整重心”的样本。HER 的核心哲学就是这样。它不关心你原本想干什么它关心你实际干成了什么。只要轨迹里已经出现了一个状态那这个状态就可以被当作目标重新写进经验里让这些原本被判为“失败”的数据变成可学习、可复用的成功样本。我第一次在论文里看到这个 idea 的时候第一反应是“这也太取巧了”。但仔细想下来它的巧妙之处恰恰在于它没有改变环境也没有改变奖励只是改变了我们对过去数据的解读方式。这种“重新解读历史”的思路比强行设计复杂奖励函数要优雅得多。1.2 HER 到底解决了什么问题众所周知强化学习里最难的一类问题就是稀疏奖励。什么叫稀疏奖励就是大多数时间你什么都得不到一旦完成某个状态就突然给一个大奖励。举个例子机械臂要把物体推到桌子上一个绿色标记点从起始位置到目标位置中间每一步动作的奖励都是 -1只有最后一步正好把物体推到标记点上才算成功。这时候你让智能体随机探索它要碰多少次才能让物体恰好停在标记点如果物体位置是连续的概率趋近于零。DDPG、PPO 这些常见算法在纯稀疏奖励下几乎学不动因为随机探索的成功率太低根本产生不了足够的正样本价值网络就只能往一个“永远失败”的乐观估计里打转。HER 的思路是我根本不需要真的成功。假设这轮 episode 里机械臂把物体推到了点 A虽然 A 不是我们想要的目标点但我完全可以再生成一条经验把目标改成 A。对这条新经验来说物体最后就在 A 上这是一段“成功轨迹”。从数学上看这相当于是对每个环境交互样本做了目标重标记goal relabeling然后把重标记后的数据丢进 off-policy 的经验池里。这样做的直接效果是原本 99% 的失败轨迹通过重标记之后有相当一部分会变成“成功达成新目标”的正样本。价值函数就有了可学习的梯度策略也能从这些伪成功经验中逐渐逼近真实目标。要理解 HER 的价值得知道它不是靠增加探索效率来解决问题而是靠提高每一次探索的数据利用率。它把你已有的轨迹反复榨干一个样本不仅为原来的目标服务还同时为多个可能目标服务。这种数据复用思想在样本昂贵、仿真速度慢的实际场景里尤其宝贵。2. HER 的核心思路与设计拆解2.1 一个关键换位把“没做到”当成“本来就想做”HER 最重要的操作就是换目标。具体来说一局 episode 跑完之后系统会通过某种策略为这条轨迹中的每一个 transition 生成新的目标然后把新目标、新的奖励、新的终止标志重新组合成一个新样本存进 replay buffer。我们拿一个二维导航任务来举例环境里有一个点起点在 (0, 0)目标在 (10, 10)动作是向上下左右移动一格。智能体跑了 20 步最后停在了 (3, 5)。原始轨迹里每一步的奖励都是 -1因为没到达 (10, 10)。但 HER 会这么做把目标改成 (3, 5)然后重新计算每一步的奖励。由于轨迹最后确实停在了 (3, 5)最后一步的奖励就变成 0整条轨迹被重新标记为“成功”。这个操作听起来像是在自欺欺人但仔细想想如果智能体从某个状态出发未来真的能够到达某个特定状态那这个状态对智能体来说就是可达的。我们把它当作目标等于告诉价值函数“这样的状态转移是能够成功完成的”。当大量这样的伪成功样本被喂进去之后价值函数对“达到附近状态”的可能性估计会越来越准确策略也就能从一个“只知道乱走”的状态慢慢变成一个“知道怎么往某类目标靠近”的状态。需要注意的是HER 不只是把最后状态拿来当目标。它还要求你保留原始目标的一部分经验否则智能体会忘掉“我真正想要什么”。一般来说每条真实 transition 会同时生成原目标经验和多条重标记经验比例大概在 1:4 左右这样既能保证探索方向不丢又能给学习提供足够多的正信号。2.2 目标重标记的四种策略推荐哪一个论文里给出了四种目标重标记策略名字都很直观策略做法特点final只用轨迹最终状态作为新目标最简单通常效果也不差但会浪费轨迹中段的信息future从当前时间步之后的某个状态中随机选一个作为新目标论文推荐兼顾后验可达性和多样性episode从整条轨迹中任意选一个状态包括过去的状态作为新目标多样性最高但可能选到“已经经历过、未来不一定能再回到”的状态random从整个经验池里随机挑一个状态作为新目标相当于全局采样目标可能完全不可达噪音大我实际跑下来大部分任务用 future 就够了。原因很朴素future 策略选的目标一定在当前轨迹的未来时间步出现过也就是说按照这条轨迹的走法智能体确实到过那里。这让“从当前状态出发能到达目标”这个结论有了实打实的轨迹支撑学起来自然更稳。episode 策略看起来样本多样性更高但它会把较早时刻的状态当作目标重标记到较晚的 transition 上这种目标在物理上可能是“绕回去”的路在没有明确可逆性的环境中会产生额外噪音。random 策略我基本只在小规模实验里玩过实用性一般。2.3 为什么 HER 必须搭 off-policy 算法如果你尝试把 HER 直接塞进 PPO 里大概率会感觉不对劲因为 PPO 这类 on-policy 算法使用当前策略采样的数据用完一轮就扔掉而 HER 的核心恰恰是对过去的经验反复重标记、反复采样。这两个设计哲学是冲突的。Q-learning、DDPG、TD3、SAC 这些 off-policy 算法允许智能体用一个旧经验池反复更新才给 HER 留出了“二次加工”的空间。HER 本质上是一种经验增强手段它要求我们的学习算法能够容忍“行为策略和目标策略不一致”的情况。重标记之后一条经验的 action 可能是在原目标下采样出来的而我们现在把它当作新目标下的经验来用这是典型的数据分布偏移只有 off-policy 方法才能兜住这种偏移。所以在确定技术方案时先别急着把 HER 和各种“看起来很先进”的 on-policy 框架硬凑。老老实实选一个 off-policy base比如 DQN、DDPG、SAC把你的目标任务改成 goal-conditioned 形式HER 才能真正发挥作用。3. 手写一个 HERBit Flipping 环境全流程复现3.1 环境定义一个能“卡死”普通 DQN 的稀疏奖励任务要复现 HER最合适的就是论文里的 Bit Flipping 环境。这个环境简单但极其典型有一个长度为 n 的二进制向量动作是翻转其中某一位目标是让向量达到某个特定的二进制串。当状态等于目标时奖励 0否则每步奖励 -1。当 n8 时状态空间是 256随机策略下每一步碰到正确目标的概率极低普通 DQN 基本学不动非常适合用来观察 HER 带来的质变。下面是我常用的环境实现用 NumPy 写不依赖 gym方便直接测试import numpy as np class BitFlipEnv: def __init__(self, n8, max_stepsNone): self.n n self.max_steps max_steps or n * 4 self._rng np.random.default_rng() def reset(self): self.state self._rng.integers(0, 2, sizeself.n).astype(np.float32) self.goal self._rng.integers(0, 2, sizeself.n).astype(np.float32) self.steps 0 return self.state.copy(), self.goal.copy() def step(self, action): self.state[action] 1.0 - self.state[action] self.steps 1 achieved self.state.copy() reward 0.0 if np.array_equal(achieved, self.goal) else -1.0 done reward 0.0 timeout self.steps self.max_steps return self.state.copy(), achieved.copy(), reward, done, timeout这里有个细节值得说我没有把超时当作 done 返回。超时只是 episode 终止不代表马尔可夫决策过程终止如果把 timeout 当成 done重标记时会把大量未完成任务误判成终局Q 值估计就会出现偏差。在一些框架里用 truncated 字段单独表示逻辑是一样的。3.2 Replay Buffer 与目标重标记核心实现HER 的 replay buffer 跟普通 replay buffer 最大的区别是它需要额外保存 achieved goal 信息并且在采样之前完成重标记。我的实现方式是在一条轨迹结束时立即为每个 transition 生成若干重标记样本再统一插入经验池。import random from collections import deque class HERBuffer: def __init__(self, capacity100_000, k4): self.buffer deque(maxlencapacity) self.episode [] self.k k def flush_episode(self): n len(self.episode) for i, t in enumerate(self.episode): # 原始目标保留一份 goals [t[goal]] # future 目标从当前时刻之后的未来状态里采样 k 个 future_idx list(range(i, n)) sampled random.sample(future_idx, min(self.k, len(future_idx))) goals [self.episode[j][achieved] for j in sampled] for g in goals: reward 0.0 if np.array_equal(t[next_achieved], g) else -1.0 done reward 0.0 self.buffer.append(( t[obs], g, t[action], t[next_obs], reward, done )) self.episode []我在这个实现里让 future_idx 从 i 开始也就是允许选当前时刻的 achieved 作为目标。如果目标设为当前时刻状态那么转移的下一步正好到达该目标奖励为 0done 为 True这是合法有效的成功样本。如果希望更严格一点可以从 i1 开始避免“原地成功”的偏差。实测区别不大但对连续控制任务建议用严格 future因为连续空间里当前状态和目标状态几乎重合会产生很多无信息量的样本。3.3 DQN HER 训练主循环有了环境、有了 buffer接下来就是把 DQN 和 HER 拼起来。网络输入是 obs 和 goal 的拼接输出是每个离散动作的 Q 值。Bit Flipping 的动作空间就是翻转位索引输出维度就是 n。import torch import torch.nn as nn import torch.optim as optim class MLP(nn.Module): def __init__(self, obs_dim, action_dim, hidden256): super().__init__() self.net nn.Sequential( nn.Linear(obs_dim * 2, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, action_dim) ) def forward(self, obs, goal): x torch.cat([obs, goal], dim-1) return self.net(x)然后看训练主循环的核心部分。我在这里简化了一些常规技巧比如目标网络软更新但保留了和 HER 强相关的全部逻辑env BitFlipEnv(n8) buffer HERBuffer(capacity100_000, k4) net MLP(obs_dim8, action_dim8) target_net MLP(obs_dim8, action_dim8) target_net.load_state_dict(net.state_dict()) optimizer optim.Adam(net.parameters(), lr1e-3) def select_action(obs, goal, eps): if random.random() eps: return random.randint(0, env.n - 1) with torch.no_grad(): q net( torch.tensor(obs).float().unsqueeze(0), torch.tensor(goal).float().unsqueeze(0) ) return q.argmax(dim1).item() for episode in range(3000): obs, goal env.reset() done False while True: action select_action(obs, goal, eps0.3) next_obs, next_achieved, reward, done, timeout env.step(action) buffer.episode.append({ obs: obs, goal: goal, action: action, next_obs: next_obs, next_achieved: next_achieved }) obs next_obs if done or timeout: buffer.flush_episode() break if len(buffer.buffer) 512: for _ in range(40): batch random.sample(buffer.buffer, 256) obs_b torch.tensor(np.array([t[0] for t in batch])).float() goal_b torch.tensor(np.array([t[1] for t in batch])).float() act_b torch.tensor(np.array([t[2] for t in batch])).long().unsqueeze(1) next_obs_b torch.tensor(np.array([t[3] for t in batch])).float() rew_b torch.tensor(np.array([t[4] for t in batch])).float().unsqueeze(1) done_b torch.tensor(np.array([t[5] for t in batch])).float().unsqueeze(1) q net(obs_b, goal_b).gather(1, act_b) with torch.no_grad(): max_next_q target_net(next_obs_b, goal_b).max(dim1, keepdimTrue).values y rew_b (1 - done_b) * 0.98 * max_next_q loss nn.MSELoss()(q, y) optimizer.zero_grad() loss.backward() optimizer.step() target_net.load_state_dict(net.state_dict())这个代码最要紧的一行在目标计算那里goal_b是从经验池里取出来的重标记目标目标网络的计算必须使用同一个 goal而不是原始 episode 的 goal。一旦这里用错整个训练就乱套了。3.4 训练结果怎么观察在 n8 的 Bit Flipping 上普通 DQN 稀疏奖励通常几十万步成功率还是 0因为随机可到达目标的概率太低。HER 的翻盘点大概出现在几万步之后随后成功率会稳定上升2000 个 episode 左右已经能到 80% 以上训练曲线非常典型前段平缓然后突然抬升看起来就像“顿悟”。我建议你看两个指标。第一个是最近 100 个 episode 的成功率这个是最直观的第二个是经验池中“重标记后成功样本”的占比。在训练前期这个比例往往能达到 10%~20%这就是 HER 能学起来的燃料。如果你发现重标记样本几乎全是失败样本那说明目标生成策略有问题或者环境本身的时间步太短轨迹里根本没有可达状态可供选择。不要只盯 loss。稀疏奖励场景下TD loss 下降很快不代表策略变好因为这个 loss 往往是被大量“无信号样本”稀释出来的。我的习惯是每训练 50 个 episode 打印一次成功率直接看行为表现。4. 实操中的坑与调参心得4.1 重标记之后reward 和 done 必须同步重算这是 HER 实现里最容易出 bug 的地方。很多新手只改了 goal忘记重新计算 reward结果就是价值网络拿着“错误的标签”做回归训练出来的 Q 值既不符合旧目标也不符合新目标越训越离谱。更隐蔽的是 done 不同步原本这步没完成目标done 是 False重标记后目标变成了某未来状态而当前 transition 的 next_state 恰好等于那个未来状态这步其实已经成功了done 应该改成 True。我的经验是把“对重标记后的 transition 计算 reward 和 done”封装成一个独立函数不要散写在代码各个角落。每次重标记生成样本时只调用这个函数避免出现“这里改了 reward那里忘了改 done”的尴尬情况。def relabel_reward_done(achieved, goal): reward 0.0 if np.array_equal(achieved, goal) else -1.0 done reward 0.0 return reward, done4.2 状态与目标拼接以及网络结构细节HER 的输入处理看似简单实际上也有讲究。Bit Flipping 里状态和目标都是二进制向量直接拼接问题不大。但在连续控制任务里obs 和 goal 的尺度可能完全不同obs 是速度、关节角goal 是末端位置两者直接拼起来会导致网络前期被尺度大的维度主导。我常用的做法是分别归一化。比如手臂速度的范围可能在 [-5, 5]目标是物体位置范围可能是 [0, 1]如果直接拼速度维度上的一点噪声就会淹没目标的梯度。在实践中我会让 obs 和 goal 各过一个 LayerNorm或者分别统计均值和方差做标准化。网络结构方面两层 256 宽度的 MLP 在大部分仿真任务里已经足够过深的网络反而更容易崩溃。还有一个小细节如果使用 DiagGaussian 策略网络像 SAC/DDPG 那样输出动作均值和对数方差重标记目标的分布变化会让方差估计很不稳定需要在策略更新时适当限制目标网络更新速率或者加一点动作熵正则。4.3 超参数速查与调参顺序HER 本身有一个关键参数重标记数量 k也就是每条原始 transition 额外生成多少个 future 目标。论文里用 k4 是一个合理的起点太小了重标记样本不够太大则会导致原始目标经验占比过低策略会变成“到处都能去但不知道去哪”。我建议在 k1 到 k8 之间做一轮快速搜索。还有个容易被忽略的点是 replay buffer 容量。HER 严重依赖经验池的覆盖度如果 buffer 太小重标记出来的目标多样性就差训练会停滞。我通常把 buffer 容量设置成整个训练总样本量的 30%~50%或者至少能装下 10 万条 transition。下面是几个我常用的初始值参数推荐值备注kfuture 目标数4每条 transition 额外生成 4 个重标记样本原始目标保留比例20%1/(1k)保证不丢失真实目标任务batch size256偏大一些更稳gamma0.98稀疏奖励任务可适当降低目标网络更新周期100~500 步太频繁会增加震荡学习率1e-3DQN3e-4连续控制通用安全范围调参顺序我建议是先固定一个可跑通的基线然后优先调 k再调 buffer 容量最后才是学习率。很多人一上来先动学习率一改就几个数量级结果实验变成了随机游走反而看不出哪个维度的影响。4.4 什么情况下用了 HER 也可能无效HER 不是万灵药。我自己就撞过几次南墙总结下来有三类场景要特别警惕。第一类是“目标空间和状态空间不对齐”。如果目标是文字指令状态是视觉像素HER 的 future 重标记根本不知道该拿哪个视觉状态去对应“把红球放在桌上”这个语义盲目标记只会制造大量虚假经验。这种情况需要先做目标表征比如把语言指令嵌入到状态空间中再考虑 HER。第二类是“环境随机转移噪音过大”。假设机械臂每次执行同一个动作物体落点都完全不同那么“曾经到达过某状态”并不能说明“从当前状态能到达该状态”future 重标记的物理有效性就会被削弱。这种情况下要先降低环境噪音或者改用更保守的多步模型。第三类是“奖励函数其实不稀疏但你没意识到”。HER 的价值在于解决稀疏奖励如果你的环境已经有比较密集的 shaping reward硬上 HER 反而可能引入目标分布的偏移破坏原有价值函数。先检查一下原始 reward 是不是真的非 0 即 -1再决定要不要加 HER。5. HER 的进化版本与应用边界5.1 从仿真环境到真实机械臂HER 最火的应用场景是机器人操作。OpenAI 的 Fetch 系列环境FetchPush、FetchPickAndPlace、FetchSlide就是专门为 HER 这类算法设计的 benchmark。在这些环境里状态通常包含夹爪位置、物体位置、速度等信息目标是“把物体推到一个指定位置”。HER 的 future 重标记在这里效果很好因为物体实际的轨迹本身就提供了一堆“可达目标”学起来非常自然。真实机械臂场景里HER 需要考虑更多工程问题。仿真环境可以随时重置真实机械臂不行而且真实控制频率低一条轨迹的代价很高。我的建议是先在仿真里用 HER 练出一个带域随机化的策略再迁移到真机。重标记目标时最好把夹爪的抓取状态也纳入 achieved goal否则会出现“物体推到位了但夹爪根本没靠近”的奇怪样本。5.2 后续改进方向HER 之后学界有一系列跟进工作。比较有代表性的包括把课程学习和 HER 结合从简单目标开始逐渐提高难度在重标记时用 value function 筛选更有信息量的目标而不是完全随机采样还有把 HER 的思想扩展到离线强化学习直接对已有的静态数据集做目标重标记。这些方向本质上都是在回答同一个问题如何自动、高效地选择“事后目标”。随机选未来状态虽然简单但不是最优的如果能知道哪些目标对当前价值函数的修正量最大重标记的性价比还能进一步提升。做工程应用时没必要一上来就追新方法把标准 HER 吃透、调顺大多数任务已经能扛住。5.3 三个问题判断你的任务需不需要 HER如果你拿不准一个任务能不能用 HER我建议直接用三个问题来过滤第一任务是否有明确的目标条件比如目标是物体位置、二进制状态、赛道终点这类目标很容易和状态对应适合 HER。如果目标是“让画面更像梵高的画风”这种模糊描述就别硬套。第二奖励是否稀疏到几乎学不动跑一个普通 off-policy baseline如果几万步成功率一动不动那 HER 值得一试。如果 baseline 已经能正常学习HER 带来的提升有限还会增加实现复杂度。第三你能否拿到一个覆盖度足够的经验池换句话说你愿意跑大量 episode 并把所有轨迹都存起来吗HER 需要足够多的轨迹来提供重标记素材样本太少等于巧妇难为无米之炊。这三个问题全通过HER 大概率能给你带来显著提升。6. 我的个人使用体会分享一个我自己的实操感受。最开始我在一个类似 FetchPush 的任务里用 HER总是不收敛后来发现是 future 目标采样太“贪心”了——每次都是从轨迹末端附近选目标结果重标记出来的全是“靠近终点”的样本前期策略还没学会怎么从起点出发就被一堆终局样本带偏了。后来改成限时采样只从当前时刻往后 3 步范围内的状态里选目标训练立刻变得稳定。这给我的启发是HER 的参数和策略不是一成不变的关键要看当前策略的学习阶段。训练前期适合选近处目标教智能体“怎么走”训练后期适合选远处目标教智能体“怎么规划”。如果你也遇到 HER 更新了但策略没有明显提升的情况不妨先检查一下重标记目标在时间上的分布。这个细节论文里没有特别强调但实际调起来非常关键。
返回列表