カルマンフィルタの基礎 第8回
ここまでやってきた線形カルマンフィルタでは、
逐次計算によって、カルマンゲイン と 共分散行列 を求めながら、状態推定を行っていた
今回は、事前に計算することによって、 と の収束値を求める方法を紹介する
定常カルマンフィルタ
これまでは、以下の状態空間モデルに対して、以下のカルマンフィルタのアルゴリズムに応じて逐次計算してきた
今回は、この逐次計算が収束する場合を考える
カルマンフィルタの収束値
事前共分散行列 と仮定すると
について整理していくと
まとめると、
この方程式の解となる [tex: P^ ] を求めれば、
そこから、カルマンゲイン [tex: G^ ] と 事後共分散行列 の収束値も求めることができる
離散時間リカッチ代数方程式
上記の方程式を解く上で、
離散時間リカッチ代数方程式(discrete-time algebraic Riccati equation)
を解く関数が、python の controlモジュールに dare
として提供されている
dare
では、入力 から下記の を求めることができる
比較すると、
dare
の関数に以下の入力をすることで、事後共分散行列の収束値 を求めることができる
計算例
前回の制御入力ありの状態モデルで、実際に、収束値を求めてみる
入力に対する行列 は、収束値に影響を与えないが、
入力ノイズに対する行列 は影響する
import matplotlib.pyplot as plt import numpy as np import pandas as pd A_ = np.array([[0, -0.7], [1, -1.5]]) Bu = np.array([[0.5], [1.]]) B_ = np.array([[0.5], [1.]]) C_ = np.array([[0.], [1.]]) Q_ = np.array([[0.01]]) R_ = np.array([[0.1]])
import control.matlab as pyctrl Pm_conv, _, _ = pyctrl.dare(A_.T, C_, B_ @ Q_ @B_.T, R_) print('Pm_conv = \n', Pm_conv) G_conv = Pm_conv @ C_ @ (C_.T @ Pm_conv @ C_ + R_)**-1 print('G_conv = \n', G_conv) P_conv = (np.eye(A_.shape[0]) - G_conv @ C_.T) @ Pm_conv print('P_conv = \n', P_conv)
Pm_conv = [[0.01243089 0.01686667] [0.01686667 0.02541876]] G_conv = [[0.13448283] [0.20267113]] P_conv = [[0.01016261 0.01344828] [0.01344828 0.02026711]]
事後共分散行列の値が、前回の数値シミュレーションの最終値とほぼ一致している
計算値による数値シミュレーション
計算で求めたカルマンゲインの収束値を用いてシミュレーションで推定を行う
def kf_conv(A_, Bu, C_, u_, y_, xhat, G_conv): xhatm = A_ @ xhat + Bu @ u_ xhat_new = xhatm + G_conv @ (y_ - C_.T @ xhatm) return xhat_new
N_ = 100 np.random.seed(0) v_ = np.array([[np.random.randn() * np.sqrt(Q_[0][0])]]) # システム雑音 w_ = np.array([[np.random.randn() * np.sqrt(R_[0][0])]]) # 観測雑音 u_arr = [] x_arr = [np.array([[0.], [0.]])] y_arr = [C_.T @ x_arr[0] + w_] xhat_arr = [np.array([[0.], [0.]])] for i in range(1, N_): v_ = np.array([[np.random.randn() * np.sqrt(Q_[0][0])]]) # システム雑音 w_ = np.array([[np.random.randn() * np.sqrt(R_[0][0])]]) # 観測雑音 u_ = np.array([[np.sign(np.random.randn())]]) # 制御入力 u_arr += [u_] x_ = A_ @ x_arr[-1] + Bu @ u_ + B_ @ v_ y_ = C_.T @ x_ + w_ x_arr += [x_] y_arr += [y_] xhat = kf_conv( A_, Bu, C_, u_, y_, xhat_arr[-1], G_conv) xhat_arr += [xhat] u_arr = np.array(u_arr) x_arr = np.array(x_arr) y_arr = np.array(y_arr) xhat_arr = np.array(xhat_arr)
fig = plt.figure(figsize=(6, 5)) ax1 = fig.add_subplot(3, 1, 1) ax1.step(np.arange(1, N_), u_arr[:, 0, 0], where='pre', ls='-', label='$u$') ax1.set_ylabel('$u$') ax1.grid(ls=':'); ax1.legend() ax2 = fig.add_subplot(3, 1, 2) ax2.plot(xhat_arr[:, 0, 0], ls='-', label='$\hat{x_1}$') ax2.plot(x_arr[:, 0, 0], ls='--', label='$x_1$') ax2.set_ylabel('$x_1$') ax2.grid(ls=':'); ax2.legend() ax3 = fig.add_subplot(3, 1, 3) ax3.plot(xhat_arr[:, 1, 0], ls='-', label='$\hat{x_2}$') ax3.plot(x_arr[:, 1, 0], ls='--', label='$x_2$') ax3.plot(y_arr[:, 0, 0], ls='-', alpha=0.6, label='$y$') ax3.set_xlabel('$k$') ax3.set_ylabel('$x_2$') ax3.grid(ls=':'); ax3.legend() fig.tight_layout()
前回と同様に、推測できていることが分かる
最初から、カルマンゲインが最適化されているため、
1サンプル目から良好な結果が得られている
おまけ 各パラメータの次元
状態モデルとカルマンフィルタのパラメータの次元を整理しておく
状態モデル
状態変数 が 次元ベクトル(行列)
入力(入力ノイズ) が 次元ベクトル(行列)
出力(観測量) が 次元ベクトル(行列)
カルマンフィルタ
具体例
4状態変数、3入力、2出力
参考文献
この記事は以下の書籍を参考にしましたが、
私の拙い知識で書いておりますので、誤り等ありましたらご指摘ください