ARTICLE DETAIL

资讯详情

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

事后重标记:从稀疏奖励到目标条件强化学习的HER原理与实战

事后重标记:从稀疏奖励到目标条件强化学习的HER原理与实战 聊到 hindsight很多人的第一反应是“事后诸葛亮”。放在日常生活里这多少带点贬义但在强化学习RL里这个词却代表一个相当优雅的算法思想让智能体学会从失败轨迹里挖掘出被隐藏的成功经验。我第一次认真研究这个概念是因为一个非常现实的困境——机械臂抓取任务练了几十万步成功率始终是零。后来把 Hindsight Experience ReplayHER的思路接进去同一个任务很快就出现了肉眼可见的进步。这篇文章就想把这个“事后重标记”的套路掰开揉碎讲清楚适合正在做机器人控制、稀疏奖励 RL 任务以及想搞明白目标条件强化学习Goal-Conditioned RL的读者。hindsight 这个词最近在圈子里热度不低但它不是一个包装出来的“新概念”而是来自 OpenAI 在 2017 年发表的 HER 论文。核心思想一句话就能概括如果一条轨迹没有达到我们设定的目标不要急着把它当垃圾丢掉换个目标再看它可能就是一次完美示范。听起来很直觉但真正把它落到代码和训练流程里还需要处理不少细节。下面我们从一个具体场景开始。1. 什么是hindsight从“事后诸葛亮”到算法武器1.1 一个场景讲清楚hindsight的本质想象一个机械臂抓取任务目标是把红色罐子抓起来放到指定区域。智能体在某一局里伸出去抓到的却是旁边的绿色杯子然后把它放到了目标点附近。按常规 RL 的套路这一整条轨迹的奖励大概率是 0因为最终目标“抓红罐子”没达成样本会被丢进回放池然后被策略网络一遍遍“学习”怎么失败。但你换个角度想这一局里机械臂明明成功地抓住了一个物体并且把它移动了一段距离——如果把“目标”从红罐子改成绿杯子这条轨迹就是一个不折不扣的成功样本。这就是 hindsight 最核心的直觉目标不是固定在环境外部的常量而是可以被重新解释的变量。我们把轨迹里实际达到过的某个状态拿出来设为新的目标再把这条经验以“成功轨迹”的身份存入回放池。智能体就不只是从“什么是失败”里学习而是从“哪些动作组合其实已经实现了某个目标”里学习。我经常拿做饭打比方你想做宫保鸡丁结果炒出了一盘鱼香肉丝按原计划这顿饭是失败的。但如果你的目标是“做一道下饭的辣味肉丝菜”那你其实成功了而且方法完全可复用。人很自然就会这样总结经验但传统 RL 算法不会它只会冷冷地给你一个 0 分奖励。HER 要做的就是把这种“事后重新定义目标”的能力写进算法里。1.2 为什么RL怕“稀疏奖励”问题根源要理解 HER 为什么有效得先理解它针对的痛点——稀疏奖励问题。很多真实任务里环境只会在所有步骤都完成时给出一个 1 奖励其余时间奖励全是 0。比如七自由度机械臂抓取状态空间有几十维动作空间连续而“成功抓取”在状态空间里只是一个极其狭窄的区域。随机初始化策略去探索几千上万步里可能一次成功都碰不到。这种情况下传统 RL 会遇到一个恶性循环回放池里几乎全是零奖励样本价值网络学不到任何有区分度的信号策略更新近似随机游走越学不到越难碰巧成功一次越碰不到成功越学不到。很多人第一反应是加 reward shaping比如用手动设计的距离函数给每一步打分数。但手工塑造奖励非常脆弱距离项设计得不好机械臂可能学会“贴近物体但永远不抓”设计得过于复杂又引入了大量人为偏好甚至会抑制智能体发现意想不到的更好策略。HER 提供的是另一条路不改奖励函数本身而是改变“这条轨迹对应的目标”。它不依赖人工对中间过程的精细刻画只需要一个能判断“某个目标是否达成”的判定函数。只要目标空间可以被定义我们就可以用真实到达过的状态来生成额外正样本。这种思路比手工调整奖励要干净得多也更容易迁移到新任务上。1.3 为什么不能简单粗暴地“重放失败”有人可能会问既然失败轨迹有价值那我直接把所有失败样本都多放几遍不就行了这种想法很常见但实际效果很差。如果你只是把一条没抓到红罐子的轨迹原封不动丢进回放池价值网络看到的依然是一个“目标红罐子、奖励0”的样本。它能从中得到的唯一信号就是“做这些动作没有好处”完全区分不开“动作本身太差”和“目标太难、动作其实还可以”。HER 的高明之处在于它改的不是样本的权重而是样本的标签。它把目标字段从“红罐子”改成“绿杯子”同时重新计算奖励。这样一来同样的状态动作序列对应的标签完全变了从“失败样本”变成“成功样本”。用这种重标记后的数据去训练价值网络才会学到“原来刚才那一串动作在目标为X的情况下是有正向价值的”。所以说 HER 解决的不只是“样本利用率”问题更是一个“语义标签”问题。它用后见之明重新审视已经发生的数据改变了算法从数据中提取信息的方式。这才是它被称为 hindsight 的原因。2. HER核心原理目标重标记Goal Relabeling是怎么运作的2.1 先看一条transition怎么被“重写”在目标条件强化学习里一条经验通常被表示成一个五元组(s, a, r, s, g)分别代表当前状态、动作、获得的奖励、下一个状态、以及当前目标。常规回放池直接缓存这些原始样本。HER 的处理方式则多了一步当一条完整的 episode 结束后不仅要保存原始数据还要额外生成一批“重标记样本”。具体怎么做假设一条 episode 有 N 个时间步我们挑其中某个时间步 t 的状态动作对(s_t, a_t, r_t, s_{t1})再从 t 之后的某个时刻t t取出状态s_{t}把它当作这条样本的新目标 g。然后重新计算奖励r reward_func(s_{t1}, g)。在稀疏奖励设定下这个函数通常就是判断“新目标是否已经达成”达成为 1否则为 0。于是我们得到一条新样本(s_t, a_t, r, s_{t1}, g)把它和原始样本一起放进回放池。这里有个很容易被忽略的细节对重标记样本done标志必须设置为 False。因为智能体在整条 episode 里并没有因为“达到新目标”而终止它是在继续探索的如果硬把 done 设为 TrueQ-learning 会把这条样本当成终止状态来更新value 估计会被严重扭曲。这个坑我至少见过三个人踩过。另外要执行重标记目标空间和状态空间必须有交集或者目标可以由状态推导出来。比如机械臂抓取的目标是“把物体移动到某个位置”目标就是位置状态本身可以互相转换。如果目标是抽象符号和状态没有直接映射关系HER 就用不了。2.2 完整算法流程逐步拆解把 HER 接到一个 off-policy RL 算法比如 DQN、DDPG、TD3 或 SAC里整体流程可以拆成五个阶段。第一阶段初始化策略网络、价值网络和回放池回放池容量建议给足够大至少能存下几百条完整 episode。第二阶段采样一个目标 g让智能体在环境中跑一整局。这一步和普通 RL 没有区别唯一要注意的是保存轨迹时要把每一步的(s, a, s_next)都存下来不要只存最终结果后面重标记需要用到中间状态。第三阶段episode 结束后遍历轨迹里的每一步对每一步执行 k 次重标记操作。每次从当前步之后的某个状态里随机抽一个作为新目标重算奖励和 done生成额外样本。这就是“用未来经验解释过去”的核心循环。第四阶段把原始样本和重标记样本一起存入回放池。注意原始样本一定要保留不能全用重标记样本覆盖否则智能体会失去对“原始任务目标”的感知最终变成“只要目标够低就能成功”的作弊机器。第五阶段从回放池里采样一批数据更新价值网络和策略网络。从分布角度看由于重标记引入了大量“成功”样本价值网络能收到更密集的正反馈信号梯度方向变得更清晰。整个流程不复杂但它对 off-policy 算法是刚需。像 PPO 这种 on-policy 算法很难直接用 HER因为每次策略更新后旧轨迹就作废了没法重放历史数据。所以打算实践 HER 的读者建议直接选 DQN 或其连续控制版本DDPG/TD3/SAC不要硬套 PPO。2.3 三种重标记策略怎么选原论文里对比了几种重标记策略最常用的是三种final、future 和 episode。final 策略最简单把整条 episode 的最终状态当作新目标相当于“这次最终成了什么就假装目标是什么”。它只提供一条额外目标计算量小但信息量也小——一条长轨迹中只有终态是有意义的中间过程全被浪费了。适合任务的目标天然就是终态的场景比如导航任务里“最终到达的位置”。future 策略是目前默认首选对每个时间步 t从t1到 episode 结束之间的状态里随机抽一个作为新目标。它的好处是新目标一定在当前时间步之后才出现天然带“时间因果性”而且能提供更丰富的中间状态信息。原论文报告里效果最好的就是 future配合 k4 左右的额外目标数训练效率提升非常明显。episode 策略则是从整条轨迹任意位置抽状态做目标包括 t 之前的过去状态。算法实现上最宽松但由于可能把“已经过去的、无法再改变的状态”设为目标有些样本会引入不必要的噪音实际表现不稳定。除非特殊情况我不建议用它。三种策略我用表格整理一下方便对照选择。策略目标来源优点缺点适用场景final只用终态简单稳定、样本噪音小中间态信息被浪费终态即目标的任务future当前步之后的随机状态信息量最大、效果通常最好实现稍复杂、目标分布偏后验通用任务默认选择episode整条轨迹任意状态写法最简单含过去状态噪音偏大快速验证想法时3. 实操从零训练一个带hindsight能力的agent3.1 环境与工具选对才能跑通实践 HER 之前第一件事是选对实验环境。OpenAI Gym 里的 FetchReach、FetchPush、FetchPickAndPlace 是经典测试场景配合 MuJoCo 物理引擎状态和动作都是连续的目标空间也天然兼容 HER。不过 MuJoCo 的安装和授权对新手有点门槛想要快速验证算法逻辑可以先搭一个简单的离散网格世界智能体在 2D 地图上移动目标是到达某个位置奖励只有“到达 1否则 0”。这种环境半小时就能跑通用来验证重标记逻辑是否正确效率很高。我在实际项目里的做法是两段式推进先在迷你网格环境里跑一个 DQN HER 的小实验确认重标记代码没写错再把同一个 HER Buffer 类直接接到 MuJoCo 的 Fetch 系列环境上配合 DDPG 或 SAC 做完整训练。这样能省下大量 debug 时间也更容易定位是算法问题、环境问题还是代码问题。工具选型上还有一些细节值得留意。比如状态向量和目标向量通常存在量纲差异机械臂关节角度可能是弧度、物体位置可能是米直接拼在一起送进网络训练初期很容易震荡。建议先分别归一化或者至少做一次标准化。再比如 reward 函数不要写在环境里就完事HER 场景下它要被反复调用一定要单独提出来方便重标记时替换目标重新计算。3.2 核心代码实现HER Buffer的核心流程对于初学者我建议自己手动实现一次 HER不要直接拿现成库。实现一遍你就会明白所谓“目标重标记”本质上就是一次数据增广。下面我给出一段可以跑通的示意代码用 Python 和 NumPy 实现一个支持 future 策略的 HER 回放池。import numpy as np import random def sparse_reward(achieved_goal, desired_goal, threshold0.1): # 稀疏奖励距离小于阈值就算成功 dist np.linalg.norm(achieved_goal - desired_goal, axis-1) return (dist threshold).astype(np.float32) class HERBuffer: def __init__(self, capacity, k4): self.capacity capacity self.k k # 每条transition额外生成多少个重标记样本 self.buffer [] self.idx 0 def store(self, data): # data是单个transition字典s, a, r, s_next, goal, done if len(self.buffer) self.capacity: self.buffer.append(data) else: self.buffer[self.idx] data self.idx (self.idx 1) % self.capacity def add_episode(self, episode, strategyfuture): # episode是一个完整轨迹包含每一步的状态、动作、以及最终目标 # 先把原始经验存进去 for t in range(len(episode[s]) - 1): data { s: episode[s][t], a: episode[a][t], r: episode[r][t], s_next: episode[s][t 1], goal: episode[goal], done: episode[done][t], } self.store(data) # 再生成额外重标记样本 ep_len len(episode[s]) - 1 for t in range(ep_len): for _ in range(self.k): if strategy future: # 从当前步之后的某时刻随机抽状态作为新目标 future_idx np.random.randint(t 1, ep_len 1) new_goal episode[s][future_idx] elif strategy final: new_goal episode[s][-1] else: future_idx np.random.randint(0, ep_len 1) new_goal episode[s][future_idx] # 用新目标重算奖励且必须把done设为False new_reward sparse_reward( episode[s][t 1][:2], new_goal[:2] ) self.store({ s: episode[s][t], a: episode[a][t], r: new_reward, s_next: episode[s][t 1], goal: new_goal, done: False, }) def sample(self, batch_size): batch random.sample(self.buffer, batch_size) # 这里把dict list转成numpy array方便直接喂给网络 states np.array([d[s] for d in batch]) actions np.array([d[a] for d in batch]) rewards np.array([d[r] for d in batch]) next_states np.array([d[s_next] for d in batch]) goals np.array([d[goal] for d in batch]) dones np.array([d[done] for d in batch]) return states, actions, rewards, next_states, goals, dones这段代码里最关键的地方有两处。第一处是new_goal的采样逻辑future 策略要求只从未来状态里取这一点直接决定了重标记的目标是不是“真实可及的”也是 HER 方法论的核心。第二处是构造重标记样本时强制doneFalse很多新手实现会忘记这一行导致后续 Q-learning 更新时价值被高估。另外原始样本一定要保留重标记只是新增样本不是替代这个我在代码里没有省略实践也别省略。配合这段 Buffer训练主循环里更新网络时从同一个 buffer 采到的数据里既包含原始目标样本也包含重标记样本。价值网络的目标值公式不需要改照常计算r gamma * max Q(s, g)就行因为奖励已经被重算过了。3.3 跑了之后成功率对比与踩坑记录我在网格世界环境里跑了一个简单的实验地图 5×5起点在左下角目标点是随机指定的状态是坐标动作是上下左右移动奖励只有到达目标才 1。对比了两组一组用普通 DQN一组用 DQN HERfuture, k4。训练 5000 个 episode 后普通 DQN 的成功率大约只有 20%而且震荡很厉害HER 版本成功率能稳定到 90% 以上。在轨迹长度只有 10 步左右的小环境里这个差距已经非常明显了。换成更复杂的 FetchReach趋势类似只是 HER 的优势需要更多步数才能完全体现。不过 HER 也不是没有代价。最大的代价是内存和算力每条经验额外生成 k 条重标记样本回放池很快会被填满采样和网络更新的计算量也成倍增加。在 k4 的默认设置下训练速度大约慢 20% 到 30%。这个成本在简单环境里可以忽略但在真实机器人仿真里会比较明显建议优先保证训练效率再考虑调大 k。另一个容易踩的坑是重标记目标可能产生大量“重复样本”。尤其当 episode 长度很短、状态空间离散时未来状态就那么几个重标记出来的目标高度雷同。这种情况下价值网络会对少数目标过拟合泛化变差。解决办法是给采样加一点随机噪音或者在同一个时间步只做一次重标记而不是机械地生成 k 条一样的样本。4. 常见问题与排查技巧实录4.1 不收敛时先查数据流HER 训练不收敛大概率不是网络结构的问题而是数据流出了问题。我最常见的排查顺序是先看回放池里正样本的比例。如果 HER 代码正确随着训练推进回放池里应该会出现相当数量奖励为 1 的重标记样本。如果训练了很久还是全 0说明重标记根本没生效——最常见的两个原因一是new_goal没有从状态里取对取了目标本身不在状态空间的量导致重算奖励永远是 0二是sparse_reward里的距离阈值设得太小实际状态离新目标还有好远。第二个排查点是奖励计算函数。HER 的重标记样本和原始样本用的是同一个reward_func但输入参数变了。如果你在环境里缓存了原始奖励重标记时忘了重算那 HER 就退化成了普通经验回放等于白做。我见过有人把新目标存进去了奖励还是原来的 0价值网络照样拿不到正样本。所以拿到一条重标记样本先打印它的goal和reward确认奖励确实对应新目标。第三个排查点是 target 网络。用 DQN 时需要定期软更新或硬更新 target 网络用 DDPG 时需要为策略和价值各配一个 target 网络。HER 会放大 value 估计的偏差因为很多重标记样本的 done 被强制设成了 False如果 target 网络更新不及时价值很容易被高估表现为训练后期 loss 突然变大、成功率断崖下降。建议训练时打印 Q 值均值如果发现 Q 值一路飙升但成功率没跟上先怀疑这里。4.2 超参调优k值没那么随意HER 最核心的超参就是 k也就是每条 transition 额外生成的重标记样本数量。原论文里默认 k4但这不是金科玉律。k 太小重标记样本不足提升不明显k 太大回放池里大量样本都是“换了目标”的旧经验原始目标和原始数据的比例被稀释智能体可能会学成“不断降低目标”的投机者。我在实验里发现k 从 4 调到 8某些任务不仅没有变快反而收敛得更慢因为数据冗余太高。另一个容易被忽略的参数是“future 策略采样窗口”。很多实现是“从 t1 到 episode 结尾随机选”如果 episode 很长后半段的目标和当前步关系太远重标记样本可能不够“当下可行”。这种情况下可以把采样窗口限制为未来固定步数内比如未来 10 到 20 步的状态能显著提升样本质量。这个技巧在长 horizon 任务里特别有用。还有 replay ratio——每次环境交互后更新几步网络。HER 多出来的样本是离线数据价值网络需要更多梯度步来消化。常见做法是每跑一步环境更新 2 到 4 步网络。如果训练震荡明显先降低更新频率让数据在回放池里多沉淀一会儿再学。4.3 疑难杂症速查表我把实践过程中比较高频的问题整理成一个速查表有问题时可以对着查一遍。现象可能原因排查方法正样本比例始终为0重标记奖励没重算打印重标记样本的goal和reward确认一致训练后期Q值爆炸target网络更新不及时增加软更新系数或提高target更新频率成功率震荡严重重标记目标过于分散限制future采样窗口或调小k学到了“降低目标”buffer里原始样本太少检查原始样本是否被重标记样本覆盖保留原始样本任务完全学不会目标空间和状态空间不对应确认新goal确实取自状态向量且reward中距离计算正确这表格里的每一个问题我都实际遇到过至少一次。尤其是第一个“正样本始终为0”通常不是算法问题而是数据链路问题把计算逻辑理顺模型立刻就开始动了。5. hindsight思想在工程实践里的延伸5.1 从RL到离线数据学习把“失败样本”重新喂给模型HER 的思想并不局限于在线强化学习。在离线强化学习、行为克隆、甚至数据增强领域都能看到它的影子。核心套路一致原始数据可能对应一个很难或者没有完成的目标但我们可以人为地为数据重新指定一个更合理的目标把它改造成有用样本。举一个行为克隆的例子假设我们采集了一批人类操作机械臂的数据目标是“把杯子放到指定位置”但操作员中途放歪了。传统行为克隆会认为这整段演示是失败的。但如果使用 hindsight 风格的处理我们可以把这段演示中的中间状态标记为“临时目标”告诉模型“当目标是到达这个位置时这段动作就是正确示范”。经过这种重新标注原本被丢弃的数据变成了可用的训练集数据利用效率大幅提升。我在离线数据集上做过类似实验效果非常直接数据量不变只改变数据标签的重标注方式最终策略的成功率提升了 15% 到 20%。这说明很多时候我们不缺数据缺的是“重新解读数据”的角度。5.2 其他领域的类比移植把 hindsight 的思想再抽象一层面对一条“未达到预期结果”的过程与其反复挣扎于原目标不如重新定义目标从已有的结果中提炼价值。这个逻辑在很多领域都能找到影子。比如推荐系统:用户没有点我们推荐的 A 商品却点了 B 商品。传统思路会记录“推荐 A 失败”但 hindsight 的思路是“与其执着于 A不如把这次行为视为用户对 B 类商品的兴趣信号”后续推荐向 B 倾斜。再比如对话系统用户问天气模型回答了新闻这算失败但如果我们把目标重定义为“给用户提供当日资讯”这个回答也许没有完全跑偏。当然这些类比没有 RL 里 HER 那样严格的数学定义更多是一种设计思路上的启发但它们证明了“换目标重解释”是一种具有普适性的思维模式。5.3 什么时候不要用hindsightHER 很强大但它不是万灵药。在三种情况下我不建议硬上 HER。第一种任务是单目标且没有任何中间状态可定义。如果整个任务的成败只发生在最后一刻比如“判断一张图片里是否有猫”轨迹里没有值得重标记的状态HER 就无从下手。第二种目标空间和状态空间不关联。如果目标是抽象标签比如“优雅地走路”而状态是关节角度从状态反推目标很困难重标记出来的意义也不大。第三种奖励本身已经很密集。比如每一步都有连续的进度奖励普通 RL 已经能学得很好HER 带来的额外提升有限却要付出额外的算力和内存代价。判断是否适合用 HER可以问自己三个问题目标能否由状态推导出来任务是否有多个可定义的目标或中间态奖励是不是极度稀疏如果前两个答案是“是”第三个也是“是”那 HER 很可能是最合适的武器如果前两个有“否”那就再想想别的办法。我实际用下来的体会是HER 学起来不难但用好的关键是理解和调试“目标-奖励”这一条数据链路。不要急着改网络结构先确认重标记样本的奖励和 done 都正确再调 k 和采样窗口。最后分享一个实用小技巧先用一个非常宽松的距离阈值跑通流程确认重标记链路没问题再逐步收紧阈值。这样能快速排除错误避免在错误的数据流上浪费大量训练时间。把这个基本动作练扎实再复杂的目标条件任务也能慢慢啃下来。
返回列表