ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

稀疏奖励下的强化学习救星:HER算法原理、实现与应用实践

稀疏奖励下的强化学习救星:HER算法原理、实现与应用实践 hindsight 这个词英文原意是后见之明也就是我们常说的事后诸葛亮。第一次看到它被用作算法名是几年前翻到 OpenAI 那篇 Hindsight Experience ReplayHER论文的时候当时我愣了几秒把后见之明用在一个稀疏奖励强化学习算法身上实在太贴切了。HER 解决的是很多强化学习实践者都会撞到的那堵墙——环境只在任务完成时给奖励其他时候全是零智能体在巨大的动作空间里摸索半天什么都学不到。它的做法是让智能体重新解释失败既然刚才没有到达目标那就把实际到达的地方当作目标并据此把整条轨迹的奖励重写一遍。这是一个可以直接复现的思路无论你是研究机器人抓取、自动驾驶决策还是想做游戏 AI都会用得上。这篇文章从原理到代码再到踩坑分享我自己的完整实操记录。1. 从一个单词说起hindsight 到底解决了什么问题1.1 稀疏奖励那个让所有策略都失效的零分我在刚接触强化学习那会儿收到过不少看起来很美好的任务比如让一个机械臂把方块推到目标位置。环境只会干一件非常残酷的事如果你把方块推到了目标点给你一个 1差一毫米对不起0 分。所有中间过程都没有任何奖励信号。也就是说智能体要在一片茫茫的状态空间里凭概率找到那种一毫米都不能差的动作序列。这种 setup 带来的结果很尴尬无论是 DQN、DDPG 还是 PPO在训练初期都会因为几乎收不到正奖励而表现得像无头苍蝇。我们用普通 DDPG 在点目标环境里训练过跑了上千个回合成功率仍然接近 0。为什么因为策略梯度依赖奖励信号来调整动作概率如果 99% 的轨迹都是 0 奖励网络根本不知道往哪个方向挪一步才是有利的。有人会想到奖励塑形就是自己给中间状态设计一个距离目标越近奖励越大的稠密奖励。这确实能缓解问题但它有一个致命缺陷奖励形状一旦设计得不对智能体就会学会钻漏洞比如绕着目标转圈但就是不推进或者为了满足中间奖励做出完全不符合直觉的动作。而且很多真实任务里中间奖励本身就难以定义你怎么量化一个倒车入库的动作进行到一半是好是坏HER 当初吸引我就是因为它不需要你多聪明地设计奖励。你只需要一个很简单的二值奖励甚至不需要任何中间引导剩下的它自己搞定。简单到让人怀疑真能有用吗事实证明在大量目标导向任务里它真的有效。1.2 HER 这个名字为什么起得漂亮hindsight在英文俗语里最有名的用法是hindsight is 20/20意思是回头看的时候一切都是那么清晰。放在强化学习环境里HER 做的事恰好就是让智能体回头看整条轨迹然后把本来想达成但没达成的目标替换成实际达成的目标。举个例子。你训练一个机器人走迷宫目标是到达出口但它这次走到了一个死胡同。普通强化学习会觉得这条轨迹没有任何学习价值因为最终没拿到奖励。HER 会怎么做它会说这条轨迹虽然没有到达人类定义的出口但它确实是从起点走到了那个死胡同。于是它把死胡同这个点标记成一个临时目标把这次失败的轨迹重新解释为一次成功的到达训练。这种方式看起来有点自欺欺人但本质上是在告诉策略你刚才那一系列动作确实有能力完成从一个地方走到另一个地方这件事。只要这种能力被反复强化当目标换成真正的出口时智能体就已经具备了寻找出口的底层技能。名字起得妙算法也漂亮。2. HER 的核心思路把失败也变成经验2.1 一个还没到手的奖励等于没有奖励要理解 HER先得理解它面对的问题稀疏奖励下的探索困境。在大多数目标导向任务里我们可以把环境抽象成三个要素状态 s、目标 g、奖励函数 r(s, g)。奖励函数通常写成一个指示函数只有当 s 与 g 足够接近时输出 1否则输出 0。这样的奖励函数让经验回放机制失效了一半。普通强化学习从之前的经验样本里学到哪个动作带来高价值如果一条轨迹里所有 reward 都是 0那这条轨迹对价值网络来说几乎是全零教案除了让网络更确定这些状态没价值之外提供不了任何正面的因果信息。坏就坏在这些没价值的状态里其实藏着大量有用的因果联系从 A 走到 B 的动作序列是稳定的只是 B 不是我们要的出口而已。HER 从哲学上做了一次倒转既然你说 B 不是目标好那我宣布它就是目标。这就解决了没有奖励的问题——因为一旦目标被替换成实际到达的状态那么这条轨迹的最后一步自然就是成功的奖励立刻变成 1。你不用再担心智能体死循环因为它的训练目标变成了到达它已经到达过的地方。这听上去像作弊但这就是课程学习的本质先学会简单的再挑战难的。2.2 事后重标把没做成改写成学会了到那里具体操作层面HER 在每一条轨迹结束后会额外生成若干条重标样本。假设一条轨迹由状态序列 s1, s2, ..., sT 组成原本的目标是 g。训练时算法先正常存储一条轨迹每个 transition 都是 (s_t, a_t, r_t, s_t1, g)其中 r_t 表示这个 step 是否完成了原始目标 g。接着算法从轨迹里挑出一个事后目标 g这个 g 通常来自轨迹中出现过的某个状态比如最后状态 sT。然后它再生成一组新 transition除了目标从 g 变成 g 之外还要重新计算每个 step 的奖励 r_t计算公式变成状态后一步 s_t1 是否达到 g。因为 g 是轨迹中出现过的状态至少轨迹的末尾一定满足了接近 g这个条件所以这条新轨迹里一定存在正奖励。生成完重标样本之后把它们和原始样本一起丢进经验回放池。策略和价值网络的更新就同时从原目标样本和事后目标样本里学习。原目标样本让智能体不要忘记真正的任务是什么事后目标样本则让它在频繁的零奖励环境中依然能获得学习信号。这个过程可以用一个公式概括对每条原始 transition额外生成 k 个重标 transition每个重标 transition 把原始目标 g 替换为 g并把奖励按照新目标重新计算。k 通常取 4这也是论文里的默认值。2.3 从失败轨迹里挖出来的信号为什么这么宝贵我一开始有个疑问既然重标目标都是已经达到过的状态那训练出来有什么意义智能体不该只是一遍遍练习它已经会的事情吗后来在实践中才想明白这个看似无用的过程其实是在给策略网络做技能锚定。假设一个策略从没拿到过正奖励它的输出和梯度之间的关系完全是乱的。HER 通过重标让那些本应被丢弃的轨迹重新产生\n好的你刚才的动作序列导致你到达了这个位置我给你正反馈的信号。于是网络里面开始出现一些稳定的 actor 输出——至少它知道这些动作对应着某一个状态域是有价值的。这也解释了为什么 HER 一般和 off-policy 算法搭配使用比如 DDPG、TD3、SAC。因为 HER 需要把所有历史轨迹都存进经验回放池反复采样on-policy 算法天生不兼容这种大量重放的模式。我用 DDPG HER 跑导航任务时明显感受到前期探索效率比普通 DDPG 高出一个量级不是因为它更聪明而是因为它能在一堆零奖励轨迹里榨出学习信号。3. 复现 HER 前必须搞清楚的几个关键细节3.1 状态与目标分得清才能拆得散HER 不是所有强化学习任务都能直接套用的它对状态和目标的表示有一个隐藏要求目标和状态必须能拆开。如果观察空间里已经混入目标信息或者奖励函数依赖于一个不可分解的全局状态那重标目标时就会很麻烦。举个反例如果状态 s 是当前画面像素目标 g 是目标物体的一小块图像区块那你可以把 g 单独作为网络输入的一部分和 s 拼接在一起。这时重标就可行因为你只需要替换 g 那一部分。但如果你把目标直接编码进环境状态里让状态变成 s[位置, 目标位置]那重标时就不只是替换目标向量还要确保奖励计算逻辑从状态中正确读出新目标否则网络会被搞糊涂。我在实现时最常用的做法是状态表示只包含与目标无关的自车状态比如机械臂关节角、末端位置、速度而目标 g 单独作为一个向量输入。两者在进入网络前拼接成一个长向量。这样重标时只需要换掉目标向量然后按新目标重算奖励非常简单清晰。3.2 四种目标替换策略别再只盯着 finalHER 原论文里给出了四种选择事后目标 g的方法策略做法我实际测试的感受final直接取轨迹最后一个状态作为目标最简单但目标永远只有一个样本多样性差random从回放池里随机挑一个状态作为目标目标来源多样但可能和当前轨迹关联太弱episode从当前这段轨迹里随机挑一个状态比 final 丰富一些但仍和这条轨迹强相关future从当前轨迹中当前时刻之后的状态里随机挑一个最符合时序逻辑学习信号最自然推荐很多新手刚接触 HER 时默认用 final因为它最好理解。但我在自己的点目标环境里做了对比final 策略虽然也能收敛但需要的 episode 数量几乎是 future 策略的两倍。原因很简单final 重标出来的样本都指向同一个目标而 future 会在一条轨迹里生成多个不同目标让网络一次看到到达不同地方的多种成功经验泛化能力自然更好。3.3 奖励函数和网络结构设计HER 的奖励函数适合用稀疏设定也就是到达目标给 1没到给 0。你可能会想既然重标样本已经把目标改成已到达状态那大部分样本奖励都是 1会不会导致网络过拟合不会。因为奖励函数仍然保持距离阈值判断的机制比如判断距离是否小于 0.01如果事后目标选得太远有些过渡状态还是 0 奖励网络依然能学到边界。网络结构方面我沿用 DDPG 的标准 Actor-Critic 架构。Actor 输入是拼接后的状态目标输出动作Critic 输入是状态目标动作输出 Q 值。两层隐藏层每层 256 个神经元激活函数用 ReLU。目标网络用软更新系数 tau 取 0.005。整个结构不需要针对 HER 做任何特殊改造HER 完全工作在采样层网络只是负责正常 off-policy 更新。有一点容易被忽略Critic 的输入包含目标向量这让 Q 函数变成了在目标 g 下状态 s 执行动作 a 的价值。重标样本能起作用的前提就是 Critic 能区分不同目标下的价值差异。所以你在搭建网络时务必保证目标向量是作为一个独立的输入通道进入网络而不是藏在某个全连接层里被过早压扁。3.4 藏在论文角落里的几个超参数HER 能跑通很多时候靠的是几个容易被忽略的参数k每条轨迹额外生成多少条重标轨迹。k4 是常用值太大容易让重标样本淹没有效的原始目标经验太小又学不到东西。回放缓冲大小我习惯设到 1e6。稀疏奖励环境下你需要保留足够多的历史失败轨迹否则重标之后样本多样性还是不够。gamma短 episode 任务可以设小一点我用 0.9太长会让价值估计过度平滑。batch size128 是比较稳的选择。这些参数没有绝对最优我的建议是固定其他条件只动一个变量然后看成功率曲线。很多复现失败不是因为算法逻辑错而是因为这些参数搭配导致学习信号太弱或者太震荡。4. 动手实现一个最小 HER 项目4.1 环境选型一个简单到可以手写代码的点目标环境为了验证 HER 的效果我没有直接跑现成的机器人仿真而是自己写了一个 2D 点目标环境。很简单智能体是一个点初始位置在 (0, 0)目标位置随机生成在 [-1, 1] 区间内。每一步它输出两个动作值x 方向速度、y 方向速度速度被 clamp 到 [-1, 1]。状态就是当前坐标 (x, y)耗时超过 50 步或到达目标附近欧氏距离 0.01则本回合结束。奖励设置每一步给 0到达目标给 1。之前我还试过每步给一个很小的 -0.01 代价结果发现会拖慢收敛因为重标样本本身已经提供了稀疏的正信号不需要额外惩罚来逼它走捷径。这个环境看起来简单但足够暴露问题没有中间奖励普通 DDPG 在里面几乎学不到任何策略。你把这个环境换成机械臂抓取核心挑战完全一致只是状态维度更高、目标更复杂而已。4.2 核心实现经验采样、目标替换、网络更新下面这段是我在项目里实际用到的核心逻辑压缩成了最小可读版本。重点是看两个环节轨迹结束后如何生成重标样本训练循环里如何采集数据。import numpy as np from collections import deque class HERBuffer: def __init__(self, capacity1000000): self.buffer deque(maxlencapacity) def store_transition(self, state, action, reward, next_state, goal, done): self.buffer.append((state, action, reward, next_state, goal, done)) def sample(self, batch_size): indices np.random.choice(len(self.buffer), batch_size) return [self.buffer[i] for i in indices] def augment_trajectory_with_hindsight(trajectory, k4): # trajectory: [(state, action, reward, next_state, goal, done), ...] augmented [] states [t[0] for t in trajectory] for _ in range(k): future_state_idx np.random.randint(1, len(states)) new_goal states[future_state_idx] for state, action, _, next_state, original_goal, done in trajectory: new_reward 1.0 if np.linalg.norm(next_state - new_goal) 0.01 else 0.0 augmented.append((state, action, new_reward, next_state, new_goal, done)) return augmented训练循环里每次跑完一条 episode 后把原始轨迹存进 HERBuffer再调用augment_trajectory_with_hindsight把生成的重标样本也存进去。之后就是正常的 off-policy 更新从 buffer 里随机采样一个 batch分别更新 actor 和 critic。我踩过的一个坑是future_state_idx必须取当前时刻之后的某个状态而不是随便从整条轨迹里抽。如果从轨迹开头抽一个状态作为新目标那前面的 transition 在重标后可能会不满足时间因果导致价值网络学到错误的自举关系。这就是为什么原论文里 future 策略是在未来状态中随机选一个而不是从全轨迹随机选一个。4.3 训练效果对比有 HER 与无 HER我在同样环境、同样超参数下做了两组实验一组用普通 DDPG一组用 DDPG HER。两组各跑 1500 个 episode评价指标是最近 100 个 episode 的平均回合成功率。实验结果非常鲜明训练方式前 300 回合成功率中 800 回合成功率1500 回合成功率DDPG0%0%0%DDPG HER12%41%87%普通 DDPG 几乎趴在地上不起身因为它的价值网络只有在碰到目标时才会收到 1而 2D 连续状态空间里的随机动作碰到目标的概率实在太低了。HER 版本在前 300 回合就已经能看到零星的成功突破这正是重标样本在起作用——它让网络在几乎没有原始正奖励的情况下也获得了稳定梯度。我后来又试了一个小改动把每次 episode 的重标轨迹数量从 4 增加到 8结果前 300 回合成功率提升到 17%但 1500 回合时反而掉到了 79%。这说明重标样本过多会挤压原始目标样本的比例让智能体太专注于到达已到达的地方忘了真正的任务目标。k 合适的才是好的。4.4 我的参数配置和实测记录这里是我最终稳定跑通的一组配置可以直接抄参数值算法DDPG HERfuture 策略k重标轨迹数量4网络隐藏层256 x 256激活函数ReLUactor 学习率1e-3critic 学习率1e-3软更新系数 tau0.005回放缓冲容量1e6batch size128gamma0.9每次 episode 最大步数50目标判定距离0.01训练时我在每 100 个 episode 打印一次当前成功率曲线同时记录 actor 输出幅度的变化。刚开始几百步actor 输出会有一点混乱这是正常的。如果看到 actor 输出慢慢变得有方向性比如在目标在右下方时动作向量倾向右下说明 HER 正在把重标信号转化为走对方向的偏好。我个人习惯以 1500 个 episode 作为一次完整实验周期用时在单张 GTX 3060 上约 20 分钟非常轻量。5. 常见问题与踩坑实录5.1 目标替换后损失不下降梯度去哪了我有一次在跑 HER 时发现 critic loss 从一开始就没怎么动演员网络输出也像死水一样。排查了很久最终发现是重标样本里的next_state和new_goal用的是同一个状态数组的引用但因为环境是逐步 reset 的旧数组在下一轮被覆盖导致 buffer 里存的数据全变成了同一个值。这种 bug 在 Python 里特别隐蔽因为states[future_state_idx]返回的是 ndarray 的一行如果你在后续代码里直接修改了state原始 buffer 里的内容也会变。解决办法很简单在存储之前调用np.copy()给每个状态和目标都做一份显式拷贝。现在我把这条原则写成了自己的代码规范凡是存进经验回放池的数组一律 copy。5.2 训练初期就学会躺平怎么办这里说的躺平是智能体发现让 episode 尽快结束能获得相对更好的结果。比如我的环境里设定 50 步超时自动结束有人还加了每步 -0.01 的惩罚于是智能体学会了一直输出超大的速度直接冲进边界外回合快速结束总惩罚反而更小。HER 的一个好处是重标样本会在加速到达某个状态这个方向上提供正信号但如果原始奖励里包含过大的惩罚项这些正信号会被抵消导致策略收敛到一个什么都不做的局部最优。我的建议是HER 配合纯稀疏奖励使用不要画蛇添足加中间惩罚。如果你确实需要限制动作幅度最好通过 clip 动作输出本身而不是靠奖励惩罚。5.3 非目标状态学多了反而拖慢收敛这其实是 HER 的另一个特性重标样本让策略学会到达各种各样的非目标状态如果这些状态离我们真正关心的目标区域太远智能体可能会花大量精力去精进到达某个犄角旮旯的技能冲淡了原目标方向的训练。怎么判断你遇到了这个问题看训练曲线如果前 500 回合成功率涨得很快之后开始停滞甚至下降大概率是因为重标样本中的目标分布太散。解决办法优先用 future 策略而非 random因为 future 至少保证了目标是从同一段轨迹中采样的和当前策略的行为分布有一定关联其次可以适当降低 k 值减少重标样本在回放池中的占比。5.4 踩坑速查表现象可能原因我的解决办法训练完全不变buffer 里存了数组引用数据被覆盖存入前np.copy()开始涨一点就崩溃actor 学习率过大训练不稳定学习率降到 3e-4或换用更稳定的 TD3成功率上不去但 loss 正常目标阈值太严格重标样本也大多 0 奖励把目标判定距离调大到 0.05 试试后期过拟合原始目标原目标样本占比太小增加原始轨迹存入比例或降低 k训练特别慢回放缓冲太小重标样本被频繁挤出缓冲容量加到 1e6这些坑我每条都真实踩过尤其数组引用那条我一度以为是算法写错了差点把整个网络推翻重来。所以如果你跑 HER 时遇到诡异问题先检查 buffer再检查奖励最后才怀疑网络结构。6. 一点个人体会和后续方向我在一次次复现 HER 的过程中越来越觉得hindsight这个词值得细品。机器学习和人生不少道理是相通的我们当然希望每次都能直奔目标但现实中大量行动注定会偏离预期。HER 的高明之处就是不让这些偏离白费而是立刻把偏过去的点当成新的参照重新校准自己的动作。这跟我们做项目复盘、做季度总结时的思路几乎一模一样——先承认失败再从失败里挖出可用的技能点。如果你打算把 HER 用到更复杂的任务里我的建议是从我这个 2D 点目标环境开始验证逻辑确认能跑通之后再迁移到机器人仿真平台。后续可以做两个方向的扩展一是把 HER 和 SAC 结合替代我这里的 DDPG通常会得到更平滑的训练曲线二是在策略里加入目标自动生成的模块让智能体自己决定什么时候该把某个状态当成临时目标而不是等一个回合结束后才整段重标。HER 的思想还能和 meta-learning、课程学习结合算是它最有挖掘空间的地方之一。说实话我直到现在看到 HER 重标样本在训练曲线上激起的那些小峰值还是觉得这个思路既简单又狡猾。希望这篇文章能帮你把它变成自己手里的一件趁手工具也祝你在自己的项目里能把那些看似无用的失败轨迹都变成后见之明的养料。
返回列表