Python 还原控制SCI论文算法系列1: 基于策略迭代的自适应最优控制器设计
0.前言
经过前两个文章的基本铺垫,
Python解决微分方程-状态方程求解作图问题
Python解决线性连续系统最优控制状态反馈问题
从本系列开始,我将把写作重心倾向于复现目前所研究的SCI科研论文提出的控制算法。本文将关注强化学习与最优控制领域的经典控制文章:
D.Vrabie, O.Pastravanu, M. Abu-Khalaf, F.L. Lewis. Adaptive optimal control for continuous-time linear systems based on policy iteration, Automatica, 2009(45): 477-484
文章将描述Lewis团队所提出的策略迭代算法相比于传统最优控制算法的创新点,并通过python编程将策略迭代控制算法程序化,通过文章中的例子实现算法复现,最后根据复现过程中发现的数值精度问题, 提出本文中算法执行时还存在什么样的问题。
1.研究问题的描述
1.1 经典线性系统最优控制器设计问题
对于线性系统:
x
˙
=
A
x
+
B
u
(
1
)
\dot{x} = A x + B u (1)
x˙=Ax+Bu(1) 问题的核心是求一个最优控制器
u
?
=
?
K
?
x
u^{*} = -K^{*} x
u?=?K?x,使得:
(i)系统的状态能够在反馈控制作用下收敛到平衡点;
(ii)系统的价值函数:
J
=
∫
t
0
∞
x
T
(
τ
)
Q
x
(
τ
)
+
u
T
(
τ
)
R
u
(
τ
)
d
τ
(
2
)
J = \int^{\infin}_{t_0} x^T(\tau)Qx(\tau) + u^T(\tau)Ru(\tau)d\tau (2)
J=∫t0?∞?xT(τ)Qx(τ)+uT(τ)Ru(τ)dτ(2) 在控制器作用下值取到最小。
传统的求解方法即只需解出经典的代数黎卡提方程的解P即可:
A
T
P
+
P
A
?
P
B
R
?
1
B
T
P
+
Q
=
0
(
3
)
A^TP+PA-PBR^{-1}B^{T}P+Q = 0 (3)
ATP+PA?PBR?1BTP+Q=0(3) 对应的最优控制器
u
?
u^{*}
u?可以表示为:
u
?
=
?
K
?
x
=
?
R
?
1
B
T
P
x
(
4
)
u^{*} = - K^* x = -R^{-1}B^TPx (4)
u?=?K?x=?R?1BTPx(4)
1.2 策略迭代问题的提出
从式(3)可以很明显的看出,如果要求出代数黎卡提方程的解P,则必须知道系统矩阵A,控制矩阵B和价值函数中的Q与R的精确值,从而进一步由(4)推导得到最优控制器。如果系统矩阵A未知时,那么很明显,直接使用代数黎卡提方程(3)来求解P将变的无法进行。因此,必须要寻找到一种新的算法,能够解决系统矩阵A不精确已知时最优控制器的求法。这就是上述论文需要核心解决的问题。
2. 论文所提的策略迭代控制算法详解
2.1 idea1: 将部分价值函数等效成Lyapunov函数增量
目前我们已知了最优控制器的结构为(4)式,唯一需要解决的是
P
?
P^{*}
P?的值。因此,可以考虑用P的迭代值来代替精确值,保证迭代足够多次后,P能够收敛到精确值
P
?
P^{*}
P?。
为此,假设存在一个K能够保证A -BK为Hurwitz矩阵,即特征值实部为负,且满足
V
(
t
)
=
x
T
(
t
)
P
x
(
t
)
=
∫
t
∞
x
T
(
τ
)
Q
x
(
τ
)
+
x
T
(
τ
)
K
T
R
K
x
(
τ
)
d
τ
(
5
)
V(t) = x^T(t)Px(t) = \int^{\infin}_{t}x^T(\tau)Qx(\tau)+x^T(\tau)K^TRKx(\tau)d\tau (5)
V(t)=xT(t)Px(t)=∫t∞?xT(τ)Qx(τ)+xT(τ)KTRKx(τ)dτ(5) P为Lyapunov方程的解,即
(
A
?
B
K
)
T
P
+
P
(
A
?
B
K
)
=
Q
+
K
T
R
K
(
6
)
(A-BK)^TP+P(A-BK) = Q + K^TRK (6)
(A?BK)TP+P(A?BK)=Q+KTRK(6)
那么,可以将价值函数进行如下等效变换:
V
(
t
)
=
∫
t
t
+
T
x
T
(
τ
)
(
Q
+
K
T
R
K
)
x
(
τ
)
d
τ
+
V
(
t
+
T
)
(
7
)
V(t) =\int^{t+T}_{t} x^T(\tau)(Q + K^TRK) x(\tau)d\tau + V(t + T) (7)
V(t)=∫tt+T?xT(τ)(Q+KTRK)x(τ)dτ+V(t+T)(7) 结合(5)式,可得
x
T
(
t
)
P
x
(
t
)
?
x
T
(
t
+
T
)
P
x
(
t
+
T
)
=
∫
t
t
+
T
x
T
(
τ
)
(
Q
+
K
T
R
K
)
x
(
τ
)
d
τ
(
6
)
x^T(t)Px(t) - x^T(t+T)Px(t+T) = \int^{t+T}_{t}x^T(\tau)(Q+K^TRK)x(\tau)d\tau (6)
xT(t)Px(t)?xT(t+T)Px(t+T)=∫tt+T?xT(τ)(Q+KTRK)x(τ)dτ(6) 定义
x
ˉ
=
(
x
1
2
,
x
1
x
2
,
.
.
.
,
x
1
x
n
,
x
2
2
,
x
2
x
3
,
.
.
.
,
x
2
x
n
,
.
.
.
,
x
n
2
)
T
∈
R
n
(
1
+
n
)
2
\bar{x} = (x_1^2, x_1x_2, ... , x_1x_n, x_2^2, x_2x_3,...,x_2x_n, ..., x_n^2)^T \in R^{\frac{n(1+n)}{2}}
xˉ=(x12?,x1?x2?,...,x1?xn?,x22?,x2?x3?,...,x2?xn?,...,xn2?)T∈R2n(1+n)?,
x
ˉ
Δ
=
x
(
t
)
?
x
(
t
+
T
)
\bar{x}_{\Delta} = x(t) - x(t+T)
xˉΔ?=x(t)?x(t+T),
P
ˉ
=
(
P
11
,
2
P
12
,
.
.
.
,
2
P
1
n
,
P
22
,
2
P
23
,
.
.
.
,
2
P
2
n
,
P
33
,
.
.
.
,
P
n
n
)
T
∈
R
n
(
1
+
n
)
2
\bar{P} = (P_{11}, 2P_{12}, ..., 2P_{1n}, P_{22}, 2P_{23},...,2P_{2n}, P_{33},...,P_{nn})^T\in R^{\frac{n(1+n)}{2}}
Pˉ=(P11?,2P12?,...,2P1n?,P22?,2P23?,...,2P2n?,P33?,...,Pnn?)T∈R2n(1+n)??,
那么(8)式可以化解为:
P
ˉ
T
x
ˉ
Δ
=
∫
t
t
+
T
x
T
(
τ
)
(
Q
+
K
T
R
K
)
x
(
τ
)
d
τ
(
9
)
\bar{P}^T\bar{x}_{\Delta} = \int^{t+T}_{t}x^T(\tau)(Q+K^TRK)x(\tau)d\tau (9)
PˉTxˉΔ?=∫tt+T?xT(τ)(Q+KTRK)x(τ)dτ(9) 在(9)式基础上,可以考虑采用P的迭代策略伪代码:
(i)初始化
P
0
=
0
P_0 = 0
P0?=0, $K_1 $满足 $A - BK_1 $是Hurwitz的;
(II)Loop 从 i = 1, … ,
∞
\infin
∞开始:
通过
P
ˉ
i
T
x
ˉ
Δ
=
∫
t
t
+
T
x
T
(
τ
)
(
Q
+
K
i
T
R
K
i
i
)
x
(
τ
)
d
τ
\bar{P}_i^T\bar{x}_{\Delta} = \int^{t+T}_{t}x^T(\tau)(Q+K^T_iRKi_i)x(\tau)d\tau
PˉiT?xˉΔ?=∫tt+T?xT(τ)(Q+KiT?RKii?)x(τ)dτ更新
P
i
P_i
Pi?, 采样点个数N要大于
n
(
n
+
1
)
2
\frac{n(n+1)}{2}
2n(n+1)?;
如果
∣
∣
P
i
?
P
i
?
1
∣
∣
<
?
||P_{i }- P_{i-1}|| < \epsilon
∣∣Pi??Pi?1?∣∣<?:
? 结束迭代,跳出循环;? 否则通过
K
i
+
1
=
R
?
1
B
T
P
i
K_{i+1} = R^{-1}B^TP_i
Ki+1?=R?1BTPi?实现控制器增益的更新; endLoop
策略迭代算法的核心是在一定时间内每隔时间间隔T取N个系统状态点
x
ˉ
Δ
1
,
.
.
.
,
x
ˉ
Δ
N
\bar{x}_{\Delta}^{1}, ..., \bar{x}_{\Delta}^{N}
xˉΔ1?,...,xˉΔN?,从而在当前控制策略
K
i
K_i
Ki?下可以评估出当前策略下的
P
i
P_i
Pi?是否取的合适。到此为止,算法实现的最大问题就落在如何解决评估P时的积分问题。
2.2 idea2:最小二乘法估算P
要实现(9),还存在一个难点就是估算(9)等号右边的积分价值函数。解决的思路是把积分当作微分方程来处理,即令
存在一个关于时间t连续的函数
v
(
t
)
v(t)
v(t), 满足:
v
˙
(
t
)
=
x
T
(
τ
)
(
Q
+
K
T
R
K
)
x
(
τ
)
(
10
)
\dot v(t) = x^T(\tau)(Q + K^TRK)x(\tau) (10)
v˙(t)=xT(τ)(Q+KTRK)x(τ)(10) 这样(9)式就可以等价为
P
ˉ
T
x
ˉ
Δ
=
∫
t
t
+
T
x
T
(
τ
)
(
Q
+
K
T
R
K
)
x
(
τ
)
d
τ
=
v
(
t
+
T
)
?
v
(
t
)
=
d
(
x
ˉ
Δ
,
k
)
(
11
)
\bar{P}^T\bar{x}_{\Delta} = \int^{t+T}_{t}x^T(\tau)(Q+K^TRK)x(\tau)d\tau=v(t+T) -v(t)=d(\bar{x}_{\Delta}, k) (11)
PˉTxˉΔ?=∫tt+T?xT(τ)(Q+KTRK)x(τ)dτ=v(t+T)?v(t)=d(xˉΔ?,k)(11) (11)式是可以通过求(10)式的微分方程得到采样时刻端点处的数值近似解的,因此在编程时只要把(10)式作为(1)式的附加系统一起进行微分方程数值计算处理即可。考虑到在每一次策略评估时,需要对系统状态(包含(10)中的v(t))采样N个点,因此令
X
=
(
x
ˉ
Δ
1
,
.
.
.
,
x
ˉ
Δ
N
)
T
X = (\bar{x}_{\Delta}^1, ..., \bar{x}_{\Delta}^N)^T
X=(xˉΔ1?,...,xˉΔN?)T,
Y
=
(
d
(
x
ˉ
Δ
1
,
k
i
)
,
.
.
.
,
d
(
x
ˉ
Δ
N
,
k
i
)
)
T
Y = (d(\bar{x}_{\Delta}^{1}, k_i), ...,d(\bar{x}_{\Delta}^{N}, k_i))^T
Y=(d(xˉΔ1?,ki?),...,d(xˉΔN?,ki?))T,
(11)式就变为:
X
T
P
ˉ
i
=
Y
(
12
)
X^T\bar{P}_i = Y (12)
XTPˉi?=Y(12) 于是可以使用最小二乘法,来求得近似数值解
P
i
ˉ
=
(
X
X
T
)
?
1
X
Y
(
13
)
\bar{P_i} = (XX^T)^{-1}XY (13)
Pi?ˉ?=(XXT)?1XY(13)
2.3 A-C 结构的控制器在线更新策略
有了2.1和2.2的分析基础,现在我们可以得到基于Actor-Critic结构的强化学习控制器在线更新策略:
Environment(环境): 被控系统 (1)+ 附加系统(10),接收Actor产生的新策略,反馈给Critic N个状态采样点的信息;
Critic(评估者):负责评估当前
K
i
K_i
Ki?控制策略下的
P
i
P_i
Pi?值,评估方法为:从t时刻开始,每隔T对系统状态和附加状态采样,当收集到N个采样点后,根据(12)式利用最小二乘法评估出近似的解
P
i
P_i
Pi?;
Actor(执行者): 负责根据评估的结果,产生新的控制策略
K
i
+
1
=
R
?
1
B
T
P
i
K_{i+1} = R^{-1}B^TP_{i}
Ki+1?=R?1BTPi?, 作用系统中产生的新的数据点,控评估者进行评估。
3. 论文仿真案例的python编程还原
3.1 系统模型建模
论文的环境模型采用的是电力系统中在可操作点处的近似线性模型。模型的标准系统矩阵
A
n
A_n
An?为
A
n
=
[
?
0.0665
8
0
0
0
?
3.663
3.663
0
?
6.86
0
?
13.736
?
13.736
0.6
0
0
0
]
B
=
[
0
0
13.736
0
]
T
A_n = \begin{bmatrix} -0.0665 & 8 & 0 & 0\\ 0 & -3.663 & 3.663 & 0\\ -6.86 & 0 & -13.736 & -13.736\\ 0.6 & 0 & 0 & 0 \end{bmatrix}\\ B = \begin{bmatrix} 0 & 0 & 13.736 & 0 \end{bmatrix}^T
An?=??????0.06650?6.860.6?8?3.66300?03.663?13.7360?00?13.7360??????B=[0?0?13.736?0?]T 在该模型下,可以通过解(3)式的代数黎卡提方程求得最优控制增益
K
=
[
0.82668936
1.70030527
0.7049475
0.41421356
]
K=\begin{bmatrix}0.82668936 \quad 1.70030527 \quad 0.7049475 \quad 0.41421356\end{bmatrix}
K=[0.826689361.700305270.70494750.41421356?],
把它作为控制策略迭代的初始增益
K
1
K_1
K1?。考虑此时电力系统的操作点发生了移动,则对应的线性化方程也会发生改变,系统矩阵不再是
A
n
A_n
An?,而是变成了A:
A
=
[
?
0.0665
11.5
0
0
0
?
2.5
2.5
0
?
9.5
0
?
13.736
?
13.736
0.6
0
0
0
]
A = \begin{bmatrix} -0.0665 & 11.5 & 0 & 0\\ 0 & -2.5 & 2.5 & 0\\ -9.5 & 0 & -13.736 & -13.736\\ 0.6 & 0 & 0 & 0 \end{bmatrix}
A=??????0.06650?9.50.6?11.5?2.500?02.5?13.7360?00?13.7360?????? 考虑此时如何运用策略迭代控制算法实现最优控制策略的生成同时保证系统仍然镇定。
3.2 Python程序
3.2.1 功能包
import numpy as np
import control as ct
import matplotlib.pyplot as plt
from scipy import linalg, integrate
np.set_printoptions(suppress=True)
3.2.2 构建环境函数
def env_lin_sys(t, state, A, B, K, Q, R):
x, V = state[:4], state[4]
dx1, dx2, dx3, dx4 = A @ x - B @ K @ x
dV = x.T @ (Q + K.T @ R @ K) @ x
return [dx1, dx2, dx3, dx4, dV]
3.2.3 Critic-Actor 策略更新函数
def calculate_delta_bx_V(state):
x1, x2, x3, x4, V = state
N = len(x1)
bx = np.zeros((N, 10))
delta_bx = np.zeros((N-1, 10))
delta_V = np.zeros((N-1, ))
for i in range(N):
bx[i] = [x1[i]**2, x1[i]*x2[i], x1[i]*x3[i], x1[i]*x4[i], x2[i]**2, x2[i]*x3[i], x2[i]*x4[i], x3[i]**2, x3[i]*x4[i], x4[i]**2]
if i != 0:
delta_bx[i - 1] = bx[i-1] - bx[i]
delta_V[i - 1] = V[i] - V[i - 1]
return delta_bx, delta_V
def critic_lstsq(delta_bx, delta_V):
bp, res, rank, s = linalg.lstsq(delta_bx, delta_V)
p = np.array([
[bp[0], bp[1]/2, bp[2]/2, bp[3]/2],
[bp[1]/2, bp[4], bp[5]/2, bp[6]/2],
[bp[2]/2, bp[5]/2, bp[7], bp[8]/2],
[bp[3]/2, bp[6]/2, bp[8]/2, bp[9]]
])
return bp, p
def policy_iteration(state, bp_current, p_current, R, B, eps):
deltabx, deltaV = calculate_delta_bx_V(state)
bp_new, p_new = critic_lstsq(deltabx, deltaV)
policy_terminal = False
print(linalg.norm(bp_new - bp_current))
if linalg.norm(bp_new - bp_current) < eps:
p_new = p_current
bp_new = bp_current
policy_terminal = True
K_new = linalg.inv(R) @ B.T @ p_new
return K_new, p_new, bp_new, policy_terminal
def init_k(A, B, Q, R, init_ctrl=False):
if init_ctrl:
P, L, K = ct.care(A, B, Q, R)
return K
else:
K = np.zeros((1,4))
return K
3.2.4 主程序
if __name__ == '__main__':
A = np.array(
[
[-0.0665, 11.5, 0, 0],
[0, -2.5, 2.5, 0],
[-9.5, 0, -13.736, -13.736],
[0.6, 0, 0, 0]
])
An = np.array(
[
[-0.0665, 8, 0, 0],
[0, -3.663, 3.663, 0],
[-6.86, 0, -13.736, -13.736],
[0.6, 0, 0, 0]
])
B = np.array([[0], [0], [13.736], [0]])
Q = np.diag([1.,1.,1,1.])
R = np.diag([1.0])
P0 = np.zeros((4, 4))
K1 = init_k(An, B, Q, R, init_ctrl=True)
bP_current = np.zeros((10, ))
traj_x = np.array([None])
traj_t = np.array([None])
init_x = np.array([0.,0.1, 0, 0.])
init_V = 100
init_state = np.append(init_x, init_V)
t = np.linspace(0, 1, 21)
policy_terminal = False
K_current = K1
P_current = P0
for i in range(8):
traj_sol = integrate.solve_ivp(env_lin_sys, [i, i+1], init_state, args=(A, B, K_current, Q, R), t_eval=t, dense_output=True,
method='RK45', first_step=0.001, max_step=0.001)
if i == 0:
traj_t = traj_sol.t
traj_x = traj_sol.y[:4]
else:
traj_t = np.hstack([traj_t, np.delete(traj_sol.t, 0)])
aug_traj = np.delete(traj_sol.y[:4], 0, axis=1)
traj_x = np.concatenate((traj_x, aug_traj), axis=1)
if policy_terminal == False:
K_new, p_new, dp_new, policy_terminal = policy_iteration(traj_sol.y, bP_current, P_current, R, B, eps=1e-2)
P_current = p_new
bP_current = dp_new
K_current = K_new
init_state = traj_sol.y[ :, -1]
t = t + 1
else:
break
3.2.5 轨迹图
plt.plot(traj_t[:41], traj_x[0][:41], '-bo', label='x(1)')
plt.plot(traj_t[:41], traj_x[1][:41], '-.go', label='x(2)')
plt.plot(traj_t[:41], traj_x[2][:41], '--ro', label='x(3)')
plt.plot(traj_t[:41], traj_x[3][:41], '-co', label='x(4)')
plt.legend(loc='best')
plt.show()
考虑在该控制更新策略下,系统状态在前两秒的轨迹图为:
通过对比,可以发现与文章中提供的图基本相同。
4. 发现的问题
通过将近一个礼拜的研究,我发现了具体实现本文算法时还存在一些问题:
- 问题1:实际计算的P无法收敛于论文中仿真部分给出的
P
?
P^*
P?值
我的思考:造成这一根本原因就是算法中采用了 (10),通过微分方程数值解来近似积分,造成了最小二乘法估算P时,总是存在误差,而这个误差经过几次迭代放大后,就会造成P不收敛。而要完全复现论文中的数据,就必须保证给critic_lstsq(deltabx, deltaV)提供的deltaV必须是完全满足(11)式。换句话说,就是如果考虑从环境接收到的
d
(
x
ˉ
,
k
i
)
d(\bar{x}, k_i)
d(xˉ,ki?)是完全精确的,那么就可以通过最小二乘法计算出唯一的精确解P,满足Lyapunov方程,最终多次迭代收敛到最优值, 如下图
|p_i - p_{i-1}|: 1.0460854795299647e-05
lyap p is:
[[0.4599705 0.69112794 0.05194142 0.464249 ]
[0.69112794 1.86677973 0.20019781 0.57995739]
[0.05194142 0.20019781 0.05331511 0.03015533]
[0.464249 0.57995739 0.03015533 2.21057234]]
calc P is:
[[0.45997191 0.69112909 0.05194147 0.46425062]
[0.69112909 1.86678649 0.20019865 0.57995827]
[0.05194147 0.20019865 0.05331522 0.03015533]
[0.46425062 0.57995827 0.03015533 2.21057422]]
因此文章中的仿真结果,值得怀疑是否是通过这种取巧的方法而不是通过本身算法要求的附加系统求微分方程来取得。
- 问题2:误差
?
\epsilon
?的取值对与算法是否收敛影响很大
在相同初始参数下,如果将误差取为
?
=
0.01
\epsilon=0.01
?=0.01, 那么通过计算,P可以收敛;
|p_i -P_{i-1}|: 0.008006732152754513
lyap p is:
[[0.45997193 0.6911291 0.05194148 0.46425065]
[0.6911291 1.86678652 0.20019866 0.57995828]
[0.05194148 0.20019866 0.05331522 0.03015533]
[0.46425065 0.57995828 0.03015533 2.21057427]]
calc P is:
[[0.46098944 0.69194825 0.05197939 0.46541675]
[0.69194825 1.87195893 0.20081381 0.58055623]
[0.05197939 0.20081381 0.05339299 0.03015332]
[0.46541675 0.58055623 0.03015332 2.21193659]]
当
?
=
0.001
\epsilon=0.001
?=0.001时,相同条件下P最终会不满足正定阵的条件,导致系统状态在不断的错误策略迭代中导致系统发散。
|p_i -P_{i-1}|:3.5830071212353114
lyap p is:
[[0.4599728 0.69112831 0.05194173 0.46425059]
[0.69112831 1.86678715 0.20019774 0.57995734]
[0.05194173 0.20019774 0.05331532 0.03015551]
[0.46425059 0.57995734 0.03015551 2.21057409]]
calc P is:
[[ 0.68940744 1.57865928 0.05172892 0.66838222]
[ 1.57865928 4.14317881 0.74308701 1.44436328]
[ 0.05172892 0.74308701 -0.20910912 -0.00497367]
[ 0.66838222 1.44436328 -0.00497367 2.38735624]]
造成这一问题的原因目前还不清楚,我的猜测是之前的更新策略使得状态迅速收敛到很小的值,可能导致
X
X
T
XX^T
XXT不满足广义可逆条件,因而采用最小二乘法时,就会出现错误的P,如上图中的calc P一样。因此,在真正采用该算法时,需要有一个假设条件,即最小二乘法评估的策略如果P不满足正定阵,则不执行策略更新,继续采用先前的策略。这样可以保证系统状态至少不发散,能够收敛到平衡点。
5 结语
通过一个礼拜对文章的理解加编程实现,中间一度考虑到是否是精度问题导致无法完全实现P收敛,还采用了matlab编程,但结果都显示不是精度问题导致的P有时不收敛。该算法能够作为自适应算法的一种补充,但我认为并没有如文章中写的那么好用,可以完全不考虑A。这个算法真正能用的前提就是一定要获取环境中的真实积分价值函数的值(这个不可能)还有就是确定好结束策略循环迭代的收敛误差
?
\epsilon
?。 后续将继续追寻这条线,往下找一下,Lewis团队是否改进了该不太好实现的方法(大概率改进了,因为这篇文章是2009年的,现在都2022年了,都采用神经网络逼近了)。如复现成功,也会作为自己的总结发表出来。
|