ARTICLE DETAIL

资讯详情

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

HER事后经验回放:破解强化学习稀疏奖励难题

HER事后经验回放:破解强化学习稀疏奖励难题 我最近在折腾机械臂抓取的小实验训练循环跑了一整夜第二天早上去看曲线奖励还是零。这种挫败感做过强化学习的人都懂。后来有个同事提醒我试试 hindsight——准确说是 Hindsight Experience Replay事后经验回放。这名字挺妙hindsight 直译就是“后见之明”“事后诸葛亮”听起来像个心理学名词实际上却是 OpenAI 在 2018 年提出的一种让智能体从失败经验里学习的经典算法。这篇文章我就把 hindsight 这个算法好好拆一遍它到底改了训练流程里的哪一步、为什么能解决稀疏奖励问题、手写实现的时候有哪些坑以及它在生活和工作复盘里给的启发。无论你是正在被稀疏奖励折磨的 RL 实验党还是想理解“失败经验怎么变成学习信号”的入门读者这篇都值得花十分钟读完。我会尽量用大白话讲清楚原理再附上可以直接抄作业的实现思路。1. 先搞懂 hindsight 这个名字的分量1.1 从“事后聪明偏差”说起“事后诸葛亮”在心理学里有个正经名字叫后见之明偏差hindsight bias。考试对答案的时候看到正确答案你会觉得“这题我会”看完球赛回放你总觉得胜负早就写在剧本里。这种认知偏差让人类高估自己事前的判断但反过来想它也有个好处我们手里多了一个极其重要的信息——结果。强化学习里的智能体其实很惨它一开始就是个瞎子在环境里随机乱试偶尔碰对了才有奖励。更惨的是很多任务里奖励极其稀疏100 次尝试里有 99 次是“失败”这 99 次经验在传统算法里基本等于废数据。但 hindsight 这个名字点出了一个关键失败之后智能体其实看到了结果状态哪怕这个结果不是它想要的。这个“结果”本身就是信息不该被扔掉。把“拿结果反推过程”这个直觉用在学习算法里就是 HER 的核心精神。我后来回头看觉得 OpenAI 这篇论文最厉害的地方不是网络结构有多复杂而是敢把一个“认知偏差”变成训练信号的来源。思路朴素效果却好得惊人。1.2 强化学习里的稀疏奖励困局先说说 HER 要解决的问题。在目标条件强化学习goal-conditioned RL里智能体的任务不是单一的“做某个动作”而是“根据目标 g 做出对应动作”。比如机械臂的目标是末端执行器移动到空间某个点目标 g 就是一个三维坐标。这种设定下奖励函数通常是二值的达到目标给奖励否则给 0 或者 -1。问题就来了FetchReach 这类环境里机械臂末端随便移动随机命中目标点的概率几乎为 0。训练过程里采到的一整条轨迹可能全是负奖励梯度信号稀疏到让算法原地打转。传统做法是奖励塑形reward shaping人为设计一个连续奖励引导智能体靠近目标。但这玩意儿是个精细活设计不好智能体就会“钻空子”——发明一条程序化的路径骗取高奖励实际却没完成真实目标。HER 换了个角度它不碰奖励函数而是直接改造经验池里“目标”的定义从根上解决信号稀疏问题。1.3 HER 用什么方式“作弊”HER 的理念用一句话说就是当一条轨迹没有达到预定目标时别把它当垃圾丢掉而是给它重新指定一个“事后可达”的目标再按新目标重新计算奖励把失败轨迹变成成功轨迹。举个例子。智能体想把物体推到位置 A它试了半天最后把物体推到了位置 B。传统 RL 看到这轨迹没到 A失败奖励 -1扔掉。HER 不这么干它把这条轨迹的目标从 A 改成 B然后重新算一遍奖励——好家伙目标 B 确实达成了这条轨迹每一步都是成功的于是智能体学到了“如果用户想要的是 B那么我刚才那串动作是正确的。”随着训练推进它在想要 A 时也能从学过的大量类似行为里泛化出合理策略。这招叫“数据增强”也好叫“自欺欺人”也罢关键是它让梯度信号从沙漠变成了绿洲。我当时看完论文脑子里就四个字还能这样然后把奖励函数的每行代码都重新审视了一遍。2. 核心机制拆解HER 到底改了哪一步2.1 一条经验的基本组成要想理解 HER先得搞清楚目标条件强化学习里一条经验长什么样。普通强化学习中一条 transition 是 (s, a, r, s)状态、动作、奖励、下一状态。目标条件强化学习里多了一个维度目标 g。所以一条经验变成 (s, a, r, s, g)奖励函数也是带目标的r(s, a, g)。这里有个细节很多人第一次写会踩坑observation 和 state 不是一回事。在 OpenAI Gym 的 Fetch 系列环境里返回的 observation 是一个挺长的向量里面既包含机器人自身状态也拼接了目标 g。HER 实现时要手动把 observation 拆成两部分一个是纯粹的“现实状态”agent state一个是“期望目标”desired goal。如果图省事把整个 obs 当输入actor 和 critic 会同时看到目标和状态学起来容易混淆效果大打折扣。HER 本身不是一个新的网络结构它是对经验回放experience replay这个机制的改造。DDPG、SAC、TD3 这些算法都能直接往上套这也是它工程上特别好用的原因——你不一定非得换成某个新模型只要在数据入 buffer 前做一次“目标替换”整个算法的数据效率就能提升一大截。2.2 relabeling 的三种策略HER 论文里提了几种目标替换策略最常用的是 final、episode、random 和 future。这几种策略的区别在于替换后的新目标是从哪里选的。final把轨迹最后一个状态当新目标。最简单但多样性差因为所有替换都指向同一个终点。episode从整个 episode 里随机选一个状态当新目标。比 final 多样一些但有个致命缺陷随机选的状态可能在当前时间步之前。random和 episode 差不多从整条轨迹或整个 buffer 里随机选。future只从当前时间步之后的未来状态里选。这个是论文推荐的也是实际用下来效果最好的。为什么 future 策略最重要因为因果性。如果一条轨迹第 1 步在门口第 10 步走到了客厅你非要把第 1 步的“门口”当成目标来替换前面几步的经验那前面几步确实是成功抵达了门口可后面几步呢它们已经到达过门口又离开了再标记成“往门口走是成功的”就会产生冲突策略学起来会疯掉。future 策略保证了新目标一定是“之后真的会发生的状态”不会制造这种虚假因果。实际操作里future 策略的实现是对轨迹中的每个时间步 t从 t1 到 T 之间随机挑一个状态作为新目标。注意不是只选终点而是随机选这样目标空间覆盖得更广经验池里的“成功经验”也更多样。2.3 改造之后的一条完整训练流程我把 HER 改造后的训练流程完整列一遍方便和普通 DDPG 做对比。用当前策略加噪声探索在环境里跑一个 episode记录每一步的 (s, a, r, s, g)。原始经验原封不动存进 replay buffer。对轨迹里每个时间步 t从 t1 到 T 随机选一个未来状态作为替代目标 g重新计算奖励 r再把 (s, a, r, s, g) 作为一条新经验也存进 buffer。从 buffer 里随机采样一个 batch更新 critic目标是 q_target r gamma * Q(s, pi(sg), g)注意这里的 g 就是采到的那条经验里带的目标。更新 actor目标是最大化 Q(s, pi(sg), g)。软更新目标网络。这套流程里步骤 3 就是 HER 的全部秘密。K 值控制的是每个原始 transition 额外生成多少条替换目标经验常见取 K4。你可能会问原经验一条替换经验四条buffer 里重复这么多没问题吗没问题因为替换后的经验目标不同、奖励不同本质上是不同的数据相当于做了五倍的数据增强。我实际写代码的时候最深的感受是这个算法好懂到让人怀疑“是不是漏了什么”但它真的有效。有效的原因在于它把“要完成目标 B”的行为范例通过替换目标的方式填充到了经验池里。智能体没做到 A但做到了 B——HER 让它把“做到 B”也学进脑子。等到未来某次它需要做类似的事情这些“失败”就变成了它出手的依据。3. 实操记录我用 HER 训练一个稀疏奖励环境3.1 环境准备与任务设定实践出真知。我选了一个相对轻量的环境OpenAI Gym 里的 FetchReach-v1。任务要求机械臂把末端执行器移动到空间中的随机目标点目标维度是三维坐标连续空间。环境接口上observation 里会同时包含机器人的关节角度、末端位置和目标的拼接向量。安装上建议直接装gymnasium加mujoco的版本如果你用的是老版gym记得把mujoco_py相关依赖也跟上。FetchReach 算是入门友好的单块普通显卡也能训练不需要像 FetchPickAndPlace 那样动辄训练几十个小时。如果你想看更复杂的效果后面也可以换 FetchPush-v1、FetchSlide-v1训练时间会明显增加但算法逻辑完全一样。奖励函数我在实验里用的很朴素机械臂末端与目标距离小于 0.05 米返回 0否则返回 -1。注意这里用的是 0/-1 而不是 1/0区别只是数值偏移不影响 HER 起作用只影响网络对 reward scale 的敏感程度。我测试下来 0/-1 配 DDPG 稳一点1/0 会感觉模型偏乐观收敛反而慢。3.2 关键超参数怎么定超参数这东西不同环境差挺多。我照着 OpenAI 论文的默认配置跑基本一次就能出效果。列个表方便你直接抄参数项取值说明K替换目标数4每个 transition 额外生成 4 条替换目标经验替换策略future从未来状态中随机选replay buffer 大小1,000,000尽量大经验多样性非常重要actor/critic 网络两层 MLP每层 256三个 Fetch 环境够用了actor 学习率1e-3用 Adam 优化器critic 学习率1e-3用 Adam 优化器折扣因子 gamma0.98稀疏任务里可以稍小一点目标网络软更新 tau0.05DDPG 家族常用范围是 0.001 到 0.05探索噪声高斯噪声标准差 0.2动作空间每个维度独立加噪声batch size256大一点训练稳定总训练步数20 万步左右FetchReach 足够看到明显效果有几个点值得解释。K4 这个值不是拍脑袋定的论文做过消融实验K 从 1 到 8 都有提升4 是性价比最高的点。K 太大会让 buffer 里替换经验占比过高原始目标信息被冲淡K 太小则替换经验的多样性不足。tau 取 0.05 是我自己用的比论文里 DDPG 常用的 0.005 更大软更新更激进在简单环境里收敛更快。复杂环境建议改回 0.005防止目标网络追着当前网络跑导致发散。3.3 核心代码片段和网络结构HER 的实现其实集中在两个地方经验缓存和奖励重算。我把最核心的 relabel 逻辑写出来方便你直接照着改。import numpy as np def compute_reward(achieved_goal, desired_goal, threshold0.05): 稀疏奖励距离小于阈值返回 0否则返回 -1。 distance np.linalg.norm(achieved_goal - desired_goal, axis-1) return (distance threshold).astype(np.float32) - 1.0 def relabel_transition(transition, achieved_goals, future_idx, new_goal): 将一条 transition 的目标替换为轨迹未来某个时刻的真实状态 并重新计算奖励。 state, action, _, next_state, _ transition new_reward compute_reward(achieved_goals[future_idx], new_goal) return (state, action, new_reward, next_state, new_goal)这里有个细节future_idx是事先从(t1, T)区间里随机选好的索引achieved_goals是这条轨迹里每个时间步“实际到达的状态”。注意实际到达状态不是整个 observation而是里面代表末端位置的那几维。很多人写的时候会把整条 obs 塞进achieved_goals结果目标空间包含了关节角速度、物体速度等无关信息维度爆炸学起来非常难。只在achieved_goal里保留必要的任务相关状态是一个被低估的关键优化。完整的训练循环大致长这样# 伪代码省略 DDPG 更新细节 episode collect_episode(env, actor, noise) achieved_goals extract_achieved_goal(episode observations) for t, transition in enumerate(episode): # 原始经验 buffer.add(transition) # HER 替换经验 for _ in range(K): future_idx np.random.randint(t 1, len(episode)) new_goal achieved_goals[future_idx] new_transition relabel_transition(transition, achieved_goals, future_idx, new_goal) buffer.add(new_transition) # 正常采样更新 actor-criticDDPG 的网络更新部分我就不贴完整代码了标准实现网上到处都是关键是确认两点actor 的输入是concat(state, goal)critic 的输入是concat(state, goal, action)。目标网络更新要用软更新别直接拷贝权重。3.4 训练效果与观察记录我用的基线是同样的 DDPG 但关闭 HER只保留原始经验。对比结果非常明显基线方法训练了 20 万步成功率几乎一直是 0偶尔蹿到 2% 又掉回去属于纯靠运气碰到目标。而 HERDDPG 在大概 5 万步左右开始有明显起色曲线稳步上升15 万步左右成功率就到了 90%20 万步时稳定在 95% 上下。训练曲线有个有趣的现象它不是平滑上升的而是会突然“台阶式”跳跃。我推测原因是buffer 里某类替换目标经验的累积量达到某个临界点后actor 突然从“只会乱动”跳到“能找到策略路径”这个转变在曲线上就表现为一个陡升。如果你看到曲线长时间横盘别急着放弃可能是替换目标的覆盖还不够继续跑或者调大 K 都会有帮助。还有一点要提醒FetchReach 其实是 HER 的前几个版本容易跑通的任务成就感也就持续了一天。后来我去跑了 FetchPickAndPlace难度上一个台阶才真正开始体会调参的苦涩。但这也说明 HER 的泛用性真的强核心逻辑没变只是网络容量和探索策略需要跟着环境调整。4. 常见问题与排查技巧4.1 问题一奖励全部为零算法完全没反应如果你发现训练了上万步所有 batch 里的奖励值永远是最小值先别急着怀疑算法大概率是 relabel 没生效。排查顺序是这样的第一打印一条 relabel 前后的 reward 对比。如果替换后 reward 还是 -1检查compute_reward里距离计算用的是什么维度。achieved_goal和desired_goal的维度必须完全一致差一个维度 numpy 广播也会悄悄出错。第二确认你有没有把新奖励和新目标一起传进 buffer。有人只改了 goal 忘了改 reward等于目标换了但惩罚还在智能体依然什么都学不到。第三检查一下achieved_goal是否真的从 observation 里正确拆出来了。FetchReach 里 observation 前面是机器人关节信息中间是末端执行器的线速度最后才是目标。如果拆错位置相当于拿一个永远变不到目标的量去算奖励结果是奖励恒定为 -1训练毫无反应。4.2 问题二用了 HER 还是学不会用了 HER 还学不会这个坑更深一点。我踩过的主要有三类。第一类替换目标选择范围太窄。如果你用的还是 final 策略每条轨迹只产生一个替换目标buffer 里的“成功经验”都在轨迹终点附近中间过程依然是稀疏的。解决办法就是换成 future 策略加 K4让替换目标分布到轨迹各个阶段。第二类探索不足。HER 能放大经验价值但它没法无中生有。如果智能体跑出来的轨迹全部挤在一个小区域替换目标再怎么换也覆盖不了整个目标空间。检查一下探索噪声是不是太小或者 epsilon-greedy 的探索比例太低适当增大噪声方差会有效果。第三类目标空间包含太多无关状态。前面提过很多人把整个 obs 当 achieved_goal导致替换目标和原始目标在高维空间里相距极远策略泛化难度剧增。解决办法是只挑出任务核心的那几维比如末端位置、物体的 xy 坐标作为 achieved_goal维度能少则少。4.3 问题三训练过程中稳定性差DDPG 本身就是出了名的不稳定选手HER 能提升数据效率但治不了 DDPG 的“情绪病”。我遇到的情况是训练曲线爬到 60% 突然崩回 5%然后又慢慢爬回来循环往复。几个有效的稳定手段把 critic 和 actor 的学习率都调低一个量级比如 1e-3 改成 3e-4进度慢一点但更稳。给训练过程加梯度裁剪特别是 critic 的 loss 在 buffer 数据分布变化时容易爆炸。对 state 和 goal 做归一化。在目标空间比较大、各维度数值范围不一致的任务里LayerNorm 或普通的均值方差归一化能明显改善收敛稳定性。如果还是不稳定把底层算法换成 SAC 或 TD3它们的 target 更新机制比 DDPG 稳再加 HER 效果通常更好。另外要提一嘴HER 的替换目标策略是“随机采样”这本身会引入噪声。训练曲线有波动是正常的别看到一点回落就慌。判断有没有在学要看整体的成功率趋势而不是单条曲线。4.4 快速检查清单我把排查经验整理成一个表下次训练出问题直接对着检查。检查项可能的问题处理方式relabel 是否执行忘记在入 buffer 前调用替换逻辑打印 transition 数量确认 buffer 里替换经验存在achieved_goal 维度把整条 obs 当目标高维灾难只保留任务核心状态比如末端坐标奖励计算距离公式或阈值写错奖励永远为负单独写脚本模拟几条轨迹验证 reward替换策略用了 final 或 random目标覆盖差改成 future从 t1 到 T 随机选K 值太小替换经验多样性不足调大 K 到 4 或 8探索噪声太小轨迹集中在局部区域增加高斯噪声标准差或使用 OU noise目标网络更新直接拷贝权重导致发散使用软更新tau 取 0.005 到 0.05state 未归一化数值范围差异大训练不稳对 state 和 goal 做标准化5. 把“后见之明”从算法带到日常工作里5.1 复盘的正确姿势研究 HER 的过程里我一直觉得它不只是一个算法更像是一种思考方式。标准复盘的思路是“看差距”目标是什么实际是什么差在哪。这当然没错但 HER 告诉我另一个维度如果目标当时定的是别的呢这段经历是不是就变成了成功比如我学一个新框架原本计划三天上手结果三天只搞懂了 HTTP 请求和异步操作如果按原目标复盘结论是“我学得太慢了”。但换个目标看这三天的收获其实非常扎实搞清楚异步这件事花多久都值。用“后见之明”重看失败经历不是自我安慰而是把现实中已经产生的价值识别出来再决定下一步往哪走。我在实际生活里试过几次一个很具体的操作是每次计划没达成时多问一句“这次我实际达成的状态是什么这个状态能不能成为一个新目标”就像 HER 里的 relabel 一样给旧经验贴一个新标签它就成为通往未来某个目标的阶梯。这个方法在学编程、做副业、健身计划失败时都好使。5.2 团队项目里的 relabeling团队项目复盘其实也能借鉴 HER 的思路。项目没按时上线这是个让人沮丧的事实。但项目过程中团队踩过的坑被记录下来了、某一套架构被验证了不可行、某个成员成长出的新技能形成了文档这些都是“实际到达的状态”。把这些状态定义成新阶段的起点经验就变成了可复用资产。但这里有个非常重要的边界HER 的替换目标必须是“轨迹里真实访问过的未来状态”不能凭空捏造。换句话说relabel 是在承认现实的基础上重新定义目标而不是把失败改说成成功。如果团队明明没做到的事情硬说做到了那不是复盘是甩锅。HER 教给我们的核心是诚实——只有忠实记录“实际发生了什么”后续的“重新定义目标”才有意义。5.3 我个人的一点体会我自己在跑 HER 实验之前一直有个坏习惯训练不收敛就想着换模型、加网络层数。跑通 HER 之后才意识到很多时候问题不是模型不够强而是训练数据里的“失败”没有被好好利用。后来不管是写代码还是做别的事情我开始刻意记录那些“没完成但有收获”的尝试攒下来的东西过几个月回头看价值远超当时那个没达成的目标。最后再分享一个小技巧如果某天你跑 HER 训练等曲线等得心焦不妨把 console 输出改成实时的成功率统计不要只看 reward loss。成功率曲线才是真正反映“智能体是否学会做事”的指标HER 的 loss 曲线可能看起来波澜不惊成功率却已经悄悄爬起来了。
返回列表