拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

强化学习稀疏奖励救星:HER事后经验回放原理与实战避坑

强化学习稀疏奖励救星:HER事后经验回放原理与实战避坑

hindsight这个英文词,大众语境里常被译成“事后聪明”,带着一点嘲讽的意味。可在强化学习这个圈子里,它也是一个绕不开的算法缩写:Hindsight Experience Replay,中文一般叫“事后经验回放”,很多论文里直接写HER。我第一次看到这个名字时,就觉得起名的人非常懂行——这个算法做的事情,恰恰是把“我当初要是那样做就好了”这句懊恼话,翻译成一条机器可以照做的训练规则。

这篇文章想聊的,就是HER到底在解决什么问题、它的目标重标记机制是怎么工作的、为什么它只能配合off-policy算法,以及从比特翻转这类小实验到机械臂仿真任务落地时,最容易踩哪些坑。这个东西不是银弹,但我个人觉得,它是在稀疏奖励场景里性价比最高的起手式之一,值得每个做RL实操的人认真过一遍。

1. 从“事后聪明”到算法:hindsight到底解决什么问题

1.1 稀疏奖励环境下为什么学不动

做强化学习的同学应该对这样的画面不陌生:智能体在环境里跑了几十万步,tensorboard上的reward曲线一动不动,偶尔出一个峰值还会以为是自己看错了。绝大多数新人第一反应是“网络结构不对”“学习率太大”,我却会先问一个问题:你的奖励,稀疏吗?

所谓稀疏奖励,指的是环境几乎不给你反馈,只有当你完成某个终极目标时,才给一个明确的信号。最典型的就是机器人推箱子:箱子里所有位置都是“没推到目标点”,只有箱子和目标点完全重合,才给出reward=1或者0。没有中间奖励,智能体一开始完全是瞎撞,撞中的概率极低,就好比你让一个从没打过篮球的人投篮,投了100次全都偏出,教练却在旁边一句话都不说,只告诉你“投进就算赢”——他大概率练到天黑也投不进第一个球。

从策略梯度的角度看,稀疏奖励意味着优势函数几乎处处为零,网络拿不到任何梯度信号,参数更新等于原地踏步。作为对比,密集奖励相当于每一步都在告诉智能体“你离目标近了还是远了,好一点还是坏一点”,这当然好训练,但问题在于,人工设计这类奖励很费劲,而且容易设计出漏洞:智能体经常能找到一堆你没想到的花样去刷奖励,却根本没学会真正的任务。于是学界就开始想,能不能不要人肉奖励塑形,让算法自己从失败里挖出信号?

HER就是顺着这个思路诞生的。它的论文标题叫Hindsight Experience Replay,作者包括Andrychowicz等一批人,后来发表在NeurIPS 2018上,很长一段时间里都是多目标强化学习和机器人操控任务的默认基线之一。

1.2 HER的核心直觉:失败也是成功之母

先讲一个生活化的类比。假设你周末想开车去一家没去过的餐厅,结果绕了好几圈,最后停到餐厅旁边的商场停车场。你当然没抵达餐厅,但你确实离餐厅非常近,只差穿过一条街。如果你是一个“事后聪明”的司机,你会想:要是刚才那个路口右转就到了。下次再来,你至少知道商场这片区域是有效的导航线索。

HER的核心直觉跟这件事完全一样。在稀疏奖励任务里,智能体跑完一整条轨迹,很可能没有达成原始目标,于是整条轨迹的每条transition都带着-1或者0的惩罚,看起来毫无价值。但HER说:不要让智能体只往原始目标上较劲,把这条轨迹里“它实际上达到过的某个状态”重新当成目标,然后重新算奖励。比如机器人推箱子没推到正中间,但把箱子推到了左上角,那就假装目标是左上角,此时轨迹末尾的“成功”就有了一个正样本,可以用来学习。

为什么可行?因为目标条件策略本身就具备一个特性:学到的策略是“给定某个目标,如何达到它”。虽然这次没达成原始目标,但轨迹中蕴含的信息——“从这些状态出发,通过这些动作,最后走到了什么状态”——在换成另一个目标后依然成立。重标记相当于把一条负样本轨迹,拆成了若干条关于“更近目标”的正样本来训练。

这个概念听起来简单,但它的思想其实带有很强的一点儿“反直觉”:传统监督学习里,数据标签错了就是错了,洗掉就好。HER却在主动篡改标签,而且越改越细。正因为目标被改成了智能体容易碰到的状态,网络才能获得足够多的高质量梯度信号。

1.3 为什么必须“事后”才重配目标

这里有个非常关键的细节:重标记的目标不是随便从数据集里抽的,而是从同一条轨迹中的“未来时间步”里采的。也就是说,t时刻的transition,重标成这条轨迹里t之后某个时刻实际达到的状态。这个“未来”是事后才知道的。

如果用一个“过去”的状态来当目标,那这个目标已经被当前策略部分达到过了,反而会让网络误以为“什么都不做也能成功”,或者给出一个很笨的策略。而用未来状态当目标,恰恰是在模拟“如果当初这一步走对了,下一步就能到达某个位置”的逻辑,和人类复盘别无二致。

所以HER的本质,是在稀疏奖励条件下,通过事后视角把失败轨迹重新注释成可用经验。这也是它名字里“hindsight”的由来——它确实做到了事后诸葛亮,但在这个场景下,“事后诸葛亮”是一种非常高效的训练资源。

2. 算法细节拆解:目标重标记机制的完整原理

2.1 目标条件策略的输入输出结构

要谈重标记,绕不开目标条件策略。一个典型的目标条件马尔可夫决策过程,状态被拆成两块:observation(智能体的真实感知,比如机械臂关节角、末端位置)和desired_goal(期望达到的目标,比如目标位置)。环境还会额外返回achieved_goal(当前实际达到的目标,比如机械臂末端的实际位置)。

绝大多数实现里,状态表示是一个字典:obs包含了observation和desired_goal,achieved_goal单独拿出来。以OpenAI的Fetch系列环境为例,observation可能是机械臂的关节信息和当前末端位置,desired_goal是目标物体的位置,achieved_goal则是物体实际所在位置。奖励函数通常是稀疏的:

  • 如果条件满足,比如物体位置和目标位置的距离小于阈值,reward=0,否则reward=-1。或者反过来,成功给1,失败给0。总之,这个反馈极其不连续。

HER重标记时,只改动一条transition中的desired_goal字段,同时根据新目标重新计算奖励值。这样,原本“我离原始目标还很远”的失败经验,就变成了“我已经很接近某个新目标”的成功经验。这个过程可以重复多次,一条轨迹能长出好几条不同的训练样本,数据利用效率一下子高了不少。

这里要特意强调一个点:如果你的环境是只有标量reward、没有achieved_goal概念的普通环境,直接用HER是个伪命题。你得先定义清楚“什么状态可以拿来当目标”,也就是可达到状态空间是什么样的。也就是说,HER并不是简单在奖励函数上动手脚,而是要求你的任务天然存在一个“目标表示”,比如位置、方位、物品类型等。

2.2 四类未来采样策略

有了目标表示,还需要决定怎么从轨迹的未来时间步里挑状态来当新目标。用过sklearn的同学都清楚,策略的选择对效果影响不小。HER原文里明确说了四种采样方式,我给它们起了比较好记的名字:

采样策略做法特点
final直接取轨迹最后一个状态作为目标实现最简单,但只有一个重标样本,多样性不足
random从整条轨迹的未来时间步里随机抽一个随机性强,目标分布较广,但可能出现“目标还没当前状态近”的情况
future从t+1到轨迹末尾之间随机抽k步内或整段的状态最常用,兼顾相关性与多样性,论文实验里效果最稳
episode从本episode所有状态里随机抽等价于random的一个变体,区分度不高

实际操作里,我做过的实验几乎都是future策略,而且一般设置k=4。意思是,当前t时刻的transition,只从t+1到t+k之间随机抽取未来状态当作候选目标;如果剩余步数不足,则截断到轨迹末尾。这样选择的原因是:时间离得太远的目标,与当前状态的相关性会大幅下降,网络学到的东西容易变成“从毫不相干的地方出发”,而k步以内的目标,更像是近期可达的、靠谱的中间里程碑,学习曲线的方差会小很多。

2.3 为什么必须搭配off-policy算法

这一点是HER最容易被人忽略、也最容易踩坑的地方。它只能用在off-policy算法上,比如DQN、DDPG、SAC、TD3;PPO这类on-policy算法,碰上HER就非常别扭,甚至可以说基本没法直接用。

原因一句话:HER改写了经验池中样本的目标,等于改写了策略的输入,这会让当前行为策略与数据分布产生偏移。on-policy算法要求每次更新必须用当前策略采样的数据,重标记相当于把一批“过去策略”的数据改头换面混合进来,策略评估就失真了。而off-policy算法的先天优势就是可以从经验池中随机采样反复学习,容忍数据分布和当前策略的偏差,所以重标记对它来说是顺手的事。

所以你在选择HER时,第一时间不是问“我用哪个算法”,而是先确认“我要用的算法支不支持从经验池里采样”。如果确实想用PPO,我的建议是绕道:先搭一个SAC+HER的基线,把任务跑通再考虑工程优化,而不是强行在on-policy上套HER。

2.4 与奖励塑形、课程学习的横向对比

很多人在稀疏奖励场景下,脑子里飞过一堆方案:奖励塑形、课程学习、HER,到底选哪个?我的理解是,这几件事互不冲突,但分工不同。

奖励塑形本质是“人工设计辅助信号”,比如机器人推箱子,你可以按物体与目标的距离给一个负惩罚。好处是训练快,坏处是设计费劲、容易被钻空子。课程学习本质是“拆难度”,先让物体和目标距离近,再慢慢拉远,让智能体循序渐进。好处是样本效率高,坏处是课程进度很难自动设计,经常需要人工调档。

HER则走了一条不同的路:它不改变环境,不改变任务难度,而是改变经验池里样本的“标签”,让失败轨迹在事后看起来更有教学价值。它的优势是几乎不需要额外的专家知识,只要任务有goal表示就行。弱势也很明显:它不会帮你找出“更好的探索方向”,如果智能体从来就没接近过任何有意义的状态,那么重标记也只能在无意义的轨迹里打转。

所以我的实践经验是:HER适合作为“算法层面的基线助推器”,如果效果还不够,再配合适当的状态表示、课程初始化或探索噪声一起用。

3. 从零手写一个 HER 实验:比特翻转

3.1 环境定义与核心代码

说了这么多原理,不如直接动手搭一个最简单的实验来验证HER的威力。我推荐用“比特翻转”问题,因为它环境极简单、可视化清晰、还能直观看到目标重标记发生的过程。

任务设定如下:有一个长度为n的比特向量,比如n=4,初始状态是[0,0,0,0],目标向量是[1,1,1,1]。智能体的动作是选择一个下标,把该下标对应的比特翻转。每一步只有全部比特都等于目标向量时,才算成功。奖励我用稀疏形式:成功reward=0,不成功reward=-1。这样一个翻转序列,在随机策略下成功的概率极低,几乎就是教科书级的稀疏奖励陷阱。

环境的状态表示,我直接用一个数组加一个目标数组。代码写起来非常简单,拿到stable-baselines3后可以自己包一个gym环境,也可以只用它的HerReplayBuffer配合自定义逻辑。这里我贴一个最精简的核心流程,让大家看清楚HER在算法层做了什么:

import numpy as np class BitFlipEnv: def __init__(self, n_bits=4): self.n_bits = n_bits self.observation_space = ... # 具体类型看框架要求 # 状态字典:observation, achieved_goal, desired_goal def reset(self): self.state = np.zeros(self.n_bits, dtype=np.int32) self.goal = np.ones(self.n_bits, dtype=np.int32) return self._get_obs() def step(self, action): self.state[action] = 1 - self.state[action] done = bool(np.all(self.state == self.goal)) reward = 0.0 if done else -1.0 return self._get_obs(), reward, done, {} def _get_obs(self): return { "observation": np.concatenate([self.state, self.goal]), "achieved_goal": self.state.copy(), "desired_goal": self.goal.copy(), }

这只是个骨架。真正交给算法的时候,还需要把desired_goal从observation里拆出来,否则网络会把目标和观测混成一团。我记得自己第一次写这个环境,就是忘了拆goal,结果网络直接记住了“目标在哪”而不是“怎么达到目标”,当然注定学不会。

3.2 目标重标记的代码实现

下一步就是HER最核心的目标重标记。假定已经采样到一条完整episode,我们可以这样生成额外经验:

def her_relabel(episode, buffer, n_relabel=4, k=4): # episode里每个元素是 (obs_t, action, reward, obs_next, achieved_goal_next, desired_goal) for t, (obs_t, action, _, obs_next, achieved_next, _) in enumerate(episode[:-1]): horizon = min(len(episode), t + k + 1) candidates = list(range(t + 1, horizon)) if not candidates: continue sampled_idxs = np.random.choice(candidates, size=n_relabel, replace=True) for idx in sampled_idxs: goal_prime = episode[idx][4] # 未来某时刻的achieved_goal,作为新目标 reward_prime = 0.0 if np.all(achieved_next == goal_prime) else -1.0 buffer.add(obs_t, action, reward_prime, obs_next, goal_prime)

注意一个细节:重标记后的奖励,不是看未来目标状态和当前状态是否一致,而是看下一步观测对应的achieved_goal是否等于新的目标。也就是说,我们要判断“执行完这个动作之后,是不是已经达到了新目标”。这一点老是有人写错,一错整个训练信号就乱了,损失函数看起来能下降,但策略完全是废的。

3.3 训练配置与日志分析

搭建好后,可以在stable-baselines3里直接这么用:

from stable_baselines3 import SAC from stable_baselines3.her import HerReplayBuffer from stable_baselines3.common.envs import BitFlipEnv model = SAC( "MultiInputPolicy", env, replay_buffer_class=HerReplayBuffer, replay_buffer_kwargs=dict( n_sampled_goal=4, goal_selection_strategy="future", copy_info_key=True, ), learning_starts=1000, buffer_size=200000, batch_size=256, gamma=0.98, tau=0.05, verbose=1, ) model.learn(total_timesteps=200000)

日志观察上,我强烈建议你看两个指标:训练reward和成功率。如果训练reward依旧是-1平的一条直线,但评估成功率一直在涨,说明重标记已经发挥作用,只是环境本身绝大多数step都是负奖励而已,这是正常的,别慌。如果评估成功率完全不动,就要回头检查目标表示、未来采样k值、重标记数量这些参数了。

比特翻转这个任务有一个非常直观的现象:不加HER时,几十万步reward纹丝不动;加上HER之后,哪怕只是final策略,成功率也能快速脱离0。我经常跟朋友开玩笑说,这个任务就是HER的“Hello World”,十分钟跑通,你对算法的信心能立刻拉满。

4. 实操避坑:从小玩具到机器人仿真

4.1 我踩过的几个坑

比特翻转跑通之后,你可别急着把同一套HER直接搬到机械臂仿真上,否则大概率会撞上一堆新坑。我把自己踩过的坑总结出来,希望你能绕开。

第一个坑是achieved_goal与observation的信息重叠问题。在部分环境里,achieved_goal本身已经包含在observation中,此时如果还是把整个observation作为策略输入,会导致网络同时看到“目标”和“当前实际值”两份信息,但又不清楚两者之间的关系,收敛速度反而变慢。我的做法是,除非环境本身设计清楚,否则应该把observation和goal在输入侧就分开处理,或至少确保网络结构能区分:观测用来判断我当前在哪,目标用来指出我要去哪。

第二个坑是重标记比例失控。n_sampled_goal设得太高,经验池里到处都是“临时捏出来的成功样本”,策略会变得盲目乐观,明明大部分失败,却以为成功满天下。我自己的习惯是先设4,也就是一条transition额外生成4个重标记样本,跑一次看效果;要是发现学得慢,先调大到8试试,而不是一上来就几十个。

第三个坑是exp回报与HER奖励尺度不匹配。用SAC这类最大熵算法时,如果HER重标记后大多数transition的奖励都是0,整个Q值尺度可能会飘。遇到这种情况,可以考虑把奖励从0/-1换成1/0,或者适当降低gamma,看训练曲线再进一步调整。

4.2 调参速查表

为了让你后续上手少走弯路,我把我常用的HER参数粗调范围整理成了一个表,对应不同的现象可以直接查:

现象可能原因调整方向
成功率完全不动goal表示不对、achieved_goal没传对先检查info字段和状态字典,用最简任务验证
前期失败率极高,评估reward为-1稀疏奖励正常现象,不代表没在学看评估成功率,别只看套路外的平均reward
学会目标切换却不稳定重标记比例过高调低n_sampled_goal,增加真实transition比例
学会了一个目标,新目标泛化差重标记k值太小,目标多样性不够适当调大k,或使用episode采样策略
与环境接触太少,buffer为空learning_starts太高,探索太少减小learning_starts,或增大初始随机动作比例
Q值发散,loss爆炸奖励尺度与gamma不匹配把0/-1改成1/0,或者适当减小学习率

这个表不能当万能药,但拿去排查大部分HER项目,效率比瞎猜强很多。

4.3 扩展到真实机械臂任务

当你把HER从比特翻转搬到光学和机械臂仿真时,最需要关注的问题变成“目标表示怎么设计”。比如一个真实的分拣任务,你可以用目标物体的三维坐标作为desired_goal,用夹爪末端坐标作为achieved_goal。这个表示直观、低维、好算距离判断,训练起来不容易翻车。

但如果你想学的是“把物体推到A处”这种语义任务,光用坐标可能还不够,可能要引入物体的类别、颜色、位姿等组合信息。目标维度一旦增大,HER的优势会打折扣,因为随机未来状态作为目标会变得稀疏且不够有区分度。此时很多人会引入hindsight goal model这类后续方法,用模型来生成更有用的目标,或者结合数据增强来处理目标空间的稠密度问题。

另外,真实机器臂每次reset成本极高,经验池的多样性与效率就更重要。我的做法是先在仿真里把HER策略训到一定成功率,再用少量真实数据微调。这不是万能公式,但在很多抓取、推箱子、插销等任务上都算得上是一条稳妥路径。切忌一上来就拿着真实机械臂直接跑数百万步,那既危险又烧钱,没必要。

5. 这个思想能走多远:一点个人体会

回到标题本身,hindsight这个词,我在不同场景里有不同理解。在认知心理学里,它描述的是“事后看来,一切都理所当然”的偏差;但在强化学习里,它反被用成了一种训练方法论:允许智能体从失败中提炼成功信号。我个人觉得,这两种解读放在一起特别有趣——人类常被事后聪明困扰,机器却靠它突破稀疏奖励。

我实际做项目时还有一个习惯:任何新任务,我都会在投入大规模算力之前,先用HER跑一遍小规模对比,看看“仅靠目标重标记,能把baseline拉多远”。多数情况下,它至少能让你对“任务本身是否真的可学”有个判断。如果加上了HER还是完全学不动,那问题往往不出在奖励上,而要回到状态表示、环境动力学或者探索策略上深挖。

如果你手头正被稀疏奖励问题折磨,不妨试着用手头的任务,先定义好achieved_goal,再套一个HER,跑几小时看看曲线。我个人体会是,这个方法不一定保证成功,但它带来的调试视角非常值钱:你会开始习惯从“经验如何被重复利用”的角度审视整个RL训练过程,而不再只是盯着奖励函数本身打转。

返回列表