ARTICLE DETAIL

资讯详情

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

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

HER事后经验回放:解决强化学习稀疏奖励难题 hindsight 这个词第一次听到的人可能会觉得有点玄乎。干我们这行的人都知道一句话hindsight is 20/20事后看什么都是清清楚楚的。做决策的时候最难受的就是这一点——当下信息不全、反馈稀疏根本不知道哪一步走对了哪一步走错了。等事后复盘的时候又觉得当初怎么就没想到。后来我在捣鼓强化学习项目的时候遇到了一个叫 Hindsight Experience ReplayHER的技术突然意识到这个思路能解决的不只是机器人的机械臂抓取问题它本质上是一种做事方法用已经发生过的事实反推回来重新标记自己的行为让算法在“看似失败”的尝试里学到东西。这个项目对我最大的价值就是它逼着我重新理解了一件事经验的价值不在于你成功了多少次而在于你能不能从每一次失败里提取出有效的信号。今天就把我从零开始实现 HER、把它跑通、再调优的完整过程拆开来讲包括核心思路、关键代码、踩过的坑以及它在更广阔场景里的延伸可能。不管你是搞强化学习的研究生还是在做推荐系统、机器人控制的工程师这篇内容应该能给你一些直接能用的启发。1. 内容整体设计与思路拆解1.1 为什么奖励稀疏是强化学习的头号杀手先聊一个基础但特别关键的问题为什么很多强化学习项目在模拟器里跑得好好的一上真实环境就拉胯或者说为什么很多任务练了几百万步都不收敛答案大概率出在奖励函数上。标准强化学习的逻辑是让智能体最大化累积奖励但如果你的奖励是稀疏的——比如机械臂抓取物体只有“抓住并放到目标位置”才给 1其他所有中间状态全是 0——那智能体在初始阶段完全是在黑暗中乱撞。它随机探索了几万步一次目标都没达成过也就一条正向反馈都没收到过。梯度传不回去策略网络根本不知道该往哪个方向调。我做过一个简单的实验用 DDPG 算法训练一个二维平面上的机械臂目标是把滑块推到指定坐标。在密集奖励距离越近奖励越高的情况下大概 20 万步就能收得很好。但把奖励改成稀疏的——只有进入目标区域才给分——训练了 200 万步成绩依然跟随机策略差不多。区别就是这么残酷。这就是 hindsight 思想登场的时机。核心假设是就算你没有达成目标你这次的轨迹本身也包含信息量。比如机械臂把滑块推到了某个位置虽然不是你设定的目标位置但至少你知道“从初始位置到当前姿态”这条路径是可行的、动态是稳定的。如果你把刚才那个实际到达的位置重新定义为目标那么这条轨迹就是一个完美的成功轨迹。1.2 事后视角的核心设计逻辑HERHindsight Experience Replay的官方名字叫“事后经验回放”它的核心设计可以用一句话概括用探索到的真实结果反向构造训练样本。具体做法是在 Replay Buffer 里存储经验的时候不只用原始的目标去存而是额外用轨迹实际达到的状态去重新定义目标然后生成新的“状态-动作-新目标-奖励”四元组一并存入经验池。这样做的直接好处是原本稀疏到几乎学不到东西的奖励信号被自动稠密化了。我举个例子帮你理清楚。一个机器人在迷宫里找出口出口在坐标 (10, 10)它随机走了一圈最后停在 (3, 4)。按原始目标来看这次探索是失败的奖励为 0。但用 hindsight 的思路我们把这次轨迹的“目标”改写成 (3, 4)——也就是它实际到达的位置——那这条轨迹就从“失败轨迹”变成了“成功轨迹”。这个转变的价值很大。它告诉策略网络从起点到 (3, 4) 这个状态你刚才那串动作是可行的。下次如果再遇到“希望到达 (3, 4)”类似的任务网络就有正向样本可以参考了。虽然严格来说这不是原始目标的直接知识但它构建了一个从易到难的课程让智能体先学会达到附近状态再一步步逼近真实目标。有意思的地方在于这种逻辑跟人类学习很像。比如学打篮球很少有人能第一次就投进三分球。但每一次投篮不管你进没进你都知道了“用这个力度、这个角度球大概会飞到哪里”。基于这些反馈下一次你就知道该调大还是调小力度。事后视角本质上就是把这个人类的直觉搬到了算法里。2. 核心细节解析与实操要点2.1 目标重标记的四种策略对比HER 里最关键的一个环节就是怎么选“用什么状态来替代失败轨迹的目标”。论文里给出了四种策略我逐一给你说清楚final直接选轨迹的最终状态作为新目标。这是最简单的策略也是大部分场景里表现最稳定的。future从轨迹中当前时间步之后的状态里随机选一个作为新目标。这个怎么做呢比如一条轨迹有 50 步处理第 10 步的经验时就从第 11 步到第 50 步的状态里随机抽一个作为新目标。它的逻辑是模拟“在完成目标的过程中某个中间状态被当作子目标”。episode整条轨迹跑完之后随机挑一个非初始状态作为新目标。这个策略比较粗糙但计算最快。random不从当前轨迹里选而是从其他轨迹或者其他地方随机挑一个状态作为目标。这个策略用的场景比较少一般是为了增加目标多样性。我实际测下来final 策略最简单也最常用future 策略效果更好但需要调参episode 适合预算有限时候用。下面是我整理的对比表策略选择方式优点缺点适用场景final轨迹末状态简单稳定收敛快目标多样性低大多数标准任务future当前步之后的随机状态构造出合理的子目标链参数多一个k需要调长轨迹、子目标明显的任务episode轨迹内随机非首状态计算开销最低目标与当前状态可能差距过大计算资源紧张时random池内随机状态目标覆盖面广样本效率可能下降目标空间比较规整的任务选中新目标之后需要重算奖励。如果你用的是稀疏奖励新目标下这条经验变成“成功轨迹”奖励直接给 1。如果原奖励是密集的那就按照新目标的距离函数重新算一次奖励。2.2 策略网络与Q函数怎么配合改造如果你用的是 off-policy 算法比如 DDPG、TD3、SAC那么 HER 的接入成本其实很低。核心是需要改三个地方。第一输入多一个目标维度。以机械臂为例普通的状态 s 是关节角度、关节角速度这些目标 g 是物体的目标位置。你把 s 和 g 拼在一起作为 Q 网络和策略网络的输入。这样 Q(s, a|g) 就表示在目标 g 条件下执行动作 a 的期望累积奖励。第二Reward 函数要能针对新目标重新计算。这个很关键因为同样的轨迹针对不同的 g奖励是不同的。在实现上我一般单独写一个函数 reward_fn(state, goal, achieved_goal)不要硬编码到环境里。第三Replay Buffer 里要存下轨迹。这个乍一听是废话但很多新手在这里踩坑。因为 HER 需要在轨迹结束之后回溯处理所以你必须以 episode 为粒度存储整条轨迹而不是像普通 DQN 那样存一个个独立的 transition。我习惯的做法是用一个临时的 episode buffer 存当前轨迹跑完之后统一处理再丢进主 buffer。而且为了不让新的 hindsight 样本污染原始经验我建议原始 transition 也保留一份。也就是说一条轨迹会产生两份经验一份是原始目标下的可能奖励为 0一份或几份是重新标记后的奖励不为 0。两个都要存不要互相替代。2.3 奖励函数重算的边界问题这里有一个特别容易被忽略的细节重算奖励的单位和尺度必须和原始奖励保持一致。我最初犯过一个错误原始奖励用的是稀疏信号0/1 逻辑但重算 hindsight 奖励时我图方便用了距离的负数作为密集奖励。结果网络训练出来的 Q 值波动极大策略完全不稳定。原因很简单Q 网络拟合的奖励分布尺度不一致它一会儿要学 0/1 的离散信号一会儿要学连续负数等于自己跟自己打架。正确的做法是定义好一个统一的奖励范式然后不管原始目标还是 hindsight 目标都用同一个范式来算。如果用稀疏奖励那就所有经验都按“是否达成新目标”给 0/1如果用密集奖励那就所有经验都按距离函数算连续值。千万不要混合。另外提醒一点重新标记的目标不能太离谱。比如你让机械臂抓杯子轨迹终点的手爪位置在桌面左侧但轨迹中有一个时刻手爪确实经过了目标点附近。如果你用 future 策略这个时刻的经验会被重标记成“成功”样本这没问题。但如果你从其他完全不相关的轨迹里 random 选一个目标这个目标可能距离真实轨迹差了好几个身位构造出来的样本意义不大反而相当于给网络加了噪声。所以新目标的选择要在时间距离或者空间距离上做限制我一般会加一个 max_goal_distance 的约束。3. 实操过程与核心环节实现3.1 环境搭建与基线设定我用的实验环境是机械臂推滑块到指定位置这个任务在 OpenAIGym 的 FetchSlide-v1 里可以直接跑。为了避免把环境配置和算法混在一起我单独写了一个统一接口把状态、目标、观测都规范化输出。先说说环境里的三个概念很多教程含糊带过但实际上特别重要observation智能体观测到的全部信息包括机械臂的关节角度、速度、物体位置、目标位置等是一个大向量。desired_goal你希望物体到达的目标位置。achieved_goal物体当前实际到达的位置。在 HER 的思想里关键操作就是改写 desired_goal用 achieved_goal 来替代。所以接口设计上这三个字段必须分开返回否则回溯的时候没法重定义目标。我初始设定了这样的环境参数机械臂的关节自由度是 7物体在二维平面内移动目标区域半径 0.05 米。每一步动作是一个 4 维向量控制机械臂末端在 x、y、z 方向的位移和夹爪开合。最大步数 50 步超出直接终止。原始稀疏奖励物体与目标点的欧氏距离小于 0.05 时给 1否则给 0。这样的设定下纯随机策略的成功率几乎是 0因为 50 步内误打误撞推到精确位置的几率太小了。3.2 核心代码结构与关键实现我先贴出 HER 里最核心的一部分代码也就是轨迹存储和事后重标记的逻辑。这块理解了整个思路就通了。import numpy as np from collections import deque class HindsightReplayBuffer: def __init__(self, capacity, reward_fn, goal_threshold, strategyfuture, k4): self.buffer deque(maxlencapacity) self.reward_fn reward_fn self.goal_threshold goal_threshold self.strategy strategy self.k k # 每条轨迹额外生成的 hindsight 样本数 def add_episode(self, episode): # episode 包含: obs_list, action_list, achieved_goal_list, desired_goal obs_list, action_list, achieved_goal_list, desired_goal episode T len(action_list) # 保存原始经验 for t in range(T): self.buffer.append(( obs_list[t], action_list[t], obs_list[t 1], desired_goal, self.reward_fn(achieved_goal_list[t 1], desired_goal) )) # 构造 hindsight 样本 for _ in range(self.k): t np.random.randint(T) # 随机选一个时间步来构造样本 if self.strategy final: new_goal achieved_goal_list[-1] elif self.strategy future: future_t np.random.randint(t, T) new_goal achieved_goal_list[future_t] elif self.strategy episode: new_goal achieved_goal_list[np.random.randint(T)] elif self.strategy random: new_goal self._sample_random_goal() new_reward self.reward_fn(achieved_goal_list[t 1], new_goal) self.buffer.append(( obs_list[t], action_list[t], obs_list[t 1], new_goal, new_reward ))这段代码看着简单但有几个点值得展开说。首先我在奖励函数里直接传入了 achieved_goal_list[t1] 和 desired_goal这两个东西本质上就是“实际到达点”和“目标点”。如果两者距离小于阈值就返回 1。正因为有了这种解耦设计同一个 transition 才能兼容不同的目标也才能实现“事后重标记”。其次这里的 new_goal 选择策略是随机采样的。为什么随机而不是每一步都生成因为如果每一步都额外生成一个 hindsight 样本buffer 里的数据量会爆炸而且相邻时间步生成的样本高度相关训练效率反而不高。所以采样 k 个时间步就够了。k 值我一般设在 4 到 8 之间太大反而会导致 hindsight 样本占比过高原始经验被稀释。最后注意我用了 collections.deque 来管理 buffer它能自动丢掉最旧的数据。但没有人会真正用 deque 来存几十万条经验因为内存和查询效率都有问题。我自己在小规模实验里这么用方便调试生产环境或者大规模训练还是建议用 Redis 或者数据库来存或者直接用成熟框架内置的 replay buffer。数据存好之后训练部分其实跟普通 off-policy 算法没有本质区别。我用的算法是 TD3因为它在连续控制上比 DDPG 稳定不少。训练的时候每一次从 buffer 里采样一个 batch然后按 TD3 的流程更新策略网络和两个 Q 网络。因为输入里多了一个目标维度网络输入维度要相应增大。# 训练循环伪代码 for epoch in range(total_epochs): for episode_idx in range(cycles_per_epoch): episode rollout(env, policy_net, max_steps50) replay_buffer.add_episode(episode) for _ in range(train_steps): batch replay_buffer.sample(batch_size256) td3_update(batch) # 标准的 TD3 更新过程这里 rollout 函数就是把策略网络输出的动作加一点探索噪声丢到环境里跑完整条轨迹返回所有状态、动作和 achieved_goal。3.3 超参数选择与训练策略我用一组参数跑了 50 个 epoch每个 epoch 里采样 20 条轨迹每条轨迹做 40 次梯度更新。完整规模是 1000 条轨迹4 万次参数更新。事实证明这个规模已经能看到明显效果了。几个关键超参数如下参数取值说明策略网络结构256-256-256三层全连接ReLU 激活Q 网络结构256-256-256与策略网络一致学习率3e-4所有网络统一折扣因子 gamma0.98因为轨迹短不用太大探索噪声0.2动作上加高斯噪声target 网络更新频率5e-3软更新系数batch size256显存够大就多塞一点khindsight样本数4每条轨迹额外生成4条目标选择策略future效果最稳定这里我特别想说说 gamma 的选择逻辑。很多人一上来就抄 0.99但 FetchSlide 这个任务轨迹最长 50 步稀疏奖励只在最后一步出现。如果用 0.99奖励往后传播的步数大约是 1/(1-0.99) 100 步是能覆盖 50 步的理论上没问题。但实际跑下来我发现 0.98 效果更好因为轨迹短的时候过大的 gamma 会让 Q 值方差变大早期训练更不稳定。所以参数要跟着任务规模走不要无脑抄经典值。探索噪声我设了 0.2这个值不算大。但要注意如果任务的动作空间维度高0.2 的高斯噪声可能不够探索我建议先用 0.3 跑前 20% 的训练再降到 0.1 做精细微调。这种噪声退火策略在很多任务里都有效。3.4 训练效果与对比实测我用统一环境下跑了三组对比实验每组随机种子固定普通 DDPG 稀疏奖励普通 DDPG 密集奖励TD3 HER 稀疏奖励结果非常直观。普通 DDPG 加稀疏奖励跑了 1000 条轨迹成功率始终是 0策略网络完全学不到东西。DDPG 加密集奖励好一些在 700 条轨迹左右开始有非零成功率到 1000 条轨迹时大约稳定在 45%。而 TD3 加 HER 的训练曲线最漂亮大约 200 条轨迹就开始出现成功案例500 条轨迹后成功率超过了 65%最终稳定在 80% 上下。我画训练曲线的时候特意对比了 HER 和普通方法的样本效率差距非常明显。HER 的核心贡献不是把上限提高多少而是让算法从“学不会”变成“能学会”把样本效率往前拉了一大截。尤其在奖励完全稀疏的设定下HER 几乎是唯一的解。4. 常见问题与排查技巧实录4.1 训练完全不收敛怎么办如果你跑 HER 发现训练曲线像心电图一样乱跳或者干脆是一条水平线先别急着怪算法。我总结了三个最可能的原因。第一个原因是目标空间和状态空间没有对齐。HER 的核心是把“状态”当作“目标”但如果你的环境里 achieved_goal 和 observation 不是同一套坐标系那重标记就全错了。比如你在做视觉抓取achieved_goal 是像素坐标里的物体位置但策略网络期望的目标是机械臂基座坐标系下的位置这两个差了十万八千里。这种 bug 特别隐蔽因为代码不会报错只是学不好。我的排查技巧是随机采样几条轨迹把 achieved_goal 和 desired_goal 打印出来直接目测是否同一个坐标系、同一个量纲。如果数值范围差太多就先做归一化或者坐标变换。第二个原因是奖励尺度不统一这个前面说过。混用稀疏和密集奖励会导致 Q 网络的目标值震荡看起来就是 loss 反复横跳。解决方案是统一奖励函数不要嫌麻烦。第三个原因比较反直觉k 值太大反而坏事。当 k 4 变成 k 10 的时候buffer 里 90% 都是 hindsight 样本原始稀疏经验被严重稀释。要知道原始经验也很重要它告诉网络“在原始目标下这个动作是失败的”这种负样本对稳定收敛有正向帮助。所以 k 值别贪多4-6 是甜点区。4.2 训练后期成功率停滞怎么办很多人在训练中期效果不错但到后期成功率卡在一个平台期上不去。这个问题我也遇到过主要原因是目标多样性不够。final 策略虽然在早期收敛快但它只会用轨迹末状态做新目标导致目标空间被限制在少数几个区域。如果初始位置分配不够均匀策略网络就没有机会学习那些“冷门”区域。这种情况下我建议切换成 future 策略并且把 k 值适当调高到 6这样可以从轨迹的不同阶段采样出多样性更高的子目标。另外一个实用技巧在 hindsight 样本里混入 10% 的真实目标样本。真实目标样本是指如果这条轨迹本来就完成了目标直接保留原始四元组如果没完成也额外生成几条以原始目标为目标的负样本。这样做的目的是让策略网络持续接触到“真实目标分布”不会因为大量改写的目标而偏移了原始任务。4.3 评估环境里性能差、训练环境里表现好怎么定位这是强化学习经典问题——sim-to-real 的缩小版。如果只在评估环境里加了随机扰动而训练环境太干净那 hindsights 构造出来的“成功轨迹”在评估环境里可能根本走不通。我的建议是在训练环境里就加入 domain randomization。比如给物体初始位置加随机偏移、给动作加更大幅度的噪声、给物理参数摩擦力、质量加随机扰动。这样 hindsight 重标记出来的轨迹才更有泛化意义而不只是记住了特定环境下的死板动作。还有一个很实际的建议评估的时候不要只看成功率要同时记录平均回合回报和最远推进距离。因为即使目标没达成推进距离也是一个连续信号能帮助你观察策略是否在退化。如果成功率没变但推进距离持续下降说明策略可能过拟合到了某个局部区域这比单纯的成功率变化更能反映问题。4.4 一个让我折腾了两天的坐标系错误最后分享一个我亲身踩过的坑给大家做个警醒。当时我在做 FetchPush 任务环境返回的 observation 是机械臂关节向量但我用了另一个库提供的相机坐标作为 achieved_goal。两者量纲差异巨大关节角是弧度坐标是米结果 HER 重标记出来的目标在奖励函数里算出来的值全是错的训练曲线从第一天就炸了。排查过程很痛苦因为报错信息只会显示 reward 偶尔出现 NaN。我先把网络输出打出来发现 Q 值在几个 step 内就发散到几千然后怀疑是梯度爆炸试了梯度裁剪没有效果。最后逐行对比环境定义时才发现 achieved_goal 选错了字段。自那以后我给自己定了一条规矩每次搭新环境先花十分钟打印 10 条随机轨迹的状态和奖励分布。确认坐标范围符合预期、奖励满足 0/1 或有限的连续值再开始训练。这个习惯让我后面避开了至少五六个类似的坑。5. 应用场景与扩展方向5.1 机器人操作之外还能用在哪HER 这个名字里的“hindsight”思想应用范围远不止机械臂。只要符合两个条件就能考虑用这套思路任务可以定义成一个“达到某个目标状态”的形式奖励函数很稀疏甚至只有成功/失败两种结果比如自动驾驶的车道保持目标状态是“车辆保持在车道中心”但真实路测中很少能完美保持大部分时间都在偏离。用 HER 的思路你可以把“偏离到某个位置”的那一刻当作临时目标重新标记让策略学习如何从各种偏离状态回归车道中心这比只奖励完美状态要高效得多。再比如对话系统里的任务型对话目标状态是“用户完成了预定操作下单、查询、预约”。如果对话失败原始奖励是 0但失败对话的最后几轮其实也包含信息——比如用户表达了拒绝或困惑。HER 可以把“成功引导到某个中间状态”作为临时目标让系统逐步学习怎么推进对话流程。甚至连推荐系统冷启动都能套上这套思路。推荐算法的目标是让用户点击或购买这在初始阶段是极稀疏的。但如果你把“用户点击了某个商品”和“用户浏览了什么品类”当作不同层级的临时目标其实就是在用 hindsight 的方式构造多层级样本让模型逐渐学会理解用户意图。5.2 与近期一些其他方法的区别现在很多人在讨论“经验池回放优先级”“课程学习”“逆强化学习”这些方法它们跟 HER 有交集但出发点不同。优先经验回放PER解决的是“经验池里哪些样本值得多学”核心是 TD error。它不改变样本的目标只改变采样概率。课程学习解决的是“从易到难的学习顺序”但它需要你主动设计难度阶梯。HER 妙在难度阶梯不是人工设计的而是从数据里自动涌现的真实的失败轨迹经过重标记之后天然就是近距离的“简单样本”。逆强化学习想从专家演示里反推出奖励函数这通常需要高质量专家数据。HER 不需要专家数据它只需要智能体自己的探索轨迹。从这个角度看HER 更接近一种自监督的奖励构造方式。我的感受是这些方法并不互斥可以串联起来用。我在一个项目里就是 HER 先行粗训练然后用 PER 提升后期样本效率最后效果比单独用任何一个都强。不过中间会引入额外两个超参数调试成本也上来了新手别一开始就叠 buff先跑通一个基础版本再说。5.3 在自己的项目里落地的最小步骤清单最后给一个可以直接抄作业的最小实施清单方便你在自己项目里快速跑起来确认你的环境能返回 achieved_goal如果环境没有这个字段自己写一个函数从 observation 或者环境内部状态里提取。重写 reward_fn(state, goal, achieved_goal)统一所有奖励计算逻辑。把过渡存进一个临时 episode buffer轨迹结束再统一处理。按 final 或 future 策略生成额外样本k 先设为 4。用一个 off-policy 算法TD3 或 SAC做底座把目标拼接到状态里输入网络。训练前先打印随机轨迹的 reward 分布确认没有 NaN 和坐标量纲问题。每 10 个 epoch 做一次评估绘制成功率曲线而不是只看网络 loss。如果这七步都走通了HER 在你的项目里基本就落地了。剩下的就是超参数微调和策略选择我上面写的对比表和调参逻辑可以直接参考。我在实际使用中发现前两次上手 HER 最大的障碍不是算法本身而是状态和目标的空间定义太随意。你只要确保 achieved_goal 定义得清晰、奖励函数严格统一HER 的学习效果通常不会让你失望。踩过几次坑之后我现在在新的时序决策任务上会先做一次“hindsight 思考”这个任务的奖励是不是太稀疏了我能不能用它自己的轨迹来构造中间目标如果答案是肯定的那 HER 大概率能帮你少走一大截弯路。
返回列表