
hindsight 这个英文词直译过来是“后见之明”说通俗点就是“事后才看明白”。我第一次接触到由 OpenAI 团队在 2017 年提出的 Hindsight Experience ReplayHER事后经验回放算法时就被它朴素的哲学打动了既然强化学习智能体在稀疏奖励环境下很难偶然撞到目标那为什么不把每一次失败的轨迹都包装成一次“朝某个目标靠近”的成功经验来学习这篇文章就围绕 hindsight 展开。我会先讲清楚稀疏奖励为什么是强化学习的老大难再拆解 HER 的工作原理然后用 PyTorch 从零实现一个二维导航任务的 HER 训练 demo最后把踩过的坑、调参心得和适用边界一并整理出来。适合正在做机器人控制、游戏 AI、推荐系统等涉及稀疏奖励场景的同学参考也适合想真正搞懂经验回放机制为什么有效的读者。1. 稀疏奖励困境为什么常规强化学习会卡死1.1 让智能体在“没有反馈”的环境里自学先看一个具体场景机械臂要从桌面上抓一个杯子。在大多数强化学习设定里奖励函数是这样写的——只有当机械臂的手指成功抓住杯子并抬离桌面才给一个 1 奖励其他所有状态都给 0。听起来很合理但问题在于智能体在训练初期完全靠随机策略探索在连续动作空间里它的手指要“瞎猫碰到死耗子”一样正好对准杯子、正好合力夹住、正好抬起来概率低到几乎可以忽略。结果是整个训练过程里智能体几乎收不到任何正奖励策略梯度没有方向Q 值网络学到的输出永远趋近于 0整个模型原地打转。这个问题在学术界有个专门的名字叫“稀疏奖励问题”。很多真实任务都有这个特征下棋要到最后才知道输赢爬楼梯要登顶才算成功导航要到终点才有奖励。奖励太稀疏意味着智能体在探索过程中得不到任何阶段性反馈它不知道自己在变好还是变坏。你可以想象一个人在沙漠里找一片特定的绿洲只有真正踩到绿洲边缘时才知道“对了”在那之前没有任何线索。正常人会在沙漠里瞎走到死强化学习智能体也好不到哪去。1.2 人类的学习本能从失败里找信号但换一个场景真实的人类婴儿是怎么学会抓东西的婴儿伸手去够一个玩具第一次没够到手停在了离玩具还有 10 厘米的地方。这时候没有奖励信号但婴儿的脑子里其实发生了一件事他看到了“我的手现在离目标更近了一点”。下一次他会尝试从这个更近的位置继续伸手。你看人类并不需要一个明确的“1”才知道自己在进步而是会从每一次“差一点”里学到“差多少”“差在哪”。这个视角非常重要在很多情况下失败本身包含着大量可学习的结构化信息。你面试失败了你会复盘答错的问题投篮没投进你会根据球的落点调整力度追公交车没追上你会判断下次要跑快几步。强化学习智能体的问题不在于它不会从失败里学习而在于我们给它的学习机制——经验回放——把失败直接丢掉了。它只存储“状态、动作、奖励、下一状态”奖励是 0意味着这段经验对策略更新没有贡献于是大量探索产生的数据被白白浪费。1.3 hindsight 这个思路解决的核心问题HER 的核心洞察就一句话既然这段轨迹已经发生了我没办法改变结果但我可以改变这段轨迹所对应的“目标”。比如机械臂这次没抓到杯子但它最终到达的位置是某个坐标点那我就把这个坐标点重新定义为“本次任务的目标”。这样一来这段原本奖励全 0 的失败轨迹在重新标记后变成了一条“成功到达目标点”的轨迹奖励变成 1可以被放入经验回放池供策略网络学习。这就是 hindsight 的含义事后回看重新解释。算法不再执着于“唯一的那个目标”而是把“实际发生的状态”当作一个个潜在目标把失败经验转变成成功经验。这个思路听起来简单得近乎作弊但它在多目标强化学习框架里是完全合法的因为训练时我们本来就要告诉智能体“你现在的目标是什么”目标只是条件输入并没有规定目标必须是环境给定的那一个。HER 解决的核心问题通俗说就是“给失败一个说法”。它不改变环境、不改变奖励函数、不增加任何人工设计的中间奖励只是修改了经验存储的方式就让原本无法收敛的稀疏奖励任务变得可训练。这就像一个老师批改作业时不看学生有没有做对标准答案而是看学生做出来的结果符不符合某个合理过程然后告诉他“这道题你的做法是对的方向变了而已”。这恰恰是很多现实问题里我们能从错误中获得价值的方式。2. HER 算法原理把“经验回放”改了一个关键点2.1 先从标准经验回放说起要理解 HER得先熟悉标准的经验回放是怎么工作的。在 DQN、DDPG、TD3 这类 off-policy 算法中智能体与环境交互时每走一步都会产生一个转移组通常记作 (s, a, r, s, done)。这些转移组会被一股脑存进一个固定大小的回放缓冲区。训练时算法从缓冲区里随机采样一小批转移组计算目标值更新 Q 网络。经验回放的意义在于打破样本的时间相关性让网络反复利用已经发生过的数据提高样本效率。这里的关键在于“回放”是对完整转移组的复用。但转移组里的奖励 r 是环境根据“固定的目标”给的也就是说缓冲区里绝大部分 r 都是 0。在稀疏奖励环境下缓冲区就成了一个 99% 都是无效样本的仓库每次采样都像在垃圾堆里翻找能用的零件效率极低。通常的解决办法是给环境设计越来越细的奖励函数比如“距离越近奖励越大”但这需要大量人工调参而且每个任务都要重新设计。HER 不这么干。它不去改奖励函数而是换了一个角度既然环境中本来就有“目标”这个概念那我把目标也作为一个变量存进转移组里存储格式变成 (s, a, r, s, done, g)。g 是这次交互的目标。这样一来经验回放就有了一个新的自由度我可以重新选择 g 的值并且根据新的 g 重新计算 r。2.2 目标重标记一次跨越性的改动HER 里这个操作叫 goal relabeling目标重标记。具体做法分四步智能体按某个目标 g 与环境交互一整条轨迹轨迹里有若干条转移组。轨迹跑完后从轨迹中挑一个或多个实际达到过的状态 s_g。把这个 s_g 当作虚拟目标替换掉原始目标 g。根据虚拟目标重新计算每条转移组的奖励和是否完成然后将这些新转移组存进缓冲区。这个“重新计算奖励”很关键。如果原始奖励是“到达目标则 1 否则 0”那用 s_g 作为目标后轨迹中有一个状态恰好就是 s_g这个状态对应的奖励自然变成 1。甚至不仅一个状态整条轨迹中后续接近 s_g 的状态都可以获得正奖励。所以重标记后的轨迹不再是全 0 奖励而是从 0 逐步过渡到 1 的一条学习信号网络可以从中学到“往这个方向走是对的”。你可以把这个过程理解成拍电影时的“换主角”镜头还是那个镜头动作还是那个动作但字幕里把主角换成了实际出现在画面里的人。电影依然成立而且突然就有了意义。HER 做的事情本质上就是这个——数据本身没变但学习信号从“无意义”变成了“有意义”。它没有提高探索的覆盖率而是提高了已有探索数据的利用率。2.3 虚拟目标怎么选才靠谱四种策略目标重标记说起来简单但“用哪个状态作为虚拟目标”直接决定算法效果。实践中主要有四种策略我把它们放在一起对比策略虚拟目标来源特点final轨迹终点状态最简单但终点状态往往离起点太远学习效率偏低episode本轨迹内随机一个状态比 final 自然样本多样性增加random从所有已见过的状态中随机选覆盖面大但虚拟目标可能与当前轨迹完全无关future从当前时刻之后的未来状态中随机选最实用符合因果顺序学习效果最好未来策略为什么最好因为一条轨迹里智能体在 t 时刻到达的状态大概率与之后某个时刻的状态存在“逐步靠近”的关系。从未来状态里随机挑一个作为目标相当于告诉智能体“你前面走的这段路最终让你接近了某个位置这个位置就是目标。”这保留了一条逻辑上合理的“路径”虚拟目标自然与当前状态有连续性网络学起来更顺。而 random 策略选出的目标可能与当前状态相距甚远重标记后会出现大量“奖励1 但状态根本不在目标附近”的矛盾样本反而干扰训练。在实际工程中一般不会只用某一种策略而是混合使用。比如每条原始转移组先保留一份原目标版本再额外生成 4 份未来策略重标记版本总共给一条经验创造 5 种“视角”。这样既照顾了原始任务又充分利用了失败轨迹。2.4 HER 的完整流程伪代码为了直观我用伪代码梳理一遍 HER 嵌入到 off-policy 算法中的完整流程初始化策略网络、Q网络、回放缓冲区 for 训练轮数: 采样一个目标 g 初始化环境状态 s 收集轨迹: for t in 0, 1, ..., T-1: 根据策略选择动作 a π(s, g) 执行 a得到 s计算奖励 r(s, g) 存储原始转移组 (s, a, r, s, done, g) s s 轨迹结束后对轨迹中的每条转移组: 以概率 k 保留原始目标以概率 k 使用 future 策略选择虚拟目标 g 用 g 重新计算奖励 r(s, g) 将新的转移组存入缓冲区 根据缓冲区采样训练策略与Q网络这段流程里的“以概率 k 保留原始目标”非常重要。如果你把每条经验都重标记为虚拟目标智能体就永远不知道环境真实目标长什么样它会在虚拟目标里自嗨最后学到的东西无法应对实际任务。通常 k 取 4 左右也就是每条原始经验最多额外生成 4 条重标记经验原始经验本身始终保留一份。这保证了真实目标的信息不会被淹没。3. 从零实现一个二维导航任务的 HER3.1 实验环境与网络搭建理论知识说完了我们来点实际的。为了不牵扯复杂的 MuJoCo 物理引擎我设计一个简单的二维目标到达任务智能体是一个质点从 (0, 0) 出发目标点是环境随机生成的一个坐标范围在 [-5, 5] × [-5, 5] 之间。每个时间步智能体可以输出一个二维动作表示 x 和 y 方向的速度增量。奖励是稀疏的如果智能体当前位置与目标点距离小于 0.5则奖励为 1本轮结束否则奖励为 0最多跑 50 步。这个任务看起来简单但对一个随机初始化的策略来说在 50 步内恰好走到一个随机目标点的概率微乎其微标准经验回放基本学不动。这正是展示 HER 价值的最小环境。策略网络和 Q 网络我都用简单的三层 MLP。输入状态是当前坐标拼接目标坐标四维输出动作是二维。激活函数用 ReLU优化器用 Adam学习率 3e-4。这套配置没什么特别的重点在后面的目标重标记模块。3.2 核心代码目标重标记模块先说清楚重标记模块要干什么给它一条轨迹轨迹里每条记录是 (s, a, s, done, g)它要生成额外的重标记版本并重新计算奖励。这里我用 future 策略即对轨迹中每个时刻 t从时刻 t 之后的所有状态里随机挑一个作为虚拟目标。def her_relabel(trajectory, k4): 轨迹格式: list of dict 每个 dict 包含 s, a, s_, done, g k: 每条经验额外生成的虚拟目标个数 extra_transitions [] horizon len(trajectory) for t, transition in enumerate(trajectory): # 保留原始经验置于主缓冲区 original transition # 生成 k 条 future 重标记经验 for _ in range(k): # 在 t 之后的未来状态中随机选一个作为虚拟目标 future_idx np.random.randint(t, horizon) virtual_goal trajectory[future_idx][s_][:2] # 假设二维状态 # 根据虚拟目标重新计算奖励 dist np.linalg.norm(transition[s_][:2] - virtual_goal) reward 1.0 if dist 0.5 else 0.0 done 1.0 if reward 1.0 else 0.0 relabeled { s: transition[s], a: transition[a], s_: transition[s_], g: virtual_goal, r: reward, done: done, } extra_transitions.append(relabeled) return extra_transitions代码很短但有三个细节值得说明。第一个虚拟目标用的是“未来某个状态的物理坐标”而不是状态向量本身因为我们的任务目标就是坐标这个选择由任务性质决定。第二个重标记后的 done 不能直接用 1你要根据虚拟目标重新判断距离是否达标否则网络会学习到“动作结束但奖励不对”的错误关联。第三个future_idx 是从 t 开始而不是 t1 开始这是因为 t 时刻结束时的状态本身也可以作为虚拟目标它意味着“我到达了当前位置就算成功”对早期探索有帮助。3.3 训练循环里的关键改动有了重标记模块训练循环相比标准 off-policy 算法只多了两处改动第一轨迹收集完调用 her_relabel 把额外经验塞进缓冲区第二采样时不需要区分原始经验和重标记经验统一训练。下面给一个简化训练循环buffer ReplayBuffer(capacity100000) for episode in range(num_episodes): g np.random.uniform(-5, 5, size2) state np.zeros(2) trajectory [] for _ in range(max_steps): action policy.sample_action(state, g) # 带探索噪声 next_state state action dist np.linalg.norm(next_state - g) reward 1.0 if dist 0.5 else 0.0 done 1.0 if reward 1.0 else 0.0 trajectory.append({ s: np.concatenate([state, g]), a: action, s_: np.concatenate([next_state, g]), g: g, r: reward, done: done, }) state next_state if done: break # 关键改动目标重标记 extra her_relabel(trajectory, k4) for t in trajectory: buffer.add(t) for t in extra: buffer.add(t) # 标准 off-policy 更新 batch buffer.sample(batch_size256) update_q_network(batch) # 标准 Q 学习更新 update_policy_network(batch) # DDPG/TD3 风格策略更新我在实现时故意没有引入复杂的优先级回放或者多步回报目的就是让读者看清HER 带来的收益完全来自目标重标记而不是其他训练技巧叠加出来的。在一次完整的训练里标准 DDPG 在 2000 个回合内成功率几乎是 0而加了 HER 的版本在同样的随机种子下大约到 600 个回合就开始出现成功轨迹1000 个回合后成功率稳定在 80% 以上。3.4 实验结果对比与解读我用同样一组超参数跑了两组对比实验结果非常直观配置前 500 回合成功率1000 回合成功率2000 回合成功率标准 DDPG0%0%2%DDPG HER (future, k4)8%65%92%标准 DDPG 在 2000 回合后偶尔能成功是因为随机探索恰好撞上了目标不是真正学到的策略。HER 版本则表现出明显的渐进式学习曲线早期靠重标记经验让网络先学会“向某个已到达过的位置移动”这一能力迁移到原始目标后成功率自然就上来了。这个实验也验证了一个认知HER 本质上教给智能体的不是一个“寻找目标”的策略而是一个“到达任何可达位置”的通用运动策略。一旦这个策略成型它几乎不用额外学习就能适应新的目标位置。这种泛化性在机器人领域非常有价值因为真实世界里任务目标总是变化的策略不应该只认得一个固定坐标。4. 训练 HER 时的参数选择与避坑要点4.1 回放缓冲区、k 值和采样策略怎么定在我自己的实践里HER 的几个关键参数踩坑概率最高逐个说一下经验值。回放缓冲区不宜太小。因为 HER 会大幅扩充经验数量一条 50 步轨迹经过 k4 的重标记后会变成 250 条经验如果缓冲区只有几千条容量重标记经验会迅速覆盖原始经验导致真实目标信号消失。我一般设至少 10 万条容量复杂任务甚至到 100 万。k 值影响虚拟目标经验的占比。k0 等于没有 HERk1 时效果已经很显著k4 是论文里的标准配置效果也最稳。k 再往上比如 8 或 16收益不再增加反而会让缓冲区里重标记经验占比过高训练变慢。我的经验是如果任务的目标空间很大k 可以适当上调否则保持 4 即可。至于采样策略前面表格已经对比过了future 策略综合效果最好。但有一个补充技巧在轨迹早期阶段future 可选的未来状态非常多后期阶段可选范围变小这会引入一定程度的不均衡。可以在选虚拟目标时做一个小改动——从未来状态里按正态分布采样让距离当前时刻较近的未来状态被选中的概率更高因为那些状态与当前状态的距离差更小重标记的信号更温和更容易学。4.2 奖励设计与目标重标记的配合HER 并不完全省掉奖励设计。虽然它不依赖密集奖励但原有的稀疏奖励必须满足“可以用目标函数重新判定”这个条件。具体说奖励必须能写成 r(s, g) 的形式也就是给定状态和目标就能算出奖励。如果你的环境奖励不是这种形式比如奖励依赖于隐藏的物体属性或者随机事件那 HER 就无法重标记。还有一个容易被忽略的坑HER 重标记后虚拟目标状态可能落在状态空间的边界之外。比如二维导航任务里智能体实际到达的坐标是 (6.2, -7.8)而环境合法目标范围是 [-5, 5]。如果不加处理网络会学到“到达这个越界坐标是成功”策略会被带偏。我的处理方式是在重标记时加一个目标合法性过滤虚拟目标必须位于合法区间内才使用否则直接丢弃这条重标记经验。另外要注意虚拟目标虽然来自实际状态但重标记后只是“奖励上成立”物理上这一条经验并不会真的以该目标为指引交互。这意味着随着训练推进缓冲区里的重标记经验会积累很多“同一物理轨迹但不同虚拟目标”的版本它们之间可能会相互干扰。所以 HER 一般建议配一个较大的 batch size比如 256 或 512让梯度更新更平滑。4.3 常见问题速查表我把实验过程中遇到过的典型问题整理成了下面的速查表方便你排查现象可能原因处理方式加入 HER 后完全不收敛虚拟目标选择范围太小轨迹长度过短检查 max_steps 是否太短future 策略需要至少 5 步以上的轨迹才有足够目标候选训练初期波动大k 值过高重标记经验占比失控降低 k 到 1~2先确认 HER 管线正确再逐步加原始任务成功率低但虚拟任务一直成功原始目标经验占比被稀释确认每条原始经验必定保留必要时把缓冲区分成原始和重标记两个池按比例采样虚拟目标导致状态分布偏移没有做目标合法性过滤对虚拟目标做范围检查越界则丢弃策略学会“原地不动”虚拟目标选成了当前状态奖励立即为 1避免 future 取当前时刻 t 的状态改成从 t1 之后采样训练后期波动缓冲区里重标记经验覆盖了高质量原始经验增大缓冲区容量或设置经验保存的优先级标志这些坑大多不是 HER 本身的问题而是工程实现细节。尤其是“虚拟目标选成当前状态”这个我第一次实现时踩得最惨——明明训练曲线一路飙升测试时成功率却是 0后来调试才发现虚拟目标很早就变成当前状态了智能体学到的是“站着不动就成功”。5. HER 的适用边界与扩展方向5.1 哪些任务适合 HERHER 不是万能的它的有效性高度依赖任务结构。适合 HER 的任务通常具备三个特征目标状态可显式定义、目标与状态在同一空间、目标空间覆盖了智能体可能到达的状态范围。机械臂抓取、物体推拉、导航、寻路这类任务都非常合适因为它们的目标就是某个点位或物体姿态而智能体的状态里直接包含这些信息。不适合 HER 的场景也很多。如果任务是“穿越迷宫”目标是一段路径而不是一个状态重标记就无法用“实际到达过的状态”代替目标因为迷宫路径的全局结构约束没法通过单点目标体现。同理棋类游戏的目标是“赢得对局”实际发生的对局结果里没有一个“接近胜利的状态”可以作为虚拟目标HER 也无从下手。我建议你拿到任务后先问自己一句如果把这次探索的终点定义为目标这段轨迹还算合理吗如果否HER 大概率不适用。还要注意多智能体场景。HER 在多智能体协同任务里的效果不如单智能体明显因为每个智能体各自重标记目标后彼此的交互关系会变得不一致。如果要做多智能体 HER可以考虑对全局联合状态做重标记而不是对单个智能体单独处理但这个方向的稳定性有限需要自己做好充分实验验证。5.2 从 HER 衍生出来的变体HER 的核心思想也被后续很多工作继承和发展。我最常用的是 Dichotomy of ControlDoC的思路它把 HER 的目标重标记和概率控制结合起来在离线强化学习里也有效果。另一个方向是基于 HER 的能量回放方法不改目标而改奖励权重适用于无法重标记目标但有事后评价函数的环境。还有分层 HER它在重标记时同时考虑子目标和子策略的时序关系适合长程任务。如果你只是想在现有项目里快速验证 HER 的价值建议循序渐进先在 gym 的 FetchReach 这类现成环境上跑通官方代码再移植到自己的环境。直接在自己复杂的业务环境里从零调试 HER很容易因为环境本身的 bug 而误以为算法无效。我见过不止一个团队把环境初始化错误当成 HER 失效排查了两三天才发现问题在别处。从工程角度看HER 的实现成本极低往往只需要几十行代码就能让原本学不动的稀疏奖励任务开始收敛。这种“低成本、高收益”的算法在实际项目中非常值得优先尝试。我自己的体会是动手写 HER 之前最好先手动算一遍重标记轨迹的奖励变化真正理解了“目标是条件的输入”这个概念代码写起来就不会绕弯了。最后再分享一个小技巧如果你在某个任务上 HER 的效果不理想先用一个足够简单的子环境比如单一固定目标排查重标记逻辑是否正确再逐步增加目标空间大小。这个排查顺序能帮你节省大量时间。