カルマンフィルタの基礎 第11回
前回のU変換を用いて、UKF(Unscented Kalman Filter)のアルゴリズムを実装する
また、線形の状態モデルで動作の確認も行う
UKFのアルゴリズム
まずは、UFKのアルゴリズムを整理する
推定値と共分散行列の初期値は下記のように定める
シグマポイントの計算
最初に、前回の推定値 と共分散行列 から、
2n + 1個のシグマポイント を計算する
各成分の重みは下記とする
( がパラメータになる)
予測ステップ
シグマポイントの遷移
生成したシグマポイントを状態モデルの から遷移させる
事前状態推定値
遷移後のシグマポイントから事前推定値 を求める
事前誤差共分散行列
同様に、事前(誤差)共分散行列 を求める
シグマポイントの再計算
求めた事前推定値 と 事前(誤差)共分散行列 から
シグマポイント を計算する
出力のシグマポイントの更新
次に、状態モデルの出力関数 から、出力のシグマポイント を計算する
事前出力推定値
から事前出力推定値 を求める
事前出力誤差共分散行列
同様に、出力 に関する事前誤差共分散行列 を求める
事前状態・出力誤差共分散行列
と の共分散行列 を求める
カルマンゲイン
、 からカルマンゲインを求める
フィルタリングステップ
状態推定値
観測値 を加えて、事後推定値 を求める
事後誤差共分散行列
次回のために、事後誤差共分散行列 を求める
UKFの実践(線形モデル)
実際にUKFの動作を確認する
状態モデルは、第6回と同じものを使用する
import matplotlib.pyplot as plt import numpy as np import pandas as pd A_ = np.array([[0, -0.7], [1, -1.5]]) B_ = np.array([[0.5], [1.]]) C_ = np.array([[0.], [1.]]) Q_ = np.array([[1.]]) R_ = np.array([[0.1]]) def f_(x_): return A_ @ x_ def h_(x_): return C_.T @ x_
カルマンフィルタの実装(線形)
単純な線形カルマンフィルタと比較する
def kf(A_, B_, C_, Q_, R_, y_, xhat, P_): xhatm = A_ @ xhat Pm = A_ @ P_ @ A_.T + B_ @ Q_ @ B_.T G_ = Pm @ C_ @ (C_.T @ Pm @ C_ + R_)**-1 xhat_new = xhatm + G_ @ (y_ - C_.T @ xhatm) P_new = (np.eye(A_.shape[0]) - G_ @ C_.T) @ Pm return xhat_new, P_new, G_, Pm
カルマンフィルタの実装(UKF)
上記で説明したアルゴリズムを実装する
def calc_Xm(x_, P_, kappa): n_ = x_.shape[0] x_ = x_.reshape(n_) L = np.linalg.cholesky(P_) Xm = [x_] for i in range(n_): mu = [] for j in range(n_): mu += [x_[j] + np.sqrt(n_ + kappa) * L[j][i]] Xm += [mu] for i in range(n_): mu = [] for j in range(n_): mu += [x_[j] - np.sqrt(n_ + kappa) * L[j][i]] Xm += [mu] Xm = np.array(Xm) Xm = Xm.reshape(2 * n_ + 1, n_, 1) return Xm def calc_mean(Xm, kappa): n2 = Xm.shape[0] nl = Xm.shape[1] n_ = int((n2 - 1) / 2) Xm = Xm.reshape(n2, nl) w0 = kappa / (n_ + kappa) wi = 1. / (2 * (n_ + kappa)) xhatm = [] for i in range(nl): xhatm += [w0 * Xm[0][i]] for j in range(n_): xhatm[i] += wi * Xm[j + 1][i] xhatm[i] += wi * Xm[j + n_ + 1][i] xhatm = np.array(xhatm) xhatm = xhatm.reshape(nl, 1) return xhatm def calc_Pm(Xm, Ym, kappa): xhatm = calc_mean(Xm, kappa) yhatm = calc_mean(Ym, kappa) Xm = Xm.reshape(Xm.shape[0], Xm.shape[1]) Ym = Ym.reshape(Ym.shape[0], Ym.shape[1]) xhatm = xhatm.reshape(xhatm.shape[0]) yhatm = yhatm.reshape(yhatm.shape[0]) l1 = Xm.shape[1] l2 = Ym.shape[1] n2 = Xm.shape[0] n_ = int((n2 - 1) / 2) w0 = kappa / (n_ + kappa) wi = 1. / (2 * (n_ + kappa)) Pm = [] for j in range(l1): Pm += [[]] for k in range(l2): Pm[j] += [w0 * (Xm[0][j] - xhatm[j]) * (Ym[0][k] - yhatm[k])] for i in range(2 * n_): Pm[j][k] += wi * (Xm[i + 1][j] - xhatm[j]) * (Ym[i + 1][k] - yhatm[k]) return np.array(Pm) def ut(f_, x_, P_, kappa): X_ = calc_Xm(x_, P_, kappa) Xm = np.array([f_(x_) for x_ in X_]) xhatm = calc_mean(Xm, kappa) Pm = calc_Pm(Xm ,Xm, kappa) return X_, Xm, xhatm, Pm def ukf(f_, h_, B_, Q_, R_, y_, xhat, P_, kappa): X_, Xm, xhatm, Pxx = ut(f_, xhat, P_, kappa) Pxx = Pxx + B_ @ Q_ @ B_.T Xp, Ym, yhatm, Pyy = ut(h_, xhatm, Pxx, kappa) Pxy = calc_Pm(Xp, Ym, kappa) kG = Pxy @ (Pyy + R_)**-1 xhat_new = xhatm + kG @ (y_ - yhatm) P_new = Pxx - kG @ Pxy.T return xhat_new, P_new, kG, Pxx
シミュレーションの実施
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])]]) # 観測雑音 x_arr = [np.array([[0.], [0.]])] y_arr = [C_.T @ x_arr[0] + w_] xhat_kf_arr = [np.array([[0.], [0.]])] P_kf_arr = [np.array([[1., 0.], [0., 1.]])] G_kf_arr = [] xhat_ukf_arr = [np.array([[0.], [0.]])] P_ukf_arr = [np.array([[1., 0.], [0., 1.]])] G_ukf_arr = [] 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])]]) # 観測雑音 x_ = A_ @ x_arr[-1] + B_ @ v_ y_ = C_.T @ x_ + w_ x_arr += [x_] y_arr += [y_] xhat, P_, G_, Pm = kf( A_, B_, C_, Q_, R_, y_, xhat_kf_arr[-1], P_kf_arr[-1]) xhat_kf_arr += [xhat] P_kf_arr += [P_] G_kf_arr += [G_] xhat, P_, G_, Pm = ukf( f_, h_, B_, Q_, R_, y_, xhat_ukf_arr[-1], P_ukf_arr[-1], kappa=2) xhat_ukf_arr += [xhat] P_ukf_arr += [P_] G_ukf_arr += [G_] x_arr = np.array(x_arr) y_arr = np.array(y_arr) xhat_kf_arr = np.array(xhat_kf_arr) P_kf_arr = np.array(P_kf_arr) G_kf_arr = np.array(G_kf_arr) xhat_ukf_arr = np.array(xhat_ukf_arr) P_ukf_arr = np.array(P_ukf_arr) G_ukf_arr = np.array(G_ukf_arr)
シミュレーションの確認
fig = plt.figure(figsize=(6, 5)) ax1 = fig.add_subplot(2, 1, 1) ax2 = fig.add_subplot(2, 1, 2, sharex=ax1) ax1.plot(xhat_kf_arr[:, 0, 0], ls='-', label='$kf$') ax1.plot(xhat_ukf_arr[:, 0, 0], ls='--', label='$ukf$') ax1.set_ylabel('$x_1$') ax1.grid(ls=':'); ax1.legend() ax2.plot(xhat_kf_arr[:, 1, 0], ls='-', label='$kf$') ax2.plot(xhat_ukf_arr[:, 1, 0], ls='--', label='$ukf$') ax2.set_ylabel('$x_2$') ax2.grid(ls=':'); ax2.legend() ax2.set_xlabel('$k$') fig.tight_layout()
fig = plt.figure(figsize=(6, 5)) ax1 = fig.add_subplot(3, 1, 1) ax2 = fig.add_subplot(3, 1, 2, sharex=ax1) ax3 = fig.add_subplot(3, 1, 3, sharex=ax1) ax1.plot(P_kf_arr[:, 0, 0], '-', label='kf') ax1.plot(P_ukf_arr[:, 0, 0], '--', label='ukf') ax1.set_ylabel('$P_{0,0}$') ax1.grid(ls=':'); ax1.legend() ax2.plot(P_kf_arr[:, 0, 1], '-', label='kf') ax2.plot(P_ukf_arr[:, 0, 1], '--', label='ukf') ax2.set_ylabel('$P_{0,1},P_{1,0}$') ax2.grid(ls=':'); ax2.legend() ax3.plot(P_kf_arr[:, 1, 1], '-', label='kf') ax3.plot(P_ukf_arr[:, 1, 1], '--', label='ukf') ax3.set_ylabel('$P_{1,1}$') ax3.grid(ls=':'); ax3.legend() ax3.set_xlabel('$k$') fig.tight_layout()
fig = plt.figure(figsize=(6, 5)) ax1 = fig.add_subplot(2, 1, 1) ax2 = fig.add_subplot(2, 1, 2, sharex=ax1) ax1.plot(G_kf_arr[:, 0, 0], '-', label='kf') ax1.plot(G_ukf_arr[:, 0, 0], '--', label='ukf') ax1.set_ylabel('$G_{0,0}$') ax1.grid(ls=':'); ax1.legend() ax2.plot(G_kf_arr[:, 1, 0], '-', label='kf') ax2.plot(G_ukf_arr[:, 1, 0], '--', label='ukf') ax2.set_ylabel('$G_{1,0}$') ax2.grid(ls=':'); ax2.legend() ax2.set_xlabel('$k$') fig.tight_layout()
推定値も共分散行列もカルマンゲインも線形カルマンフィルタとUKFで一致している
参考文献
この記事は以下の書籍を参考にしましたが、
私の拙い知識で書いておりますので、誤り等ありましたらご指摘ください