ARTICLE DETAIL

资讯详情

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

事后经验回放HER:破解强化学习稀疏奖励困境的实战指南

事后经验回放HER:破解强化学习稀疏奖励困境的实战指南 hindsight这个词英文原意是“后见之明”翻译成大白话就是事后看一切都很明显。但在强化学习这个圈子里它代表的是一个特别能“变废为宝”的算法思想——Hindsight Experience Replay简称HER中文一般叫“事后经验回放”。如果你正在做机器人控制、游戏AI、自动驾驶决策或者任何奖励特别难设计、环境里“成功”特别稀疏的任务那迟早要跟它打照面。我最早接触HER是因为训练一个机械臂抓取任务奖励函数怎么设计都学不动智能体在一个空旷的仿真环境里原地转圈成功率始终是零。后来把HER接进训练流程情况立刻不一样——不是那种“调参缓解”的不一样而是从完全学不会变成肉眼可见地逼近目标。这篇文章我会把这个词的来龙去脉、算法原理、最小可复现代码、以及我在实际项目中踩过的坑全部捋一遍无论是RL新手还是已经跑过几个环境的老人都能从里面拿走点东西。1. hindsight 的真实身份不只是“事后诸葛亮”1.1 一句话理解失败轨迹也是宝贵数据很多人第一次看到“hindsight”这个词会下意识觉得它是一个工程项目名或者产品代号。确实搜索这个词能看到一些撞名的东西比如某个老牌的日志分析工具也叫Hindsight但本文要聊的是强化学习里的HER算法这也是这个词在近几年AI社区里被反复讨论的主要原因。HER的核心思想用一句话说就是不要让智能体只从成功中学习也要让它从失败中学习而学习的方法是把失败轨迹“重新解释”成成功轨迹。拿人类来类比特别直观——你小时候学骑自行车摔了十次才学会。第十一次成功当然教会了你很多但前十次摔倒就完全没用吗事后回看你会发现自己每一次都比上一次多蹬了半圈、多平衡了一秒。如果有一种训练方法能把“我还没骑到终点”这段经历改写成“我已经骑到了当前位置”的阶段性成功那么每一次失败都在积累经验。HER干的就是这件事。在强化学习的标准范式里智能体通过与环境交互获得奖励信号奖励越稀疏学习越困难。比如机械臂抓取抓到了给1没抓到给0那绝大多数回合都是0算法根本无从下嘴。HER的思路就是既然你没抓到杯子但你至少把手臂伸到了杯子旁边那我们可以重新定义一个目标“把手臂移动到杯子旁边”。这个新目标在原来的轨迹里是“已经达成”的于是这条轨迹从失败变成功成功经验就凭空多出来了。1.2 为什么叫“事后经验”重新解释的艺术“Hindsight”这个词用得很妙。日常生活中“事后诸葛亮”有点贬义但在强化学习里这种“事后重新解释”恰恰是训练加速的关键。算法在收集完一整条轨迹之后站在终点往回看发现“哦原来我这一路走过来其实也达成了不少子目标”然后把这些子目标当作训练目标重新计算奖励生成一批新的经验。这里有个重要的点新经验并没有改变物理过程本身——动作序列还是原来那些动作状态转移还是原来那些状态——改变的只是“这段经历是为了达成什么目标”的解释。原本这条轨迹是“朝向目标A的失败尝试”现在被解释为“朝向目标B的成功示范”。目标B虽然没有被预先指定但它的状态确实在轨迹中被真实达到过。这种重新解释完全合理甚至比人类的事后反思还要严谨因为它是用真实达到过的状态做目标不是凭空幻想。理解了这一点再看HER的应用范围就很清晰它特别适合目标可参数化的任务也就是你能把一个任务的目标表示成一个向量或一组数值比如目标位置坐标、目标物体的位姿、目标温度值等等。只要满足这个条件无论你是做机器人操作、迷宫导航、游戏关卡通关还是某种工业控制HER都能作为训练加速器来用。2. 核心原理HER怎么把稀疏奖励盘活2.1 稀疏奖励问题强化学习里最扎手的坎之一要理解HER的价值先得搞清楚它到底在解决什么问题。假设一个简单的寻路任务智能体从一个起点出发目标是到达某个终点。奖励设计成到达终点得1分其他所有情况得0分。这看起来合理但训练起来会发现一个致命问题随机探索几乎不可能碰到终点所以智能体从头到尾收到的奖励都是0。0奖励意味着什么意味着Q值更新完全没有梯度方向。在DQN这样的算法里目标Q值是用奖励加上下一状态的估计值算出来的如果所有奖励都是0目标Q值退化成对下一状态的估计而下一状态又回到了0于是Q值到处都差不多策略自然也不会优化。你可以把这种情况理解为学生在毫无反馈的考试里蒙答案蒙对一次给一分但事实上他一辈子也蒙不对一次那这次考试对他来说就是纯噪音。通常解决稀疏奖励有三种思路第一种是设计稠密奖励比如用距离作为负奖励但这类奖励函数很容易被“钻空子”智能体学会靠近目标而不是真正完成任务第二种是课程学习从简单情况逐步过渡到困难情况但课程设计本身就需要大量的手工劳动第三种就是HER这一类方法不改变奖励定义而是通过重放机制制造出更多的“成功经验”让稀疏奖励环境也能有可学习信号。HER没有改变任务的难度但它改变了训练数据的结构让数据里“有正例”的比例大幅提升。2.2 目标重标记把“没做到”改成“做到了”HER的数学表述并不复杂。在标准的多目标强化学习设定中每个时间步的经验可以记为(s, a, r, s, g)这里的g是当前回合指定的目标r是根据目标g算出的奖励。HER的关键操作是对一条完整轨迹中的每个时间步从某种规则中挑选一个新目标g然后重新计算奖励r r(s, g)生成一条新经验(s, a, r, s, g)。新目标g从哪里来一个最朴素的选择是取这条轨迹的最终状态s_T。如果这条轨迹没有达成原始目标那s_T就是一个“失败状态”但如果把目标改成s_T那么轨迹的最后一步恰好达成了新目标奖励r就会变成成功奖励。更一般地我们还可以不从终点取而是从轨迹的未来某个时间步取状态当目标甚至从整条轨迹里随机取一个状态当目标。需要注意的是每次采样得到的新经验和原始经验是并存的不是互相替换。也就是说原始经验(s, a, r, s, g)保留在经验池里新经验作为额外的训练样本也丢进去把原本只有0奖励的轨迹池变成一个正负样本混合的池子。这样训练数据的信息量就上来了。我自己在工程里的习惯是原始经验和新生成经验的比例大约控制在1:1到1:2之间太少效果不明显太多会冲淡原始目标的学习。2.3 四种目标重标记策略怎么选HER论文里给出了四种目标重标记策略我用一个表格把它们的逻辑、适用场景和我的经验总结一下策略新目标来源适用场景实践经验final用轨迹的最终状态当成目标任务的目标在轨迹终点附近失败也离成功不远最简单先跑通这个再考虑别的future当前时间步之后随机选k个状态大部分连续控制任务默认选择k取4左右效果不错episode从整条轨迹中随机选一个状态轨迹中包含多种子结构/子技能适合长轨迹任务采样效率高但偏置大random从经验池中随机采样一个状态你想增加全局目标的多样性我很少用因为与当前轨迹相关性弱我个人的建议第一次上手HER直接用final策略跑通全部流程理解机制之后再切到future并设k4。别一上来就堆复杂策略否则你会发现自己在同时调试好几个变量出了问题都定位不了。2.4 为什么HER只能配off-policy算法这里要强调一个容易踩坑的点HER不是独立算法它是一个经验增强模块必须配合off-policy强化学习算法使用比如DQN、DDPG、TD3、SAC。原因在于HER生成的新经验里动作a是在原始目标g下产生的而不是在新目标g下产生的。也就是说这条经验的“行为策略”和“训练目标”不一致它是一条带有“异策略”性质的数据。Q-learning类的方法天然允许这种异策略数据只要经验池里数据的覆盖性足够Q函数就可以用任意行为策略产生的数据来更新。但策略梯度类方法比如PPO、A3C严格要求数据来自当前策略因为它们是按照当前策略的轨迹来估计梯度方向的。HER生成的经验在目标空间里已经被改写了直接用来算策略梯度会引入严重偏差训练几乎必定漂移。所以如果你看到有人在PPO里硬接HER然后说效果很差那不是HER的问题是算法兼容性没搞对。3. 手把手实现一个带HER的最小训练流程3.1 先从网格世界理解机制为了把HER讲透我用一个最简单的网格环境来演示。假设有一个5x5的网格智能体从左上角出发每次可以向上、下、左、右移动一格目标点是某个格子到达目标返回奖励1其他动作返回0。这是一个标准的稀疏奖励任务纯随机探索几乎学不会。在这个环境里一个状态就是当前格子坐标(x, y)目标也是一个坐标(x_g, y_g)。为了能用HER我们需要把目标和状态都喂给学习器。在表格型Q-learning里这意味着把状态和目标拼起来当作索引Q表的大小是25个状态乘25个目标再乘4个动作也就是2500个项完全可控。如果用神经网络就是把状态向量和目标向量拼接成一个输入这个思路贯穿始终。我现在给你一段可以直接跑的伪代码级实现核心就是HER重标记函数import random def hindsight_relabel(episode, strategyfuture, k4): episode: list of (s, a, s_next), 按时间顺序排列 返回值: 原始经验 重标记后的新经验 original_experiences [] her_experiences [] goal episode[-1][2] # 原始目标当前设定为最终想达到的位置 for t, (s, a, s_next) in enumerate(episode): # 原始经验目标是原始目标奖励按原始目标计算 r 1.0 if s_next goal else 0.0 original_experiences.append((s, a, r, s_next, goal)) # 重标记新目标 if strategy final: new_goals [episode[-1][2]] elif strategy future: # 从当前时间步之后选k个状态作为候选目标 future_states [item[2] for item in episode[t 1:]] new_goals random.sample(future_states, min(k, len(future_states))) elif strategy episode: all_states [item[2] for item in episode] new_goals [random.choice(all_states)] else: raise ValueError(Unknown strategy) for new_goal in new_goals: r_prime 1.0 if s_next new_goal else 0.0 her_experiences.append((s, a, r_prime, s_next, new_goal)) return original_experiences, her_experiences这段代码的核心逻辑就是跑完一整条episode后把“这条轨迹本身达到过的状态”当作新目标重新构造一批奖励不为零的经验。注意这里我为了直观用的是离散状态比较真实项目里状态是连续向量时“s_next goal”通常要改成“距离小于阈值”或者直接用奖励函数计算。代码里我没做任何复杂的向量化可读性优先你完全可以照着改成numpy数组版本。3.2 把HER接进Q-learning训练循环有了重标记函数之后整个训练循环就清晰了replay_buffer [] for episode_idx in range(2000): episode [] s env.reset() done False while not done: a select_action(s, goal, epsilon0.1) s_next, done env.step(a) episode.append((s, a, s_next)) s s_next orig_exp, her_exp hindsight_relabel(episode, strategyfuture, k4) replay_buffer.extend(orig_exp) replay_buffer.extend(her_exp) if len(replay_buffer) 10000: replay_buffer replay_buffer[-5000:] # 简单截断 # 每次episode后从buffer里采样一批更新Q表 batch random.sample(replay_buffer, 128) for s, a, r, s_next, g in batch: td_target r 0.9 * max(Q[(s_next, g, a_next)] for a_next in actions) td_error td_target - Q[(s, g, a)] Q[(s, g, a)] 0.1 * td_error这个例子里我故意用Q表而不是神经网络因为表格型展示得最清楚目标g被当作状态的一部分参与索引和更新。训练中你会发现一个有趣的现象HER不是让智能体“更聪明”地踩到目标而是让每一次失败轨迹都变成可视化的学习信号Q值不再到处为0策略慢慢有了方向感。跑2000个episode之后成功率通常能达到八成以上而不带HER的对照组成功率稳定在个位数。3.3 从网格世界迁移到连续控制网格世界里的逻辑迁移到连续控制任务只需要改两个地方一是把状态和目标向量拼接后输入神经网络二是把Q-learning换成DDPG或SAC这类适合连续动作的算法。以机械臂抓取为例状态向量可能是关节角度、速度、末端位置等目标向量是物体位置或者目标位置。在PyTorch里一个典型的Critic网络前向计算就是目标拼接后进网络import torch.nn as nn class Critic(nn.Module): def __init__(self, state_dim, goal_dim, action_dim): super().__init__() self.fc nn.Sequential( nn.Linear(state_dim goal_dim action_dim, 256), nn.ReLU(), nn.Linear(256, 256), nn.ReLU(), nn.Linear(256, 1) ) def forward(self, state, goal, action): x torch.cat([state, goal, action], dim-1) return self.fc(x)训练循环里收集到一整条轨迹之后还是调用同样的hindsight_relabel函数生成新经验然后丢进统一的经验池让SAC或DDPG去更新。奖励函数这里用的是最朴素的稀疏形式末端与目标距离小于阈值给1否则给0。我试过用负距离作为稠密奖励和HER搭配效果反而不如纯稀疏奖励稳定因为强的reward shaping会干扰重标记经验的分布这个后面调参章节还会细讲。3.4 监控指标不要只看loss用HER训练时最容易被误导的指标就是TD误差或critic loss。我见过很多次loss曲线漂亮地下降可成功率纹丝不动。这里的原因在于HER生成了大量目标是“最后一步状态”的正例这些正例学得很快但原始目标的成功率才是任务真正关心的。所以我建议监控三个指标原始目标成功率、HER目标成功率、平均回合长度。原始目标成功率就是每个episode结束时是否达成初始设定的目标HER目标成功率稍微绕一点是你在重标记样本中采样的目标达成比例它能反映探索的有效性平均回合长度反映的是策略有没有在学习“怎么活得久/走得远”。这三个指标组合起来比单看loss健康得多。如果原始成功率在涨HER目标成功率也在涨说明策略正在形成“主动接近目标”的行为模式如果HER成功率很高但原始成功率是零多半是策略开始钻HER的空子只学重标记目标把原始目标丢了。4. 实战调参成功率上不去的十个检查点4.1 经验池里的正负样本比例HER最直接的调参旋钮是重标记比例也就是每条轨迹额外生成多少条新经验。论文里常用的future策略k4意思是从轨迹的未来状态中选4个当作新目标每条轨迹的经验量变成原来的5倍左右。但这不代表k越大越好。我有一次在机械臂任务里把k从4调到20结果训练反而变慢原因是重标记经验把原始经验挤出了经验池智能体只顾着学习“事后目标”原始目标的信息密度被稀释了。要理解这个现象得回到经验池的容量问题。经验池是有限容量的新经验不断挤掉旧经验如果重标记经验占比过高池子里大部分都是“事后”样本原始目标对应的样本占比就会低。所以实际做法是控制重标记经验的占比上限比如让它占池子总量的60%以内或者干脆在采样时按来源做分层采样原始经验和重标记经验各采一半。我在项目里常用一个更简单省事的做法重标记之后原始经验全部保留新经验只保留一份比例天然维持在1:1附近效果稳定。4.2 奖励函数稀疏好还是稠密好这个问题的答案取决于你要不要用HER。如果你铁了心用HER我推荐优先尝试纯稀疏奖励。原因在于HER本身就是用来对抗稀疏性的它已经通过重标记制造了大量正样本你再叠加一个距离稠密奖励反而会引入两个麻烦一是奖励尺度需要重新平衡距离量纲和成功奖励量纲不一致导致Q值预测不稳定二是稠密奖励提供了额外的梯度信号智能体容易只学着“靠近目标”而不去真正“达成目标”重标记经验的价值被削弱。我见过最典型的反面案例是有人给机器人倒水任务设计了一个多阶段奖励——接近杯子给0.1分、抓住杯子给0.5分、倒水给1分然后用HER训练结果训练了很长时间都没有出现一套连贯行为。后来改成稀疏奖励只在倒水成功时给1分其他情况给0分再用HER反而两个epoch就出现了抓取动作的雏形。当然如果你的任务里成功事件本身就特别难以触发比如成功率在百万分之一以下那么纯稀疏HER也可能不够这时可以先用行为克隆做预训练再用HER微调。4.3 目标抽样的相关性重标记目标不能太离谱HER的教科书示例总是强调“把没做到的目标改成做到了的目标”但实操中目标抽样策略对训练稳定性影响巨大。future策略比final策略效果好的原因我个人理解是future采样的目标与当前时间步在时间上相关新目标通常就在轨迹前方不远处状态空间上距离合理重标记后的奖励信号不会太过稀疏。final策略的问题在于一条轨迹的前期状态离终点状态可能很远如果你把每个时间步都配上最终状态当目标大量的早期transition会被硬解释成“目标还很远”的经验与真实目标分布差异大训练噪声高。还有一个容易忽略的细节抽样目标的时间范围。future策略的“未来”窗口到底取多长很多人直接用t1到episode结束的所有状态但这样候选目标太多质量参差不齐。我自己的做法是限制窗口长度比如只取未来10到20步内的状态作为候选然后随机采样。窗口太短会让目标太密集轨迹信息冗余窗口太长会让目标与当前状态的相关性减弱。这个参数并不在论文的默认建议里属于工程经验的范畴你可以用网格搜索跑两三个值看看趋势。4.4 探索噪声与初始状态分布稀疏奖励环境里探索是头号瓶颈。HER虽然能把“接近目标”的轨迹变成有效经验但前提是轨迹里得有“接近目标”的片段。如果探索动作是完全均匀的随机噪声那么无论怎么重标记生成的新目标也只是一堆随机位置学不出任何结构。这就是为什么很多成功案例里的智能体都加了强探索机制动作空间加奥恩斯坦-乌伦贝克噪声、参数空间噪声、或者使用熵系数较高的SAC。初始状态分布也很重要。我踩过的坑是环境初始化时物体位置完全随机导致前200个episode里智能体连物体的影子都摸不到HER生成的经验里新目标分布和原始目标分布完全脱节。后来我把初始状态改成“机械臂末端在物体附近”加了一个domain randomization的范围限制训练速度立刻起飞。这不是作弊这是课程学习的思路——让早期探索的成功概率不要低到零给HER一个可以重标记的基础素材。4.5 网络容量与输入表征HER要求把目标送入网络所以目标的表征方式会直接影响学习质量。最简单的做法是把目标向量和状态向量拼接但如果目标本身是高维的比如目标是一张图片拼接就不够用了更合理的是用两个编码器分别处理状态和目标然后再融合。我之前做一个视觉抓取项目目标是用一个目标物体的图像块直接拼接特征效果一般换成两个卷积编码器之后收敛速度翻了不止一倍。网络容量方面别指望HER能扛住所有的表征瓶颈。如果状态维度很高动作空间很大但网络只有两层64个神经元那重标记再多经验也塞不进去。我的一般做法是三层256维起步有图像输入就换CNN训练时候打开TensorBoard盯一下Q值的分布如果Q值长期不分化很可能就是网络表达力不够。5. 常见问题速查表与避坑清单5.1 “症状-原因-解法”对照表症状常见原因解决办法原始成功率始终为0探索太弱轨迹中没有接近目标的状态加大探索噪声调整初始状态分布loss下降但成功率不动重标记经验过多挤占原始经验降低重标记比例分层采样训练前期崩溃奖励尺度过大或过小把奖励控制在0到1区间策略只完成重标记目标忽略原始目标重标记目标与原始目标分布差距过大限制future窗口降低k值目标空间太大HER无能为力目标表征过于稀疏离散考虑目标编码器或分阶段课程和PPO等on-policy算法搭配效果差算法与HER不兼容换DQN、DDPG、TD3或SAC5.2 三个我印象最深的坑第一个坑是“奖励函数里隐藏着时间步惩罚”。某次我训练一个需要尽快完成的任务在奖励里加了一个每一步-0.01的微小惩罚希望智能体动作更高效。结果和HER一搭配重标记出来的新经验里原轨迹的累计奖励全部变了导致那些“本来能成功”的重标记目标也被微小惩罚压成了负奖励。这个坑特别隐蔽因为单看任何一条经验都合理但整个经验池的统计特性被扭曲了。现在我的经验是HER情况下的奖励函数尽量精简别夹带私货把“希望快速完成”的诉求放到折扣因子或终止条件里去表达。第二个坑是“重标记目标时忘了过滤当前状态”。在future策略里如果候选目标包含了当前时间步的状态本身那么重标记后会有大量“原地不动即成功”的经验智能体学会原地罚站。这个问题在连续控制任务里特别隐蔽因为你很难肉眼看出一条轨迹里的s_t和s_{t1}很近但在稀疏奖励下它们的确会被当作一个成功事件。我的修复办法很简单重标记时排除当前时间步的状态严格保证新目标来自未来且不等于当前状态。第三个坑是“数据增强与HER叠加的复杂度”。有些任务里我们会做随机扰动、裁剪、颜色增强之类来提升泛化但这些增强会改变状态表征如果增强后的状态被当作重标记目标就可能产生现实中不存在的“伪造目标”。举个例子你给图像加了噪声导致目标图像和实际场景不匹配HER就会学到环境里根本不存在的一致性。这个坑业内讨论不多但我吃过亏现在做视觉任务时我会在重标记之前禁用随机增强等目标空间稳定后再开。6. 收尾说点实际的我在实际项目中用HER最大的体会是它不会让一个完全学不会的任务“变成”一个能学会的任务但它能让一个“理论上能学会”的任务在可接受的时间内收敛。如果你觉得自己的任务稀疏奖励严重我建议先别急着设计花哨的reward shaping也别一上来就堆课程学习先老老实实把HER基线跑起来让它在便宜的快节奏环境里给你一个“学习信号是否充足”的答案。另外分享一个小技巧HER对经验池的更新频率很敏感。我习惯在每个episode结束后立刻做重标记并采样更新而不是攒一堆轨迹再统一处理。这个细节在论文里没有强调但在工程里非常影响收敛速度。原因很简单越早让Q值感知到新目标分布策略就能越早调整探索方向。hindsight这个词教会我的其实不止是算法——它提醒我项目里那些“失败”的实验日志、那些没跑通的路、那些指标没涨的周末可能只是目标定义得不对。换一个目标重新审视它们它们就成了最有价值的训练数据。这个思路放到工程管理、代码调试和个人成长里也完全成立。希望你跑HER的时候也能体会到这种“重新解释”带来的快乐。
返回列表