我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

HER算法拆解:用后见之明解决强化学习稀疏奖励难题

HER算法拆解:用后见之明解决强化学习稀疏奖励难题 hindsight后见之明。如果你最近在折腾强化学习尤其碰过那种“死活等不到正奖励”的稀疏奖励任务这个词你绕不开。这里说的不是什么“早知道我当初就……”的人生感慨而是 OpenAI 在 2017 年开源的一套经典到不能再经典的算法——Hindsight Experience Replay简称 HER。它借用了同一个思考角度既然没达到原来的目标那就把你真正达到的状态重新定义成目标于是每一条失败轨迹都变成了可学习、可复用的经验。这篇文章就把 HER 从思想到实现、从参数到踩坑完整拆开适合正在调稀疏奖励任务的算法工程师也适合刚把 DQN 跑通、想往更真实场景进发的同学。1. 先搞懂 Hindsight 到底在解决什么问题1.1 稀疏奖励是强化学习的“哑火”时刻大多数强化学习算法能正常工作依赖一条隐式假设奖励信号要足够密集或者至少能提供一个渐进的梯度方向。但真实场景恰恰相反机器人伸手去抓一个物体、游戏里完成一个多阶段任务、推荐系统里用户完成一次转化这些过程的奖励往往是“0 一直持续直到最后一刻突然变成 1”。在稀疏奖励下标准 off-policy 算法会出现一种特别尴尬的局面智能体拿着随机策略在环境里瞎逛逛了几万步没拿到一次正奖励回放缓冲区里全是奖励为 0 的样本。算法更新时看到的所有 data 都指向同一个结论——“做什么都无所谓”于是策略网络越学越平最后干脆躺平。你观察 loss 曲线它可能一直在下降但智能体依然什么都不会这就是典型的“哑火”。Hindsight Experience Replay 的设计初衷就是专门收拾这种局面。它不要求你改环境、不要求你设计复杂的奖励塑形函数而是在经验回放这个层面做文章用“失败”数据自己生成“假想成功”数据。很多人第一次听到这个思路会反应“这不就是作弊吗”其实它不是作弊它是在重新定义什么叫做“学习信号”。1.2 把失败重新解读成经验HER 的核心思想HER 适用的任务有一个共同特征目标条件化goal-conditioned。也就是说环境有一个目标 g智能体的观测里明确带了 desired_goal同时每一步还能拿到当前已经达到的目标 achieved_goal。典型例子是机械臂抓取目标是一个三维坐标机械臂每一时刻的末端位置就是 achieved_goal。传统 replay 里存的是形如 (s, a, r, s, done) 的 transitionHER 则把目标也存进去变成 (s, a, r, s, g, done)。最关键的一步在采样之后一条 episode 走完发现原始目标 g 根本没达到奖励全是负的这时候 HER 会从这条 episode 里挑一些时刻把原目标 g 替换成轨迹未来某个时刻实际达到的 achieved_goal再按照新的目标重新计算奖励。一旦目标被替换成“已经发生过的事实”奖励就不是全 -1 了而是最后一次触达时给 0中间步骤也会出现大量“差一点就成功”的相对信号。你可能已经意识到这意味着智能体的学习目标变了它学会的不是“完成原任务”而是“在任何给定目标下都能从当前状态逼近目标”。原任务成了所有可能目标任务中的一个采样点。正是这种“对所有目标负责”的策略让 HER 在训练早期就能从完全随机的经验里挖出梯度而不是干瞪眼。1.3 为什么它叫“后见之明”不叫“事后诸葛亮”中文语境里“事后诸葛亮”带点贬义指的是事情结束之后谁都会说两句。但 HER 的 hindsight 是一个中性的认知偏差人在复盘时会自然地用“最终结果”替代“当初目标”来评价自己的行为。比如你想打篮球投进某个角度没投进但球碰巧弹出在另一个位置你用这次弹跳轨迹练习了“从这个新位置出手”下一次遇到类似位置就更从容。这就是后见之明在帮人创造课程而不是在欺骗自己。HER 把这种复盘逻辑机械化、数据化了。它不关心智能体原来想干什么只关心它实际去了哪里、哪里是可达到的。只要一个状态在轨迹中真实出现过它就可以被作为后续学习的“伪目标”回放给策略网络。这种方式有一个隐藏的好处伪目标全部来自真实物理轨迹不会像人工随机撒点那样超出环境动力学允许的范围。对机器人操作这类任务来说这一点非常重要因为脱离物理可行的目标根本不可能被学到。2. 拆开 HER目标重标记的细节与关键参数2.1 进入 HER 的数据流采样、重放、重标记HER 不是一个一上来就能直接部署的独立算法它是一个“外挂”需要挂在任意 off-policy 算法下面常见搭档是 DQN、DDPG、TD3 和 SAC。下面这一段是 HER 的数据流全貌建议对照着代码理解。当智能体在环境里跑完一条 episode它会得到一串原始 transition比如 50 步。普通的回放缓冲区直接把这 50 条塞进池子就结束了HER 会在入池之前多做一件事为每条原始 transition 额外伪造若干个“目标被替换后的 transition”。伪 transition 里的状态、动作、下一个状态、done 全部保持不变只改目标然后按照新目标重新计算奖励。伪 transition 跟原始 transition 一起进入回放缓冲区。之后采样时训练算法根本不区分哪些是原始样本、哪些是伪造样本一视同仁地更新网络。这里有一个很容易忽略的点奖励函数必须是“给定一个目标就能独立计算”的形式。如果你原来依赖环境自动返回 reward那么没法重算你需要把 reward 函数拆出来比如基于 achieved_goal 和 desired_goal 的距离阈值自己写一个 compute_reward。设计时要注意这个函数只依赖 s 的 achieved_goal 和传入的 goal不依赖动作历史否则重标记就没有意义。2.2 四种目标重标记策略怎么选原文给出了四种替换新目标的策略区别只在于“伪目标从哪里取”。策略名伪目标来源特点与适用场景final整条 episode 最后一步的 achieved_goal实现简单、计算量最小但目标分布单一训练早期容易学不到多样化经验random整条 episode 里随机取一步的 achieved_goal增加了目标多样性但会出现“先到 A 再到 B”这种不一致轨迹噪声较大future同一条 episode 中当前时刻 t 之后某步的 achieved_goal最常用、效果最稳保证伪目标在当前状态之后是可达的episode同一条 episode 中任意一步的 achieved_goal介于 random 和 future 之间用的较少实操里绝大多数项目直接选 future。它的逻辑是当前状态 s_t 后面的某个未来状态 s_{t} 一定是从 s_t 经过真实动作走出来的所以“从 s_t 出发朝着 s_{t} 对应的 achieved_goal 前进”是一个物理合理、可逆推的学习信号避免了 random 策略里那种时间顺序错乱带来的学习冲突。future 策略里的 t 也有讲究。原文在使用 future 时是从 [t, T-1] 区间里均匀采样一个未来时间点T 是 episode 总步数。你也可以限制一个固定窗口比如只从未来 10 步以内取这在一些长 horizon 任务里更稳。具体窗口多长需要调核心原则是伪目标与当前状态距离既不能太近信号太弱也不能太远和原目标差不多稀疏。2.3 k 值、未来步数与 reward shaping 怎么配HER 原文里最核心的超参数是 k表示每条轨迹额外生成多少条伪 transition。假设一条 episode 有 50 步k4那么这 50 步会变成 50×(14)250 条样本入池其中 200 条是伪造的。k 的取值直接决定伪样本量占总样本的比例。OpenAI 在 Fetch 系列机械臂任务上验证过 k4 效果很好我自己的经验是任务越难、目标越稀疏k 可以适当加大到 8~16但 k 太大会导致回放缓冲区里伪造样本占绝对主导原始目标样本被稀释算法会过度善于“完成已经到达的目标”却在原始目标上毫无动静。所以 k 不是越大越好它本质上是“实际目标”经验和“事后目标”经验之间的平衡旋钮。奖励塑形方面HER 原文用的是最简单的二值奖励距离小于阈值给 0否则给 -1。很多同学一上来觉得二值奖励信息量太少想手动加一个连续距离奖励。加是可以加但要小心一旦加了距离奖励HER 未来策略的优化方向和原始任务的优化方向可能会产生冲突。我的建议是先用纯二值把 HER 的效果跑出来再加连续项并且用实验对比别拍脑袋。3. 落地实操把 HER 装进你的 off-policy 算法里3.1 环境侧准备goal-conditioned 接口怎么改在动任何算法代码之前先确认你的环境是 goal-conditioned 的。标准 gym 接口里观测通常是单一状态向量但 goal-conditioned 环境需要同时提供当前状态、desired_goal、achieved_goal 三份信息。常见的做法是用字典观测形如 obs {observation: ..., desired_goal: ..., achieved_goal: ...}gym 自带的 FetchReach 和 FetchPush 都是这种格式。如果你的环境不是现成的需要自己动手改。假设你有机器人仿真环境状态里包含机械臂关节角和末端位置你可以把末端位置单独抽出来做成 achieved_goal再另外定义 desired_goal 为任务目标。步骤大致是reset 时随机生成一个 desired_goalstep 时从状态向量里提取新的 achieved_goalreward 用自定义函数计算并作为 step 返回值done 是 reward 达到 0 的标志。还有一点很关键每一步返回的 info 里必须带上 achieved_goal因为 HER 的伪目标重标记要拿它做替换。如果 info 里丢了整条经验就废了。3.2 缓冲区改造一条轨迹拆出N条经验的实现下面给一个可直接参考的简化实现核心逻辑就是入池前对整条 episode 做重标记。为了表达清晰我用了最简单的 list 结构实际工程里建议直接用高效的环形缓冲区。import numpy as np def compute_reward(achieved_goal, desired_goal, threshold0.05): # 二值奖励到位给 0否则 -1 dist np.linalg.norm(achieved_goal - desired_goal, axis-1) return (dist threshold).astype(np.float32) - 1.0 def relabel_episode(episode, k4, strategyfuture, threshold0.05): episode: list of dicts每条包含 obs, action, obs_next, done, achieved_goal 这里假设原始 reward 已经在 episode 里了但重标记后会重新计算。 new_transitions [] T len(episode) for t, trans in enumerate(episode): # 1. 原始样本原样保留 new_transitions.append(trans) # 2. 额外生成 k 条伪样本 for _ in range(k): if strategy future: # 从 t 到 T-1 里均匀等概率取一个未来时间点 future_idx np.random.randint(t, T) new_goal episode[future_idx][obs_next][achieved_goal] elif strategy final: new_goal episode[-1][obs_next][achieved_goal] else: idx np.random.randint(0, T) new_goal episode[idx][obs_next][achieved_goal] # 3. 基于新的目标计算新奖励 new_reward compute_reward( trans[obs_next][achieved_goal], new_goal, threshold ) # 4. 构造伪 transition只替换 goal 和 reward fake dict(trans) fake[obs] {**trans[obs], desired_goal: new_goal} fake[obs_next] {**trans[obs_next], desired_goal: new_goal} fake[reward] new_reward new_transitions.append(fake) return new_transitions这段代码有几个值得注意的边角细节。第一obs 和 obs_next 都要替换 desired_goal保证网络输入和目标一致否则策略看到的 goal 和计算 reward 的 goal 对不上训练直接乱套。第二fake 里 done 标志保持原样不要跟着新奖励改HER 的终止条件是环境物理上的终止不是目标是否达成这个细节很多人会在实现时忽略。第三如果是 off-policy 算法且使用 N 步回报重标记后连续样本的 done 关系也要重新审视一般建议先跑一步 TD 版本别一上来就上复杂设定。3.3 网络与训练配置一个可以直接抄作业的配方HER 在原文里配合的是 DDPG后来大家发现 SACHER 更稳。网络结构上状态和目标先各自过一层编码再拼接进主网络这是最常见的做法。你也可以直接把 observation 和 desired_goal 拼接成一个长向量输入效果在低维目标空间里差别不大。我提供一个基于 SACHER 的可复现配置这套参数在 FetchReach 上大概 20 个 epoch 内能稳定解决FetchPush 上大约需要 100~150 个 epoch策略网络两层全连接各自 512 个神经元激活函数 ReLUQ 网络两层全连接各自 512 个神经元输出层去掉激活优化器Adam学习率 3e-4折扣因子 gamma0.98不要贪心到 0.99会把早年的稀疏奖励也拉得太重目标网络更新soft updatetau0.05回放缓冲区大小1e6 条 transition采样 batch size1024比普通 SAC 默认的 256 大因为伪样本噪声大batch 大一点梯度更稳探索噪声动作空间为连续时初始高斯噪声 std0.3随着训练衰减到 0.05episode 长度50 步超过就强制终止训练节奏上我习惯每个 epoch 先采 50 个 episode再更新 100 次网络。采样和更新的比例里隐含了 HER 的一个特性因为伪造样本让有效信息密度大大增加你不需要像普通稀疏奖励任务那样疯狂采样反而要把更新次数拉高让网络充分消化这些“事后目标”。如果你发现智能体早期行为变得很奇怪可以试着把更新次数降一半回头检查一下是不是伪样本占比过高导致原始目标被淹没。4. 踩坑记录与参数调优实录4.1 训练不收敛先检查目标分布是否太窄我第一回把 HER 跑在自研的机械臂仿真环境上连续三天不收敛。后来把伪目标打出来看分布发现整条轨迹的 achieved_goal 几乎挤在一个小球区域内因为我的随机控制器前期只会让机械臂在一个很小的邻域里抖动。所有轨迹的伪目标都长得很像等于自学了一门“如何完成同一个简单目标”的课程对真实目标毫无泛化。解决办法最直接的是加强探索增大动作噪声、随机重置初始状态、甚至在早期用随机策略跑长一些。HER 的效果上限取决于经验池里状态覆盖的多样性。很多论文复现经验强调“HER 在好的探索策略下效果极好在差的探索策略下几乎没提升”就是这个原因。在采样阶段花时间把覆盖度拉起来比在训练阶段反复调学习率有效得多。4.2 HER 失效的几种典型情况HER 不是万能灵药我整理三种最典型的失效场景对排查问题很有参考价值。第一种目标空间状态跟任务成功条件不对齐。比如你抓取物体但 achieved_goal 定义成机械臂末端位置而不是物体位置那么即便机械臂末端“到达”了伪目标物体也没被抓起来奖励函数给 0 就产生了虚假成功信号。这种问题通过修改 achieved_goal 定义解决务必让目标充分描述任务成败。第二种环境奖励本身有依赖历史或隐藏信息的成分。HER 重新计算奖励的公式是 r(s, a, g) 或 r(s, g)一旦你的原始奖励函数依赖当前状态之外的信息比如“是否执行了某动作序列的前置动作”重标记后奖励就失真了。第三种目标空间维度高于可探索状态空间的有效维度。比如目标是一个 100 维像素图而智能体只能控制 7 个关节角可达到的状态只占目标空间里极小一块流形。HER 用欧氏距离计算伪目标奖励时绝大多数伪目标都离流形很远信号失去区分度。这种情况的出路是先把高维观测压缩成低维潜空间表示在潜空间上做 HER或者引入距离学习网络。4.3 混合经验重放与后续扩展方向HER 虽然是 2017 年的工作但它带火了一系列围绕“目标重标记”的后续方法。现在的项目里我更推荐把 HER 和优先级经验回放结合伪造样本不是同等重要的有些伪目标距离原目标近、信息价值高应该优先被采样。做法很简单给每条 transition 维护一个 TD error按它的绝对值计算采样概率能明显加速 Fetch 类任务收敛。另一个工程上的常用扩展是“目标切换器”。在训练过程中按比例调整伪目标的生成策略前期多用 future 和 random 提供多样性后期逐渐增加 final 的比例让策略把注意力拉回到真实任务。这个思路有点类似课程学习的退火过程在复杂工业场景里比固定单一策略更稳定。我还会在上手新任务时先跑一个最小模板环境允许的话先拿 DDPGHER 在 FetchReach 一类标准任务上验证代码正确性再迁移到自己的环境。HER 算法本身不算复杂但环境接口、奖励重算、目标对齐这些小细节一旦出错症状都很像“算法不收敛”排查起来非常消耗耐心。把这套验证流程固定下来能少熬不少夜。
返回列表