ARTICLE DETAIL

资讯详情

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

强化学习稀疏奖励难题:HER目标重标注原理与PyTorch实现

强化学习稀疏奖励难题:HER目标重标注原理与PyTorch实现 你有没有过这种体验一盘棋下完复盘才发现有一步其实早就该看到了。对局里怎么都想不明白的局面局后一眼就能看出答案。这就是hindsight——事后才看明白。原先我一直觉得这只是人类认知里一个有点讽刺的小毛病直到我在机器人控制和游戏AI的训练里真正遇到它才发现“事后”这两个字在强化学习里简直是救命的。说的是Hindsight Experience Replay习惯叫它HER2017年发表在NeurIPS上到现在依然是处理稀疏奖励问题最经典的思路之一。大概意思是一个智能体在任务里明明失败了但算法可以把“失败时实际到达的状态”重新解释成“目标”于是这条原本价值为零甚至为负的经验摇身一变成了有效学习素材。我见过太多入门强化学习的人一上来就在稀疏奖励环境里跑半天曲线纹丝不动最后沮丧地认为算法有问题。而HER常常就是那个能把训练曲线从地板拉到天花板的转折点。这篇文章我想从直觉讲起先把HER为什么要这样做、这样做的数学逻辑是什么说清楚然后给出一份可以在PyTorch里直接跑通的最小实现思路再结合我在几个环境里的实际训练记录聊聊调参和踩坑。适合正准备搞RL项目、被稀疏奖励折磨得够呛的读者哪怕你只是对AI感兴趣只要愿意花十分钟跟着思路走也能理解它到底聪明在哪里。1. 从“事后才懂”到“事后学习”稀疏奖励这道坎1.1 为什么普通RL在稀疏奖励面前容易歇菜先复刻一个经典场景。机器人要推一个方块到目标位置任务成功才有奖励没到就不给任何反馈。这个实验听起来简单实际上会让绝大多数强化学习算法崩溃。原因是数学上的强化学习的核心是通过奖励信号估计价值函数或者通过策略梯度方向更新策略。如果几乎所有动作的即时奖励都是0只有极少数偶然动作能触发奖励那么梯度信号就跟沙漠里的水一样稀缺。以Fetch类环境为例目标在三维连续空间里动作也是连续的四维向量靠随机探索命中目标附近的概率低到可以忽略不计可能训练几十万个时间步一次正向奖励都碰不到。没有正向经验价值函数没有下降方向可以学习策略更新就成了原地打转。我经常用学投篮来类比这件事。一个没看过任何教学视频、也没人给反馈的人第一次站到三分线外如果规则是“进了才有奖励不进连你怎么投的都不评价”那他练上一整晚也不太可能进步因为他根本不知道该朝哪个方向调整动作——实际上他连“离篮筐还差多远”这条信息都拿不到。HER做的恰恰是补上这条缺失的信息你虽然没投进但这次发力方向、起跳时机对你“想投到另一个相对更近的位置”这件事是有参考价值的。1.2 常见的补奖办法为什么总有点别扭稀疏奖励这个问题太普遍了所以社区里早就有很多应对方案我先快速过一轮这样你能理解HER的位置在哪里。第一类是奖励塑形。人为给一些中间信号比如“越靠近目标奖励越大”地图上画一个势能场引导智能体。做法直观但代价是得写很多领域知识进去而且实测中塑形奖励经常被钻空子智能体会学会绕圈刷分而不是真正完成任务。我在一个导航任务里就遇过用奖励塑形训练出绕着目标画圈的“人工版曹冲称象”表面上累积奖励很高一看轨迹完全不能看。第二类是课程学习。先让智能体学容易版本再逐步加大难度。比如先让目标离得近一点再慢慢拉远。这个方法有效但难点在于“课程”是人工设计的你需要知道什么难度是合适的什么节奏不会让智能体遗忘旧任务。设计课程本身不亚于重新写一个任务。第三类是内在好奇心动机。让模型自己产生探索奖励比如预测误差大的状态有更高探索价值。这个方法效果不错但额外引入一个动力学模型或预测头训练复杂度上了一个台阶超参数也更多对刚入门的团队并不友好。而HER走了第四条路不动奖励函数不设计课程不引入额外模型只把已有经验的“目标标签”改掉让同一条轨迹可以被复用很多次。这个思路之所以漂亮是因为它不跟环境的奖励机制较劲而是在数据本身做文章。1.3 核心洞察失败里其实藏着半条成功路一句话讲透HER的核心洞察一条没完成任务的轨迹不是一无是处的废数据它只是“没完成预设目标”的废数据如果换一个目标来看它完全可能是一条合格甚至优秀的示范数据。举一个生活里的例子。周末你导航去找一家新开的咖啡馆结果绕了半天到了一个小公园。对“去咖啡馆”这个目标来说这趟全白走了。但如果你把目标改成“熟悉这片区域”那这趟路立刻变得有价值你知道了哪个路口是死路哪条小径可以穿到主干道。同样的轨迹目标不同价值完全不同。HER把这件事形式化到强化学习里每个episode结束之后取轨迹中实际到达过的某个状态把它定义为新的“goal”然后重新计算这个奖励把原来的失败轨迹变成以这个新goal为目标的成功轨迹放回经验回放池。智能体下一次抽样时就会发现——“原来状态s过后动作a确实能让状态变得更接近某个目标”。这种梯度信号是稠密的因为几乎每一段轨迹都能通过重标注产生正样本。这就是HER能撬动稀疏奖励环境的根本原因。2. HER原理逐层拆解2.1 先补一点强化学习和目标条件设定的符号要理解HER需要把强化学习的记号稍微调整一下。传统标准设定里智能体根据状态s选择动作a环境给一个奖励r。这里我们关心的是目标条件设定策略输入不再只是状态本身而是状态和目标的组合。形式化地说策略是π(a | s, g)其中g是当前希望完成的目标。环境会给一个额外的“当前完成度”信息通常叫achieved goal也就是智能体当前实际到达的状态对应的目标描述。拿刚才推方块的任务来说目标g是一个三维坐标点方块实际所在的位置是achieved goal记作ag。奖励很稀疏如果ag距离g小于某个阈值r1否则r0。这个奖励函数不提供任何中间信息全程只有0和1。这种形式化表达的用处是它让“目标”变成了和其他状态一样可以被操作的数据。目标不再是环境固定的属性而是可以作为变量替换。HER之所以能成立靠的就是把目标当数据处理。2.2 经验重标注是怎么发生的HER的数据操作核心叫goal relabeling中文一般叫“目标重标注”或“经验重写”。具体来说HER在训练过程中会得到一个transition元组原本是(s_t, a_t, r_t, s_{t1}, g)。这里的s_{t1}对应环境实际返回的achieved goal所在状态。重标注之后会额外生成一条新tuple变成(s_t, a_t, r_t, s_{t1}, g)。其中g不是原始目标而是轨迹中某个实际到达过的状态对应的目标描述r_t则根据“s_{t1}是否已经满足g”重新计算通常是1。举个例子。机器人尝试把方块推到(5, 0, 0)但实际推到了(2, 1, 0)。原始目标下这个transition的奖励是0。重标注时把新目标改成(2, 1, 0)那么对于新目标而言这个transition的奖励就变成1。这个“我本来的目标没完成但我确实完成了一个别的目标”的过程就是HER的心脏。还有一个关键在于重标注不是把原始目标扔掉而是把“原始目标的经验”和“新目标的经验”同时保留。实际操作里一般以一定比例混合比如一半的数据保留原始goal另一半用替换后的goal。为什么这样设计如果全部重标注智能体就会认为所有状态都是成功达到的目标分布和真实任务目标分布会产生很大偏差最后真正评估时它反而不理解“完成指定目标”意味着什么。保留一部分原始目标是为了让智能体记住真实任务是什么。2.3 新目标怎么选final和future策略确定了要重标注下一步自然的问题是新目标从哪来我在这类实验里试过两种主流策略教科书里一般叫final和future。final策略很直接每个episode结束时取这条轨迹最后一个状态作为新目标然后把整条轨迹的所有transition都用这个最终状态来重标注。这个策略实现简单但它有一个隐藏问题如果这个episode最终离原始目标很远那么最终状态本身也是个很难完成的目标训练后期容易拖慢收敛。future策略我更喜欢在一条轨迹里随机挑一个未来时刻k步之后的状态作为新目标只对当前位置之前的一段transition做重标注。为什么说“未来”因为我们是在事后采集所以未来的状态已经发生了自然可以作为目标。这样可以保证新目标是“真实可达的状态”难度可控不会出现用终局死路当目标的情况。两种策略对比起来final偏稳健省事future偏激进高效。我实测中future的收敛上限更高尤其在任务较长、轨迹状态分布丰富时但future多一个采样超参数k需要额外调。下面是两个策略的对比。策略新目标来源优点缺点适用场景final整条轨迹最终状态实现简单、参数少终局可能过远样本利用率受限任务较短、轨迹终局状态有一定价值future未来k步内的状态目标可达性高、曲线更稳额外采样参数k需要调任务较长、轨迹状态分布广3. 自己动手PyTorch下的最小HER实现3.1 选型为什么我用PyTorch DDPG思路讲完原理接下来是落地。我建议第一次实现HER的人不要直接上复杂的分布式框架最好从单机单卡、目标环境足够简单的设置开始。我用的环境是OpenAI的FetchReach和FetchPush它们都是标准稀疏奖励测试床相关文档和评测指标也成熟。算法方面HER本身并不规定必须用哪个基础RL算法它只负责改造经验数据。因为HER需要经验回放池所以天然配合off-policy算法。我这边用的是DDPG架构一个Actor网络、一个Critic网络、一个exploration noise。如果你熟悉DQN道理也是一样的只是连续控制需要Actor-Critic结构。这里的关键提醒是HER不改变损失函数本身只改变进到buffer的数据分布。很多人拿着HER的代码怎么Debug都找不出问题是因为他们一直盯着loss看但HER改动的地方不在这里。我建议的代码结构分四块经验元组定义、ReplayBuffer支持目标重标注、Actor和Critic网络、训练循环。这个结构清晰出问题也容易排查。3.2 经验重放的代码骨架经验缓冲是HER的核心容器。普通的ReplayBuffer存的是原始transition而HER的buffer必须额外保存轨迹级别的信息否则无法在episode结束后做重标注。我先给你一个最小实现思路。class HerReplayBuffer: def __init__(self, capacity, relabel_ratio0.5): self.capacity capacity self.relabel_ratio relabel_ratio # 重标注比例通常0.5 self.episodes [] # 按episode保存便于整段重标注 self.transitions [] # 拉平后的全局transition def add_episode(self, episode): # episode: list of (obs, achieved_goal, action, reward, new_obs, new_achieved_goal) self.episodes.append(episode) self.transitions.extend(episode) # 超出容量时简单策略丢弃最老的episode while len(self.transitions) self.capacity: old_ep self.episodes.pop(0) self.transitions self.transitions[len(old_ep):]重标注的采样逻辑是另一个重点。采样时对于每条transition我用random.random()判断它走原始目标还是替换目标。替换目标直接从对应episode的未来状态里抽一个achieved_goal作为新目标然后重新算奖励。def sample(self, batch_size): batch random.sample(self.transitions, batch_size) sampled [] for trans in batch: # trans dict with keys: obs, ag, act, rew, next_obs, next_ag, goal if random.random() self.relabel_ratio: # future策略找一个该episode里未来时刻的achieved goal ep trans[episode_id] future_idx random.randint(trans[step], len(self.episodes[ep]) - 1) new_goal self.episodes[ep][future_idx][next_ag] # 根据新目标和next_ag的距离重新计算稀疏奖励 new_rew 1.0 if _is_success(new_goal, trans[next_ag]) else 0.0 trans {**trans, goal: new_goal, rew: new_rew} sampled.append(trans) return sampled这个实现比较玩具但保留了HER的关键要素按episode存储、按比例重标注、future采样。你在工程化时还需要注意预分配内存、用numpy数组代替list、将transition按字段分列存储避免采样时反复拆装对象导致速度瓶颈。我第一版直接用了list of dict1M步训练跑起来慢得想砸电脑改成numpy分列存储后提速至少3倍。3.3 训练循环里最容易忽略的细节有了buffer训练循环本身其实和普通off-policy算法差不多。下面这个伪代码框架可以直接套for epoch in range(max_epochs): ep [] obs env.reset() goal obs[desired_goal] for step in range(max_steps): # 用当前策略加噪声选动作 action actor(obs[observation], goal) noise() next_obs env.step(action) # done表示是否到达目标 reward 1.0 if done else 0.0 ep.append({ obs: obs[observation], ag: obs[achieved_goal], action: action, rew: reward, next_obs: next_obs[observation], next_ag: next_obs[achieved_goal], }) obs next_obs buffer.add_episode(ep) # 只有整段episode结束后才重标注一次 # 然后正常sample 更新Actor/Critic transitions buffer.sample(batch_size) ... # 普通DDPG更新这里有两个细节值得反复强调因为我自己都踩过。第一不要每一步都立刻重标注和放buffer那会失去“事后”的意义也会破坏同一条轨迹在不同目标下的样本关联。HER的正确姿势是等整条episode跑完拿到轨迹全集重标注然后再允许这条轨迹的数据被sample。第二obs和achieved goal不是同一个东西。很多场景里observation会比achieved goal丰富得多比如observation里可能还有手臂关节角度而achieved goal只是方块位置。重标注时我们改写的是goal字段和reward绝不能愚蠢地把observation也一起换掉否则动作和行为之间的因果就乱了。我见过有新手对着observation维度做拼接把重标注后的goal直接拼进obs结果环境状态观测和内部表示错位训练曲线完全不动。4. 训练效果对比与调参实录4.1 我在FetchPush上的实际观察先说我重复实验的典型曲线。环境是FetchPush目标是一个固定方块位置评价指标是500个测试episode里的成功率。没有HER的普通DDPG基线我训练了2000个epoch跑接近100万步成功率曲线基本贴着0轴偶尔震荡到个位数随后又掉回零。这不是DDPG本身有多差而是稀疏奖励下样本里几乎找不到正向信号。加了HER之后同样步数曲线表现差异非常明显。epoch前200个左右成功率还在低位徘徊此时重标注产生的伪成功样本开始逐渐塑造value函数大概训练到400600个epoch成功率开始爬上20%30%到1000个epoch以后普遍能稳定到60%80%之间个别seed能过85%。这个“先平后陡”的曲线形状几乎每次复现都能看到只是具体收敛步数会波动。这里要提醒一点HER不是提高了一个算法的上限而是把样本利用率提高了好几个量级。同一份数据普通DDPG只能学“当前目标下的成败”HER则让数据同时服务几十个不同的目标。这个倍数效应在复杂环境里更明显。4.2 四组关键超参数怎么调我调过HER的不少参数最后能稳定影响效果的其实是这么几个。重标注比例代码里的relabel_ratio。这个控制着采样时用替换目标的比例我一般0.5起步偏高到0.8会加速前期学习但也容易让真实目标分布被稀释后期评估时策略有点“飘”。所以我的习惯是先0.5跑通确认梯度信号没问题再往上加不要一上来就0.9。future采样范围k。future策略中新目标来自未来k步内的某一点。k太小接近final重标注信息量有限k太大目标采样过于稀薄不太贴切。我常用的范围是48具体看任务的episode长度。如果episode是50步k8相对充分如果是500步的任务可能需要更大但更大的k也意味着需要更多buffer容量。buffer容量和batch size。HER的样本利用率高但对buffer的依赖也更强。我建议buffer最少能装下几千条transition最好覆盖几十个episodebatch size建议用256以上。因为重标注后样本多样性高小batch容不下这么多目标类型梯度会偏。最后是目标表示。HER对goal的表示形式特别敏感如果goal是三维坐标最好做归一化让它落在相近的数值范围。如果不同维度量纲差距大比如位置是01速度是010Actor网络很容易一个维度主导。这个不是HER独有的问题但HER会让整个buffer同时装好几种目标纬方差问题被放大了。4.3 出问题别慌先查这几个地方训练总会有翻车的时候。我把自己实际踩过的坑梳理成一张速查表照着排查能省很多时间。现象可能原因排查思路加了HER反而比基线更差重标注比例过高原始目标样本不足把relabel_ratio降到0.30.5重新训练训练前期挺快后期停在50%左右不涨future采样k太小后期缺乏远距离目标样本把k从8提到16并检查buffer是否覆盖足够长episode训练曲线很陡但测试成功率很低网络过拟合重标注样本真实目标泛化不足增加真实目标保留比例或者加大目标状态空间采样多样性100%成功率但行为明显不对奖励函数写错了把“完成”判定得过于宽松检查_is_success里的距离阈值可视化测试episode轨迹我还想额外分享一个排查技巧先关掉HER只跑普通算法确认基础RL实现没问题再打开HER。如果关掉HER后普通算法的曲线依然不涨问题很可能不在HER而在底层实现加什么包都救不了。这个顺序我在调试里屡试不爽看起来笨但效率极高。5. 从HER延伸出去思想比算法走得更远5.1 目标重标注还能用在哪些地方HER的价值不止于跑通一个Fetch环境。你可以把它这个“重新定义目标”的数据操作抽象出来迁移到很多地方。在多任务强化学习里不同任务对应的goal空间不同训练时最怕各任务数据互相干扰。借用HER的思路把“任务”重标注成“当前可达目标”可以让不同任务的数据在目标空间统一起来相当于一个隐式的课程学习。我见过有团队在做机械臂多物体抓取时用类似HER的样本重标注让一个模型同时学习不同物体的抓取策略效果比单任务独立训练更稳。在Sim-to-Real迁移里仿真里失败的轨迹往往也包含了大量真实世界物理信息。如果把仿真环境的失败轨迹在目标空间重标注再配一点真实数据做微调可以缓解仿真和现实之间的分布差。这个方向我还在探索目前看有不错的潜力。日常工作中思路也是通的。项目复盘本质上就是一种“事后重标注”项目没达成原定目标但你重新审视后发现过程中积累的某个能力、踩过的某个坑、验证过的某个路径本身就是可以参考的成果。把复盘目标从“达成原计划”调整为“沉淀可复用的经验”这条轨迹立刻就变成了有效的学习材料。5.2 我踩过几次坑之后留下的三个原则做了这么多次实验最后说点实在的经验。第一HER是数据管道层面的改进不是银弹。它解决的是“样本里没有正反馈”的问题解决不了“智能体本身容量不足”的问题。如果你的网络太浅、特征表达太差重标注再多也是垃圾进垃圾出。第二永远先跑通一个最小闭环再慢慢加量。我见过有人第一次就用FetchPickAndPlace配复杂Actor-Critic加分布式buffer训练到第三天发现环境reset接口写错了全部白费。先在小环境上用最简实现拿到一条能涨曲线的结果再考虑扩展。第三珍惜“负样本”。好多人一开始用HER就想把所有失败样本都变成“正样本”这是一个认知误区。失败样本之所以必须保留不仅是给真实任务目标留学习信号更是给智能体一个负向约束让它在不同目标之间做一个正确的区分。重标注不是掩盖失败是换个角度利用失败。这一点想明白你的调参思路都会清晰很多。HER教给我的不止是一个算法更是一种看待问题的姿态大多数失败不是无效的只是还没找到合适的坐标系去衡量它。这和生活中事后复盘时的心态几乎一模一样。希望这篇内容能让你少走几步弯路也让你在面对稀疏奖励或稀疏反馈的问题时多一个顺手好用的工具。
返回列表