模仿学习是机器人获得复杂技能的重要途径,它通过学习专家演示来绕过困难的奖励函数设计问题。本章深入探讨从简单的行为克隆到复杂的逆强化学习的各种方法,重点关注实际部署中的分布偏移问题及其解决方案。我们将学习如何从人类演示中提取有效策略,理解不同数据收集方法的权衡,以及如何在真实机器人系统中实现稳定的模仿学习。通过特斯拉FSD的案例分析,我们将看到这些技术如何在工业级系统中落地。
学习目标:
| 行为克隆将模仿学习问题转化为监督学习问题。给定专家演示数据集 $\mathcal{D} = {(s_t, a_t)}{t=1}^N$,其中 $s_t$ 是状态,$a_t$ 是专家动作,我们的目标是学习策略 $\pi\theta(a | s)$ 来最小化: |
其中 $\ell$ 是损失函数。这种方法的核心假设是:如果策略能够准确预测专家在每个状态下的动作,那么执行这个策略就能复现专家的行为。
然而,这个假设在实践中存在重要限制:
损失函数的选择直接影响学习效果:
连续动作空间:
| MSE损失:$\ell_{MSE} = | a - \hat{a} | ^2$ |
离散动作空间:
架构设计需要考虑输入模态和任务特性:
视觉输入处理:
图像输入 → CNN特征提取器 →
↓
空间注意力机制
↓
特征融合层 → 策略头
多模态融合架构:
视觉流: ResNet/ViT → 视觉特征
↘
融合模块 → 共享表示 → 动作预测
↗
本体感知流: MLP → 本体特征
时序建模:
遥操作是收集机器人演示数据的主要方法之一。设计良好的遥操作系统需要在易用性、精度和数据质量之间取得平衡。
遥操作接口类型:
主臂(人操作) → 运动捕捉 → 运动重定向 → 从臂(机器人)
↓
数据记录系统
数据同步与时间戳:
# 伪代码:多模态数据同步
class DataRecorder:
def __init__(self):
self.time_offset = calibrate_clocks()
self.buffer = CircularBuffer(size=1000)
def record_step(self):
t = get_synchronized_time()
data = {
'timestamp': t,
'rgb': camera.get_frame(t),
'depth': depth_camera.get_frame(t),
'joint_pos': robot.get_joints(t),
'joint_vel': robot.get_velocities(t),
'ee_pose': robot.get_ee_pose(t),
'action': teleop.get_action(t),
'force_torque': ft_sensor.get_reading(t)
}
self.buffer.add(data)
直接从人类演示中学习(无需机器人硬件)有其独特优势:
动作捕捉系统:
从人类演示到机器人动作的映射:
运动重定向(Retargeting): \(a_{robot} = f(s_{human}, \phi_{human}, \phi_{robot})\) 其中 $\phi$ 表示形态学参数
数据增强对提高策略泛化性至关重要:
几何增强:
时序增强:
域随机化:
视觉域随机化参数:
- 光照:强度[0.5, 2.0],色温[4000K, 7000K]
- 纹理:随机替换物体和背景纹理
- 相机:内参扰动±5%,位置噪声±5cm
- 物理:摩擦系数[0.5, 1.5],质量±20%
混合增强策略: \(\mathcal{D}_{aug} = \mathcal{D}_{orig} \cup \mathcal{T}_1(\mathcal{D}) \cup \mathcal{T}_2(\mathcal{D}) \cup ...\) 其中 $\mathcal{T}_i$ 是不同的增强变换
行为克隆的核心问题是训练时和测试时的状态分布不匹配。这种分布偏移(distribution shift)会导致误差累积和性能退化。
问题的数学描述:
误差累积分析: 假设单步预测误差为 $\epsilon$,在长度为 $T$ 的轨迹中:
这种二次增长说明了为什么即使很小的预测误差也会导致长期任务失败。
可视化分布偏移:
专家轨迹: s₀ → s₁ → s₂ → s₃ → ... → sₜ
↓ ↓ ↓ ↓ ↓
专家动作: a₀* a₁* a₂* a₃* ... aₜ*
学习轨迹: s₀ → s₁' → s₂' → s₃' → ... → sₜ'
↓ ↓ ↓ ↓ ↓
预测动作: â₀ â₁ â₂ â₃ ... âₜ
偏差累积: 0 → δ₁ → δ₁+δ₂ → δ₁+δ₂+δ₃ → ...
Dataset Aggregation (DAgger) 通过迭代收集数据来解决分布偏移问题:
算法流程:
算法: DAgger
输入: 初始数据集 D₀ = {(s,a*)}从专家收集
专家策略 π*
迭代次数 N
1. 在 D₀ 上训练初始策略 π₁
2. for i = 1 to N:
a. 用当前策略 πᵢ 收集轨迹
b. 对轨迹中的每个状态 s,查询专家动作 a* = π*(s)
c. 聚合数据:Dᵢ = Dᵢ₋₁ ∪ {(s, a*)}
d. 在 Dᵢ 上重新训练策略 πᵢ₊₁
3. 返回最终策略 πₙ₊₁
关键改进:
理论保证: DAgger 提供了误差界: \(J(\pi_{DAgger}) - J(\pi^*) \leq O(\epsilon T)\) 相比行为克隆的 $O(\epsilon T^2)$,这是显著改进。
SafeDAgger: 为了安全性,在危险状态下切换到专家控制:
def safe_dagger_step(state, learned_policy, expert_policy, safety_checker):
if safety_checker.is_safe(state):
action = learned_policy(state)
label = expert_policy(state) # 仍然记录专家标签
else:
action = expert_policy(state) # 专家接管
label = action
return action, (state, label)
HG-DAgger (Human-Gated DAgger): 让人类专家决定何时介入:
ThriftyDAgger: 选择性查询最有价值的状态:
EnsembleDAgger: 使用策略集成提高鲁棒性:
class EnsembleDAgger:
def __init__(self, n_models=5):
self.models = [create_model() for _ in range(n_models)]
def predict(self, state):
predictions = [m(state) for m in self.models]
action = np.mean(predictions, axis=0)
uncertainty = np.std(predictions, axis=0)
return action, uncertainty
def should_query_expert(self, uncertainty, threshold=0.1):
return np.max(uncertainty) > threshold
逆强化学习的核心思想是:专家的行为隐含了某个未知的奖励函数,通过观察专家演示来推断这个奖励函数。
问题形式化: 给定:
目标:学习奖励函数 $R(s,a)$ 使得专家策略 $\pi^*$ 是最优的
基础IRL算法:
1. 初始化奖励函数 R
2. 重复:
a. 用当前R求解最优策略 π
b. 计算特征期望:
μ_π = E[Σ γ^t φ(s_t, a_t) | π]
μ_E = E[Σ γ^t φ(s_t, a_t) | 专家演示]
c. 更新奖励函数使 μ_π 接近 μ_E
3. 返回学习到的奖励函数 R
特征匹配原理: IRL假设奖励函数可以表示为特征的线性组合: \(R(s,a) = w^T \phi(s,a)\)
专家策略应该最大化期望奖励: \(\pi^* = \arg\max_\pi \mathbb{E}_{\tau \sim \pi}[w^T \mu_\pi]\)
最大熵IRL (MaxEnt IRL) 解决了奖励函数的歧义性问题:
原理:在所有解释专家行为的奖励函数中,选择导致最大策略熵的那个。
概率模型: 轨迹的概率正比于其累积奖励: \(P(\tau) \propto \exp(R(\tau)) = \exp(\sum_t R(s_t, a_t))\)
目标函数: 最大化对数似然: \(\mathcal{L} = \sum_i \log P(\tau_i^*) - \log Z\)
其中 $Z = \sum_\tau \exp(R(\tau))$ 是配分函数。
软值迭代:
def soft_value_iteration(R, gamma=0.99, threshold=1e-4):
V = np.zeros(n_states)
while True:
V_new = soft_bellman_backup(V, R, gamma)
if np.max(np.abs(V - V_new)) < threshold:
break
V = V_new
# 计算软Q函数
Q = R + gamma * expected_V(V)
# 导出策略
π = np.exp(Q - logsumexp(Q, axis=1, keepdims=True))
return π, V, Q
def soft_bellman_backup(V, R, gamma):
Q = R + gamma * V_next # V_next是下一状态的值
return logsumexp(Q, axis=1) # 软最大值
神经网络奖励函数:
class NeuralRewardFunction(nn.Module):
def __init__(self, state_dim, action_dim, hidden_dims=[256, 256]):
super().__init__()
input_dim = state_dim + action_dim
layers = []
for h_dim in hidden_dims:
layers.extend([
nn.Linear(input_dim, h_dim),
nn.ReLU(),
nn.Dropout(0.1)
])
input_dim = h_dim
layers.append(nn.Linear(input_dim, 1))
self.net = nn.Sequential(*layers)
def forward(self, states, actions):
x = torch.cat([states, actions], dim=-1)
return self.net(x)
Guided Cost Learning (GCL): 结合采样和重要性权重的高效IRL算法:
对抗IRL (AIRL): 使用判别器区分专家和策略轨迹: \(D(s,a) = \frac{\exp(f_\theta(s,a))}{\exp(f_\theta(s,a)) + \pi(a|s)}\)
其中 $f_\theta$ 是学习的奖励函数。
Generative Adversarial Imitation Learning (GAIL) 将模仿学习转化为生成对抗问题,避免了显式的奖励函数建模。
核心思想:
目标函数: \(\min_\theta \max_\omega \mathbb{E}_{\pi^*}[\log D_\omega(s,a)] + \mathbb{E}_{\pi_\theta}[\log(1-D_\omega(s,a))] - \lambda H(\pi_\theta)\)
其中 $H(\pi_\theta)$ 是策略熵,用于鼓励探索。
与IRL的联系: GAIL可以看作是IRL和RL的组合,但跳过了显式的奖励函数学习:
传统IRL+RL: 专家演示 → 奖励函数 → 最优策略
GAIL: 专家演示 → 最优策略 (通过对抗学习)
判别器架构:
class Discriminator(nn.Module):
def __init__(self, state_dim, action_dim, hidden_dims=[256, 256]):
super().__init__()
input_dim = state_dim + action_dim
layers = []
for h_dim in hidden_dims:
layers.extend([
nn.Linear(input_dim, h_dim),
nn.Tanh(), # Tanh通常比ReLU更稳定
])
input_dim = h_dim
layers.append(nn.Linear(input_dim, 1))
self.net = nn.Sequential(*layers)
def forward(self, states, actions):
return torch.sigmoid(self.net(torch.cat([states, actions], dim=-1)))
def compute_reward(self, states, actions):
# 用判别器输出作为奖励信号
D = self.forward(states, actions)
return torch.log(D + 1e-8) - torch.log(1 - D + 1e-8)
策略生成器: 使用任何策略梯度算法(PPO/TRPO/SAC),将判别器输出作为奖励:
class GAILTrainer:
def __init__(self, policy, discriminator, expert_dataset):
self.policy = policy
self.discriminator = discriminator
self.expert_dataset = expert_dataset
self.policy_optimizer = PPO(policy)
def train_step(self):
# 1. 收集策略轨迹
policy_batch = self.collect_trajectories(self.policy)
# 2. 采样专家数据
expert_batch = self.expert_dataset.sample(len(policy_batch))
# 3. 更新判别器
self.update_discriminator(expert_batch, policy_batch)
# 4. 计算策略奖励
rewards = self.discriminator.compute_reward(
policy_batch.states,
policy_batch.actions
)
# 5. 更新策略
self.policy_optimizer.update(policy_batch, rewards)
GAIL训练容易不稳定,需要特殊技巧:
1. 梯度惩罚 (WGAN-GP): \(\mathcal{L}_D = -\mathbb{E}_{expert}[D] + \mathbb{E}_{policy}[D] + \lambda \mathbb{E}_{\hat{x}}[(||\nabla_{\hat{x}}D||_2 - 1)^2]\)
其中 $\hat{x}$ 是专家和策略数据的插值。
2. 谱归一化: 限制判别器的Lipschitz常数:
from torch.nn.utils import spectral_norm
class SpectralNormDiscriminator(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.net = nn.Sequential(
spectral_norm(nn.Linear(state_dim + action_dim, 256)),
nn.Tanh(),
spectral_norm(nn.Linear(256, 256)),
nn.Tanh(),
spectral_norm(nn.Linear(256, 1))
)
3. 缓冲区重放: 维护历史策略数据缓冲区,防止判别器过拟合当前策略:
class ReplayBuffer:
def __init__(self, capacity=10000):
self.buffer = deque(maxlen=capacity)
def add(self, trajectories):
self.buffer.extend(trajectories)
def sample(self, batch_size):
return random.sample(self.buffer, batch_size)
4. 学习率调度:
# 判别器学习率应该较小
d_lr = 3e-4
g_lr = 3e-4
# 使用不同的更新频率
d_updates_per_g_update = 5 # 判别器更新更频繁
变体算法:
InfoGAIL: 加入互信息最大化,学习可解释的潜在码: \(\mathcal{L} = \mathcal{L}_{GAIL} + \lambda I(c; \tau)\)
SQIL (Soft Q Imitation Learning): 简化的离线模仿学习方法:
特斯拉FSD (Full Self-Driving) 系统是工业界最大规模的模仿学习部署之一。其核心是从百万级人类驾驶数据中学习驾驶策略。
数据规模:
系统架构:
传感器输入 → 感知网络 → 特征融合 → 策略网络 → 控制输出
↑ ↑
8个相机 BEV特征空间
1. 大规模数据收集系统:
2. 多任务学习架构:
# 伪代码:多任务策略网络
class FSDPolicyNetwork(nn.Module):
def __init__(self):
self.backbone = VisionTransformer()
self.bev_decoder = BEVDecoder()
# 多个输出头
self.trajectory_head = TrajectoryHead()
self.speed_head = SpeedHead()
self.lane_change_head = LaneChangeHead()
self.traffic_light_head = TrafficLightHead()
def forward(self, camera_inputs):
features = self.backbone(camera_inputs)
bev_features = self.bev_decoder(features)
outputs = {
'trajectory': self.trajectory_head(bev_features),
'speed': self.speed_head(bev_features),
'lane_change': self.lane_change_head(bev_features),
'traffic_light': self.traffic_light_head(bev_features)
}
return outputs
3. 端到端学习vs模块化设计:
1. 主动学习循环:
部署 → 收集困难案例 → 人工标注 → 重训练 → 验证 → 部署
↑ ↓
←────────────────────────────────────
2. 对抗样本挖掘:
3. 在线适应:
延迟优化:
安全保障:
离线强化学习从固定数据集学习,不需要与环境交互,这对机器人应用特别重要:
核心挑战:
CQL通过学习Q函数的下界来避免过估计问题:
CQL损失函数: \(\mathcal{L}_{CQL}(\theta) = \alpha \mathbb{E}_{s \sim \mathcal{D}}[\log \sum_a \exp(Q_\theta(s,a)) - \mathbb{E}_{a \sim \pi_\beta(a|s)}[Q_\theta(s,a)]] + \mathcal{L}_{SAC}(\theta)\)
其中:
实现细节:
class CQL:
def __init__(self, q_network, policy, alpha=1.0):
self.q_network = q_network
self.policy = policy
self.alpha = alpha
def compute_cql_loss(self, states, actions, rewards, next_states, dones):
# 标准TD损失
q_values = self.q_network(states, actions)
with torch.no_grad():
next_q = self.compute_target_q(next_states)
target_q = rewards + (1 - dones) * self.gamma * next_q
td_loss = F.mse_loss(q_values, target_q)
# CQL正则化项
# 计算log-sum-exp over actions
num_samples = 10
random_actions = torch.rand(states.shape[0], num_samples, self.action_dim)
random_q = self.q_network(states.unsqueeze(1), random_actions)
logsumexp_q = torch.logsumexp(random_q, dim=1)
# 数据集动作的Q值
dataset_q = self.q_network(states, actions)
# CQL损失
cql_loss = (logsumexp_q - dataset_q).mean()
return td_loss + self.alpha * cql_loss
IQL (Implicit Q-Learning): 避免显式策略改进,直接从数据学习:
def iql_loss(q, v, states, actions, rewards, next_states):
# 学习V函数(期望Q值的分位数)
with torch.no_grad():
target_q = q(states, actions)
v_loss = expectile_loss(v(states), target_q, tau=0.7)
# 学习Q函数
with torch.no_grad():
next_v = v(next_states)
target = rewards + gamma * next_v
q_loss = F.mse_loss(q(states, actions), target)
# 通过advantage加权行为克隆学习策略
with torch.no_grad():
advantage = q(states, actions) - v(states)
weight = torch.exp(advantage / beta)
policy_loss = -weight * log_prob(actions).mean()
return q_loss + v_loss + policy_loss
数据质量评估:
def assess_dataset_quality(dataset):
metrics = {
'coverage': compute_state_coverage(dataset),
'return_distribution': compute_return_stats(dataset),
'trajectory_length': compute_trajectory_lengths(dataset),
'action_diversity': compute_action_entropy(dataset)
}
return metrics
混合在线/离线学习:
本章系统介绍了模仿学习的核心方法和实践技术:
关键概念回顾:
核心公式汇总:
方法选择指南:
练习15.1:分布偏移的影响 给定一个简单的1D导航任务,专家策略是 $a^* = -\text{sign}(s)$(向原点移动)。如果行为克隆的策略有误差 $\hat{a} = a^* + \epsilon$,其中 $\epsilon \sim \mathcal{N}(0, 0.01)$,计算T=100步后的期望位置偏差。
练习15.2:DAgger数据聚合 假设初始数据集有1000个专家样本,每轮DAgger收集500个新样本。如果使用0.5的混合比例(50%新数据,50%历史数据),第3轮训练时的有效数据集大小是多少?
练习15.3:GAIL判别器输出解释 如果GAIL的判别器对专家数据输出0.9,对策略数据输出0.3,这意味着什么?应该如何调整训练?
练习15.4:多模态动作处理 设计一个混合密度网络(MDN)来处理十字路口左转/直行的多模态决策。网络应该输出什么?如何从输出中采样动作?
练习15.5:IRL奖励函数设计 给定抓取任务的专家演示,设计一个包含以下特征的奖励函数:距离物体、夹爪开合度、末端速度。如何确定特征权重?
练习15.6:离线RL数据集诊断 给定一个机器人操作数据集,设计一个诊断流程来评估其是否适合离线RL训练。应该检查哪些指标?
练习15.7:安全DAgger实现 设计一个安全的DAgger变体用于真实机器人,要求:1)防止危险动作,2)最小化人工干预,3)保证学习效率。
练习15.8:GAIL训练调试 你的GAIL训练出现模式崩塌(所有轨迹相似)。列出可能的原因和对应的解决方案。
错误:只收集成功轨迹
# 错误:偏向性数据收集
if task_successful:
dataset.add(trajectory) # 只记录成功案例
正确:包含失败和恢复
# 正确:全面数据收集
dataset.add(trajectory)
if not task_successful:
dataset.add_recovery_demo() # 添加恢复演示
错误:独立处理每个时间步
# 错误:忽略历史
action = policy(current_state)
正确:考虑时序依赖
# 正确:包含历史
action = policy(current_state, history[-k:])
错误:训练后直接部署
# 错误:忽视分布偏移
model.eval()
deploy_to_robot(model) # 危险!
正确:渐进式部署
# 正确:安全部署
for confidence_threshold in [0.9, 0.7, 0.5]:
deploy_with_safety_net(model, confidence_threshold)
collect_failure_cases()
retrain_model()
错误:固定超参数
# 错误:不adaptive
d_optimizer = Adam(lr=1e-3)
g_optimizer = Adam(lr=1e-3)
正确:自适应调整
# 正确:动态调整
if discriminator_accuracy > 0.8:
d_lr *= 0.5 # 降低判别器学习率
if generator_loss > threshold:
g_lr *= 1.1 # 提高生成器学习率
错误:直接最大化Q值
# 错误:Q值过估计
action = argmax(Q(state, a) for a in action_space)
正确:保守估计
# 正确:CQL保守策略
action = sample_from_policy(state)
if Q(state, action) < conservative_threshold:
action = closest_dataset_action(state)