ARTICLE DETAIL

资讯详情

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

HER原理与实战:用事后复盘破解强化学习稀疏奖励难题

HER原理与实战:用事后复盘破解强化学习稀疏奖励难题 如果让我用一句话来概括 Hindsight Experience Replay也就是我们常说的 HER那么它解决的问题非常朴素强化学习智能体能不能像人一样在失败之后停下来说一句“刚才我虽然没做到 A但我其实做到了 B那我把 B 当成目标再学一次”这个思路最早吸引我是我在跑机械臂推方块实验的时候。目标点是桌面上的某个固定位置奖励设计得很稀疏做到就给 1做不到就是 0。结果智能体跑了一整夜永远在那几个角落打转完全不收敛。不是网络太弱也不是超参数崩了是它在整个探索空间里几乎拿不到任何正向反馈。后来我把 HER 接上效果立竿见影。这篇文章就围绕 hindsight 这个关键词把它的原理、实现方式、目标选择策略、调参经验以及我实际踩过的坑一次性讲透。如果你是做机器人控制、稀疏奖励任务或者多步规划决策的这篇内容可以直接拿去参考。1. 为什么“事后复盘”比“事前设计奖励”更香1.1 稀疏奖励是强化学习的隐形天花板很多人一开始上手强化学习都是从 CartPole、MountainCar 这类环境入的门。这类环境里奖励几乎是连续可得的杆子每多立一秒都有反馈小车每往东挪一点也有累积信号。但真实的控制任务根本不是这个画风。机械臂抓取没有物体在手里的中间奖励无人车泊车没有接近车位就自动加分的信号游戏里的隐藏关更是只有通关才知道。奖励越稀疏智能体能获得的梯度信号就越少随机探索就像你在一个巨大的仓库里闭着眼找一把钥匙摸到门把手之前所有动作都被判定为“无用”。传统做法是设计密集奖励也就是人工势场那一套给智能体铺一条看得见的梯度坡道。比如按照离目标点的欧氏距离给负奖励离得越近惩罚越小。听起来合理做起来全是坑。首先是 reward shaping 容易引入局部最优智能体很可能找到一个“既不靠近目标、但又能稳定获得比乱动更高回报”的姿势然后卡死在那里。其次是目标稍微变一下整套奖励函数就得重新调环境一换之前的努力全部作废。密集奖励本质上是在用人的先验知识替智能体做规划这与强化学习自己探索的初衷是冲突的。hindsight 这类方法解决的是另一条路不改变环境奖励而是改变智能体的“记忆方式”。它让智能体学会从已经发生的轨迹里提取有价值的信息哪怕这条轨迹没有达成原始目标。1.2 hindsight 到底在“复盘”什么我先举一个日常生活的例子。你周末想去一家新开的火锅店结果导航出错把你带到了旁边一条全是奶茶店的巷子。你走进一家不起眼的奶茶店发现他家的杨枝甘露意外好喝。虽然这趟行程没有达成“吃火锅”的目标但你获得了另一个有效认知这条巷子有家奶茶店不错。下一次你想约朋友喝下午茶你会直接想到这家店。HER 做的事情完全一样。在一次强化学习的 episode 中假设原始目标是 $g$智能体从状态 $s_0$ 出发执行了一系列动作最终却停在状态 $s_T$。按照原始目标去计算奖励整条轨迹的 reward 全是 0没有任何学习价值。但 hindsight 的做法是把“最终实际达到的状态 $s_T$”当作这次 episode 的新目标重新计算每一步的奖励。你会发现如果以 $s_T$ 为目标这条轨迹其实是完美成功的每一步都能得到正向奖励。这个操作的本质是通过修改采样数据来制造一条“成功轨迹”。原轨迹不用重跑同一批状态和动作只要换一个目标去解读就凭空多出一批高质量训练样本。这就是“事后复盘”的核心不是让智能体在环境里多转几圈而是在已有经验里多挖掘一层信息。1.3 一个关键前提目标条件策略HER 使用的基础模型不是普通的 Q 网络或策略网络而是目标条件策略也就是网络输入里除了状态 $s$还要额外带一个目标 $g$。策略输出动作时必须同时回答“我现在在哪”和“我想去哪”。这样的设计是为了让同一个策略能够被反复套用到不同目标上也是后续换目标重算奖励能生效的前提。举个例子。状态是机械臂关节角度和末端位置目标可以是桌面上的一个坐标点。网络输入拼接状态和目标输出动作。当 HER 把目标从“左上角的目标点 A”换成“当前实际到达的右下角位置 B”时策略会自动把这个新目标 B 当作控制指令来处理。这种网络结构在 robotic manipulation 领域非常常见DDPG、TD3、SAC 都能很自然地扩展到这种目标条件版本。2. 拆解 HER 的核心机制目标重放与四种目标选择策略2.1 轨迹中目标的“二次标注”过程我们先看一条原始经验轨迹的组成。一个 transition 通常记作 $(s_t, a_t, s_{t1}, g, r_t)$其中 $g$ 是这个 episode 希望达成的目标$r_t$ 是根据 $g$ 计算出的即时奖励。在多步 episode 中假设 episode 长度为 $T$那么轨迹包含 $T$ 个这样的 transition。原始轨迹因为最终没有到达 $g$所以绝大多数 $r_t$ 都是 0。hindsight 重放时我随机给这条轨迹指定一个新目标 $g$。这个 $g$ 往往来自轨迹中某个实际访问到的状态。然后整条轨迹的 transition 重写为 $(s_t, a_t, s_{t1}, g, r_t)$。这个 $r_t$ 由新目标 $g$ 和 $s_{t1}$ 的接近程度决定如果足够接近就设为正的奖励否则设为 0。原来不足 $T$ 步就能到达的位置现在因为目标换了原本的时序关系依然有效相当于轨迹内部的时间顺序没有变只是“目的地”变了。这里有一个很容易忽略的细节轨迹重放时也会重写 episode 结束标志。原始轨迹因为没到达目标很多情况下被标记为未完成但以实际到达状态作为新目标后轨迹末尾就变成完成状态了。如果算法里有单独给 done 标志加惩罚或折扣的逻辑这个变化会显著影响训练信号分布。我见过不少人只改了 reward 忘了改 done结果训练过程反复震荡。2.2 四种目标选择策略final、future、episode、randomHER 论文里测试了四种从轨迹中选择新目标的方式尽管后来实践中大家基本都默认用 future但理解四种策略仍然能帮助你判断自己的任务适合哪一种。第一种是 final。整条 episode 结束后只把终点状态 $s_T$ 作为新的目标所有 transition 全部使用同一个 $g$。优点是最接近“事后复盘”的直觉缺点也很直观如果轨迹特别长、状态变化很大把最后一个状态当成所有中间步骤的目标中间很多状态可能与终点状态差异巨大学习信号会变弱。第二种是 future。从时刻 $t$ 开始的 transition 中新目标 $g$ 从 $t1$ 到 $T$ 之间的某个状态里随机抽取。这个策略的关键点在于新目标总是与当前 transition 的下一步状态更接近所以重新标注后的轨迹每一步都显得“离目标更近一点”学习信号更强。这也是未来目标策略在实践中效果最好的原因。第三种是 episode。从同一条轨迹的所有状态里随机选一个作为目标不考虑时间先后。这种策略实现最简单但效果和未来目标策略相比有明显的退化因为它可能把早期状态当作后期 transition 的目标导致智能体学习到“时间越走越离目标越远”的错误关联。第四种是 random。整条轨迹使用一个与这条轨迹完全无关的随机目标。这个策略基本只用于基线对比效果最差因为它完全不引用轨迹本身的信息。表格对比会更直观策略目标来源与轨迹时刻的关系常见效果final轨迹终点状态全区间同一目标中低效future未来时刻状态按时间向后采样最佳episode本轨迹任意状态无时间约束中效random随机状态无关联较差2.3 目标表示方式直接影响网络学习不是随便一个状态向量都能当目标用的。你需要在设计状态表示时就想清楚目标 $g$ 和状态 $s$ 是否处在同一个空间或者是否通过一个稳定的映射函数关联起来。HER 最舒服的场景是目标就是状态空间的一部分比如二维坐标、关节角度、像素位置这样直接拿实际状态当目标即可。如果目标是离散的比如“开灯/关灯”“抓取红色物体/蓝色物体”那么目标选择策略就要针对性修改。离散目标和连续状态之间的相似度度量需要单独定义不能用欧氏距离。这种情况下通常做法是引入一个目标编码函数把离散目标编码成向量再和状态拼接。还有一种情况是目标不可观测比如目标是“某个隐藏物体的位置”智能体永远看不到目标状态只能通过传感器间接感知。这种环境下 HER 的收益会明显缩水因为你无法从实际轨迹中提取到可用的目标表示后面会细讲这个问题。3. 上手实现在 DDPG 类算法上接入 HER3.1 先搭一个目标条件网络我用 PyTorch 风格的伪代码来展示实际项目里随时可以套进自己的框架。目标条件策略网络的结构一般是输入状态和目标各自通过一个编码层然后拼接再通过几层全连接输出动作。Q 网络也类似。import torch import torch.nn as nn class GoalConditionedActor(nn.Module): def __init__(self, obs_dim, goal_dim, action_dim, hidden256): super().__init__() self.obs_encoder nn.Sequential( nn.Linear(obs_dim, hidden), nn.ReLU() ) self.goal_encoder nn.Sequential( nn.Linear(goal_dim, hidden), nn.ReLU() ) self.fc nn.Sequential( nn.Linear(hidden * 2, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, action_dim), nn.Tanh() ) def forward(self, obs, goal): h_obs self.obs_encoder(obs) h_goal self.goal_encoder(goal) h torch.cat([h_obs, h_goal], dim-1) return self.fc(h)这里有个容易被忽略的细节Actor 网络的输出层使用了 Tanh把动作限制在 [-1, 1]。如果你的环境动作范围不是这样要在环境层面做动作缩放而不要粗鲁地在网络后直接裁剪否则梯度在饱和区会消失影响探索效率。Q 网络的输入则是状态、目标、动作三部分拼接。训练时目标条件 Q 函数需要估计的是 $Q(s, g, a)$也就是在给定目标的前提下状态动作对的价值。这一差异决定了 HER 重放的数据必须连带目标一起喂给网络不能只替换状态动作。3.2 判定奖励的改写函数HER 里最核心的一小段代码就是根据新目标重算奖励。我通常封装一个函数传入原 transition 和新目标输出重写后的 transition。def rewrite_transition(state, action, next_state, new_goal, threshold0.05): # 计算 next_state 与新目标之间的距离 dist torch.norm(next_state - new_goal, dim-1) reward torch.where(dist threshold, torch.tensor(1.0), torch.tensor(0.0)) done (dist threshold).float() return state, action, reward, next_state, new_goal, donethreshold 的选取是这个函数成败的关键。threshold 太大智能体很容易“碰到”目标奖励饱和失去精细调节的动力。threshold 太小HER 产生的新轨迹中绝大多数 transition 仍然拿不到正奖励就没有达到创造学习信号的目的。我的经验是先看环境的状态尺度机械臂末端坐标如果是米为单位0.05 米比较合理如果是像素坐标threshold 可能要放到 5 到 10 个像素。也可以做成随时间衰减的课程式 threshold前期宽松一点让智能体先见识到成功后期收紧逼它更精确。3.3 完整主循环伪代码下面给一个比较完整的 HER 采样和训练伪代码配合 DDPG 类算法使用。这个伪代码去掉了冲突处理等外围逻辑聚焦在“如何把 HER 数据加入到 replay buffer 里”这一块。# replay buffer 额外保存目标字段 class HindsightBuffer: def __init__(self, capacity): self.capacity capacity self.buffer [] def push(self, transition): if len(self.buffer) self.capacity: self.buffer.pop(0) self.buffer.append(transition) def sample(self, batch_size): return random.sample(self.buffer, batch_size) def her_sample_a_batch(buffer, batch_size, k4): normal_size batch_size // (k 1) her_size batch_size - normal_size normal_batch buffer.sample(normal_size) her_batch [] for _ in range(her_size): # 随机抽取一条完整 episode episode random.choice(completed_episodes) t random.randint(0, len(episode) - 1) transition episode[t] # 用 future 策略从 t1 到末尾随机选一个作为新目标 future_idx random.randint(t 1, len(episode)) new_goal episode[future_idx].next_state rewritten rewrite_transition( transition.state, transition.action, transition.next_state, new_goal ) her_batch.append(rewritten) return normal_batch her_batch这段代码中一个关键点是completed_episodes的维护。为了生成 HER 数据你不能丢失 episode 的结构信息buffer 里存的不能只是一条一条切碎的 transition还要保留哪些 transition 属于同一条 episode。所以实际工程中我会用一个字典结构给每个 episode 一个唯一编号训练完一个 episode 后把整条轨迹缓存起来等采 HER 样本时直接从这些完整轨迹里抽取。3.4 四比一的经验混合比例是怎么来的原论文里提到每一份原始经验搭配大约四份 HER 重放经验也就是 HER replay ratio 为 4。这个比例不是拍脑袋定的它是在平衡“从失败中学习”和“不要忘掉真实目标分布”之间的关系。如果你把 HER 数据比例拉得太高比如 10 比 1智能体会被大量“容易达成”的替代目标淹没对原始目标的真实分布反而失去敏感性比例太低比如 1 比 1成功轨迹仍然太少稀疏奖励问题又回来了。我在做机器人抓取实验时明显感受到 4 比 1 是一个甜点值。低于 3 时训练曲线会出现很长的平台期高于 6 时原始目标上的 episode 平均回报会飘出现训练后期掉点的现象。如果你的任务目标特别分散可以试着把 her 比例加大到 5 或 6如果目标空间很集中适当降到 3 会更稳。4. 调参与避坑我踩过的那些 hindsight 相关的坑4.1 目标必须满足“事后可观测”这一前提HER 名字里有 hindsight但它的实现非常依赖一个条件新目标必须从轨迹中直接观察到。如果你的环境里目标是一个潜变量比如“把钥匙插进锁孔”但锁孔位置被遮挡实际轨迹中并没有可直接采样的锁孔状态那 HER 就无从下手。我曾经尝试在一个三指灵巧手的任务里用 HER目标是“旋转阀门到指定角度”。阀门转动的真实角度可以从环境内部读出但视觉输入里只能看到手指和阀门外观。我最初用了像素作为目标结果 HER 效果极差因为像素向量和关节控制之间的关联太稀疏目标编码器学不出有效语义。后来我改回用机械臂关节角度加阀门角度作为目标表示效果立刻上来了。所以如果你的任务视觉信息很丰富还是要先抽取出结构化状态再做目标重放。4.2 没有正确维护 done 标志会导致价值崩塌我前面提过一次 done 标志的问题但值得单独拿出来说。HER 重写目标之后原本很多未达成的轨迹变成了达成轨迹这时候 transition 的 done 必须从 0 改成 1。否则Critic 网络会学到“接近目标的状态并不是终止状态我还需要继续动作”这个信号是自相矛盾的。在实际编码中常见错误是只对最后一个 transition 的 done 做修改而中间步骤的 done 全部保持 0。这在大多数环境里没有大问题因为中间步骤本来就没真正到达目标。但如果你使用了 future 策略新目标可能在第 5 步就已经到达那么第 5 步之后的 transition 按新目标来看都处于“已经完成”状态。这种情况下更稳妥的做法是把到达目标之后的所有后续过渡全部标记为 done或者干脆截断轨迹不把成功之后的抖动数据喂给网络。4.3 把 HER 搬到多目标场景时的奖励尺度陷阱多目标任务的难点在于不同目标的完成阈值可能不同。比如“移动到一个点”的阈值可以是 0.05但“转动画到一个角度”的阈值可能是 5 度。如果统一用一个 threshold某些目标会变得极其容易另一些又极其困难。这会造成 HER 重放后的奖励分布不一致训练信号忽大忽小。我的解决思路是对目标做归一化。在 pre-processing 阶段把每个目标映射到一个标准空间让“距离”的量纲尽量一致。比如点位置除以最大距离角度除以 180 度然后再算距离和 threshold。归一化后整个训练过程稳定很多也不再需要针对每个子任务单独调阈值。4.4 常见问题速查表症状可能原因建议解决方式训练初期完全没有正向奖励threshold 过小或探索噪声太大放宽 threshold限制探索噪声幅度训练中后期原始目标回报掉点HER 比例过高把 replay ratio 从 4 降到 2-3稳定原始目标分布智能体一直做小幅抖动的无效动作目标编码维度太少动作输出 Tanh 饱和增加目标特征维度检查动作缩放逻辑多个目标训练效果差异极大未做目标归一化对目标空间做标准化统一阈值用 future 策略但训练发散future 采样区间过大限制 future 采样范围比如只从 t1 到 t10 采样4.5 如何判断你的环境是否真的需要 HER不是所有稀疏奖励任务都必须上 HER。如果环境本身就是单目标、无随机初始化、且初始位置固定HER 的价值会打折扣因为新目标虽然能造出更多成功轨迹但是这些成功轨迹全都指向同一条路线泛化意义不大。反过来如果你的任务是多目标、多初始位置、多障碍布局HER 相当于天然免费扩充了一倍以上的训练样本它从“失败”中提取的成功经验能覆盖更多状态空间区域泛化性会显著提高。判断标准很直接你可以先统计当前随机策略产生的 episode 中有多少比例能到达目标。如果前 1000 个 episode 成功率趋近于 0而且每个 episode 的轨迹形态差异很大HER 大概率适合你。如果成功率本来就不是零也可以用 HER但收益没那么明显。5. 我的实战心得与扩展想法5.1 从单目标 HER 到多任务复用的自然延伸HER 不只是解决单任务的稀疏奖励问题。我在后来的多任务机器人控制实验里发现如果把任务描述也当作目标的一部分拼到网络中HER 天然就能在多任务之间迁移经验。比如今天训练“推到左上角”明天训练“推到右下角”这两个任务在环境层面是两套不同的 reward 函数但 HER 把失败轨迹重写之后它们在目标空间里就是一体的。最后一次训练出的网络输入不同目标坐标就能完成不同搬运任务不需要重训。这种把目标空间做大做满的思路后来也启发我理解了离线强化学习中一些数据增强方法。很多 offline RL 算法在处理“次优轨迹”时本质也是在做某种 hindsight 式的数据重写。只是它们不叫 HER而是换了一套理论包装。5.2 一小段值得反复琢磨的代码设计在实现层面我最推荐的工程习惯是把 HER 的目标采样逻辑与算法主体解耦。无论你用 DDPG、TD3 还是 SACHER 都只是数据层的一个 wrapper不应该侵入策略更新逻辑。这样你可以在不同算法之间快速切换对比它们在相同 HER 数据下的表现。很多开源实现把 HER 写死在算法类里换算法时就得整段重写非常痛苦。具体做法是维护一个独立的her_transformer模块只负责从完整 episodes 里生成重放数据。策略更新时只需要从 replay buffer 中取普通样本同时从her_transformer中取 HER 样本然后合并训练。如果实验不需要 HER直接把 transformer 关掉即可。5.3 如果你只带走一件事我做强化学习这几年最大的感受是很多性能瓶颈根本不是模型容量或优化器的问题而是训练数据里有效信号太少。HER 从一个非常巧妙的角度帮我们绕过奖励设计的诅咒它不改变环境不改变算法也不增加任何仿真成本只是在读取经验时换了一种“脑补”方式把失败变成了成功把稀疏变成了密集。这件事听起来简单但把它做对、做好需要理解目标表示、采样策略、阈值设计、比例调整这些细节。希望这篇文章能让你少走一点弯路下次遇到稀疏奖励任务时第一时间想到 hindsight而不是崩溃地堆算力。
返回列表