前段时间整理自己手头的深度强化学习笔记,复习到模仿学习(Imitation Learning)这一讲的时候,我特意停下来多看了几遍。原因很朴素:这两年我实际在做的一些控制类小项目里,折磨我最久的从来不是策略网络结构,而是奖励函数。某个机械臂摆放Demo,我调奖励函数调了一个多星期,始终搞不定那种“手臂到位但东西没放好”的得分漏洞,最后妥协改用模仿学习,三天就跑通了完整流程。今天这篇就把我关于模仿学习这一讲的复盘整理出来,把行为克隆、逆强化学习、对抗式模仿学习三条路线拆开讲,包括它们各自的原理、适用边界,以及我在实际训练中踩过的坑。
1. 模仿学习解决的真正痛点:手写奖励函数这条老路经常走不通
1.1 强化学习的隐藏假设,在真实任务里经常不成立
正统的强化学习问题设定是:环境提供一个奖励信号,智能体通过试错去最大化累积回报。这个设定成立的前提是你能写清楚“什么是对的”,但在绝大多数工程任务里,这一关就卡死了一大半人。读论文的时候,看到的都是精心设计的奖励函数,算法跑得风生水起;可轮到自己动手,奖励函数往往写得漏洞百出。你可能以为奖励函数只是整个实验里的一个小组件,实际上它决定了探索方向、梯度信号和最终行为风格,比网络结构还要敏感。很多项目做不动,问题根本不出在算法,而是出在“我们压根表达不清楚任务目标”。
最典型的是稀疏奖励。机械臂抓取一个杯子,如果奖励只在“成功抓稳并放回指定位置”时给+1,那么前几百次尝试大概率全是零奖赏,策略根本不知道哪个动作是有效的。另一个常见问题是欺骗性奖励。你为了给算法更密集的反馈,会随手把“接近杯子”也算奖励,结果算法很快学会“伸手靠近但不碰”,因为这样可以一直刷小分成而不用承担把杯子弄掉的风险。这种奖励规格化引发的钻空子行为,我在项目里见过太多次了。
我自己经历过的那个机械臂摆放Demo也是这个套路。一开始想着“离目标近一点加分、方向一致加分、速度合适加分”,规则越加越多,策略反而越学越怪,最后学会了一套看起来像模像样、实际上完全没完成任务的假动作。调了一个星期之后我把心一横,决定不写了,干脆让算法直接学习演示数据里的动作。这里其实触及模仿学习最核心的直觉:与其费尽心思发明一套评分规则,不如直接把“高手是怎么做的”展示给算法看。人类学开车、学打球、学做菜,大多数时候也不是靠一个精确的奖励函数,而是看别人做、跟着模仿、再微调动作。模仿学习想干的就是同一件事。
1.2 模仿学习的三条主路线:监督式、逆向式、对抗式
模仿学习并不是单一算法,而是一族方法。根据“专家信息如何被利用”,主流做法可以分成三类。
第一类是行为克隆,英文叫Behavior Cloning,简称BC。思路很直接:把专家演示当成一份带标签的数据集,状态是输入,动作是标签,用监督学习训练策略网络。它最轻量,但后续问题也最多。
第二类是逆强化学习,英文Inverse Reinforcement Learning,简称IRL。它不直接学策略,而是先根据专家轨迹反推出专家背后认同的奖励函数R,再用这个奖励函数去做一次正向强化学习。优点是可解释性强,缺点是计算开销大。
第三类是近年更流行的对抗式模仿学习,代表是GAIL(Generative Adversarial Imitation Learning)。它借用了生成对抗网络的思路,用一个判别器去区分“专家轨迹”和“策略自己产生的轨迹”,迫使策略在轨迹分布上逼近专家。它特别适合高维连续的状态动作空间。
我按关键特征做了一张表,方便后续展开时对照:
| 路线 | 代表方法 | 核心思想 | 主要成本 |
|---|---|---|---|
| 行为克隆 BC | DAgger也归这一族 | 用监督学习拟合状态到动作的映射 | 数据质量和数量,分布偏移 |
| 逆强化学习 IRL | 最大熵IRL、学徒学习 | 先从轨迹反推奖励,再做正向RL | 每轮都要解一次RL,非常贵 |
| 对抗式模仿学习 | GAIL及大量变体 | 判别器与策略博弈,直接匹配轨迹分布 | 对抗训练不稳定,需要环境交互 |
这三条路线之间不是互斥关系。GAIL在数学上甚至可以理解成一种隐式的IRL,这个点后面我会再展开讲。
2. 行为克隆:最容易想到的基线,也最容易在长任务里翻车
2.1 行为克隆的标准做法和它的适用边界
行为克隆的操作流程非常简单:先收集一批专家演示轨迹,每条轨迹包含一串状态和动作;然后把(状态,动作)当成普通的监督学习样本,训练一个神经网络让它在给定状态时输出和专家一致的动作。连续动作一般用均方误差做损失,离散动作就用交叉熵。
如果画成伪代码,核心训练循环只有几行:
for state, action in expert_data: pred = policy(state) # 连续动作空间 loss = mse_loss(pred, action) # 离散动作空间 # loss = cross_entropy(pred, action) optimizer.zero_grad() loss.backward() optimizer.step()很多人在初学阶段会觉得BC太“朴素”了,不像强化学习。但它在工程上有一个无可替代的优点:完全不需要环境交互,也不需要在线专家实时给标签。只要手里有一批高质量的演示数据,离线训练就能拿到一个能动的策略,用来做基线、做产品原型、给后续算法做初始值都非常合适。不过BC的适用范围有硬边界:任务长度不能太长,状态分布不能太复杂,专家演示必须尽量覆盖运行时可能出现的状况。理解了这三条边界,才能真正理解后面为什么会出现DAgger这种改进。
2.2 坑一:分布偏移让一步错步步错
BC最大的坑,是那种离线训练时各项指标都挺好看,一放到真实环境里就跑几步就彻底崩掉的坑。背后原因就是分布偏移(covariate shift)。
训练BC时,策略看到的输入是“专家采集数据时”留下的状态分布;但测试时,策略看到的是“自己动作造成”的状态分布。策略学得再准也有误差,一步小误差会导致状态偏离专家轨迹,进入一个专家数据里几乎没出现过的地带,策略在这个陌生地带上完全不知道该做什么,于是产生更大的误差,偏离得更远。这是个正反馈闭环,步数越长,偏得越狠。
拿开车举例最好懂:专家录数据时,副驾上坐着一个经验丰富的教练,看到新手一偏就立刻纠正,所以数据里的状态基本都是“正常到接近安全边界但还没出事”的样子。新手模型从这批数据里学到的是“在正常道路上开得不错”。可真正上路时没有教练随时纠正,模型一次打方向偏了半米,接下来的状态就全是“贴近护栏”这种极端情况,而它在这些状态下几乎没受过训练。
我在机器人导航Demo里也复现过这个现象:BC模型在测试集上的离线准确率超过90%,但放到真实走廊连续走五步以上就会撞墙。原因不是模型没学好映射关系,而是它在训练分布上做得足够好,却在真实自产分布上毫无泛化能力。这也解释了为什么BC论文里的成功率大多会标注“任务长度较短”——短任务误差还没累积到致命程度,偏航还没放大就已经结束了。
2.3 坑二:多个“标准答案”会被平均成“四不像”
BC第二个容易被忽视的坑是:专家演示往往不是单一答案,而是多个同样合理的答案。监督学习的平均逻辑会把它们揉成一个“四不像”。
举个例子。让专家操控机器人绕过一块挡板,左侧绕是个好选择,右侧绕也是个好选择,两条轨迹动作完全相反。专家录制时录了30条,15条向左绕,15条向右绕。BC网络如果采用均方误差训练,它学到的是在挡板面前输出“两个反向动作的平均值”,也就是既不左也不右,直直朝挡板撞过去。
这个问题的根源在于,MSE等传统回归损失假设目标服从单峰分布,而专家行为恰恰可能是多峰的。你给模型一堆标签,它觉得最安全的拟合方式是取期望,但期望往往不代表任何一个真实可行的动作。缓解办法有几个:动作空间离散化后用交叉熵,可以让模型把概率分散到两个方向而不是硬取平均;使用混合密度网络MDN,让网络输出多个高斯分量和对应的权重;或者在后处理阶段加一层约束。但这些方案只是让模型有能力表达多峰,并没有根治BC依赖独立同分布数据这一根本问题。只要状态分布开始偏移,多峰表达也很难救回来。
2.4 DAgger:用在线纠偏救一救行为克隆
既然BC的问题在于“训练时的状态分布”和“运行时的状态分布”不一致,最直接的解法就是让训练数据也来自运行时分布。DAgger(Dataset Aggregation)走的就是这条路。
DAgger的训练循环长这样:先用离线专家数据训练一个初始BC策略;然后让这个策略自己在环境里跑,跑到任何状态s时,都停下来请专家给出“在这个状态应该执行的动作”;把这些新样本加入数据集,重新训练策略;重复多轮。关键变化在于,模型不再只在专家轨迹里学习,还能在“自己即将走偏的那些状态”上得到专家纠正。相当于学车时教练不是只坐在副驾看你在标准路线上开,而是专门等你开到沟边时告诉你这时候该打方向。它对分布偏移的修复效果非常明显,经常能让BC策略从几步就撞墙变成顺利跑完全程。
但DAgger的工程代价也很实在:你需要一个随时在线的专家,这个人或者这套系统必须能够对策略产生的每个状态给出标签。如果是人类专家,一场标注下来工作量并不小。我的经验是,不要真的要求专家标注每一条轨迹里的每个状态,可以先把策略跑出来的轨迹聚一下类,挑出那些偏离专家轨迹最远的代表性状态交给专家标,效率会高很多。另外,如果专家其实就是另一个策略,例如一个规划算法,那么DAgger就非常划算,只需挂一个接口就能自动执行。
3. 逆强化学习:不学动作学偏好,从轨迹反向推导奖励函数
3.1 逆强化学习的问题设定:一个条件宽松到有点“病态”的问题
行为克隆学的是“状态到动作的直接映射”,逆强化学习换了个角度:既然专家行为可以被看成在最大化某个未知奖励,那能不能先把这个奖励函数反推出来,再交给正向强化学习用?
问题设定听起来很吸引人,但细想就不对劲:给定一堆专家轨迹,能够解释这些轨迹的奖励函数有无数个。极端情况下,一个恒等于0的奖励函数会让所有策略都“最优”,专家轨迹当然也是最“优”的;一个只在专家轨迹上给高分、其它地方随意给负分的奖励函数也可以。我们缺少一个标准来挑出“真正合理的那个奖励”,这就是所谓的病态问题。
要让这个问题可解,必须人为加约束。最常见的一类做法是假设奖励是状态的线性组合:R_w(s,a)=w·φ(s,a),其中φ是手工设计的特征向量,w是待学参数。比如在“学习驾驶”这个任务里,特征可以包括速度、车道位置、与前后车距离;在“学习走迷宫”里,特征可以是“是否靠近出口”“是否撞墙”等等。特征工程虽然听着老派,但在IRL里很有用,因为只有在特征空间中,才能定义“专家和学徒的行为差异”。
3.2 最大熵IRL:在众多可能奖励里挑一个“最不武断”的
最大熵IRL(MaxEnt IRL)给出了一种选择标准:在“满足专家特征期望”的所有轨迹分布里,选熵最大的那个分布,再用这个分布反推奖励参数。
“满足专家特征期望”是约束条件。意思是:专家轨迹的平均特征值,应该和学徒策略产生的平均特征值一致。这个约束保证了学徒学到的宏观行为模式和专家一致,比如都保持车道居中、都保持安全跟车距离。
熵最大是正则项。它要求我们不要把轨迹分布限定得太死,而应该让模型认为“只要特征匹配,各种轨迹都有可能出现”。这个选择标准背后是最大熵原理:在信息不足时,不引入任何没有依据的假设,选可能性最丰富、不确定性最高的一种解释。这条思路下的优化目标可以理解成两步:先固定当前奖励参数w,用正向强化学习求出在这个奖励下的策略分布;再看这个策略分布算出的特征期望和专家差多远,朝着缩小差距的方向更新w。整个求解过程本质上是在奖励空间里搜索,而不是像BC那样直接在策略空间里拟合。
3.3 学徒学习循环:每学一轮都要做一次正向强化学习
最大熵IRL的迭代流程放在工程里长这样:第一步,初始化奖励函数参数w;第二步,在当前奖励函数下用任意强化学习算法求解最优策略,得到学徒策略π;第三步,让学徒策略与环境交互,采样一批轨迹,计算当前的特征期望;第四步,把当前特征期望和专家特征期望对比,用梯度方向更新w,尽量让专家特征期望更高的那些特征获得更大的权重;第五步,回到第二步重复。
这个循环最大的成本在第二步。每更新一次奖励函数,都要重新求解一次完整的强化学习问题。低维离散状态空间还好说,可以用表格型方法快速算;一旦状态变成图像、动作变成连续高维向量,每一次正向RL本身就是个耗时工程,IRL迭代几十上百轮,完全可能把项目拖到不可接受的时长。所以实践里IRL更多的使用场景是:状态空间低维、特征可解释、需要奖励函数做迁移。例如先在一款小型机器人上学到“走到目标点”的奖励函数,再把这套奖励函数用到任务相同但结构略有差异的机器人上,因为意图是可迁移的,而具体硬件差异可以通过正向RL重新适应。
3.4 IRL为什么很少出现在视觉高维任务里
我一直觉得IRL在教科书里地位很高,但工业项目里很少被直接选作主力方案,主要卡在两个地方。第一是特征工程和奖励建模需要人工参与,视觉高维任务里你很难把“什么是好的行为”压缩成几个手工特征。第二是迭代成本太高,每次调整奖励都要重新训练策略。
但IRL的思想价值非常大:它提醒我们,专家演示里最有价值的部分可能不是动作本身,而是动作背后折射出的意图和偏好。后来的GAIL之所以能火,很大程度上就是因为它用神经网络替代了手工特征和显式概率建模,把IRL中“显式重建奖励函数”替换成“隐式学习密度比”。理解IRL之后再看GAIL,会清楚很多。
4. 对抗式模仿学习:让判别器替我们写奖励
4.1 从生成对抗到轨迹分布匹配的逻辑
GAIL全称是Generative Adversarial Imitation Learning,它的核心思想一句话就能概括:让策略生成的轨迹分布尽量接近专家轨迹分布,逼近的手段是“判别器与策略互相博弈”。
回想一下生成对抗网络GAN的玩法:生成器负责造出以假乱真的图片,判别器负责判断图片是生成还是真实,两者对抗迭代,最后生成器输出的分布逼近真实数据分布。GAIL把这里的“图片”换成“轨迹”或“状态-动作对”:判别器D的输入是一段状态和动作,输出是“这像不像专家”;策略π相当于生成器,负责产生轨迹去骗过判别器。
为什么这个思路能绕开IRL的显式奖励重建?因为判别器天然是一个函数近似器,它会不断学习“专家轨迹和策略轨迹哪里不一样”。这个“哪里不一样”就是最有价值的监督信号。GAIL并不要求你把这个信号写成可解释的奖励公式,它会自动隐式地把它编码成给策略的梯度。
4.2 GAIL的训练循环:伪奖励是怎么作用到策略上的
GAIL标准训练流程分成内外两层循环。外层更新判别器,内层更新策略。每轮先分别从专家数据集和当前策略中采样轨迹。判别器D训练的目标是:给专家样本打高分,给策略样本打低分。之后策略更新时,会用到一个“伪奖励”:在某个状态动作对(s,a)取自当前策略时,伪奖励可以写成 r̂(s,a)=-log D(s,a)。如果D输出趋近1,说明当前状态动作看起来像专家,伪奖励就小;如果D输出趋近0,说明这状态动作明显不像专家,伪奖励就很大,策略会通过强化学习算法避开这类状态动作。
伪代码大概是:
for iteration in range(max_iter): expert_batch = sample(expert_dataset) policy_batch = collect_rollouts(policy) update_discriminator(expert_batch, policy_batch) reward = -log(discriminator(s, a)) update_policy(reward) # 常见搭配PPO或TRPO有一个必须注意的细节是熵正则项。GAIL的目标函数一般会在模仿损失后面加一个策略熵项,即 -λH(π)。它的作用很简单:防止策略在怼赢判别器之后退化成只输出单一动作的模式。没有熵正则或系数太小,策略很容易崩塌;系数太大,又会牺牲模仿精度。这个λ是我每次训练GAIL都要反复试的第一个超参数。
4.3 训练不稳定问题与我实际调过的几个旋钮
GAIL的工程体验比BC差得多,训练不稳定是常态。我踩得最多的问题有三个。
第一个是判别器过拟合太快。判别器网络如果容量太大,几十步之后就能把专家和策略样本分得干干净净,准确率冲到接近100%。这时候策略侧的伪奖励基本全是很小的值,梯度信号趋近消失,模仿学习直接变成盲优化。我的调法是:判别器网络结构要比策略网络小一号,或者用梯度惩罚和谱归一化限制判别器的表达能力,同时把判别器的更新频率降下来,让策略有时间适应。
第二个是单步判别器会忽略轨迹结构。如果D每次只看一个状态动作对,它很容易被“局部动作像不像专家”带偏。比如某个机器人搬运任务,策略学会了把机械臂挪到专家常出现的位置,但完全忽略了整体轨迹的先后顺序,整体任务依然是失败的。后来我把判别器换成输入一整段轨迹的版本,让它在轨迹尺度上做判断,问题明显改善。
第三个是策略更新算法和判别器训练的节奏配合。GAIL里策略侧一般都是用PPO这类在线RL算法,策略更新太激进会放大判别器信号中的噪声。我实践下来的稳妥设置是:判别器每更新1步,策略PPO只做1到2个epoch,学习率不宜过大,整体训练曲线以缓慢爬升为主。想加速就把专家数据增量灌得慢一点,不要指望一次给满。调GAIL和调GAN很像,它没有一个万能超参组合,但“先闹清楚是梯度消失还是策略崩塌再动手”这个排查顺序,能省下大量时间。
5. 三条路线怎么选:数据成本、交互要求与最终效果对比
5.1 一张表看完BC、IRL、GAIL的适用范围
| 维度 | 行为克隆BC | 逆强化学习IRL | 对抗式模仿学习GAIL |
|---|---|---|---|
| 专家数据需求 | 仅需离线数据 | 需要专家轨迹+环境模型可供RL | 需要专家轨迹+环境可供采集 |
| 环境交互 | 训练零交互 | 每轮RL都要大量交互 | 策略每轮采样需要交互 |
| 在线人工标注 | 传统不需要,DAgger需要 | 不需要 | 不需要 |
| 可解释性 | 低,策略不可解释 | 高,奖励函数可解释 | 中,判别器可近似解释 |
| 训练稳定性 | 高,离线监督 | 低,迭代重 | 低,对抗不稳定 |
| 高维输入 | 勉强可用,但分布偏移严重 | 几乎不可用 | 擅长处理图像等高维输入 |
| 主要风险 | 分布偏移、多模态被平均 | 迭代成本过高 | 判别器过拟合、策略崩塌 |
| 典型场景 | 短任务、快速基线、产品原型 | 低维状态、奖励需要迁移 | 连续控制、视觉机器人、复杂轨迹 |
5.2 按项目类型选型而不是按“哪个最新”选型
很多初学者会直接跳到GAIL,因为听起来先进。实际上,大部分项目的资源约束根本喂不饱GAIL,反而是BC先跑通基线更加划算。
如果你的场景是“只有一批静态专家数据,环境不便宜甚至不允许随意试错”,BC是唯一合理起点。比如危险环境操作,这些场景根本不可能让模型在真实环境里自由探索,那就老老实实离线训练,把BC当作上限,再后续想办法做仿真迁移。如果手里有可用的专家策略且环境能够快速重置,BC+DAgger通常比GAIL便宜不少,效果也更稳定。
如果项目需要奖励的可解释性和迁移性,IRL值得投入。比如一个教育机器人需要把“完成拼图”的意图迁移到不同尺寸的拼图上,IRL给出的奖励函数可以复用。但前提是你愿意承担每轮正向RL的高成本。如果你的状态是高维图像、动作是连续控制,同时仿真环境便宜,GAIL就是最值得试的路。一个比较典型的配置是:GAIL做模仿学习,从专家演示得到一个初步策略,再用PPO在这个策略基础上微调,完成超越专家水平的目标。GAIL最好用的场景是“你不太清楚怎么设计奖励,但有一堆好的演示数据”。
5.3 一个我反复在用的混合玩法:先模仿后强化
最后说一个我反复推荐的组合方式,也是项目里最稳的收尾方案:先用模仿学习把策略初始化到一个基本能跑的水平,再用强化学习做后续微调。
这么做是为了解决纯强化学习的冷启动问题。高维连续控制任务一开始全凭随机探索,在稀疏奖励下很难碰上有效状态。有了IL先导策略,环境里前期的探索集中在高价值区域,PPO这类算法常常能在一个相对较短的时间内把策略提升到超过专家水平。这一点与RL理论上限高于IL的观点是一致的:模仿学习的上限是专家,而强化学习理论上没有这个限制。
但混合玩法有一个前提:模仿阶段产出的初始策略不能带太离谱的“错误先见”。如果BC因为分布偏移学出了撞墙轨迹,直接拿它初始化PPO,后期往往很难摆脱这个局部最优。我的习惯是,在进入RL微调前,先花一点时间在验证集上观察模仿策略的长轨迹特性,确认它不会犯低级规则错误,再开启微调。
我在几个机器人和仿真控制项目里实际对比过:单独用PPO从零开始,往往需要很长的探索期;单独用BC,策略上限被专家水平锁死;先BC后PPO则能在相对短的时间内得到既稳定又超过BC基线的策略。这个“先模仿后强化”的流程,可能才是模仿学习在工程上最大的价值出口。
另外想提醒一句,专家数据质量比模型容量重要得多。你花大量时间去设计更大的网络,不如先把演示数据里的噪声动作、过短轨迹、不一致的标注清洗干净。我见过太多次“模型怎么调都没用,最后发现是专家数据本身有一半是废的”的情况。真的要做高价值项目的同学,手里至少要有一种模仿学习方法的动手经验。它不一定会是你项目的最终方案,但它往往是让你从“奖励函数调不动”的泥潭里最快爬出来的那条路。