ARTICLE DETAIL

资讯详情

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

强化学习稀疏奖励难题:HER事后经验回放原理与实现

强化学习稀疏奖励难题:HER事后经验回放原理与实现 做强化学习这一年多要说哪个词最让我又爱又恨一定绕不开 hindsight。这个单词本身的意思是“事后聪明”翻译成大白话就是“事后诸葛”。放在算法语境里它对应的是一个极其经典的思路——Hindsight Experience ReplayHER事后经验回放。我最早接触它是因为做机器人抓取任务遇到了典型的稀疏奖励问题让机械臂学会把随机位置的方块推到目标点如果只在方块到达目标时给一个 1其余时候给 0智能体在前期几乎学不到任何东西完全靠撞运气。后来我真正把 HER 用起来才发现它的巧妙之处简直像是给强化学习开了个“后悔药”。它不要求智能体每次都成功而是允许我们站在结果的角度“重新解释”已经发生的轨迹这次没抓到红色方块但你的机械臂最终到达了桌子左上角那我们就假装你刚才的目标本来就不是抓红方块而是走到左上角然后照样给你算一次成功经验。就是这种“自欺欺人”式的操作让大量原本毫无价值的失败轨迹反过来变成了学习信号。今天这篇文章我想把这套方法的原理、代码实现和我在实际任务里踩过的坑从头到尾拆开揉碎了讲一遍给正在做稀疏奖励任务、机器人控制或者需要处理目标导向任务的朋友做个参考。1. 为什么 hindsight 这招能救命1.1 稀疏奖励的尴尬处境先回忆一下强化学习的基本流程智能体在环境里做动作环境返回状态和奖励智能体根据奖励调整策略。这套流程在游戏领域跑得非常顺主要是因为奖励信号往往是稠密的你每移动一步屏幕上的分数可能就会变化模型知道哪个动作让它得分更高哪个动作让它掉分。可一旦切换到机器人控制比如让机械臂把物体推到一个精确位置或者让四足机器人完成一次复杂的翻身动作奖励就变得极其稀疏——只有当你完全到达目标时环境才会给一个非零奖励。这种奖励结构下策略梯度方法的训练效率会非常低。用学术一点的说法叫 credit assignment problem也就是“功劳分配困难”。智能体做了一连串动作前 99 个动作看起来都无关紧要只有最后一步碰到了目标可到底该把功劳算到哪个动作头上模型很难从这种近乎全零的回报里提取出有效梯度。在实际训练中我见过太多这种情况奖励曲线挂在 0 附近一动不动训练跑了几十万步策略一点改进都没有甚至还不如随机策略。一个最本能的解决办法是设计密集奖励函数。比如用机械臂末端到目标点的欧氏距离作为负奖励距离越近、扣分越少。但稠密奖励也不是银弹它往往会把任务“带偏”。我在早期项目里就吃过亏用距离做奖励函数机械臂学会了“把手降低到桌面以下”这种钻空子的行为因为那样末端坐标离目标点的平面投影距离更短虽然任务本质上完全没做对。换句话说奖励塑形reward shaping做不好无异于在教模型钻系统的漏洞。1.2 从失败轨迹里“硬挖”有效经验HER 的出发点就特别直白既然稀疏奖励导致大量轨迹的回报是 0那我们就别让这些轨迹白白浪费干脆把它们重新解释成“对某个其他目标达成了成功”的轨迹。打个比方你想考清华没考上但你的数学考到 140 分。这个事实对于“考清华”这个目标来说是失败的但它对“数学拿到高分”这个目标来说就是一次成功的样本。你完全可以用这份试卷的经验去学习“如何在数学部分考高”而不是直接把它丢进废纸篓。在强化学习中这个操作叫做目标重标注goal relabeling。具体来说我们原本给智能体设了一个期望目标g智能体在这个目标下跑完一整段交互轨迹最终没能达成。在把这条轨迹存入经验池之前我们不看原始目标而是从这段轨迹里挑一个实际到达过的状态比如轨迹最后几步的某个末态g然后重新计算这条轨迹在“目标是g”这个条件下的奖励。这样一来原来一整段全零回报的经验就被改写成了一段带有正奖励的成功经验。为什么这一招有效因为强化学习的经验池本质上是一个样本分布。如果一个任务里 99% 的经验都是稀疏零奖励价值网络就很难学到什么东西。但经过重标注之后经验池里会出现大量“目标可达、且有正奖励”的样本价值函数对状态-动作对的估计会变得更加准确策略自然也就有了优化的方向。用更本质的话说HER 利用了“无论目标是什么这条轨迹代表的动作模式至少证明了一件事从这个状态出发是可以朝某个方向成功前进的”。这种结构化信息是随机探索很久都很难得到的。2. hindsight 重标注原理拆解2.1 目标重标注的数学表达要真正理解 HER还是得看几行数学不过别担心它没有复杂的推导主要是让你搞清楚“我们到底改了什么”。在一个目标条件强化学习Goal-Conditioned RL设定里状态往往会被拆成两部分观测到的当前状态s_t和目标任务g。策略的输入是(s_t, g)输出动作a_t环境返回下一时刻状态s_{t1}和奖励r_t R(s_t, a_t, s_{t1}, g)。HER 做的重标注操作发生在轨迹τ (s_0, g, a_0, s_1, r_0, ..., s_T, g, a_{T-1}, s_T, r_{T-1})已经采集完毕、准备写入经验池之前。我们选择一个替代目标g把它替换掉轨迹里出现的原始目标g。替换之后后半段所有时序上的奖励都需要用新的目标函数重新计算一遍r_t R(s_t, a_t, s_{t1}, g)。也就是说我们并不是把这整条轨迹的经验删掉重来只是把标签换了样本内部的转移关系、动作序列保持原样。这里有一个容易混淆的点替代目标g必须在原轨迹中有对应的真实状态通常是轨迹里某个时间步的状态s_t ∈ τ。这保证了重标注后的目标确实是“这条轨迹实际抵达过的状态”从而保证“这个目标至少在该 trajectory 中实现过一次”。有了这个现实支撑重标注后的正奖励经验就不是凭空捏造的。在编程实现时HER 的经验池里其实会同时存放两组经验一组是原始目标的经验另一组是重标记目标的经验两份都参与后续的网络更新。这样既保住了原始任务的学习又额外塞进了大量多样化成功样本。2.2 四种重标记策略final、future、episode、randomHER 论文里提出过四种从轨迹中选择替代目标的策略我在实际工程里也逐一试过效果差异挺明显值得单独拎出来说。final直接选轨迹的最后一个状态s_T作为替代目标。这是最简单粗暴的做法相当于把“整条轨迹最终走到了哪里”当作目标。缺点是如果轨迹很长并且状态空间很大最终状态很可能是一个极端或者无关的位置信息量不够丰富。future在轨迹的同一个 episode 中从当前时间步t之后挑k个状态作为候选替代目标。这个策略最核心的直觉是未来的状态相比于当前状态更能反映“接下来发生的动作所带来的直接结果”。位置移动类任务里这个策略产生的目标往往分布合理而且与轨迹本身高度相关。episode从整个 episode 的任意状态里随机抽取若干个作为替代目标。这个策略保证了目标的多样性但不一定和当前动作的因果链最近。它适合用来增强数据的全局覆盖度。random从所有已存储的状态里随机抽取目标不限于当前 episode。这个策略的目标与轨迹的相关性最低所以最不推荐但在某些特定设定下可以少量混合使用作为正则。我在评测表里列一下这些策略在我做 FetchPush 任务时的经验感受基于我自己训练 20 万步后的效果对比重标注策略目标相关性样本多样性训练收敛速度我的评价final高低中等适合任务目标单一的场景future高中等最快最常用强烈推荐优先尝试episode中等高中等偏快想增加多样性时混合用random低最高慢不稳定不建议单独使用2.3 为什么 future 策略往往最好用我自己一开始想当然地觉得 episode 策略应该更好毕竟它采样范围广、数据多样。但几次对比实验下来future 策略在大多数连续控制任务里都稳赢。原因其实也简单future 策略选择的替代目标来自当前时间步之后的状态这意味着在重标注后的样本里替换目标与智能体后续的动作在时间上是“严格对齐”的。比如机械臂在第 10 步推了一下方块到第 14 步方块到达了一个位置那么把第 14 步的状态设为目标第 10 步那个推的动作就和“成功达成第 14 步目标”之间建立了直接的因果联系。这种因果对齐是 HER 能高效学习的根本。而 episode 策略虽然多样性高但免不了会选到当前轨迹时间线之前的状态作为目标。这时候目标与动作之间的因果关系就接不上了重标注后的正奖励样本虽然看起来是成功的但模型没法从中理清到底是哪个动作带来了成功学习信号会被稀释。random 策略就更不用说了选到的目标很可能完全不在当前轨迹的状态覆盖范围之内那就根本不能算是一次“成功的执行”。所以我现在的项目里除非目标是做高多样性的预训练不然 future 都是默认选项。配套的k值通常取 4 到 8意思是每个时间步重标注出 k 条额外经验。k太小经验池成功样本的比例不够高k太大数据重复率飙升训练内存和时间成本受不了而且收益边际递减。常见的默认配置是把k设为 4正好在做 trade-off。3. 实操过程从零实现 HERDDPG3.1 网络结构与重放缓冲区改造接下来进入正题讲讲我实际动手实现 HER 的一个完整过程。我选 DDPG 作为基础算法因为 HER 本质上是一个经验处理技巧不挑剔底层算法但它必须配合 off-policy 方法使用on-policy 的 PPO 在原理上没法直接套 HER。DDPG、TD3、SAC 都可以经验池越大HER 的效果发挥得越充分。在这个实现里actor 网络的输入是拼接后的(observation, goal)输出是连续动作critic 网络的输入是(observation, goal, action)输出 Q 值。一个在目标条件 RL 里常见的细节是我们通常不会把 goal 直接拼成一段扁平向量送入网络而是先分别提取 observation 和 goal 的特征再拼接特征向量。我这里先用最朴素的形式——直接 concat——来演示实际工程里建议对高维状态先过几层 MLP 做特征提取。重放缓冲区是 HER 的核心改动点。除了普通的(s, a, r, s, done, g)之外我们还额外存储轨迹的时间步索引和 episode 索引方便在采样完一条轨迹之后离线做重标注。一个很好的工程技巧是把经验存储按“episode”为单位来组织每个 episode 存一个列表列表里按时间顺序放下每一步的经验。只有当整个 episode 结束之后才统一执行目标重标注并把最终样本推入训练缓冲池。这比“边采集边存”更贴合 HER 的逻辑因为你直到 episode 结束才知道哪些未来状态可以作为替代目标。3.2 核心代码实现我用一段接近 PyTorch 风格但简化掉环境包装的代码把 HER 的存储逻辑写出来方便你照葫芦画瓢import numpy as np from collections import deque class HindsightReplayBuffer: def __init__(self, capacity, k4, strategyfuture): self.capacity capacity self.k k self.strategy strategy self.buffer deque(maxlencapacity) self.episode_buffer [] # 临时存放当前 episode def store_transition(self, transition): # transition 包含 (obs, action, reward, next_obs, done, goal, t) self.episode_buffer.append(transition) def finish_episode(self): # 1. 先把原始经验写入训练缓冲池 for trans in self.episode_buffer: self.buffer.append(trans) # 2. 离线重标注对每个时间步额外生成 k 条 relabeled 经验 episode self.episode_buffer T len(episode) if self.strategy future: for t in range(T): for _ in range(self.k): future_steps np.random.randint(t 1, T 1) # 未来某个时间步的真实状态作为替代目标 new_goal episode[future_steps - 1][next_obs][:3] # 重算奖励假设任务达成条件是状态距离 阈值 dist np.linalg.norm( episode[t][next_obs][:3] - new_goal ) new_reward 1.0 if dist 0.05 else 0.0 relabeled episode[t].copy() relabeled[goal] new_goal relabeled[reward] new_reward self.buffer.append(relabeled) self.episode_buffer.clear()代码逻辑很简单但有几个关键点容易写错。第一new_goal一定要从next_obs里取不能从当前obs取否则会造成目标泄漏相当于让模型提前看到当前动作之后的环境反馈。第二重算奖励的规则必须和原始环境完全一致否则价值函数学到的奖励分布会变得精分。第三done信号一般不需要重算因为重标目标并不能改变物理交互是否结束。在我做机械臂仿真环境时一条 episode 做完就是结束了不会因为替换目标而继续延长。为了让代码跑起来更接近真实项目通常还会把 relabel 次数和往经验池里塞原始重标注样本的比例做成超参。我自己的建议是原始经验和重标注经验的比例维持在 1:3 到 1:4 之间比较合适。如果重标注经验太多模型对原始目标任务的拟合反而会被稀释毕竟这些经验并不完全代表真实任务的分布。3.3 参数选择与训练配置训练配置我直接给出我习惯的一套起始参数你可以在这个基础上做微调。我用的环境是 OpenAI Gym 里的 FetchPush-v1动作维度 4观测维度 25包含机械臂末端位置、物体位置、目标位置等目标维度 3。超参数我的配置说明经验池容量1e6越大越好但注意内存开销HER k 值4每个时间步生成 4 条重标注经验策略网络结构(256, 256) MLP ReLU对 Fetch 系列足够目标网络软更新系数 τ0.05比默认 0.005 稍大收敛稍快批大小512更大批量有助于稳定 value 更新Actor 学习率1e-3Adam 优化器Critic 学习率1e-3Adam 优化器目标阈值0.05m欧氏距离低于该值视作成功每个 episode 最大步数50过长会拖慢经验填充速度关于目标阈值这里特别提醒一下这个值会影响重标注后正奖励样本的比例。如果阈值设得太小那么重标注后的正奖励数量就少HER 的优势发挥不出来如果设得太大模型会很容易“骗到”奖励学会一些粗糙的近似策略。我一般会先用环境自带的 success 判定阈值的三到五倍作为重标注阈值因为 HER 并不要求精确达到目标它只需要提供一条乐观的、有梯度的成功示范。训练时我建议每跑完 20 个 episode 就做一次验证评估看看在原始目标下策略的成功率。HER 在训练初期可能会表现出一种有意思的现象策略在重标注目标下成功率高但在原始目标下成功率上升很慢。这不是 bug说明模型正在利用重标注经验逐步建立状态-动作-目标之间的映射关系耐心等下去原始目标的成功率一般会在某个转折点后突然跃升。我在 FetchPush 上大概跑到 3 万到 4 万步就开始有起飞迹象10 万步左右可以达到 80% 左右的成功率。4. 常见问题与排查技巧实录4.1 重算奖励最容易踩的坑我在自己实现 HER 的过程中遇到过的最大问题就是重标注后没有重新计算奖励直接把原来的 0 奖励和新的目标存进经验池。结果训练出来的模型看起来在动但任务成功率永远是 0——因为价值网络里存的都是“目标达成了却给 0 分”的错误样本Q 值估计自然一团浆糊。这个问题在调试时还特别隐蔽因为你不会有任何报错loss 甚至能稳步下降只是最终测试时策略永远学不会。后来我把验证逻辑改成“在重放缓冲区里随机抽一批样本统计目标达成且奖励为正的比例”发现这个比例接近 0才意识到问题出在奖励没有重算。所以建议大家在实现 HER 的早期就在 git commit message 里标注“奖励重算”这一步或者写一个简单的单元测试专门验证“同一个状态不同目标奖励输出是否符合预期”。另外还有个坑是关于状态归一化的。HER 的重标注是拿轨迹里的实际状态作为目标如果 state 里不同维度量纲差别很大比如位置是米级速度是厘米级直接做欧氏距离会导致目标阈值完全失去意义。我之前在处理一个带速度状态的任务时距离计算被速度维度完全主导正奖励判断几乎全部失效。解决办法是提前做 state 归一化或者距离计算时只取和目标直接相关的维度。4.2 HER 不适合哪些场景HER 并不是万能的它解决的问题非常具体目标条件任务、稀疏奖励、且任务目标可以明确从一个状态分量中提取。如果你的任务是那种“只关心最终分数、没有一个明确 goal 向量”的场景比如玩游戏拿高分HER 就没办法直接套用因为你没有一个可操作的目标向量来重标注。还有一种情况是目标空间离散且非常小比如任务目标只有“打开灯”和“关闭灯”两个选择。这种情况下 trajectory 里实际到达的状态和这两个目标可能根本没有重叠重标注无从谈起HER 反而会制造大量虚假经验。另外如果环境本身奖励虽然稀疏但动态非常复杂比如需要多步精确操作才能达成目标HER 可能会倾向于学到“能达成中间目标”的策略而不是“达成最终目标”的策略这时候需要额外设计层次化结构让 HER 只负责低层技能学习。4.3 稳定训练的技巧与调优心法如果你已经在用 HER 但训练仍然不稳定可以从下面几个方向逐一排查。先看经验池样本分布如果重标注后的正奖励样本占比超过 60%可能是 k 值太大了模型会被“过度乐观”的信号带偏如果占比低于 5%可能是 k 值太小或者阈值太严格需要调整。再看策略网络更新频率。HER 本质上是一种“经验再利用”技术它让训练过程中每个 batch 里的成功样本占比大幅提升这会导致 Q 值的更新幅度变大网络更新频率过快时容易引入震荡。一个很实用的做法是把 critic 的学习率降到 1e-3 甚至 5e-4同时加大 target network 的软更新系数让目标网络追踪得更平滑。最后有条件的话推荐在仿真环境里多做几组不同k值的对比实验。我之前做过一次系统扫描从k1到k20结论是k4到k8表现最佳超过8后性能反而轻微下降因为过多的重复样本让经验池的多样性变差。你不需要跑太多次留 30% 的训练预算做这种超参扫描收益通常比继续调网络结构要明显得多。写在最后一点个人体会我做机器人操作任务时最费时间的其实不是网络设计而是跟奖励函数较劲。HER 让我换了一种思考方式别老盯着最终目标不放先看看手里的轨迹里已经有什么把“过程里的结果”变成可学习的目标再一步步把目标拉回最终任务。这种思路不仅用在抓取、推块这种仿真任务上我现在和一些做仓储机器人的朋友聊天他们在做拣选任务初期也会用类似手法先把机械臂学会“到达货架前”这种子目标再逐步扩展。如果你正准备在自己的稀疏奖励任务里试试 HER我的建议很简单先用 future 策略k 设成 4把重标注奖励的计算逻辑写在最显眼的位置然后耐心跑几万步。训练初期的迷惑期是正常的关键是确认重标注后经验池里的成功样本在稳定增加。等你看到原始目标任务的成功率在某一天突然开始拉升那种感觉真的挺上头的。希望这篇拆解能帮你少踩几个我已经踩过的坑。
返回列表