人工智能【第37篇】模型预测控制MPC:AI的预测与规划能力

作者的话 :在前面的强化学习系列中,我们学习了如何让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的核心要点

  1. 核心思想:利用模型预测未来、滚动时域优化、反馈校正
  2. 关键优势:自然处理约束、可解释性强、样本效率高、安全性可控
  3. 实现要点:线性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、优化控制、倒立摆、约束优化、实时控制

相关推荐
jrrz08282 天前
Apollo MPC Controller
c++·自动驾驶·apollo·mpc·横向控制·lateral control
jrrz08283 天前
Apollo OSQP库交叉编译
vscode·apollo·交叉编译·mpc·osqp
Zevalin爱灰灰23 天前
现代控制理论——专题 模型预测控制【上】
mpc
Robot_Nav1 个月前
DPMPC-Planner:复杂静态环境与动态障碍物下的无人机实时轨迹规划框架
c++·无人机·mpc
ergevv1 个月前
从阿克曼几何到 QP 求解器输入:自动驾驶横向 MPC 的完整数学链条(1)
自动驾驶·控制·mpc
?Anita Zhang1 个月前
隐私计算平台技术选型指南:核心能力对比与工程实践建议
数据安全·联邦学习·隐私计算·技术选型·mpc
白云千载尽4 个月前
交换空间扩容与删除、hugginface更换默认目录、ffmpeg视频处理、清理空间
python·ffmpeg·控制·mpc·navsim
白云千载尽4 个月前
LQR与MPC.入门知识与实验
python·控制·mpc·lqr
沛沛老爹5 个月前
MCP 实战:打造股票分析助手系统
java·rag·企业开发·mpc·发展趋势·web转型