(四)LQG和iLQG原理、公式推导以及代码演示
在实际工程应用中,噪声是系统无法避免的因素,主要包括过程噪声和观测噪声。在自动控制、机器人、自主驾驶等领域,噪声的影响尤其显著。为了提高控制系统的鲁棒性和性能,像LQG和iLQG这样的算法被广泛应用。其主要思想是在LQR和ILQR的状态方程中增加高斯噪声,LQG 是结合LQR和卡尔曼滤波器减少噪声干扰,ILQG通过在Q-函数对状态的二阶导数中增加噪声的协方差矩阵来更新控制律,使得系统能够在噪声环境下得到更为鲁棒的控制方案。
1. LQG(Linear Quadratic Gaussian)
1.1. LQG 原理
LQG(线性二次高斯)控制用于解决线性系统在存在高斯噪声扰动下的最优控制问题。它结合了 LQR(线性二次调节器)与 Kalman 滤波器,能够同时处理不确定性和噪声。其中,LQR解决没有噪声的最优控制问题,Kalman 滤波器解决噪声下的状态估计问题。
状态方程:
其中,x_k是状态向量,u_k是控制输入,w_k是过程噪声,假设 w_k为均值为零的高斯白噪声,其协方差矩阵为 Q。
观测方程:
其中,y_k是观测向量,v_k是测量噪声,假设 v_k为均值为零的高斯白噪声,其协方差矩阵为 R。
1.2. LQG 应用场景
LQG 适用于以下场景:
- 系统受过程噪声和测量噪声干扰;
- 系统可以用线性模型描述,或者可以通过线性化处理非线性系统;
- 需要设计基于部分观测值进行最优控制的系统。
LQG 广泛应用于:
- 航空航天领域,如飞行器的轨迹跟踪和姿态控制;
- 机器人导航与控制;
- 自动驾驶中对车辆的路径跟踪;
- 电力系统的负载控制等。
1.3. LQG 公式推导
LQG 控制的推导分为两个主要步骤:首先是通过 Kalman 滤波器进行状态估计,然后通过 LQR 控制器设计最优控制律。
1.3.1 Kalman 滤波器推导
Kalman 滤波器的目的是在有噪声的情况下对系统状态进行最优估计。假设初始状态 x_0 的估计为且初始协方差为P_0,Kalman 滤波器的推导如下:
预测步骤:
预测协方差矩阵:
更新步骤:
更新状态估计:
更新协方差矩阵:
通过Kalman滤波器,系统可以得到噪声干扰下的状态估计。
1.3.2 LQR 推导
LQR 的目的是最小化某个性能指标 J,通过找到最优控制律。性能指标定义为:
其中,Q 是状态的权重矩阵,R 是控制输入的权重矩阵。
最优控制律可以通过解 Riccati 方程得到:
控制增益矩阵 K 为:
因此,LQR 控制律是:
1.3.3 LQG 控制律
将 Kalman 滤波器得到的状态估计代入 LQR 控制律,就可以得到 LQG 的控制律:
其中是Kalman滤波器估计得到的状态向量。
1.4 python代码
我们使用一个简单的状态方程 实现LQG 。考虑如下的线性系统:
其中,x(k)是状态,u(k)是控制输入,w(k)是过程噪声。
其中,y(k) 是观测值,v(k) 是测量噪声。
python
import numpy as np
import matplotlib.pyplot as plt
# 系统参数
A = np.array([[1]]) # 状态矩阵
B = np.array([[1]]) # 控制输入矩阵
Q_lqr = np.array([[1]]) # LQR 中的状态权重
R_lqr = np.array([[1]]) # LQR 中的控制权重
# 迭代求解 Riccati 方程
def solve_lqr_iterative(A, B, Q, R, max_iterations=1000, tol=1e-8):
P = Q.copy() # 初始化为状态权重矩阵 Q
for i in range(max_iterations):
# 计算控制增益矩阵 K
K = np.linalg.inv(B.T @ P @ B + R) @ (B.T @ P @ A)
# 更新 Riccati 矩阵 P
P_new = A.T @ P @ A - A.T @ P @ B @ K + Q
# 检查收敛
if np.max(np.abs(P_new - P)) < tol:
break
P = P_new
return K, P
# 使用迭代法求解 LQR 控制增益
K_lqr, P_lqr = solve_lqr_iterative(A, B, Q_lqr, R_lqr)
# LQR 增益已计算完毕,打印 K_lqr
print("LQR 控制增益 K_lqr:", K_lqr)
# Kalman 滤波器相关参数
Q = 1 # 状态噪声协方差
R = 1 # 测量噪声协方差
P0 = 1 # 初始协方差
n_steps = 50 # 仿真步数
# 初始化状态和估计
x_true = np.zeros(n_steps) # 真实状态
x_est = np.zeros(n_steps) # 估计状态
y = np.zeros(n_steps) # 观测值
u = np.zeros(n_steps) # 控制输入
P_est = P0 # 初始协方差
x_true[0] = 0 # 初始状态
x_est[0] = 0 # 初始估计
# 噪声
process_noise = np.random.normal(0, np.sqrt(Q), n_steps) # 过程噪声
measurement_noise = np.random.normal(0, np.sqrt(R), n_steps) # 测量噪声
# 仿真循环
for k in range(1, n_steps):
# 系统动态
x_true[k] = x_true[k-1] + u[k-1] + process_noise[k] # 真实状态更新
y[k] = x_true[k] + measurement_noise[k] # 观测值
# Kalman 滤波器预测
x_pred = x_est[k-1] + u[k-1]
P_pred = P_est + Q
# Kalman 滤波器更新
K_kalman = P_pred / (P_pred + R)
x_est[k] = x_pred + K_kalman * (y[k] - x_pred)
P_est = (1 - K_kalman) * P_pred
# LQR 控制律
u[k] = -K_lqr[0, 0] * x_est[k] # 这里直接用标量运算
# 绘制结果
plt.figure(figsize=(12, 6))
plt.subplot(3, 1, 1)
plt.plot(x_true, label='True State')
plt.plot(x_est, label='Estimated State', linestyle='--')
plt.legend()
plt.title('State (True vs Estimated)')
plt.subplot(3, 1, 2)
plt.plot(y, label='Measurements')
plt.legend()
plt.title('Measurements')
plt.subplot(3, 1, 3)
plt.plot(u, label='Control Input')
plt.legend()
plt.title('Control Input')
plt.tight_layout()
plt.show()
2. iLQG(iterative Linear Quadratic Gaussian)
2.1 iLQG原理
iLQG算法是iLQR的扩展版本,专门用于处理具有过程噪声的非线性系统。iLQG的基本原理是利用高斯分布来建模系统的过程噪声,并通过迭代优化策略,使得系统能够在噪声存在的情况下仍能得到一个稳定的最优控制策略。
假设系统的状态方程为:
其中:
是系统在时间步长的状态向量。
是控制向量。
是零均值高斯过程噪声,服从分布,其中是过程噪声的协方差矩阵。
目标是最小化以下具有期望的成本函数:
其中:
是运行成本函数。
是终端成本。
目标是通过选择合适的来最小化整个期望成本。
2.2. 系统的线性化
和iLQR类似,我们首先对系统状态方程和成本函数进行泰勒展开,以便能够在线性化的基础上进行局部优化。假设我们已有当前的控制策略,可以对进行一阶泰勒展开:
其中:
是对状态的雅可比矩阵。
是对控制的雅可比矩阵。
是状态偏差。
2.3. Q-函数的展开(考虑过程噪声)
Q-函数描述的是当前状态和控制下的瞬时成本以及未来期望成本之和。它可以表示为:
为了计算 Q-函数,需要对未来状态的期望进行计算,考虑到是随机变量,因此值函数也需要计算其期望值。通过状态方程我们可以知道:
这意味着状态更新的方差会影响未来期望成本的计算。
我们首先对值函数进行二阶泰勒展开:
带入状态更新方程:
由于是零均值的高斯噪声,且,可以得到:
噪声项的贡献体现在最后一项,它代表了未来状态的不确定性对期望成本的影响。这个项与过程噪声的协方差直接相关,且通过值函数的二阶导数进行权重调整。
2.4. Q-函数的梯度与二阶导数修正
现在,考虑的影响我们对 Q-函数的导数进行详细推导。
Q-函数的一阶导数
对于状态的导数:
这与无噪声的情形一致,因为噪声不影响状态的偏导数。
对于控制的导数:
同样,这里的一阶导数部分没有受到噪声的直接影响。
Q-函数的二阶导数
我们重点分析二阶导数部分,因为噪声的影响主要反映在这里。
对于状态的二阶导数:
在考虑噪声后,未来状态的不确定性对成本的影响需要通过增加协方差的贡献,因此的修正形式为:
对于控制的二阶导数:
这个部分并不会受到过程噪声的直接影响,因为过程噪声主要影响的是状态的传播和期望。
对于状态与控制之间的交叉导数:
这个部分也是由于噪声通过状态更新影响未来的值函数导数,但直接对控制的噪声影响较小。
基于修正后的 Q-函数导数,我们可以通过最小化 Q-函数对控制偏差进行更新:
新的控制策略仍然通过这个更新公式来迭代优化,只是现在的 Q-函数二阶导数需要考虑噪声的影响。
2.5 python代码
与上一章节相同,我们用一个简单的非线性系统,其状态和控制输入满足以下非线性动力学方程:,这里的非线性在于控制输入的影响是通过正弦函数进入系统的,噪声是高斯噪声。我们希望通过控制输入使状态快速收敛到目标值 0,同时控制量尽可能小,因此二次成本函数:
。
python
import numpy as np
# 系统参数
N = 5 # 时间步长
x0 = np.array([1.0]) # 初始状态
Q_w = 0.05 # 过程噪声的协方差矩阵(假设标量)
state_cost_Q = 1.0 # 状态代价,原Q_x
control_cost_R = 1.0 # 控制代价,原R_u
# 初始控制输入和状态轨迹
u_init = np.zeros(N)
x_traj = np.zeros(N+1)
# 定义系统动态
def dynamics(x, u, noise):
return x + np.sin(u) + noise
# 定义代价函数
def cost(x, u):
return 0.5 * (state_cost_Q * x**2 + control_cost_R * u**2)
# 终端代价
def terminal_cost(x):
return 0.5 * state_cost_Q * x**2
# iLQG算法
def ilqg(x0, u_init, N, Q_w, max_iter=100, tol=1e-4):
# 初始控制输入
u = u_init.copy()
x_traj[0] = x0
V_x = np.zeros((N, 1)) # 初始化值函数一阶导数
V_xx = np.zeros((N, 1, 1)) # 初始化值函数二阶导数
for iteration in range(max_iter):
# 前向传播得到状态轨迹
x_traj[0] = x0
for k in range(N):
noise = np.random.normal(0, np.sqrt(Q_w)) # 过程噪声
x_traj[k+1] = dynamics(x_traj[k], u[k], noise)
# 初始化终端代价
V_x[N-1] = np.array([[state_cost_Q * x_traj[-1]]]) # 终端一阶导数 (正确初始化)
V_xx[N-1] = np.array([[state_cost_Q]]) # 终端二阶导数 (直接为state_cost_Q)
# 反向传播,计算Q函数导数
for k in range(N-2, -1, -1):
x_k = x_traj[k]
u_k = u[k]
# 计算Q-函数的一阶导数
Qx_value_func = x_k + V_x[k+1] # Q-函数对状态的导数
Qu_value_func = control_cost_R * u_k + np.cos(u_k) * V_x[k+1] # Q-函数对控制的导数
# 计算Q-函数的二阶导数
Qxx_value_func = 1.0 + V_xx[k+1] # Q-函数对状态的二阶导数
Quu_value_func = control_cost_R + np.cos(u_k)**2 * V_xx[k+1] # Q-函数对控制的二阶导数
Qxu_value_func = np.cos(u_k) * V_xx[k+1] # 状态和控制之间的交叉项
# 增加tr(V_xx * Q_w)项,表示噪声对Q_xx的影响
tr_Vxx_Qw = np.trace(V_xx[k+1]) * Q_w # V_xx是1x1的矩阵,trace就是它本身
Qxx_value_func += tr_Vxx_Qw # 增加tr(V_xx * Q_w)项到状态二阶导数
# 通过过程噪声对V_xx的修正
V_xx[k] = Qxx_value_func # 更新值函数的二阶导数
# 更新值函数的一阶导数
V_x[k] = Qx_value_func - Qxu_value_func * np.linalg.inv(Quu_value_func) @ Qu_value_func # 更新值函数的一阶导数
# 控制更新
du = - np.linalg.inv(Quu_value_func) @ Qu_value_func # 控制更新量
u[k] += du # 更新控制输入
# 检查收敛
if np.max(np.abs(du)) < tol:
print(f"Converged in {iteration} iterations.")
break
return u, x_traj
# 运行 iLQG 算法
u_opt, x_opt = ilqg(x0, u_init, N, Q_w)
# 打印结果
print("Optimal control:", u_opt)
print("Optimal trajectory:", x_opt)
设定不同的过程噪声协方差,输出结果为:
时:
Optimal control: [-0.65172071 -0.37033912 -0.25424763 -0.16477019 0. ]
Optimal trajectory: [1. 0.65477667 0.39857155 0.26902991 0.16407018 0.15620706]
时:
Optimal control: [-0.61400224 -0.43741123 -0.1530362 0.20908395 0. ]
Optimal trajectory: [ 1. 0.42858991 0.61577511 -0.04834357 -0.70982535 -0.8874807 ]
从上述结果可以看出,越大,控制输出的变化越小,这是因为噪声越大,系统不确定性越强,为了避免因为过大的噪声导致状态失控,算法可能减少控制量,使得控制策略更加平滑和保守。