ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Hindsight Experience Replay:稀疏奖励下的目标重标记实战解析

Hindsight Experience Replay:稀疏奖励下的目标重标记实战解析 最近如果常刷强化学习社区看到“hindsight”这个词被反复提起大概率不是在聊人生感悟或项目管理复盘而是在说Hindsight Experience Replay事后经验回放。这是 2017 年 NeurIPS 上的一篇经典论文也是这几年解决稀疏奖励问题最朴素而有效的手段之一。我最早接触 HER 是在做机械臂抓取任务的训练上。当时用 DDPG 在 FetchReach 环境里跑效果还不错但一换到 FetchPush 这类需要把物体推到指定位置的任务整个训练过程立刻变得像教一只猫握手——它完全不知道你在干什么奖励始终是 -1梯度信号几乎为零策略纹丝不动。后来把 HER 加进去同样的超参数、同样的网络结构训练曲线肉眼可见地活了过来。这篇文章就把我实现 HER 的核心细节、代码思路和踩过的坑完整梳理一遍适合正在做目标条件强化学习、或者被稀疏奖励折磨到怀疑人生的读者。1. 为什么“hindsight”是稀疏奖励的最强解药1.1 稀疏奖励问题的本质你的智能体永远得不到反馈先说说没有 HER 时训练一个目标条件智能体到底卡在哪里。假设任务定义为一个元组 (s, a, g)其中 s 是状态a 是动作g 是目标。奖励函数 $r_g(s_t, a_t)$ 通常写成这样$$ r_g(s_t, a_t) \begin{cases} 0 \text{如果 } |s_{t1} - g| \delta \ -1 \text{其他情况} \end{cases} $$这里 δ 是成功判定阈值比如 0.05。也就是说只有当智能体把物体推到了距离目标 5 厘米以内才给一个 0 的奖励其余所有行为一律 -1。训练初期机械臂在空中乱晃它连“把物体抓起来”这个基本动作都做得断断续续更别说把物体推到一个随机指定的坐标点。整个回合里所有 transition 的奖励全是 -1批评者网络能学到的唯一信息就是“做什么都错”但哪个状态离目标更近、哪个动作更可取完全无从判断。策略梯度在这种反馈下退化成了随机搜索而且是很慢的随机搜索。1.2 人类天生自带“事后诸葛亮”能力换个角度想如果让你蒙上眼睛去把一个杯子从桌子中央推到桌子角落你推歪了杯子停在了靠墙的位置。虽然你没有完成“推到角落”这个任务但你是否从这段经历里学到了什么当然可以。你会记住我用了多大的力、朝哪个方向推能把杯子推到靠墙的位置。下一次如果有人让你把杯子推到靠墙的位置你至少有一个先验的方向感。人类能从失败中学习因为我们会本能地重定义“好的结果”——既然我到了一个状态那这个状态就当目标好了。HER 把这种“事后诸葛亮”的能力搬进了强化学习训练流程如果智能体没有达到原始目标那就把你实际到达的状态当作替代目标重新标注这条经验放进回放池里。这样一来原本全是 -1 的轨迹也能产生大量“成功”样本批评者网络就有了可以学的东西。这里值得停下来想清楚 HER 到底改了什么它没有改网络结构没有改策略优化方式也没有改探索策略它只改了你往回放池里存数据的方式。正因为如此它能和 DDPG、SAC、TD3 这类 off-policy 算法无缝结合你只需要在取经验、存经验的地方动几行代码。2. HER 的核心原理目标重标记这层窗户纸2.1 从一条失败轨迹到四类成功样本HER 在论文里提出了四种目标重标记策略很多人第一次看论文的时候容易被这四种策略绕晕我把它们的区别整理在下面这张表里。策略名称新的目标从哪里选特点论文推荐度final取整条轨迹的最终状态最简单但目标分布单一后期容易过拟合某一类目标一般future未来 K 个状态中随机选一个引入随机性目标分布更广训练稳定推荐episode当前步之后任意一个状态与 future 类似但选目标的时间范围更大可以random回放池中随机选一个状态目标完全脱离当前轨迹学习效率偏低不推荐实际跑下来future 策略效果最稳。原因也不难理解它给每条 transition 重标记的目标是“未来若干时间步内实际到达的状态”这些状态与当前 transition 在时间上相近状态转移逻辑上连贯智能体学到的“从状态 A 出发经过动作 a到达状态 B”的关系是真实发生过、可验证的。一个完整的 HER 回合流程是这样的智能体在环境中执行策略收集了一条长度为 T 的轨迹 ${s_0, a_0, s_1, a_1, \dots, s_{T-1}, a_{T-1}, s_T}$原始目标为 g。如果最终状态 $s_T$ 与 g 的距离超过阈值说明任务失败此时对轨迹里的每一条 transition (s_t, a_t, r, s_{t1}) 做如下操作从时间步 t 之后的时间范围 $[t, T]$ 中随机选出 K 个索引这就是 future 策略中的 K取出对应的状态集合 ${s_{t}}$。将集合里的每个状态当作重标记目标 $g$。用 $g$ 重新计算奖励 $r r_{g}(s_t, a_t)$注意此时判定成功的条件变成了 $s_{t1}$ 是否接近 $g$。把重标记后的整条 transition (s_t, a_t, r, s_{t1}, g) 存进回放池。原轨迹的 transition 也会保留只是原始目标的奖励是 -1 罢了。这样回放池里既有“失败”样本又有“重新解释出的成功”样本比例大概通过一个 HER 比例参数控制。论文推荐的 K 值是 4也就是说每条失败 transition 额外生成 4 条重标记样本。K 太大回放池里的目标分布会过度偏向中途状态挤掉了真实任务目标的有效经验K 太小重标记样本数量不够信号还是稀疏。2.2 目标重标记为什么能让价值函数变得可学你可以把 HER 的作用理解为“给价值函数铺了一条从失败通往成功的缓坡”。没有 HER 时批评者网络 Q(s, g, a) 在所有未完成目标上的评估几乎全是 -1高维状态空间里像一块毫无起伏的平板策略梯度根本找不到方向。重标记之后回放池里充满了“我虽然没做到原始目标但我做到了这个替代目标”的样本批评者网络开始在局部区域学会区分不同动作的好坏。举个具体例子机械臂的末端执行器当前在位置 A动作 a 把末端执行器移动到了 B原始目标是 G路径上 A 引出的动作 a 并没有把物体带到 G。如果不重标记这条经验对 Q 函数的贡献是“从 A 出发做 a 得到 -1”。重标记一个替代目标 GB 之后这条经验变成了“从 A 出发做 a 得到了 0”批评者网络学到“从 A 向 B 移动是一个好的动作”。随着回放池里越来越多这种“伪成功”样本Q 函数在状态空间里就逐渐有了形状策略网络随之被牵引着朝更高 Q 值的方向更新。有一个细节需要注意你重标记的目标 g 是智能体未来实际到达的一个状态那么这个 g 在“当前时间步”这个语境下通常会比原始目标更容易达成。这就是 HER 算法的刻意设计——用大量“容易实现的目标”来训练智能体学会基础操作当基础操作熟练之后策略会越来越多地碰到原始目标形成正循环。3. 从零实现 HER一个能跑通的 DDPGHER 方案3.1 环境与网络结构的选择我建议直接用 OpenAI Gym 的 Fetch 系列环境体验 HER 的前后差异其中 FetchReach 最简单适合验证代码逻辑FetchPush 有一定难度适合观察 HER 的明显收益FetchPickAndPlace 和 FetchSlide 则更接近真实机器人场景难度再上一个台阶。这些环境的状态空间比较特殊observation 是一个 dict里面至少包含三样东西observation机械臂关节角度、末端位置、物体位置、相对距离等achieved_goal智能体当前实际到达的目标状态desired_goal环境指定的原始目标状态你不需要自己拆这个 dict环境已经帮你分好了。HER 重标记的时候只需要替换 desired_goal 字段并基于新的 desired_goal 重新计算奖励即可。网络结构我用的是经典的 DDPG 双网络结构Actor 和 Critic 的输入都拼接了状态和目标的联合向量。目标条件策略用 $\pi(s, g)$ 表示批评者用 $Q(s, g, a)$ 表示这是 HER 生效的前提——如果网络中根本没有目标输入你连往哪里重标记都找不到。3.2 训练循环中 HER 的核心代码逻辑下面这段伪代码是我从实际项目里精简出来的完整展示了 HER 在训练循环中的位置和作用。def her_relabel(episode_transitions, k4): # episode_transitions: 一个回合的原始transition列表 # 每条transition形如 (obs_t, action_t, rew_t, obs_next_t, done_t) relabeled [] episode_states [t[0][observation] for t in episode_transitions] # 实际到访状态序列 for i, transition in enumerate(episode_transitions): obs_t, action_t, rew_t, obs_next_t, done_t transition # 原始目标 original_goal obs_t[desired_goal] # 判断这条transition在原始目标下是否成功 achieved_goal obs_next_t[achieved_goal] original_success is_success(achieved_goal, original_goal) # 原始transition照常入库 relabeled.append(transition) # 如果原始目标已经成功没必要再重标记 if original_success: continue # future策略: 从当前步之后的状态中随机选k个未来状态 future_indices np.random.choice( np.arange(i, len(episode_transitions)), sizemin(k, len(episode_transitions) - i), replaceFalse ) for future_idx in future_indices: future_goal episode_states[future_idx][achieved_goal] new_reward compute_reward(achieved_goal, future_goal) # 用新目标覆盖desired_goal new_obs_t obs_t.copy() new_obs_t[desired_goal] future_goal new_obs_next_t obs_next_t.copy() new_obs_next_t[desired_goal] future_goal relabeled.append((new_obs_t, action_t, new_reward, new_obs_next_t, done_t)) return relabeled这套逻辑里最关键的一点是未来状态集合里取的是 achieved_goal 而不是 observation这一点很容易搞错。Fetch 环境的 observation 里既有完整的机械臂关节信息又有物体位置你不能把整条 observation 当作目标目标空间维度必须与奖励函数计算所依赖的状态维度一致通常是物体位置的三维坐标或者末端执行器的三维坐标。is_success 和 compute_reward 不需要自己花心思写gym 环境本身就提供了。直接调用gym_goal_env里封装好的判定逻辑避免自己手写距离阈值导致环境和算法之间标准不一致。3.3 模型更新的完整流程HER 改的是数据生成端模型更新流程与标准 DDPG 基本一致。每个训练步从回放池里随机采样一个 batch按 DDPG 的方式更新批评者和 Actor 网络def update_model(replay_buffer, batch_size256, gamma0.98): samples replay_buffer.sample(batch_size) # 数据组织: 状态, 动作, 奖励, 下一状态, 完成标志 obs_t, action_t, reward_t, obs_next_t, done_t samples # 计算target Q值 target_action_next actor_target(obs_next_t) target_q_next critic_target(obs_next_t, target_action_next) target_q reward_t gamma * (1 - done_t) * target_q_next # 更新critic current_q critic(obs_t, action_t) critic_loss F.mse_loss(current_q, target_q.detach()) critic_optimizer.zero_grad() critic_loss.backward() critic_optimizer.step() # 更新actor actions_pred actor(obs_t) actor_loss -critic(obs_t, actions_pred).mean() actor_optimizer.zero_grad() actor_loss.backward() actor_optimizer.step() # 软更新target网络 soft_update(actor_target, actor, tau0.05) soft_update(critic_target, critic, tau0.05)训练过程本身我建议跑 200 到 400 个 epoch每个 epoch 包含 50 个回合每回合最多 50 步。FetchReach 大概几十个 epoch 就能看到成功率明显上升FetchPush 需要更多耐心但如果实现正确成功率通常会从 0 稳步爬到 70% 到 90% 区间。需要强调一个细节回放池的大小直接决定 HER 的效果。我见过不少人用默认的 1e6 容量但如果你机器内存紧张至少也要保证 1e5 以上否则重标记产生的多样化样本会被很快挤出回放池HER 的优势就发挥不出来。另外机器人环境中每个 transition 可能占用几十 KB 内存建议先估算好容量再跑长实验避免中途内存爆掉。4. 训练实录我在 Fetch 系列环境踩过的坑4.1 只对失败轨迹重标记成功轨迹别画蛇添足这是很多人实现 HER 时会犯的第一个错误。如果你对成功轨迹也做重标记会发生一个很搞笑的情况机械臂明明已经把物体推到了目标位置你却把目标重标记成另一个地方原来一分都不差的完美轨迹被你强行改成了“拿到 -1 奖励的失败样本”。这会主动给回放池注入噪声而且是方向完全相反的噪声。正确的做法是先判断原始轨迹是否成功如果成功了直接原样存入回放池完全不做重标记。如果失败了再考虑重标记。上面的伪代码里面已经体现了这个逻辑实战中这一行判断大约能让最终成功率提升 5 到 10 个百分点。4.2 HER 比例不是越高越好所谓 HER 比例是指一个回合里重标记样本与原始样本的比例。论文里其实默认用了“一个原始样本配 K 个重标记样本”的结构这等于变相把重标记比例顶到了接近 80%。但实际训练中我发现如果重标记比例过高回放池里绝大多数目标的难度都偏低策略会盯着容易的目标猛练对原始目标反而变得不敏感。这会形成一个隐患训练后期智能体在“四个替代目标”上表现完美但在原始目标上始终差一口气。我自己的解决方法是引入一个 HER 开关参数比如 0.8意思是每个失败回合有 80% 概率启用重标记剩下 20% 概率保留为纯失败样本。这样做能让回放池里始终保留一部分“原汁原味”的困难样本压制策略对简单目标的过度偏好。4.3 奖励阈值 δ 的长度单位陷阱Fetch 环境里默认的成功阈值往往是欧氏距离 0.05但这个值对不同任务、不同动作尺度来说感受差很多。FetchReach 里机械臂末端离目标 5 厘米可能已经算“不太远了”但 FetchPickAndPlace 里物体被放在桌面角落5 厘米的容差对整个操作来说难度不低。如果你用了自定义环境或者改用真实机器人一定要回到坐标系里确认阈值的物理含义。太宽松的阈值会让智能体学会“擦边球”动作——物体停在目标附近但没完全到位奖励却照样给 0训练曲线可能显得很漂亮实际部署时一测就露馅。我的经验是开局用环境默认阈值跑通流程然后逐步收紧观察训练曲线从“很快收敛但精度差”过渡到“收敛稍慢但精度扎实”。4.4 网络输入不统一导致状态漂移HER 重标记的时候你把未来状态当作新目标但别忘了同时把这个新目标拼接到所有相关网络的输入里。实际操作中遇到过一种隐蔽的 bug重标记 transition 时只替换了 desired_goal却没有重新拼接 observation 向量或者把新目标的数值拼接错了位置导致 Actor 网络其实根本没感知到目标变化训练半天等于白跑。这种问题特别难排查因为训练曲线不会完全不动只是涨得极慢。建议在重标记函数里加一个断言检查拼接后的向量长度是否等于网络输入层维度同时随机挑几条重标记样本手动打印目标值和观测向量确认拼接位置和数值范围都正确。4.5 与算法结合的选型建议为什么是 off-policyHER 和 on-policy 算法的结合效果通常不好根本原因在于重标记改变了经验分布而 on-policy 算法要求当前策略采集的数据分布接近在线分布。重标记后的样本分布明显偏离了“当前策略自然遇到的分布”PPO 这类算法直接用会引入偏差。DDPG、SAC、TD3 这类 off-policy 算法本身就用大规模历史回放池天然容忍经验分布漂移HER 重标记出来的“伪成功样本”对它们来说不过是回放池里多了一类数据分布。所以如果你计划用 HER第一步就是确认自己的基座算法是 off-policy。我在 Windows 上用 PyTorch 复现的时候用的就是 DDPG因为它的实现简单、稳定最适合做算法验证。5. HER 能走到哪扩展思路与适用边界5.1 从仿真到真实机器人的迁移价值HER 之所以在机器人界这么流行不只是因为它能跑 gym 实验更因为它把“从失败中学习”这件事做成了通用机制。真实机械臂操作里一次成功的抓取可能需要几十次上百次尝试每一次失败的轨迹数据本身绝不廉价。HER 让这些失败数据全部变成有效训练资源等于把数据利用效率翻了几番。工业场景里经常做动作轨迹的记录大量历史失败轨迹如果不是用在 HER 重标记里基本就是躺在数据库里吃灰尘。接上 HER 之后你可以把历史轨迹和对应时间点的实际状态抽出来重标记成多样化的目标把这个数据库直接变成训练集。我在实验里试过把一万条失败轨迹重标记成四万条有效样本模型预训练的成功率明显高于从零随机初始化。5.2 与课程学习、对比学习的组合思路HER 的重标记目标分布毕竟是环境自己生成的你无法控制它的难度递进节奏。课程学习提供了一个补充思路启动训练时用少量简单目标比如把物体推到起始点附近训练中后期逐步引入真实目标分布。HER 和课程学习并不冲突甚至可以在重标记目标时人为施加一个“困难度过滤器”优先选择与当前策略能力匹配的目标。近两年的工作也有把对比学习引入 HER 的尝试。核心思路是把状态表征和动作表征经过编码器投影到同一表示空间重标记目标时在表示空间度量距离期望比原始欧氏距离更贴合“语义上的接近”。这类方法目前还没有形成统一范式但对于研究型读者来说是一个值得追踪的方向。5.3 HER 不灵的场景和原因HER 不是万灵药。如果任务的目标空间极其狭窄或者“成功”的状态几乎测不到重标记出来的替代目标也大多在目标空间之外奖励信号依然稀疏。比如目标是一个特定频率的声音信号、一个精确到小数点后四位的数值这些状态在轨迹里基本不会自然出现HER 就无从下手。同样如果任务本身不存在多个可达成状态只是简单的一条直线走到终点用 HER 也意义不大。HER 的适用范围本质上是“多目标条件化任务”任务里必须有一个丰富的目标空间且智能体的行为能够自然地访问这个空间里的多个区域。先确认这个前提再考虑引入 HER否则就是南辕北辙。5.4 给初学者的复现路线建议如果你准备在自己的项目里复现 HER我的建议是先跑通顺序验证再考虑优化。第一步不碰任何复杂环境用自己熟悉的简单环境把 HER 加进 DDPG确认训练曲线相对基准有明显提升。第二步切换到 FetchPush把 future 策略的 K 值从 4 开始扫观察不同 K 对收敛速度的影响这一步能帮你建立对重标记分布的直观感觉。第三步再往任务里加障碍、加随机扰动测试 HER 的鲁棒性。跑通这套流程之后你会发现 HER 最大的价值不是期末考试式的调参技巧而是它改变了你的思维方式训练一个目标条件智能体时会下意识地追问“这条失败经验能变成什么任务的训练信号”。带着这个视角看很多稀疏奖励的问题都会多一条可行的路。这套代码我现在还保存在本地周围朋友复现的时候遇到问题也经常来找我讨论多数问题都出在目标拼接和重标记逻辑的边界条件上。如果你在实验中也卡住了不妨回头看看自己回放池里的样本分布打印几条样本出来对着环境定义捋一遍通常很快就能找到症结。
返回列表