作者的话 :在前面的强化学习系列中,我们学习了如何让AI通过与环境交互来学习最优策略------这是无模型(Model-Free) 的方法。但在许多实际应用中,我们拥有系统的动力学模型(如物理定律、运动学方程),这时可以使用模型预测控制(Model Predictive Control, MPC)。MPC利用模型预测未来,通过优化求解最优控制序列,具有可解释性强、安全性高、sample效率高等优点。本文将带你深入理解MPC的原理,并实现完整的MPC控制器!
一、为什么需要MPC?
1.1 强化学习的局限
回顾Model-Free RL的特点:
- 通过与环境交互学习
- 不需要系统模型
- 适用于复杂、难以建模的系统
Model-Free RL的局限:
| 局限 | 说明 | 影响 |
|---|---|---|
| 样本效率低 | 需要大量交互数据 | 训练时间长,成本高 |
| 安全性问题 | 探索可能导致危险行为 | 难以用于安全关键系统 |
| 可解释性差 | 神经网络是黑盒 | 难以调试和验证 |
| 泛化能力有限 | 对分布外情况表现差 | 需要重新训练 |
1.2 模型-based方法的优势
当我们拥有系统模型时:
x_{t+1} = f(x_t, u_t)
其中:
- x_t:系统状态
- u_t:控制输入
- f:动力学模型(已知)
模型-based方法的优势:
| 优势 | 说明 |
|---|---|
| 样本效率高 | 不需要与环境交互,直接利用模型预测 |
| 安全性可控 | 可以加入约束,避免危险状态 |
| 可解释性强 | 优化问题有明确的数学形式 |
| 实时性好 | 单次优化求解速度快 |
1.3 MPC的诞生与发展
MPC的核心思想:
每一步:
1. 测量当前状态 x_t
2. 求解优化问题(预测未来N步)
3. 执行第一个控制动作 u_t*
4. 等待下一步,重复
MPC vs 传统反馈控制:
| 特性 | PID控制 | MPC |
|---|---|---|
| 使用模型 | 否 | 是 |
| 处理约束 | 困难 | 自然融入 |
| 多输入多输出 | 需解耦设计 | 天然支持MIMO |
| 预测能力 | 无 | 有 |
| 计算成本 | 低 | 中-高 |
二、MPC的核心原理
2.1 MPC的标准形式
优化问题:
min J = Σ[(x_k - x_ref)^T Q (x_k - x_ref) + u_k^T R u_k] + 终端代价
约束条件:
x_{k+1} = f(x_k, u_k) (动力学约束)
x_min ≤ x_k ≤ x_max (状态约束)
u_min ≤ u_k ≤ u_max (输入约束)
2.2 滚动时域优化(Receding Horizon Control)
MPC的独特机制:
时间 t:求解未来N步的最优轨迹
x_t → x_{t+1} → ... → x_{t+N}
u_t u_{t+1} u_{t+N-1}
只执行 u_t,然后:
↓
时间 t+1:重新测量状态,再次求解
x_{t+1} → x_{t+2} → ... → x_{t+N+1}
u_{t+1} u_{t+2} u_{t+N}
重复此过程...
2.3 线性MPC
当系统为线性时:
x_{k+1} = A x_k + B u_k
优化问题变为二次规划(QP):
min 0.5 * u^T H u + g^T u
s.t. A_eq u = b_eq
A_ineq u ≤ b_ineq
QP的优势:
- 凸优化问题,有全局最优解
- 求解速度快(毫秒级)
三、线性MPC的完整实现
3.1 MPC控制器类
import numpy as np
from scipy.linalg import block_diag
import cvxpy as cp
class LinearMPC:
"""
线性MPC控制器
适用于系统:x_{k+1} = A x_k + B u_k
"""
def __init__(self, A, B, Q, R, P, N, dt,
x_min=None, x_max=None, u_min=None, u_max=None):
self.A = A
self.B = B
self.Q = Q
self.R = R
self.P = P
self.N = N
self.dt = dt
self.n_states = A.shape[0]
self.n_inputs = B.shape[1]
# 约束
self.x_min = x_min if x_min is not None else -np.inf * np.ones(self.n_states)
self.x_max = x_max if x_max is not None else np.inf * np.ones(self.n_states)
self.u_min = u_min if u_min is not None else -np.inf * np.ones(self.n_inputs)
self.u_max = u_max if u_max is not None else np.inf * np.ones(self.n_inputs)
# 预计算QP问题矩阵
self._build_qp_matrices()
def _build_qp_matrices(self):
"""构建QP问题的Hessian矩阵和约束矩阵"""
n, m = self.n_states, self.n_inputs
N = self.N
# 构建预测矩阵
A_powers = [np.eye(n)]
for i in range(1, N + 1):
A_powers.append(A_powers[-1] @ self.A)
# 构建可控性矩阵
S = np.zeros((n * (N + 1), m * N))
for i in range(N + 1):
for j in range(N):
if i > j:
power = i - j - 1
S[i*n:(i+1)*n, j*m:(j+1)*m] = A_powers[power] @ self.B
# 构建权重矩阵
Q_bar = block_diag(*([self.Q] * N + [self.P]))
R_bar = block_diag(*([self.R] * N))
# Hessian矩阵: H = S^T Q_bar S + R_bar
self.H = S.T @ Q_bar @ S + R_bar
self.H = (self.H + self.H.T) / 2
self.S = S
self.A_powers = A_powers
# 构建约束矩阵
self._build_constraint_matrices()
def _build_constraint_matrices(self):
"""构建约束矩阵"""
n, m = self.n_states, self.n_inputs
N = self.N
# 输入约束
A_u = np.vstack([np.eye(m * N), -np.eye(m * N)])
b_u = np.hstack([np.tile(self.u_max, N), -np.tile(self.u_min, N)])
self.A_con = A_u
self.b_con = b_u
def solve(self, x0, x_ref):
"""求解MPC优化问题"""
n, m = self.n_states, self.n_inputs
N = self.N
# 构建线性项 g
x_ref_stacked = np.tile(x_ref, N + 1)
x0_stacked = np.zeros(n * (N + 1))
for i in range(N + 1):
x0_stacked[i*n:(i+1)*n] = self.A_powers[i] @ x0
Q_bar = block_diag(*([self.Q] * N + [self.P]))
self.g = 2 * self.S.T @ Q_bar @ (x0_stacked - x_ref_stacked)
# 使用CVXPY求解QP问题
u = cp.Variable(m * N)
objective = cp.Minimize(0.5 * cp.quad_form(u, self.H) + self.g @ u)
constraints = [self.A_con @ u <= self.b_con]
problem = cp.Problem(objective, constraints)
problem.solve(solver=cp.OSQP, verbose=False)
if problem.status not in ["optimal", "optimal_inaccurate"]:
print(f"Warning: MPC solver status: {problem.status}")
return np.zeros(m), None, None
# 提取最优控制序列
U_pred = u.value.reshape(N, m)
u_opt = U_pred[0] # 只执行第一个控制
# 预测状态轨迹
X_pred = np.zeros((N + 1, n))
X_pred[0] = x0
for i in range(N):
X_pred[i + 1] = self.A @ X_pred[i] + self.B @ U_pred[i]
return u_opt, X_pred, U_pred
def get_control(self, x, x_ref):
"""获取控制输入"""
u, _, _ = self.solve(x, x_ref)
return u
3.2 双积分器系统示例
class DoubleIntegratorMPC:
"""
双积分器系统的MPC控制
系统:位置p,速度v,控制输入a(加速度)
p_dot = v
v_dot = a
"""
def __init__(self, dt=0.1, N=20):
self.dt = dt
# 状态: [位置, 速度]
# 输入: [加速度]
A = np.array([[1, dt],
[0, 1]])
B = np.array([[0.5 * dt**2],
[dt]])
# 权重矩阵
Q = np.diag([1.0, 0.1]) # 更关注位置
R = np.array([[0.01]]) # 小惩罚加速度
P = np.diag([10.0, 1.0]) # 终端代价
# 约束
x_min = np.array([-10, -5])
x_max = np.array([10, 5])
u_min = np.array([-2]) # 最大减速度
u_max = np.array([2]) # 最大加速度
self.mpc = LinearMPC(A, B, Q, R, P, N, dt,
x_min, x_max, u_min, u_max)
self.x_history = []
self.u_history = []
def simulate(self, x0, x_ref, T=100):
"""仿真MPC控制"""
x = x0.copy()
for t in range(T):
self.x_history.append(x.copy())
u = self.mpc.get_control(x, x_ref)
self.u_history.append(u.copy())
# 应用控制
x = self.mpc.A @ x + self.mpc.B @ u
if np.linalg.norm(x - x_ref) < 0.1:
print(f"Goal reached at step {t}")
break
return np.array(self.x_history), np.array(self.u_history)
# 运行示例
if __name__ == "__main__":
controller = DoubleIntegratorMPC(dt=0.1, N=20)
x0 = np.array([5.0, 0.0]) # 初始状态
x_ref = np.array([0.0, 0.0]) # 目标状态
print("🚀 Simulating Double Integrator with MPC...")
x_hist, u_hist = controller.simulate(x0, x_ref, T=100)
四、非线性MPC简介
4.1 非线性系统的挑战
当系统为非线性时:
x_{k+1} = f(x_k, u_k)
其中 f 是非线性函数。
挑战:
- 优化问题变为非凸
- 可能存在多个局部最优
- 求解速度慢
- 需要迭代优化方法
4.2 常用求解方法
| 方法 | 原理 | 适用场景 |
|---|---|---|
| SQP | 序列求解QP | 一般非线性系统 |
| Interior Point | 内点法 | 大规模问题 |
| DDP | 动态规划变体 | 机器人控制 |
| iLQR/iLQG | 迭代LQR | 轨迹优化 |
五、实战项目:倒立摆MPC控制
5.1 倒立摆系统模型
系统方程(线性化后):
状态: [theta, theta_dot, x, x_dot]
输入: [F]
A = [[0, 1, 0, 0],
[g/l, 0, 0, 0],
[0, 0, 0, 1],
[0, 0, 0, 0]]
B = [[0],
[-1/(m*l^2)],
[0],
[1/M]]
5.2 完整实现
class CartPoleMPC:
"""
倒立摆MPC控制器
控制目标:保持摆杆直立,同时控制小车位置
"""
def __init__(self, m=1.0, M=5.0, L=2.0, dt=0.05, N=30):
self.m = m
self.M = M
self.L = L
self.dt = dt
self.g = 9.81
# 线性化系统矩阵(在theta=0附近)
A = np.array([
[0, 1, 0, 0],
[self.g / L, 0, 0, 0],
[0, 0, 0, 1],
[0, 0, 0, 0]
])
B = np.array([[0], [-1 / (m * L)], [0], [1 / M]])
# 离散化(欧拉法)
A_d = np.eye(4) + A * dt
B_d = B * dt
# 权重矩阵
Q = np.diag([10.0, 1.0, 5.0, 0.5]) # 重视角度和位置
R = np.array([[0.1]]) # 适度惩罚力
P = np.diag([20.0, 2.0, 10.0, 1.0]) # 终端代价
# 约束
x_min = np.array([-np.pi/4, -5, -5, -10]) # 角度限制±45度
x_max = np.array([np.pi/4, 5, 5, 10])
u_min = np.array([-20]) # 最大力±20N
u_max = np.array([20])
self.mpc = LinearMPC(A_d, B_d, Q, R, P, N, dt,
x_min, x_max, u_min, u_max)
self.x_history = []
self.u_history = []
self.ref_history = []
def simulate(self, x0, trajectory, T=200):
"""仿真MPC跟踪轨迹"""
x = x0.copy()
for t in range(T):
ref = trajectory(t * self.dt)
x_ref = np.array([ref[0], 0, ref[1], 0])
self.x_history.append(x.copy())
self.ref_history.append(x_ref.copy())
u = self.mpc.get_control(x, x_ref)
self.u_history.append(u.copy())
# 应用控制(使用真实动力学)
x = self._true_dynamics(x, u)
if abs(x[0]) > np.pi/3:
print(f"Pole fell at step {t}!")
break
return (np.array(self.x_history),
np.array(self.u_history),
np.array(self.ref_history))
def _true_dynamics(self, x, u, noise=True):
"""真实动力学(非线性,可加噪声)"""
theta, theta_dot, pos, vel = x
F = u[0]
cos_theta = np.cos(theta)
sin_theta = np.sin(theta)
denom = self.M + self.m * sin_theta**2
theta_ddot = ((self.M + self.m) * self.g * sin_theta
- self.m * self.L * theta_dot**2 * sin_theta * cos_theta
- F * cos_theta) / (self.L * denom)
pos_ddot = (F + self.m * self.L * theta_dot**2 * sin_theta
- self.m * self.g * sin_theta * cos_theta) / denom
theta_dot_new = theta_dot + theta_ddot * self.dt
theta_new = theta + theta_dot_new * self.dt
vel_new = vel + pos_ddot * self.dt
pos_new = pos + vel_new * self.dt
if noise:
theta_new += np.random.normal(0, 0.01)
pos_new += np.random.normal(0, 0.01)
return np.array([theta_new, theta_dot_new, pos_new, vel_new])
# 运行示例
if __name__ == "__main__":
controller = CartPoleMPC(m=1.0, M=5.0, L=2.0, dt=0.05, N=30)
x0 = np.array([np.pi/12, 0, 0, 0]) # 15度偏角
# 目标轨迹:从x=0移动到x=2,再回到x=0
def trajectory(t):
period = 10.0
x_ref = 2.0 * np.sin(2 * np.pi * t / period)
return [0, x_ref]
print("🚀 Simulating CartPole with MPC...")
x_hist, u_hist, ref_hist = controller.simulate(x0, trajectory, T=400)
六、MPC与强化学习的结合
6.1 两者互补
| 特性 | MPC | RL |
|---|---|---|
| 需要模型 | 是 | 否 |
| 处理约束 | 自然 | 困难 |
| 样本效率 | 高 | 低 |
| 适应能力 | 有限 | 强 |
| 复杂系统 | 困难 | 适合 |
6.2 结合方式
1. 用RL学习MPC的终端代价
2. 用RL学习残差模型:
# 真实动力学 = 标称模型 + 残差
# x_next = f_nominal(x, u) + f_residual(x, u)
# 用神经网络学习 f_residual
3. MPC作为RL的策略:
# 使用MPC生成专家数据
# 用模仿学习训练神经网络策略
七、MPC的应用领域
| 领域 | 应用 | 特点 |
|---|---|---|
| 自动驾驶 | 路径跟踪、避障 | 安全约束、实时性 |
| 机器人 | 机械臂控制、行走 | 多关节协调、避障 |
| 化工过程 | 反应器控制、精馏 | 慢动态、安全关键 |
| 能源 | 电网调度、储能 | 多目标优化、约束多 |
| 航空航天 | 飞行器控制、着陆 | 高精度、强约束 |
| 建筑 | HVAC控制 | 节能、舒适度 |
八、总结
8.1 MPC的核心要点
- 核心思想:利用模型预测未来、滚动时域优化、反馈校正
- 关键优势:自然处理约束、可解释性强、样本效率高、安全性可控
- 实现要点:线性MPC → QP问题;非线性MPC → NLP问题
8.2 学习路径总结
Model-Free RL(第33-36篇)
├── Q-Learning / DQN
├── Actor-Critic / PPO
└── MARL
Model-Based Control(本篇文章)
├── MPC(线性)
└── MPC(非线性)
结合方向
├── Learning-based MPC
└── MPC-guided RL
下一篇预告:【第38篇】模仿学习入门:让AI向专家学习
我们将进入模仿学习领域,探索如何让AI通过观察专家行为来学习策略,而不需要与环境进行大量交互!
本文为系列第37篇,详细介绍了模型预测控制MPC的原理与实现。有任何问题欢迎在评论区交流!
标签:模型预测控制、MPC、优化控制、倒立摆、约束优化、实时控制