机器人学习 机器人学习(robot learning)架起了算法与物理动作之间的桥梁。本文件涵盖运动学、动力学、经典控制、模仿学习、sim-to-real 迁移、操作、移动与安全——这些技术赋予机器人移动、抓取、行走以及与真实世界交互的能力。 在前几章中,我们学习了如何感知世界(第 8 章、第 11 章第 1 节),以及如何从数据中学习(第 6 章)。但光有感知和学习是不够的。机器人必须能够行动:移动手臂去抓一个杯子,走过不平坦的地形,或者在仓库里导航。这就是机器人学习登场的地方。 核心挑战在于:物理世界是连续的、高维的、富含接触的,而且毫不留情。图像识别里的一次分类错误,不过是一个错误的标签;而机器人控制里的一次错误,可能就是一台摔坏的机器人或一个掉落的物体。代价完全不同。
机器人学习(robot learning)架起了算法与物理动作之间的桥梁。本文件涵盖运动学、动力学、经典控制、模仿学习、sim-to-real 迁移、操作、移动与安全——这些技术赋予机器人移动、抓取、行走以及与真实世界交互的能力。
在前几章中,我们学习了如何感知世界(第 8 章、第 11 章第 1 节),以及如何从数据中学习(第 6 章)。但光有感知和学习是不够的。机器人必须能够行动:移动手臂去抓一个杯子,走过不平坦的地形,或者在仓库里导航。这就是机器人学习登场的地方。
核心挑战在于:物理世界是连续的、高维的、富含接触的,而且毫不留情。图像识别里的一次分类错误,不过是一个错误的标签;而机器人控制里的一次错误,可能就是一台摔坏的机器人或一个掉落的物体。代价完全不同。
**运动学(kinematics)**在不考虑力的情况下描述运动的几何。一个机械臂是由若干刚性连杆通过关节串联而成的链条。每个关节有一个自由度(degree of freedom,DoF):要么旋转(旋转副),要么滑动(移动副)。
机器人的位形(configuration)是所有关节角度(或位移)的集合 \mathbf{q} = [q_1, q_2, \ldots, q_n]^T。这个向量生活在关节空间(或位形空间)里,这是一个 n 维空间,每个轴对应一个关节。一个 6-DoF 的机械臂有一个 6 维的位形空间。
正运动学(forward kinematics,FK)根据关节角度计算末端执行器("手")的位置和朝向。这是一个从关节空间映射到任务空间(末端执行器的 3D 位置和朝向,也叫笛卡尔空间)的函数 \mathbf{x} = f(\mathbf{q})。
每个关节用一个 4 \times 4 的齐次变换矩阵描述(回顾第 2 章的仿射变换)。Denavit-Hartenberg(DH)约定用四个参数来刻画每个关节:连杆长度 a、连杆扭角 \alpha、连杆偏置 d 和关节角 \theta。关节 i 的变换为:
完整的正运动学是所有关节变换的乘积:T_{0 \to n} = T_1 T_2 \cdots T_n。这就是变换的矩阵连乘(第 2 章):每个关节的变换依次施加,把坐标系从基座一路旋转、平移到末端执行器。
**逆运动学(inverse kinematics,IK)**是反过来的问题:给定期望的末端执行器位姿 \mathbf{x}^*,求出关节角 \mathbf{q} 使得 f(\mathbf{q}) = \mathbf{x}^*。这要难得多,因为:
解析解只对特定的机器人几何存在。对于一般的机器人,IK 用**雅可比(Jacobian)**迭代求解。雅可比 J(\mathbf{q}) 把关节角的微小变化与末端执行器位置的微小变化联系起来(回顾第 3 章的雅可比):
要把末端执行器移动一小段 \Delta \mathbf{x},我们需要 \Delta \mathbf{q} = J^{-1} \Delta \mathbf{x}(当 J 不是方阵时用伪逆 J^+ \Delta \mathbf{x})。重复迭代直到末端执行器到达目标,这本质上就是把牛顿法(第 3 章)应用到运动学方程上。
在**奇异点(singularities)**附近,雅可比会丢失秩(某些列变得线性相关,我们在第 2 章学过)。物理上的含义是机器人丢失了一个自由度:无论关节动得多快,末端执行器都无法朝某些方向移动。伪逆在奇异点附近会爆炸,所以改用阻尼最小二乘(加一个正则项 \lambda^2 I):
其中 M(\mathbf{q}) 是质量(惯性)矩阵,C(\mathbf{q}, \dot{\mathbf{q}}) 刻画科氏力和离心效应,\mathbf{g}(\mathbf{q}) 是重力向量,\boldsymbol{\tau} 是关节力矩向量(即控制输入)。这是一组二阶微分方程,每个关节一个。
质量矩阵 M 总是对称正定的(回顾第 2 章:正定矩阵保证唯一的最小值,在这里它确保系统对施加的力矩做出可预测的响应)。
PID 控制是机器人领域最广泛使用的控制器。对每个关节,它根据误差 e(t) = q_{\text{desired}}(t) - q_{\text{actual}}(t) 计算力矩:
调 K_p, K_i, K_d 是一种平衡:K_p 太大会引起振荡,K_d 太大会让系统迟钝,K_i 太大会引起积分饱和(在持续误差期间积分项无限增长)。
**模型预测控制(Model Predictive Control,MPC)**会向前看。在每个时间步,它求解一个优化问题:在有限的时间范围内,找到一串未来的控制量,使得代价函数(如跟踪误差 + 控制能耗)最小化,同时满足动力学模型和约束。只施加第一个控制量,然后在下一个时间步重复整个过程。
这里 \|\mathbf{x}\|_Q^2 = \mathbf{x}^T Q \mathbf{x} 是用正定矩阵 Q(第 2 章)定义的加权范数,可以让你对不同状态误差施以不同的惩罚。MPC 天然地处理约束(关节限位、力矩限位、避障),因为这些约束被显式地写进了优化里。
**阻抗控制(impedance control)**调节的是力与运动之间的关系,而不是去死板地跟踪一条轨迹。它不再下达"去到位置 x"的指令,而是下达"表现得像一个以 x 为中心的弹簧-阻尼系统"的指令:
与其手工设计控制器,不如从演示中学习控制策略。人类执行任务,机器人观察,学习算法提取出策略。这就是模仿学习(imitation learning)(也叫从演示中学习)。
**行为克隆(behavioural cloning,BC)**是最简单的方法:把演示当作一个监督学习数据集。给定来自专家的观测-动作对 \{(\mathbf{o}_t, \mathbf{a}_t)\},训练一个策略 \pi_\theta(\mathbf{a} \mid \mathbf{o}) 来从观测预测专家的动作。这就是标准的监督学习(第 6 章):最小化损失:
问题在于分布偏移(distribution shift)(也叫累积误差问题)。训练时,策略看到的是专家的状态。部署时,策略自身的小误差会把它推到专家从未访问过的状态。这些陌生状态会导致更差的动作,进而导致更陌生的状态,误差迅速累积。
想象一下通过观察一个完美司机来学开车。你从未见过一次轻微偏离之后会发生什么,因为专家从不会偏离。第一次你稍微跑偏时,你完全不知道怎么纠正。
DAgger(Dataset Aggregation,数据集聚合)通过迭代来解决这个问题:
经过若干轮迭代,数据集会覆盖学到的策略实际访问的状态,而不仅仅是专家的轨迹。策略会改进,因为它见过并学会从自己的错误中恢复。
**基于 transformer 的动作分块(Action Chunking with Transformers,ACT)**是一种现代方法,策略一次预测一串未来动作(一个"块"),而不是一次只预测一个动作。它用一个带 transformer 主干的条件 VAE 来实现。预测动作块更鲁棒,因为它捕捉了时序相关性:一个抓取动作的平滑性被编码在块里,而不是依赖于容易漂移的自回归单步预测。
Diffusion Policy 把扩散模型(第 8 章)用于动作生成。它不预测单个动作,而是建模在给定观测下所有可能动作的完整分布。从噪声出发,它迭代地去噪以产生一个动作序列。这天然地处理了多模态性:当完成任务存在多种有效方式时(从左边够还是从右边够),扩散模型可以同时表示这两种模式,而回归式策略则会把它们平均(结果可能哪边都不是,落在中间无效位置)。
在真实世界中训练机器人既昂贵又缓慢,还危险。一个通过试错学习抓取的机器人可能需要上千次尝试,过程中还会弄坏物体和自己。**仿真(simulation)**提供无限、安全、快速的经验。但仿真器并不完美:物理是近似的,视觉是合成的,接触是被简化的。
sim-to-real 鸿沟指的是仿真性能与真实性能之间的差距。一个在仿真中表现完美的策略,可能在真实机器人上彻底失败,因为它过拟合了仿真器特有的细节。
**域随机化(domain randomisation)**通过在大量不同的仿真器设置下训练来对抗这一问题。不是只用一个仿真,而是用上千个,并随机化:
思路是:如果策略在所有这些变化下都能工作,那么真实世界不过是分布中"又一个变体"。策略学到的是对随机化属性不变的特征,而这些不变特征能够迁移。
**系统辨识(system identification)**走相反的路线:不是随机化一切,而是仔细测量真实系统的物理参数,并把仿真器调到与之匹配。这能给出更准确的仿真,但很脆弱(任何未建模的效应都会造成差距)。
实践中,最好的结果是把两者结合:用系统辨识把仿真器调到大致接近,再用域随机化覆盖剩余的不确定性。
通过微调实现 sim-to-real主要在仿真中训练,然后做少量真实世界微调。仿真提供了良好的初始化,真实世界数据则修正仿真器特有的偏差。这比从头训练所需的真实世界数据少得多。
上面所有的 RL 和模仿学习方法都是**无模型(model-free)的:策略通过直接交互(或演示)学习行动,而不会显式地建模世界的运作方式。另一种选择是基于模型(model-based)**的学习:先学习一个环境动力学模型,再用这个模型去规划或生成合成经验。
**世界模型(world model)**学习转移函数 p(s_{t+1} \mid s_t, a_t):给定当前状态和一个动作,预测下一个状态(第 10 章已介绍)。在机器人领域,这意味着预测机器人采取某个动作后会发生什么:"如果我把这个方块往左推,它会滑动 3cm,后面的杯子会倒。"
它的吸引力在于样本效率。真实世界的机器人交互非常昂贵。如果机器人能从适量的真实数据中学到一个世界模型,它就可以在脑中"推演"模型来"想象"成千上万条轨迹,在不接触物理世界的情况下规划和改进策略。这类似于棋手在脑中模拟棋局来预演。
DreamerV3 是一个通用的基于模型的 RL 智能体。它联合学习三个组件:
然后智能体通过在潜在空间中多步推演转移模型来"做梦",在这些想象的轨迹上训练策略,再把策略迁移到真实环境。关键创新在于所有的想象都发生在潜在空间(紧凑的学习表示)而不是像素空间,从而在计算上变得可行。
转移模型 f_\theta 和奖励模型 g_\theta 在真实经验上训练,而策略在想象的推演上训练。这把数据采集和策略优化解耦了。
对于机器人操作,世界模型使得**心理预演(mental rehearsal)**成为可能。在尝试一次抓取之前,机器人可以在它学到的模型中模拟几种方案,挑出最可能成功的那一个。这对富含接触的任务尤其有价值,因为真实世界的试错既慢又有风险。
世界模型还自然而然地与 sim-to-real 联系起来:在真实数据上训练的世界模型实际上就是一个学到的仿真器,它自动捕捉真实世界物理,完全绕过了 sim-to-real 鸿沟。对于人们已经充分理解的场景,它可能不如手工搭建的仿真器精确,但它能捕捉那些手工仿真器经常搞错的效应(摩擦、变形、接触动力学)。
JEPA(Joint Embedding Predictive Architecture,联合嵌入预测架构,第 10 章已介绍)为像素级预测提供了一种替代方案。JEPA 不预测精确的未来观测,而是在嵌入空间中预测:"下一个状态的潜在表示会接近这个向量。"这避免了预测像素级完美未来的困难(既无必要又在计算上浪费),把精力集中在预测那些对决策有意义的未来方面。
世界模型的局限是累积预测误差。转移模型中的微小不准确会在长程推演中累积,导致想象的轨迹偏离现实。缓解手段包括:短的想象时域、集成模型(用不确定性来检测预测何时变得不可靠)、以及周期性地用新鲜的真实数据来校准模型。
**操作(manipulation)**是使用机器人的末端执行器与物体交互的艺术:拾取、放置、推、插、装配。
**抓取(grasping)**是操作的基础技能。目标是找到一个稳定的抓取位姿:夹爪的一个位置和朝向,能够牢固地握住物体。
解析式抓取规划用物理来判定。如果接触力能够抵抗外部力旋(力和力矩),那么这个抓取就是稳定的。对于平行爪夹爪,最简单的判据是**力闭合(force closure)**条件:接触法线必须张满所有力方向,这样抓取才能抵抗任何扰动。这涉及检查抓取力旋矩阵的秩,这是第 2 章秩概念的直接应用。
数据驱动的抓取学习从传感输入预测抓取是否成功。给定桌上物体的深度图,一个网络为每个候选夹爪位姿预测一个抓取质量分数。GraspNet 及类似架构使用点云编码器(PointNet 风格,第 8 章)来预测带置信度分数的 6-DoF 抓取位姿(位置 + 朝向)。
**灵巧操作(dexterous manipulation)**超越了简单的拾取和放置。一只多指手有 20 多个 DoF,能完成诸如手中旋转(在手指间转笔)、使用工具、精细装配等任务。状态空间巨大、接触复杂,使这成为机器人领域最难的问题之一。
学习灵巧操作通常使用仿真中的强化学习(第 6 章),并大量使用域随机化。OpenAI 用 Shadow 手解魔方的工作,就是在随机化物理的仿真中训练 PPO 策略,并成功迁移到真实机器人手上。
富含接触的任务(如销钉插孔或擦拭表面)要求机器人与环境保持受控的接触。这类任务需要力感知和柔顺控制(阻抗控制),而且很难精确仿真,因为接触物理出了名地难建模。
移动(locomotion)是让机器人的身体在世界上穿行:走、跑、爬、游。与操作的关键区别在于:机器人在移动时必须保持平衡,而且与地面的接触点会随时间变化。
足式移动之所以困难,是因为它本质上是不稳定的。一个双足机器人(人形)在迈步时单腿站立,就像一个倒立摆。质心必须保持在支撑多边形(与地面接触的脚所构成的凸包)之上,否则机器人就会摔倒。
**零力矩点(Zero Moment Point,ZMP)**是地面上这样一个点:来自重力和惯性力的合力矩在该点为零。如果 ZMP 保持在支撑多边形内,机器人就不会翻倒。传统的人形控制器(如本田的 ASIMO)会规划轨迹以把 ZMP 保持在边界内。
**中枢模式发生器(Central Pattern Generators,CPGs)**是受生物学启发的、基于振荡器的控制器。动物利用脊髓中的神经回路产生节律性移动模式(走、小跑、奔跑),而不需要大脑时刻介入。CPG 模型使用耦合的微分方程:
其中 \phi_i 是振荡器 i 的相位,\omega_i 是固有频率,w_{ij} 是耦合强度,\psi_{ij} 是期望的相位偏移。不同的相位关系产生不同的步态:所有腿同步(跳跃)、交替成对(小跑)、依次(行走)。正弦耦合会自然地让振荡器同步,类似于傅里叶级数(第 3 章)把运动分解为频率分量。
用于移动的强化学习已成为敏捷四足和人形机器人的主流方法。机器人通过在仿真中的试错来学习策略 \pi(\mathbf{a} \mid \mathbf{o})(第 6 章),奖励包括前进速度、稳定性和能效,惩罚包括摔倒、违反关节限位和动作顿挫。
近期工作(如 Agility Robotics、Boston Dynamics 以及学术实验室)的关键洞见是:RL 训练出的移动策略比手工设计的控制器鲁棒得多。它们自然而然地学会了从推搡中恢复、适应地形变化,并应对工程师未曾预料的情况。训练通常使用 PPO(第 6 章)加域随机化。
四足机器人(如 Boston Dynamics Spot 或 Unitree Go2)已成为足式机器人的主力。四条腿天然稳定(三条腿总能撑住身体,同时腾出一条腿移动)。四足的 RL 策略取得了令人瞩目的成果:以 3+ m/s 奔跑、爬楼梯、穿越碎石地形、并从踹击中恢复。
人形移动更难,因为双足的支撑多边形更小、质心更高。最近的进展(Tesla Optimus、Figure、Unitree H1)使用在仿真中训练的 RL,并配合精心的奖励塑形。人形不仅要学会走路,还要协调摆臂以保持平衡、在不平地面上行走、并从扰动中恢复。
一个通过随机探索来学习的机器人(如 RL 那样)可能损坏自己、环境或附近的人。安全的机器人学习约束探索过程,以避免灾难性后果。
**约束 RL(constrained RL)**在 MDP(第 6 章)中加入安全约束。目标变成:在满足 J_c(\pi) \leq d 的条件下最大化奖励,其中 J_c 是期望累计代价(如碰撞事件),d 是允许的最大代价。像 Constrained Policy Optimisation(CPO)这样的算法把 PPO 扩展到处理这些约束。
**安全包络(safety envelopes)**定义了机器人绝不能越过的硬边界,无论学到的策略怎么说。一个安全控制器监视机器人的状态,并在某个约束即将被违反时(如接近关节限位、在人附近移动过快、或超过力阈值)覆盖学到的策略。这是一种分层架构:学习算法负责性能,安全层负责约束。
**风险感知规划(risk-aware planning)**显式地对环境和机器人自身状态估计中的不确定性建模。它不是为最可能的结果做规划,而是在某个置信区间内为最坏情况做规划。这与条件数的概念(第 2 章)相关:一个良态系统对扰动鲁棒,而风险感知规划寻求在扰动下仍然安全的控制策略。
import jax.numpy as jnp import matplotlib.pyplot as plt def forward_kinematics(q1, q2, l1=1.0, l2=0.8): """计算二连杆机械臂的关节和末端执行器位置。""" x1 = l1 * jnp.cos(q1) y1 = l1 * jnp.sin(q1) x2 = x1 + l2 * jnp.cos(q1 + q2) y2 = y1 + l2 * jnp.sin(q1 + q2) return jnp.array([0, x1, x2]), jnp.array([0, y1, y2]) fig, ax = plt.subplots(figsize=(6, 6)) configs = [(0.5, 0.3), (1.0, -0.5), (1.5, 1.0), (2.0, -1.5)] colors = ["#e74c3c", "#3498db", "#27ae60", "#9b59b6"] for (q1, q2), c in zip(configs, colors): xs, ys = forward_kinematics(q1, q2) ax.plot(xs, ys, "o-", color=c, linewidth=2, markersize=6, label=f"q=({q1:.1f}, {q2:.1f})") ax.set_xlim(-2, 2); ax.set_ylim(-2, 2) ax.set_aspect("equal"); ax.grid(True); ax.legend() ax.set_title("2-Link Robot Arm: Forward Kinematics") plt.show()
import jax import jax.numpy as jnp import matplotlib.pyplot as plt l1, l2 = 1.0, 0.8 def end_effector(q): x = l1 * jnp.cos(q[0]) + l2 * jnp.cos(q[0] + q[1]) y = l1 * jnp.sin(q[0]) + l2 * jnp.sin(q[0] + q[1]) return jnp.array([x, y]) jacobian_fn = jax.jacobian(end_effector) target = jnp.array([0.5, 1.2]) q = jnp.array([0.1, 0.1]) trajectory = [end_effector(q)] for _ in range(50): pos = end_effector(q) error = target - pos if jnp.linalg.norm(error) < 1e-4: break J = jacobian_fn(q) # 阻尼伪逆以处理接近奇异点的情形 dq = J.T @ jnp.linalg.solve(J @ J.T + 0.01 * jnp.eye(2), error) q = q + dq trajectory.append(end_effector(q)) traj = jnp.stack(trajectory) plt.plot(traj[:, 0], traj[:, 1], "b.-", label="end-effector path") plt.plot(*target, "r*", markersize=15, label="target") plt.gca().set_aspect("equal"); plt.grid(True); plt.legend() plt.title(f"IK converged in {len(trajectory)-1} steps") plt.show()
import jax.numpy as jnp import matplotlib.pyplot as plt # 期望轨迹:平滑的正弦运动 dt = 0.01 t = jnp.arange(0, 5, dt) q_desired = jnp.sin(2 * t) # 模拟二阶动力学:m * q_ddot + b * q_dot = tau m, b_damp = 1.0, 0.5 for Kp, Kd, Ki, label in [(10, 5, 0, "PD only"), (10, 5, 2, "PID"), (50, 10, 2, "Aggressive PID")]: q, q_dot, integral = 0.0, 0.0, 0.0 qs = [] for i in range(len(t)): error = q_desired[i] - q integral += error * dt d_error = -q_dot # 误差的导数(期望速度已知,这里做了简化) tau = Kp * error + Kd * d_error + Ki * integral q_ddot = (tau - b_damp * q_dot) / m q_dot += q_ddot * dt q += q_dot * dt qs.append(float(q)) plt.plot(t, qs, label=label) plt.plot(t, q_desired, "k--", label="desired", linewidth=2) plt.xlabel("Time (s)"); plt.ylabel("Joint angle") plt.legend(); plt.title("PID Controller Tracking") plt.show()