ARTICLE DETAIL

资讯详情

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

HER算法实战:目标重标注如何破解强化学习稀疏奖励难题

HER算法实战:目标重标注如何破解强化学习稀疏奖励难题 1. 从后见之明说起这个词在技术圈到底指什么hindsight 直译过来是后见之明好听点叫事后复盘难听点就是事后诸葛亮。有意思的是技术圈里至少有三种完全不同的东西都叫这个名字。前端领域它曾是 Google 提过的一种高容量本地存储方案那时候 Chrome 想做一个比 localStorage 更简单的大容量 API后来无疾而终交互设计领域它是可用性测试里常用的 retrospective think-aloud让用户回看操作录像、边看边复盘自己当时的想法而强化学习领域它是一项改变训练效率的经典算法——Hindsight Experience Replay缩写 HER。今天要聊的是第三个但它的思考方式其实跟前两个一脉相承都是利用事后才知道的信息回头改造原来的过程。HER 这个名字起得非常妙。你设了一个目标让智能体去够它没够到但它在失败的过程中其实碰巧完成了另一个目标。如果事后我们把这个碰巧达成的状态当作目标重新训练原本的失败轨迹就变成了一条成功轨迹。这个思路 2018 年被 Andrychowicz 等人写成论文发表在 NeurIPS论文标题就叫《Hindsight Experience Replay》。当时它解决的是一个让无数强化学习工程师头疼的顽疾稀疏奖励任务里智能体随机探索半天一个正样本都遇不到训练过程完全停滞。这篇文章不是给你念论文翻译而是把我自己实际跑 HER 时理解透的东西、踩过的坑、验证过的工程细节全部讲清楚。适合三类人看刚入门强化学习、想搞清楚 HER 到底是什么的小白已经在用 DDPG、PPO、SAC 这类算法、想把 HER 接进自己代码里的工程师以及做机械臂抓取、路径规划这类连续控制任务、天天对着奖励一直为零发愁的算法同学。1.1 三种不同语境下的 hindsight为什么我一直强调这个词先说存储 API 和交互设计这两个分支是为了避免你搜资料的时候被带偏。你搜 hindsight API搜出来的可能是一堆过时浏览器提案搜 hindsight usability出来的是用户体验测试方法。只有在强化学习上下文里hindsight 才指经验回放技巧。所以对初学者第一步不是看公式而是先确认你搜到的资料跟你想学的是不是同一个东西。交互设计里的回顾式出声思考其实最能帮助我们建立直觉用户在完成任务后回看屏幕录像边看边说我这一步本来想点哪里我以为这个按钮是干嘛的。这些事后吐露的信息比操作过程中的实时反应更完整因为人在操作瞬间往往来不及解释自己的意图。HER 的思路与之惊人相似智能体在 Episode 运行中根本来不及思考目标是否合理只有跑完了我们才能回头说你刚才其实成功到达了这里。这种事后补标签的哲学就是 HER 的灵魂。1.2 这篇文章要解决的核心问题HER 的官方定义很短一种用于多目标强化学习的经验回放技巧通过目标重标注goal relabeling把失败的 Episode 转变成有学习信号的数据。但定义短不代表好懂。绝大多数人看完公式后依旧不知道代码该怎么写重标目标到底选哪个状态奖励要不要重新算done 要不要跟着改重标后的样本跟原始样本按什么比例混合这些问题论文里不会手把手教你只有自己把代码写一遍、实验跑崩几次才会真正记住。我写这篇文章的目标很直接让你看完之后能自己动手实现一个最小可运行的 HER并理解每个设计选择背后的原因。我会从稀疏奖励这个痛点讲起拆解目标重标注的原理给出可复现的 PyTorch 代码框架再列一份我在实际工程里整理的踩坑清单。读完全文你至少能明白为什么 HER 能奏效在什么条件下它会失效以及怎么把它接到你自己的算法里。2. HER 是怎么把失败变教学样本的核心原理解析2.1 稀疏奖励问题到底难在哪先看一个典型困境。假设你训练一个机械臂去抓取桌面上一个位置随机的小方块奖励设置很稀疏只有机械爪成功抓住方块并抬到指定高度时给 1其余每一步都是 0。这个设定非常自然因为它不需要人为设计中间奖励但训练起来却极其痛苦。原因在于强化学习的本质是由奖励驱动的试错。随机初始化的策略在绝大多数情况下碰不到方块于是你收集到的所有经验奖励都是 0。Q 网络的更新公式里目标值是 r γ max Q(s, a)当 r 恒为 0 时网络只是在反复拟合下一步的 Q 约等于当前 Q根本学不到哪个动作离成功更近。就算你让智能体跑了几百万步它连一次正反馈都没见过梯度方向完全随机策略自然原地踏步。更麻烦的是许多工程师遇到这种情况的第一反应是加 reward shaping比如离方块越近奖励越大。这确实能缓解稀疏问题但 shaping 函数的设计本身就是一门玄学距离度量选欧氏距离还是曼哈顿距离要不要考虑机械臂关节角度奖励系数多大才不会压制真正目标的信号我见过太多项目在 shaping 上一调就是几个月最后策略学会了在目标附近抖动骗取 shaping 分数真实任务成功率反而上不去。HER 的价值就在于它不需要任何 shaping直接让失败经验自己长出奖励来。2.2 目标重标注goal relabeling的两步逻辑HER 能成立的前提是任务必须是目标条件化goal-conditioned的。换句话说环境里存在一个明确的目标变量 g策略的输入是 (状态, 目标)奖励函数 R(s, g) 负责判断当前状态是否接近目标。很多真实任务天然满足这个条件机械臂抓取任务的目标是物体最终位置导航任务的目标是目的地坐标游戏任务是通关但也可以改造成到达某个关键点位。现在想象一个具体场景。智能体的目标是到达坐标 A (2.0, 3.0)但它这一轮实际走了个弯最终停在了 B (1.5, 1.2)。从目标 A的视角看这条轨迹完完全全是失败的50 步里每一步奖励都是 0。但从目标 B的视角看呢这个智能体最后就站在 B 点上如果它一开始的目标就是 B那么它确实成功了最后一步应该给 1。HER 的核心操作就是把这个视角切换落到数据上把轨迹中每一步的 goal 字段从 A 改成 B并用 R(s, B) 重新计算每一步的奖励。于是原来一条零奖励的失败轨迹变成了一条末尾带 1 的成功轨迹。这就叫目标重标注英文叫 goal relabeling。但它不是把整条轨迹所有样本都简单粗暴地塞进 Buffer 就完事了。实际操作分两路一路保留原始目标 A 的经验让策略始终知道用户真正要的是什么另一路生成重标目标 B 的新经验用来提供学习信号。两条数据流混在一起智能体既不会忘记真正的目标又能从失败中获得梯度。这个混合的比例和采样策略直接影响效果后面代码部分我会细说。2.3 为什么重标目标没有破坏 Q 学习的正确性第一次接触 HER 的人都会有一个合理质疑你把目标从 A 换成 B这不是篡改环境吗Q 学习的收敛性不就毁了吗这里的关键在于HER 并没有修改环境的转移函数和奖励结构它只是额外增加了对另一个目标的经验估计。打个比方。一个学生做题题目要求解得 x5他算错了得到 x3。老师改卷时不会说啊你算出了 3那这道题算你对。但如果另外出两道题一道问3 的平方是多少一道问21 等于多少学生可能都能答对。错误答案本身包含正确信息只是对应的题目不同。HER 做的不是把解 x5改成解 x3而是额外生成了一批以 x3 为目标的新题目用学生刚才的错误过程来回答这些新题目。对于 Q 函数而言每个 (s, a, g) 的奖励都是根据既定规则 R(s, g) 重新计算的数学上依然自洽。严格来说论文里论证了重标目标最好取自与当前状态相对独立的分布所以future 采样从未来时间步的状态里挑一个当目标通常比final 采样只用最后一个状态当目标效果更稳定因为时间距离越远目标跟当前状态的相关性越低越不会引入大的估计偏差。这个细节是 HER 实现效果好坏的分水岭新手往往忽略后面实操部分会再次强调。3. 手把手实现 HER一个可跑通的最小代码框架3.1 环境准备用二维 reaching 任务做试验田在跳到机器人环境之前强烈建议先在一个几秒钟就能跑完的最小任务上验证 HER 代码。我常用的是一个二维 reaching 环境智能体是一个点状态是它当前的二维坐标目标是从屏幕随机位置采样出的一个二维点动作是 x、y 方向的速度范围 [-1, 1]。只有智能体在一步之后与目标的欧氏距离小于 0.1奖励为 1否则为 0。每轮 Episode 最多 50 步超时自动终止。这个环境虽然简单却完整包含了稀疏奖励的所有要素随机探索很难精确落在一个半径 0.1 的小圆里如果不加 HERDDPG 大概率长时间学不出任何东西。你可以在它上面快速验证你的重标逻辑、调试网络结构跑通后再迁移到 Fetch 这类机械臂环境。我把环境接口设计成跟 OpenAI Gym 的标准 goal-conditioned 接口一致reset(goal...)接收目标参数step(action)返回(obs, reward, done, info)。这样后续接任何强化学习库都不需要改太多代码。3.2 核心代码her_relabel 与训练循环骨架HER 的全部精髓都在一个函数里对 Episode 里每个时间步除了保留原始样本再生成若干条以未来实际达到状态为目标的新样本。下面这段代码是我在项目里反复使用后精简出来的你可以直接抄走改改用。import numpy as np def her_relabel(episode, compute_reward, strategyfuture, k4): episode: list of (obs, act, reward, obs_next, done, goal) compute_reward(obs_next, goal) - float strategy: future / final / episode / random horizon len(episode) transitions [] for t, (obs, act, _, obs_next, done, goal) in enumerate(episode): # 原始样本永远保留让策略记住真实的用户目标 transitions.append((obs, act, compute_reward(obs_next, goal), obs_next, done, goal)) # 构造候选目标集合从未来状态里挑 candidates [] if strategy future: for i in range(t 1, horizon): candidates.append(episode[i][3]) # episode[i][3] 是 obs_next elif strategy final: if horizon 0: candidates [episode[-1][3]] elif strategy episode: for i in range(horizon): candidates.append(episode[i][3]) elif strategy random: candidates [sample_random_goal() for _ in range(horizon - t)] if not candidates: continue # 从候选中无放回抽 k 个作为新目标 idxs np.random.choice(len(candidates), sizemin(k, len(candidates)), replaceFalse) for idx in idxs: new_goal candidates[idx] new_reward compute_reward(obs_next, new_goal) # 注意done 保持不变不因为重标成功就提前终止 transitions.append((obs, act, new_reward, obs_next, done, new_goal)) return transitions然后是在训练循环里怎么用它。每跑完一个 Episode把它交给her_relabel得到一批扩展后的样本全部推进 replay buffer。网络训练时从 buffer 里随机采样一个 batch每个样本都带有自己的 goal更新Q(s, a, goal)和策略。def train_one_episode(env, policy, replay_buffer, goal, her_k4): episode [] obs env.reset(goalgoal) done False while not done: act policy.sample_action(obs, goal) # 加探索噪声 obs_next, reward, done, info env.step(act) episode.append((obs, act, reward, obs_next, done, goal)) obs obs_next if len(episode) 50: # 超时 break # HER 重标 extended her_relabel(episode, env.compute_reward, strategyfuture, kher_k) for trans in extended: replay_buffer.push(*trans)如果你用的是 PyTorch 写的 DDPGbatch 里通常包含obs、goal、act、reward、obs_next、done六个量。拿 Q 网络更新举例输入要把状态和目标拼接起来import torch import torch.nn.functional as F # 采样 batch obs, goals, acts, rewards, obs_next, dones replay_buffer.sample(batch_size) # 让 target Q 网络计算目标值 with torch.no_grad(): next_actions target_policy(obs_next, goals) # actor 输入也拼接 goal q_next target_q(obs_next, next_actions, goals) q_target rewards (1 - dones) * gamma * q_next q_loss F.mse_loss(q(obs, acts, goals), q_target)这里最容易犯的错是Q 网络输入漏掉 goal。很多人从普通 DDPG 代码起步网络输入只写了obs和act完全忘了把goal拼进去结果 HER 的样本即使换了目标网络也根本感知不到差异训练自然没有任何进展。记住三个输入位Q(s, a, g)、actor(s, g)、target_Q(s, a, g)都不能少。3.3 效果观察加 HER 与不加 HER 的差别把同一个 reaching 任务分别在普通 DDPG 和 DDPGHER 下各跑一遍你会看到近乎教科书式的差别。普通 DDPG 在几万步之内成功率往往一直是 0Q loss 曲线看起来像一条几乎没有变化的水平线因为所有 batch 里的 reward 都是 0网络只是在学输出一个固定值。而 DDPGHER 往往几千步之后就能看到成功率开始爬升一开始是偶尔成功然后稳步提高。我自己实测下来的经验是观察训练不能只看成功率更值得盯的是 replay buffer 里正样本比例。HER 起作用的时候同一个 batch 里会有一定比例的 reward1 样本Q 网络才能真正学到某些状态是好的。如果跑了很久 buffer 里正样本依然是 0那说明重标逻辑根本没生效得回头检查奖励函数或者未来状态索引有没有写错。另外我建议你做一个简单消融把 strategy 从future改成final看看成功率变化。在很多连续控制任务里final策略因为只使用最后一个状态当目标目标与中间状态的相关性高效果往往不如future。这不是玄学而是因为future随机性更强相当于给数据做了更多元化采样。这也是论文里重点强调的发现。4. HER 能用在哪些真实场景工程应用与扩展思路4.1 机器人操作任务里的经典用法HER 目前最有名的应用场景是机械臂操作尤其是 OpenAI 论文里那组 Fetch 环境FetchReach、FetchPush、FetchPickAndPlace、FetchSlide。这些任务里机械臂自由度很高随机策略几乎不可能直接把方块推到目标位置。论文给出的曲线显示单纯用 DDPG 在这些任务上成功率接近 0而 DDPGHER 在训练后期能达到 90% 以上。我第一次看到那个对比图的时候第一反应是怎么可能差距这么大但自己复现之后发现确实如此。道理并不复杂机械臂每推一次方块方块都会停在一个新位置。从要把方块推到目标点 P的角度看这一次尝试失败了但从方块现在在位置 Q的角度看这次尝试完美地把方块推到了 Q。HER 把这次失败经验转化成了如何把方块从起始位置推到 Q的成功经验等于每一次试错都在为附近的目标点积累有效训练数据。在一个目标密集的任务空间里学习效率自然暴涨。值得提醒的是真实机器人场景里 Episode 数据的收集成本很高所以 HER 的一鱼多吃效果特别宝贵。我见过不少实际项目拿历史失败轨迹做离线 HER 重标效果堪比额外的在线探索。只要你有足够多样的历史状态覆盖重标出来的样本质量是有保证的。4.2 HER 与其他强化学习算法的组合方式HER 最初搭配的是 DDPG但它并不挑食。只要是离策略off-policy算法理论上都能接 HERTD3、SAC、DQN处理离散动作目标任务时都可以。核心要求只有一个你的经验可以进 replay buffer并且可以重复采样训练。PPO、A2C 这类同策略on-policy算法就不太适合直接加 HER因为重标改变了经验分布同策略算法对数据来源有严格假设。与 SAC 结合时网络输入依然要拼上 goal。SAC 的更新公式里熵项、Q 项、策略项都要带上 goal 维度。我在实际项目里通常把obs和goal先分别过一个小编码器再拼接起来这样高维状态和稀疏目标能各自抽象一部分语义。另一个工程细节是HER 的样本量会成倍增加buffer 容量要给足不然原始目标经验和重标目标经验互相挤掉效果反而下降。顺便提一句如果你用的是 TD3它本身双 Q 网络的设计可以缓解 Q 值过估计而 HR 提供的正样本又能解决稀疏奖励问题两者互补性很好。所以现在很多人直接把 TD3HER 作为连续控制基线而不是原来的 DDPGHER。4.3 状态空间和 goal 空间不一致时的处理HER 最舒服的情况是 state 和 goal 在同一个空间比如目标就是某个坐标位置重标时直接把未来状态当作新目标就行。但真实任务不是都这么幸运。比如机械臂抓取任务的目标可能是把红色物体抓起来放到蓝色区域目标很难直接用机械臂状态表达又比如导航任务的目标可能是到达某个房间而状态是激光雷达点云。这时候重标不能直接拿状态当目标而是需要一个从状态到目标空间的映射函数 φ(s)。例如机械臂任务里你可以定义 φ(s) 物体当前抓取位置然后目标重标就是把未来状态的 φ 值当作新目标。逻辑上没变只是多了一道转换。如果映射很难手工设计也可以用学习到的编码器训练一个 encoder 把高维观测映射成隐向量goal 也映射到同一隐空间再在隐空间里算奖励。这个思路在很多视觉机器人任务里已经被验证是可行的。但这里也藏着 HER 的边界如果成功条件根本无法从状态判断或者目标空间离散且与实际达到的状态没有自然映射HER 就失效了。比如写一首关于秋天的诗这种开放目标你没法说这首诗恰好到达了某个状态。所以使用 HER 前先问自己一个问题我把未来时刻的状态映射成目标奖励函数还能公允地判断成功吗能就可以上 HER不能就得换方案。5. 踩坑实录HER 实现中的常见问题与排查方法5.1 重标之后 reward 没更新这是我第一次自己写 HER 时犯的错误。当时我把原始样本直接复制只改 goal 字段忘记用新目标重新调用奖励函数。结果 replay buffer 里塞满了目标明明是 B奖励却还是 0的样本。更坑的是这种错误不报错训练也不崩只是学习效率没有提升让你怀疑 HER 是吹出来的。排查手段非常简单从 replay buffer 里随机抽一批样本打印new_goal和reward手动算一遍compute_reward(obs_next, new_goal)对比是否一致。只要不一致说明重标后奖励没更新。统计上还有个小技巧实时打印每个 batch 的正样本比例HER 生效时这个比例应该随着训练上升如果始终为 0优先怀疑这里。顺带说一个容易被忽略的细节compute_reward里的阈值要跟环境真实判断成功用的阈值保持一致否则会出现HER 觉得成功了、真实环境判定没成功的尴尬局面。让策略学到的成功和环境的成功不同频后续评估肯定拉胯。5.2 done 标志重标错误导致训练崩溃这个坑非常隐蔽。很多实现为了图省事重标后看到compute_reward为正就顺手把done也改成 True。这在单步任务里问题不大但在多步任务里是灾难。为什么因为 Q 更新时done1会截断未来回报让目标值变成reward本身。重标样本本来就容易产生正奖励如果同时把done置为 1等于告诉策略你到达这个新目标任务就结束了不用管后续。但真实环境里智能体在 50 步之内不会因为到达某个中间位置而终止它后面还可以继续运动。错误地把done设为 1 会让策略出现到点就停的惰性甚至在真实评估时不敢走出成功区域。我在一次抓取任务里就吃过这个亏训练曲线前几万步看着挺好一换评估环境直接崩到 0。记住 HER 重标只改 goal 和 reward永远不要动done。除非你的环境本身定义就是达到任意状态立即结束否则这条铁律不要破。5.3 future 策略的索引与采样细节future策略的代码看起来只有几行但边界情况很多。比如在 Episode 的最后一步range(t1, horizon)是空的候选集为空此时应该直接跳过重标而不是报索引越界。另一个常见问题是如果你把obs_next存成了 tuple比如(x, y)坐标而 goal 需要 numpy array直接拿 tuple 当 goal 会把维度搞混。我建议在环境返回的状态上统一套一层np.asarray(obs, dtypenp.float32)保证所有数据都是 numpy 数组。采样数量 k 的选择也值得说道。k 太小正样本密度不够k 太大buffer 里有大量重复度高且高度相关的新样本训练效率不升反降。论文里常用 k4很多开源实现也默认 4我自己做过小范围扫参4 到 8 之间差别不大但明显小于 2 或大于 16 都会让效果变差。另外候选集较大时用np.random.choice(..., replaceFalse)无放回采样避免同一个未来状态反复出现。5.4 HER 和 on-policy 算法混用的误区我见过有人把 PPO 训练循环里每个 Episode 都做 HER 重标然后像 off-policy 那样重复训练几十轮结果策略完全不收敛。根源在于 PPO 的损失函数依赖当前策略产生数据的概率比值你用重标后的目标重新回放旧数据时数据分布已经和当前策略不一致了但 PPO 没有 off-policy 纠正机制强行多轮利用会导致优化方向失真。这不是说 on-policy 场景完全不能用 HER而是不能像 off-policy 那样直接往经验池里堆。正确的姿势是先用普通 on-policy 方式采样一批数据重标后只用来额外训练一个目标判别器或奖励模型再把这些信号合并进 PPO 的奖励计算里。这已经是更高级的改法了新手不要一上来就这么折腾。最稳妥的建议是想体验 HER 的效果直接用 DDPG、TD3 或 SAC 起步先把离策略这条路走通再说。6. 一点个人心得我在实际项目里用 HER 解决过不少稀疏奖励问题最大的感悟其实是思维方式的转变。以前看到一个任务学不动第一反应永远是调奖励函数、加 shaping、设计课程学习恨不得把怎么算成功这句话写进代码里。用了 HER 之后我开始习惯反过来问这次失败里有没有一个隐含的成功智能体没达到我指定的目标但它到达了什么能不能把这个到达变成教学信号这个小问题威力巨大。它不仅适用于强化学习也适用于日常的模型迭代和数据处理一次失败的实验、一批无标签的数据、一段跑偏的日志背后往往藏着别处用得上的规律关键在于你愿不愿意换个目标重新审视它们。HER 这个算法名字本身就是给所有工程师的一句提醒——事后聪明不是贬义词懂得从失败里重标目标才是真正能落地的智慧。
返回列表