ARTICLE DETAIL

资讯详情

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

多篇博客标题:HER算法如何从失败中学习?稀疏奖励强化学习实战

多篇博客标题:HER算法如何从失败中学习?稀疏奖励强化学习实战 提到 hindsight大部分人想到的是事后诸葛亮。但在强化学习里Hindsight Experience ReplayHER把这个词变成了一种非常漂亮的算法机制让智能体从自己的失败轨迹里学习。我第一次认真琢磨这个算法是因为一个机械臂仿真任务把我卡了整整两周——DDPG 跑了 50 万步success rate 一直稳定在 0%我一度怀疑是环境写错了后来才发现问题出在奖励本身稀疏得离谱智能体根本没有收到过任何有效学习信号。这篇博客就把 HER 从思想动机到代码实现完整拆一遍并结合我实际训练中踩过的坑讲清楚它为什么好用、什么时候不好用。1. 为什么稀疏奖励会让强化学习彻底哑火1.1 一个让我怀疑人生的机械臂任务先说那次翻车经历。任务本身挺简单四维动作空间推动一个方块到指定的三维位置。环境用的是 OpenAI 经典的 Fetch 仿真平台奖励是二值的——方块和目标点的欧式距离小于 5 厘米才给 1其余情况一律给 0。我当时想得很乐观有连续观测有标准动作空间DDPG 不是专门干这个的吗结果训练曲线从头到尾都在贴着地板评估时成功率 0%而且理直气壮。我把 rollout 数据打出来逐条看才发现问题不在代码而在探索。机械臂的动作是连续的要从初始位置出发随机探索出一条恰好能把方块推进以目标点为中心、半径 5 厘米的球里的动作序列概率低到可以忽略。换句话说智能体在训练中收到的 reward 几乎全是 0它根本没有任何这次做对了的反馈可以依赖。这就像是让一个从没见过篮球的人闭着眼睛投篮还要在一万次试投后自己总结出正确姿势那只能靠奇迹。1.2 奖励全是零时梯度到底去了哪里要理解为什么全是零奖励会杀死训练得看清楚策略优化对奖励信号的依赖方式。策略梯度方法的本质是让高回报轨迹对应的动作概率变大、低回报轨迹的概率变小。当所有轨迹的累计回报都是 0 时不同轨迹之间的优势函数差异也为 0梯度均值近似归零剩下的全是噪声。Q-learning 这边更极端所有状态动作对的 TD-target 都是 0Q 网络学出来的就是一张万事皆空的值函数误差项根本没法把有用的信息反向传播回去。这里有一个很反直觉的点不是奖励太少导致学得慢而是奖励太少导致几乎学不动。你调大学习率、换网络层数、改 exploration noise都只是在噪声上打转。我见过不少人包括当时的我在这种局面下反复调参越调越怀疑人生其实问题从开始就不在优化器上。1.3 手工塑形奖励不是银弹遇到稀疏奖励第一反应通常是那我给个稠密奖励不就得了比如每一步都按方块到目标的距离给一个负值当作惩罚。这个思路能缓解问题但它引入了新的麻烦。首先是领域知识成本。距离奖励的系数、温度参数、是否裁剪都需要针对具体任务反复调。调好之前你也不知道到底该让智能体多怕远还是多想近。更麻烦的是塑形奖励很容易被钻空子。我见过一个真实案例某个导航任务给智能体加了一条越靠近目标奖励越大的势函数结果智能体学到的策略是在目标点附近来回振荡——因为它发现只要不停留在目标附近就能持续刷小奖励而真正完成任务的瞬间奖励反而没那么关键。这种 reward hacking 现象在机械臂、游戏环境里非常常见本质上是人为塑形项和目标函数之间的缝隙被利用。所以当时我在想能不能不要手工设计这些稠密奖励而是让算法自己从失败数据里挖掘出有用的信号HER 就是这个思路的集大成者。2. 核心机制拆解把没达到的目标重写成已经达到的目标2.1 篮球教练的目标重标定要理解 HER先看一个生活场景。假设你在教小孩投篮他第一次投出去球落在篮筐左边 30 厘米。你不会只说一句失败了再来更好的教法是告诉他刚才这次你的动作其实很适合投到左边 30 厘米那个点。于是小孩记住了一条经验以左边 30 厘米为目标时我这套发力是管用的。下一次他又投偏到右边你再给他一套右边 40 厘米的经验。练上一百次他虽然一个球都没投中但已经积累了发力方式到落点位置的丰富映射。等到正式比赛要投正中间这个目标时他只需要在已有映射上做一点泛化。这就是 HER 的全貌。这篇论文是 2017 年 OpenAI 团队发表在 NeurIPS 上的《Hindsight Experience Replay》解决的是目标条件强化学习goal-conditioned RL中的稀疏奖励问题。在这类设定里智能体每一步同时看到状态 s 和目标 g策略写成 π(a|s,g)环境还提供一个函数 f(s) 用来提取当前已达目标比如机械臂方块的位置坐标。真目标 g 和已达目标 f(s) 处于同一个目标空间二者可以互相比较。2.2 重标定究竟改了什么HER 的算法逻辑很短一句话就能说完每跑完一条 episode除了用原始目标 g 存储样本再额外生成 1 到 4 个替代目标替代目标取自这条轨迹里某些状态的已达目标 f(s)。然后把 transition 里的目标字段换成替代目标按新目标重算奖励r 1 if ||f(s_{t1}) - g|| ≤ ε else 0在原始目标下这条 transition 可能是个失败样本r0但在替代目标下它往往是条成功样本r1。同一个s_t, a_t, s_{t1}于是被存进 replay buffer 两次一次以原始目标为条件、奖励为 0一次以替代目标为条件、奖励为 1。下面是对照原论文逻辑的伪代码for 每个训练 episode: 从目标分布中采样一个目标 g 用策略 π(a|s, g) 和环境交互得到轨迹 τ for t 0, ..., T-1: 把 (s_t, a_t, r_t, s_{t1}, g) 存入 replay buffer # 原始目标 for _ in range(k): 从当前 transition 之后的时间步里随机挑一个 tt t ≤ T 令 g f(s_{t}) # 未来某个已达目标 重算奖励 r binary_reward(f(s_{t1}), g) 把 (s_t, a_t, r, s_{t1}, g) 存入 replay buffer # 替代目标 从 buffer 采样 batch更新策略和 Q 函数论文里把这种从未来状态取目标的方式命名为 future 策略实验显示它在多数任务上效果最好。除此之外还有 final、random、episode 等变体我后面会用一张表专门对比。2.3 为什么这个事后重标定不是作弊第一次接触 HER很多人会质疑把失败样本当成成功样本存进 buffer网络不会学歪吗我之前也有同样的疑问后来想明白了关键在于两点。第一重标定并没有删掉或修改原始目标 g 的样本。原始 transition 依旧按原始目标入库模型对真正目标 g 的奖励估计照常更新。新增的样本只是以其他目标为条件的额外数据教给网络的是这些目标下怎么行动是好的。第二HER 本质上是给通用价值函数UVFA造数据。UVFA 的核心想法是用一个同时以 (s,g) 为输入的值函数让网络在不同目标之间共享经验。HER 等于免费给这个共享值函数提供了大量正样本让它在目标空间里变得连续、可学习、可泛化。换个角度理解HER 其实是一种自动课程学习训练初期重标定目标全是智能体自己刚到达过的状态难度很低随着策略变强、探索范围变大重标定目标逐渐接近真实目标分布。它不会改变最终要解决的原始问题只是把通往原始目标路上的半成品经验也利用了起来。3. 动手实践给 DDPG 装上 HER 重放池附完整实现3.1 为什么基座算法偏偏选 DDPGHER 不是独立算法它是一套数据组织方式必须挂在某个 RL 基座上。理论上它可以挂任何算法实践里却几乎清一色选择 off-policy 方法比如 DDPG、TD3、SAC。原因很直接HER 产生的重标定 transition 本质上是 off-policy 的它的目标分布和你当前 rollout 的目标分布并不一致。on-policy 算法PPO、A2C训练时只用当前策略最近生成的数据辛辛苦苦重标定出来的样本用过一次就被丢弃效率大打折扣。DDPG 是原论文的官方选择因为它同时满足两个关键条件一是带 replay buffer天然 off-policy二是不做离散假设直接支持连续动作空间正好适配机械臂类环境。现在我自己做同类任务时更喜欢用 SAC 换掉 DDPG训练稳定性好得多但底层对接 HER 的方式完全一样。下面为了对照论文以 DDPG 为例。3.2 环境与数据格式的准备论文实验主要跑两类环境。一是 Bit Flipping二值翻转任务逻辑简单适合快速验证算法正确性二是基于 MuJoCo 的 Fetch 机械臂系列包括 FetchReach、FetchPush、FetchPickAndPlace。Bit Flipping 并没有直接内置在标准 Gym 里但 OpenAI 官方把 HER 的示例代码放在了 baselines 仓库的 baselines/her 目录下里面自带该环境和训练脚本。想快速跑通算法逻辑的话这是最省事的入口。Fetch 环境的 observation 不是普通向量而是一个 dict至少包含三个字段字段含义维度示例observation机器人关节角、速度、物体位置等约 25 维achieved_goal当前已达目标比如方块位置3 维desired_goal当前任务目标3 维这个结构提醒我们两件事。第一把 obs 直接喂给神经网络之前需要自己写 wrapper 把 dict 拼成向量第二HER 重标定要用到 achieved_goal 字段数据管线里必须把每条 transition 对应的已达目标完整存下来否则后面没法做 future 采样。看起来很简单却是新手踩得最多的坑。3.3 核心代码HER 重放池与训练循环先把重标定逻辑单独写成函数。计算奖励的时候要注意维度Fetch 的 achieved_goal 通常是batch, 3求范数要沿最后一维算。import numpy as np from collections import deque GOAL_THRESHOLD 0.05 # Fetch 系列评估成功的距离阈值 def compute_reward(achieved_goal, desired_goal, thresholdGOAL_THRESHOLD): 二值奖励距离小于阈值给 1否则 0。注意保持最后一维对齐。 dist np.linalg.norm(achieved_goal - desired_goal, axis-1) return (dist threshold).astype(np.float32) def relabel(transition, new_goal, thresholdGOAL_THRESHOLD): 把 transition 的目标换成 new_goal并重算奖励。 t dict(transition) t[desired_goal] new_goal.copy() t[next_desired_goal] new_goal.copy() t[reward] compute_reward(t[next_achieved_goal], new_goal, threshold) return t重放池本身不复杂关键在于维护当前 episode 的 transition 顺序以便按 future 策略采样。下面这个简化版只保留和 HER 直接相关的逻辑class HERBuffer: def __init__(self, capacity1000000, k4): self.capacity capacity self.k k self.buffer deque(maxlencapacity) self.episode_transitions [] def on_episode_end(self): 一条 episode 结束后重标定并入库。 ep self.episode_transitions T len(ep) for i, trans in enumerate(ep): self.buffer.append(trans) # 原始目标版本 if self.k 0 or i T - 1: continue for _ in range(self.k): # future从当前时刻之后均匀采样一个未来状态 future_idx np.random.randint(i 1, T) new_goal ep[future_idx][achieved_goal] self.buffer.append(relabel(trans, new_goal)) self.episode_transitions [] def sample(self, batch_size): idx np.random.randint(len(self.buffer), sizebatch_size) return [self.buffer[i] for i in idx]训练主循环的大致形状是这样的for epoch in range(EPOCHS): # --- 采样一条 episode注意 obs 是 dict 结构 --- obs env.reset() episode_trans [] for step in range(MAX_STEPS): obs_vec obs_to_vector(obs) # 自写 wrapper action actor.select_action(obs_vec) # 加探索噪声 next_obs, reward, done, info env.step(action) episode_trans.append({ observation: obs_vec, achieved_goal: obs[achieved_goal], desired_goal: obs[desired_goal], action: action, next_observation: obs_to_vector(next_obs), next_achieved_goal: next_obs[achieved_goal], next_desired_goal: next_obs[desired_goal], reward: reward, done: done, }) obs next_obs # --- HER 重标定入库 --- her_buffer.episode_transitions episode_trans her_buffer.on_episode_end() # --- 更新 DDPG --- for _ in range(UPDATE_STEPS): batch her_buffer.sample(BATCH_SIZE) ddpg_update(batch) # 拆解 batch更新 critic 和 actor一个容易被忽略的细节一条 episode 的最后一条 transitioni T-1在 future 采样时没有可选区间代码里直接跳过重标定即可。这不会带来问题因为更早的 transition 已经在自己的未来区间里采样过足够多的目标。3.4 有 HER 和没有 HER差距有多大我在 FetchPush 上做过直接对照。纯 DDPG 跑 100 万步成功率曲线基本贴着 0加上 HERfuturek4之后一般 20 到 40 个 epoch每个 epoch 大概 50 条 episode就能看到成功率开始爬升最终稳定在 80% 到 100%具体看随机种子。FetchPickAndPlace 比 Push 难HER 依然能拿下八成上下的成功率。Bit Flipping 那边更夸张几十位翻转任务普通方法完全学不动HER 能在合理样本量内收敛。这里要提醒看训练曲线的人HER 训练时 buffer 里的正样本比例被人为抬高critic 的 Q 值通常会显得虚高甚至出现成功率已经稳定但 Q 还在涨的现象。这不代表训练出错恰恰是重标定在起作用。评估时仍然以环境反馈的 is_success 为准别拿 Q 值自我感动。4. 实操中最容易翻车的几个细节子目标采样、k 值与奖励阈值4.1 future 策略为什么普遍比 final 强论文对比过四种重标定策略我的经验排序和论文基本一致future 最优final 是可靠的降级方案random 和 episode 一般更弱。它们的区别只在替代目标 g 的采样范围策略替代目标来源特点final当前 episode 最终状态的已达目标实现最简单每条轨迹只多一条样本多样性差future当前 transition 之后、同一 episode 内的某个未来状态与 transition 时间上邻近目标可达性最好random当前 episode 内任意状态目标与 transition 无关正样本比例低episode整个 replay buffer 里随机一个 episode 的任意状态最随机效果通常垫底从数据角度看HER 的功效就是提高 buffer 里成功 transition的比例。future 策略只利用这条轨迹自己走向的未来天然保证了替代目标的可达性这是它表现稳定的根本原因。如果你的场景里未来采样代价高比如 k 很大的时候退而求其次用 final 也能跑只是收敛慢一些。4.2 k4 的经验值以及它背后的存储账论文默认 k4也就是每条 transition 额外生成 4 条重标定样本。算一笔账原始样本 1 条加重标定样本 4 条一共 5 条replay buffer 的存储消耗变成原来的 5 倍。换来的是 buffer 里正样本密度大幅提高学习速度明显加快。k 不是越大越好。第一存储和训练开销线性增长第二超过一定数量后额外样本的边际收益递减因为同一条 transition 能提供的可学习信息是有限的。我的建议是从 k1 起步跑到成功率爬不动了再加如果发现 Q 值震荡得厉害先别急着加 k回头检查 future 采样区间是否设计正确。另一个容易被忽略的工程问题是 buffer 容量。因为 HER 会生成大量重复样本容量设置不能只看原始 rollout 数量。我习惯按原始 transition 数量估算任务复杂度然后给 buffer 容量留出 5 倍余量否则早期有效样本会被新数据过早挤出。4.3 奖励阈值 ε一个容易被低估的隐性超参数目标空间里是否成功往往依赖一个距离阈值 ε。Fetch 系列默认是 0.05即 5 厘米。ε 同时作用于两条链路HER 重标定时的奖励重算以及最终评估的成功判断二者必须保持一致。ε 太大重标定出来的成功太廉价智能体学到的是粗放的到达能力精度上不去。ε 太小重标定样本里几乎找不到正样本HER 直接退化。我在一个精密装配任务里把 ε 从 0.05 调到 0.01训练初期的正样本瞬间变得稀薄收敛速度肉眼可见地变慢。后来我改用两阶段策略先用较大 ε 跑出基本能力再缩小 ε 做精细微调效果不错。4.4 事后重标定是不是作弊这个误解必须澄清理论上较真的朋友会问HER 改变了优化目标吗原本目标从均匀分布里采样现在 buffer 里混入大量非原始目标样本最终策略会不会只擅长那些偶然到达过的目标答案是不会。原始目标的样本一份都没少模型更新的目标函数仍然覆盖原始目标分布。重标定样本扮演的是课程素材的角色它让网络对目标空间的响应变得更平滑。打个比方这就像学生既做真题也做模拟题最终考试考的是真题但模拟题把知识点练扎实了。唯一要警惕的场景是训练和测试的目标分布差别过大比如训练时只采样容易目标、测试时全给极难目标那成功率会很难看——这个属于分布不一致不是作弊是评估设计问题。5. 适用边界、常见坑位和我的搭配经验5.1 我踩过的坑按严重程度排序第一个坑transition 结构里漏存 achieved_goal。我第一版代码只存了 observation 和 action等想写 future 采样时发现无米下锅只能重新跑数据。记住HER 重放池的每条样本必须包含原始目标、下一时刻已达目标以及重建轨迹顺序所需的信息比如 episode 内索引。第二个坑future 采样跨越 episode 边界。如果把所有历史数据塞进同一个大 buffer 又不区分 episodefuture 采样极可能采到另一条轨迹的状态目标可达性崩坏训练直接乱掉。重标定必须在当前 episode 内部完成。第三个坑在 on-policy 算法上硬套 HER。PPO 刷完一轮就丢数据重标定样本第二次更新就用不上了收益非常有限。老老实实上 DDPG、TD3、SAC 才是正路。第四个坑把 obs dict 直接喂神经网络。Fetch 环境返回的是 dict大多数网络层只吃向量忘了写拼接 wrapper 会得到一堆形状不匹配的报错这种报错还容易和真正的算法 bug 混在一起浪费大量排查时间。第五个坑DQN 风格的 buffer 里只存标量 reward重标定目标时忘了同步更新奖励。更稳妥的做法是 buffer 里只存原始观测和目标数据采样时再调用 relabel 函数现场计算奖励把奖励计算和存储解耦这也是我推荐前面那种代码写法的原因。5.2 哪些任务不适合 HERHER 不是万能的它依赖一个关键前提目标空间和状态空间之间存在可达性渐近的关系。具体来说有三类任务要谨慎。第一目标是符号式组合的任务。比如钥匙在 A 柜子且门是打开的重标定出来的新目标几乎永远不可达正样本依旧稀薄。第二强依赖精确动作序列的任务。比如在特定时间窗口内按顺序按下三个按钮目标空间的连续重写学不到时序结构换了目标之后轨迹依然不像成功演示。第三目标只有少数离散档位的任务。比如只需要到达 A 点或 B 点可达目标集合太小HER 的多样性优势无从发挥。判断一个任务适不适合 HER我有个土办法问自己把失败轨迹的终点换成目标这条轨迹看起来像不像一次成功的演示如果答案是肯定的HER 值得试如果换了目标后依然不像成功说明失败信息对别的目标也没什么价值不如先解决探索问题。5.3 把 HER 和其他探索手段搭配使用HER 擅长利用已有轨迹的信息但它不能凭空创造探索。如果智能体连环境边界都没摸过重标定也没有素材可用。遇到探索特别困难的任务我通常会在 HER 外面再叠加一层探索机制。内在好奇心ICM是一个高频搭档给智能体加一个预测误差奖励引导它去那些没见过的状态正好能为 HER 提供更丰富、更多样的轨迹素材两者互补。显式课程学习也值得考虑虽然 HER 本身已经隐含了课程性但在任务难度梯度很大的时候人为安排从简单目标到困难目标的顺序仍然能进一步加速收敛。基座算法上最近两年我更多用 SAC 替代 DDPG配合 HER 训练稳定得多稀疏奖励下也能跑得很好。最后说一个很个人的习惯。我现在遇到跑了几十万步、成功率还是零的任务第一个排查的往往不是网络结构而是先问自己这条任务的失败样本里是不是有很多本该被利用的半成品如果有HER 几乎肯定能帮上忙。这个判断帮我省掉了大量盲目调参的时间也希望能帮到你。
返回列表