跳转到正文

11.1 行为克隆与交互式模仿学习

本节导读

核心内容

  • 理解行为克隆(BC)如何把专家示范转化为监督学习问题——这是最简单也是最常用的模仿学习方法
  • 看到行为克隆的致命缺陷:分布偏移——训练时见专家状态,部署时一旦犯错就再也回不来
  • 理解 DAgger 如何通过"让策略跑、让专家标"的交互式流程解决分布偏移问题
  • 对比 BC、DAgger、GAIL 三条路线的数据需求和适用场景,理解为什么需要从"模仿动作"进化到"推断奖励"

前面几章我们学了很多强化学习算法:DQN、PPO、SAC……它们都有一个共同的前提——环境提供奖励函数。智能体每走一步,环境告诉它这一步得多少分,然后算法努力让总分最大。

但是——让我们停下来想一下。现实中很多任务,你能找到专家示范,但你写不出奖励函数。比如:

  • 自动驾驶:人类驾驶员开了几百万公里,有大量录像,但你没法用几行代码定义"什么是好的驾驶"
  • 机器人操作:工人熟练地组装零件,但你很难精确写出每个关节应该怎么动
  • 大语言模型:高质量的问答对随处可见,但"好回答"的奖励函数根本没法手写

这些场景有一个共同点:专家会做,但你说不清楚他为什么这么做。如果我们不设计奖励,直接让智能体"抄作业"——看专家在什么状态下做什么动作,然后学着做——行不行?

这就是模仿学习(Imitation Learning)要解决的问题。第 11 章沿着三步展开:先用行为克隆与 DAgger 直接学习专家动作,再用逆强化学习与 GAIL 从示范推断奖励,最后进入 MAML、RL²、PEARL 与上下文强化学习,研究策略如何快速适应新任务。本节先回答四个问题:行为克隆怎样训练,误差为什么会累积,DAgger 怎样收集错误状态,以及几种路线各自需要什么数据。

1. 把专家示范写成监督学习

回忆第 6 章的策略梯度方法,它们都假设环境提供 reward。但很多真实任务中我们只有专家示范——人类驾驶员的轨迹、熟练工人的操作记录、高质量问答对。模仿学习直接从示范学策略,跳过奖励函数的设计。

你可能会问:"没有奖励怎么学?"答案很简单:把它变成监督学习。

1.1 行为克隆的训练目标

最直接的方法是把专家数据写成监督学习样本:状态 是输入,专家动作 是标签。策略给专家动作的概率越高,损失越小:

让我们把这个式子拆开看:

  • :这是专家示范数据集,里面有 对"状态-动作"——专家在状态 时做了动作
  • :这是我们的策略网络,它在状态 下选择动作 的概率
  • :专家动作的对数概率——这个值越大,说明策略越"同意"专家的选择
  • 负号:把"提高专家动作概率"变成最小化问题——我们最小化负对数似然,等价于最大化专家动作的概率

让我们用具体数字算一下这个损失。专家数据里有一条样本:车正好在车道中央(状态 ),专家的动作是"向左轻打方向"(动作 )。两个策略在这条样本上的输出是:

  • 策略一给这个动作的概率 ,损失是
  • 策略二给这个动作的概率 ,损失是

策略二更"同意"专家,损失就更小。行为克隆的训练就是把数据集里所有样本的这种损失最小化——和手写数字分类里的交叉熵训练是同一个操作。

离散动作通常使用交叉熵损失(就是分类问题),连续动作可以改用均方误差或高斯分布的负对数似然。LLM 的监督微调(SFT)也使用同样的条件似然目标,只是"动作"变成了下一个 token——本质上就是行为克隆。

python
def behavior_cloning_step(policy_net, expert_batch):
    states, actions = expert_batch
    log_probs = policy_net.log_prob(states, actions)
    loss = -log_probs.mean()  # 负对数似然
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    return loss.item()

这看起来太简单了——收集专家数据,做监督学习,就完了?如果真这么简单,强化学习早就被监督学习取代了。让我们停下来想一下:行为克隆在什么情况下会出问题?

2. 为什么行为克隆会累积错误

BC 训练时看到的是专家访问的状态分布 ,部署时访问的却是当前策略产生的 。一次小错误可能把智能体带到训练集中没有出现的状态,后续动作就更容易继续出错。

让我们用一个具体例子感受一下。假设你在学开车,教练只在"正确的驾驶状态"下教你:车在车道中间、速度合适、前车距离正常。你在这些状态下学得很好,转向、油门、刹车都很到位。

但是——万一你某一下方向盘打多了,车偏向了车道边缘,进入了一个训练时从来没见过的状态。你不知道该怎么办,可能会慌乱中再打一把方向,结果更偏;然后下一个状态更陌生,错得更离谱……这就是误差累积,也叫分布偏移(distribution shift)。

我们用数字算一下。设策略在专家访问过的状态上错误率只有 ——99% 的时候都和专家一致。单独看每一步,这个错误率低得可以忽略;但任务是连续执行 步的,全程不出错的概率是 ,它随步数急剧衰减:

任务长度 10 步50 步100 步1000 步
全程无错的概率0.900.610.370.00004

一个 100 步的任务,三局里有两局会在中途犯至少一次错。而且这还是乐观估计——它假设偏离专家轨迹后错误率仍然是 1%;实际上一旦偏离,状态变成了训练集中没有的,错误率会急剧上升。DAgger 论文(Ross et al. 2011)把累计任务代价写成 量级:

这个式子告诉我们什么?

  • 是任务长度(步数), 是监督学习误差
  • 表示错误会滚雪球:早期错误不仅影响当前一步,还会改变后面许多步看到的状态
  • 任务越长,只在专家状态上训练的代价越明显——1000 步的任务,代价可能是 1 步任务的 100 万倍

这就是为什么行为克隆在短任务、简单任务上能工作(比如 Atari 游戏的简单关卡),但在长决策任务(自动驾驶、机器人操作)上经常失败。

加餐:Ross 等人的 DAgger 理论界

BC 的误差界是 ,这来自于"协变量偏移"(covariate shift)的累积——每次遇到一个分布外的状态,策略就可能犯一个新错误,这些错误又会把后续状态推得更远。Ross 等人证明,如果能在训练过程中不断让专家标注当前策略访问的状态,这个界可以改进到 ——线性而不是平方。

3. 用 DAgger 收集策略真正访问的状态

既然问题出在"训练时没见过策略自己犯错的状态",那解决方案就很直接了:让策略跑,跑到它会犯错的状态,然后请专家告诉它在这些状态下应该怎么做。这就是 DAgger(Dataset Aggregation,数据集聚合)的核心思想。

Dataset Aggregation 直接补充策略会访问、而专家数据没有覆盖的状态。当前策略先执行任务,专家再为这些状态提供正确动作。我们来看代码:

python
def dagger(env, expert, policy_net, n_iterations=20, n_traj_per_iter=50):
    dataset = []
    for it in range(n_iterations):
        # 1. 用当前策略 rollout(注意:不是用专家!)
        trajectories = []
        for _ in range(n_traj_per_iter):
            s = env.reset()
            traj = []
            done = False
            while not done:
                # β 混合:早期多用专家保证安全,后期多用策略
                beta = max(0.0, 1.0 - it / 10)
                if np.random.rand() < beta:
                    a = expert(s)
                else:
                    a = policy_net.act(s)
                s_next, r, done, _ = env.step(a)
                traj.append((s, a))
                s = s_next
            trajectories.append(traj)

        # 2. 关键:对策略访问到的状态(包括失败状态)请专家重新标注
        for traj in trajectories:
            for s, _ in traj:
                a_expert = expert(s)
                dataset.append((s, a_expert))

        # 3. 用扩展后的数据集重训策略
        train_bc(policy_net, dataset)

让我们拆解 DAgger 的三个关键步骤:

第一步:跑策略。用当前策略(还不完美的那个)去环境里交互,收集它实际访问到的轨迹。注意代码里有一个 混合策略——早期迭代多用专家(避免一开始就乱撞出危险),后期逐渐多用策略(让它暴露自己的错误)。

第二步:专家标。这是 DAgger 最核心也最"昂贵"的一步:不管轨迹里的动作是专家选的还是策略选的,对轨迹里出现的每一个状态 ,都请专家给出"在这个状态下正确的动作应该是什么"。哪怕策略在这个状态下犯了傻,专家也要告诉它正确答案。

第三步:重训练。把这些新标注的"策略犯错状态"加进数据集,重新做行为克隆。这样数据集里就既有专家正常访问的状态,也有策略容易犯错的状态了。

让我们跟着训练走一遍。最初几轮, 接近 1,车基本由专家驾驶,收集到的都是"车道中间、车速正常"的状态,标注毫无压力。几轮之后, 降到一半,车有一半时间由还不熟练的策略控制,开始出现"压线""车速过快"的状态——这些状态专家示范里从来没有,现在专家针对它们给出正确动作,数据集补上了这些容易出事的位置。训练后期 归零,策略独立驾驶,仍会犯错,但每个犯错状态都会被标注、入库,下一轮策略在这些位置就学乖了。数据集就这样一轮轮滚大,逐渐覆盖的正是策略自己的分布

在无遗憾在线学习等条件下,累计代价可以从 BC 的 改善到 量级——从平方变成线性!

但是——你可能已经想到了一个问题:代价是训练期间必须反复调用专家。

4. 比较 BC、DAgger 与 GAIL

让我们把三种方法放在一起对比:

方法训练数据来源是否解决分布偏移需要专家在线标注
BC仅离线专家数据
DAgger专家 + 策略访问的状态✅(关键限制)
GAIL专家 + 策略 rollout✅(隐式)❌(只需状态-动作对)

DAgger 的工程瓶颈是需要专家在线交互。人类驾驶员很难实时为策略访问到的异常状态标注正确动作——你不可能让教练坐在旁边,每次车偏了都立刻告诉你方向盘该打多少度;更不可能让他为你标注成千上万次失败轨迹。

这就推动我们思考下一个问题:能不能不让专家实时标注?能不能只从专家现成的轨迹里,不仅学"做什么动作",还能反推出"为什么这么做"——也就是奖励函数?这就是下一节逆强化学习与 GAIL 要解决的问题。

本节总结

行为克隆(BC)是最朴素的模仿学习——把专家轨迹当作监督数据训练策略。它简单、计算成本低,和大语言模型的 SFT 本质是一回事。但它有分布漂移问题:训练时只在专家状态分布上学习,部署时一旦偏离就再也回不来,误差会按 累积。DAgger 通过让专家纠正 agent 的实际轨迹解决这个问题,把误差界改进到 ,但代价是需要专家在线反复标注。

下一节 11.2 逆强化学习与 GAIL 不再直接模仿动作,而是从专家行为反推奖励函数——这就是逆强化学习(IRL)。

现代强化学习实战课程