强化学习虽然强大,但它的一个老大难问题是奖励函数难设计。让机器人学会倒一杯水,你需要为"杯子没摔碎""水量合适""动作流畅"等目标分别设计奖励,稍有不慎Agent就会学到投机取巧的策略。模仿学习提供了另一条路:直接给Agent看人类专家是怎么做的,让它从演示数据中学习策略,省去繁琐的奖励工程。本文将系统介绍模仿学习Agent的核心范式、常见问题与改进方案。

模仿学习的三大核心范式
模仿学习的研究已经有几十年历史,目前形成了三种主流技术路线:行为克隆、逆向强化学习和生成对抗模仿学习。这三条路线的思路差异很大,理解它们的区别是掌握这一领域的基础。
行为克隆是最直接的方式,把模仿问题转化为监督学习问题。假设专家演示数据是一系列状态-动作对,Agent要做的事情就是学习一个映射函数,输入状态、输出动作,让预测动作尽量接近专家动作。这种方式实现简单,只要标注好演示数据就能用现成的监督学习框架训练。但它的致命弱点在于误差累积:训练时看到的都是专家访问过的状态,而一旦Agent自己在执行中出现小偏差,进入一个演示数据中从未出现过的状态,模型就会手足无措,错误越滚越大。
逆向强化学习则换了一个角度思考。它不直接学习专家的动作,而是先推断专家到底在优化什么样的奖励函数。核心假设是:专家之所以那样做,是因为那个行为在某个隐藏的奖励函数下是最优的。学到了奖励函数之后,再结合强化学习求解最优策略。IRL的优势是泛化能力强,因为它学到的是意图而非具体动作,但它计算成本高,IRL内部往往要反复求解强化学习问题,在大规模环境下代价惊人。
生成对抗模仿学习是2016年以后兴起的方案,借鉴了GAN的思想。它包含一个策略生成器和一个判别器,判别器的任务是区分轨迹来自专家还是Agent,生成器的任务是让自己的行为骗过判别器。两者博弈到纳什均衡时,Agent的策略就与专家难以区分。GAIL兼顾了数据效率和泛化能力,是目前应用最广的模仿学习算法之一,缺点是训练不够稳定,对超参数比较敏感。
用行为克隆快速实现一个模仿学习Agent
理论说完了,动手写一个最简单的行为克隆Agent感受一下。假设我们收集了一批专家在某个环境中交互的轨迹数据,存成了JSON文件,下面的代码用PyTorch实现策略网络并进行训练。
import torch
import torch.nn as nn
import numpy as np
# 定义策略网络,输入状态维度 state_dim,输出动作维度 action_dim
class PolicyNet(nn.Module):
def __init__(self, state_dim, action_dim):
super(PolicyNet, self).__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, 128),
nn.ReLU(),
nn.Linear(128, action_dim)
)
def forward(self, state):
return self.net(state)
# 准备专家演示数据
# expert_data 是一个列表,每个元素是 (state, action) 元组
def prepare_dataset(expert_data):
states = np.array([s for s, a in expert_data], dtype=np.float32)
actions = np.array([a for s, a in expert_data], dtype=np.float32)
return torch.from_numpy(states), torch.from_numpy(actions)
# 训练循环,标准监督学习流程
def train_bc(expert_data, epochs=100, lr=1e-3):
states, actions = prepare_dataset(expert_data)
state_dim = states.shape[1]
action_dim = actions.shape[1]
policy = PolicyNet(state_dim, action_dim)
optimizer = torch.optim.Adam(policy.parameters(), lr=lr)
loss_fn = nn.MSELoss()
for epoch in range(epochs):
pred_actions = policy(states)
loss = loss_fn(pred_actions, actions)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 10 == 0:
print(f"epoch {epoch}, loss: {loss.item():.4f}")
return policy
代码结构非常简洁,本质上就是一个回归任务。对于连续动作空间用均方误差损失,对于离散动作空间则把最后一层换成Softmax并用交叉熵损失。需要注意的是,如果演示数据中动作分布是多峰的,直接用MSE回归会学到所有动作的平均值,导致策略完全失效,这时候应该改用混合高斯策略或者离散化处理。
训练完成后,在实际环境中部署时要把策略输出直接作为动作执行。第一次实验建议在简单环境(比如经典控制任务)上做,观察行为克隆的误差累积现象,对理解这个算法的局限非常有帮助。实践中一个经验规律是:演示轨迹数量增加一个数量级,执行成功率往往能显著提升,但分布外状态的问题依然存在,靠堆数据无法彻底解决。
分布偏移问题与DAgger算法
行为克隆失败的根源是训练分布与执行分布不一致,学术上称为协变量偏移。专家数据里的状态分布很集中,而Agent自己跑起来之后,任何一步的小偏差都会把它带到没见过的状态区域,这些区域上模型的预测质量完全没有保障。这个问题有几种经典的应对思路。
第一种是数据增广,让专家演示中包含从错误状态恢复的数据。比如请专家在演示时故意加入一些扰动,或者人为把Agent放到偏离轨迹的状态再让专家接管示范纠正动作。这样训练数据就覆盖了执行时可能遇到的状态,Agent学到的不仅是"怎么做好",还有"做错了怎么补救"。
第二种是著名的DAgger算法,全称Dataset Aggregation。它的流程是:先用初始的演示数据训练一个策略,然后让这个策略在环境中跑,把访问到的状态收集起来,请专家为这些状态标注正确动作,把这些新数据加入训练集重新训练。如此迭代几轮后,训练数据的状态分布就逐渐逼近Agent实际执行时的状态分布。DAgger的代价是需要专家在线标注,交互成本不低,但效果在多数任务上都非常稳定。伪代码如下:
# Dagger 算法伪代码
# 1. 用初始专家数据 D 训练策略 pi
# 2. 循环迭代:
# a. 让当前策略 pi 在环境中执行,收集状态序列 S
# b. 请专家为 S 中每个状态标注动作,得到新数据 D_new
# c. D = D 并上 D_new,重新训练策略
# 3. 输出最终策略
def dagger(env, expert, initial_data, iterations=5):
dataset = list(initial_data)
for i in range(iterations):
policy = train_bc(dataset)
states = run_policy_collect_states(env, policy)
new_data = [(s, expert.action(s)) for s in states]
dataset.extend(new_data)
return train_bc(dataset)
第三种思路是从损失函数下手,比如引入噪声感知训练,在训练输入状态时人为添加扰动,强迫模型在扰动后的状态上仍输出接近专家的动作,相当于对策略做了平滑处理,提升了鲁棒性。
模仿学习的典型应用场景与发展方向
模仿学习最成功的落地领域之一是机器人操作。机械臂抓取、装配、穿针引线这类任务,奖励函数几乎无法手工设计,但人类示教却很容易做到。通过遥操作系统采集几十到上百条专家轨迹,再用行为克隆或扩散策略训练,机器人在很多精细操作任务上已经能达到接近人类成功率的水平。近年来扩散模型与模仿学习结合形成的扩散策略,在多峰动作分布建模上表现出色,成为机器人学习领域的研究热点。
另一个重要场景是自动驾驶和游戏AI。自动驾驶中大量使用人类驾驶数据做行为克隆,再结合强化学习微调,能够同时兼顾数据规模和性能上限。游戏领域的应用则更为人熟知:早期AlphaGo就先通过人类棋谱做监督学习模仿,达到业余高段水平后再靠自我博弈强化学习超越人类,这个两阶段范式证明了模仿学习作为初始化手段的巨大价值——它能让强化学习从一个远好于随机的起点出发,大幅缩短训练时间并提升最终性能。
当前的研究前沿集中在几个方向:一是减少对专家数据的依赖,比如结合视觉基础模型做表征预训练,让少量演示发挥更大作用;二是离线模仿学习,完全在固定数据集上训练而不与环境交互;三是模仿学习的安全性问题,如何保证学到的策略满足硬约束,避免出现专家演示中未暴露的危险行为。对于想入门的开发者,建议从Gymnasium提供的经典控制环境入手,先复现行为克隆,再尝试用Stable-Baselines3或模仿学习专用库Imitation实现GAIL和DAgger,逐步建立起完整的技术认知。