Unscented Kalman Filter(UKF):非線形状態推定の理論とPython実装

UKFのUnscented変換から状態推定アルゴリズム、EKFとの比較、Python実装までを解説します。

はじめに

カルマンフィルタは線形ガウスシステムで最適ですが、非線形システムでは直接適用できません。**拡張カルマンフィルタ(EKF)**はヤコビアンによる線形化で対処しますが、高次の非線形性では精度が低下します。

Unscented Kalman Filter(UKF)は、確率分布を代表する少数のシグマポイントを非線形関数に通すことで、ヤコビアンの計算なしに高精度な推定を実現します。

Unscented 変換

シグマポイントの生成

\(n\) 次元の状態 \(\mathbf{x}\)(平均 \(\bar{\mathbf{x}}\), 共分散 \(\mathbf{P}\))に対し、\(2n+1\) 個のシグマポイントを生成します。

\[\boldsymbol{\chi}_0 = \bar{\mathbf{x}} \tag{1}\]\[\boldsymbol{\chi}_i = \bar{\mathbf{x}} + \left(\sqrt{(n + \lambda) \mathbf{P}}\right)_i, \quad i = 1, \ldots, n \tag{2}\]\[\boldsymbol{\chi}_{i+n} = \bar{\mathbf{x}} - \left(\sqrt{(n + \lambda) \mathbf{P}}\right)_i, \quad i = 1, \ldots, n \tag{3}\]

ここで \(\lambda = \alpha^2(n + \kappa) - n\) はスケーリングパラメータです。

重み

\[W_0^{(m)} = \frac{\lambda}{n + \lambda}, \quad W_0^{(c)} = \frac{\lambda}{n + \lambda} + (1 - \alpha^2 + \beta) \tag{4}\]\[W_i^{(m)} = W_i^{(c)} = \frac{1}{2(n + \lambda)}, \quad i = 1, \ldots, 2n \tag{5}\]

典型的なパラメータ: \(\alpha = 10^{-3}\), \(\beta = 2\)(ガウス分布で最適), \(\kappa = 0\)

変換後の統計量

シグマポイントを非線形関数 \(f\) に通した後:

\[\boldsymbol{\mathcal{Y}}_i = f(\boldsymbol{\chi}_i) \tag{6}\]\[\bar{\mathbf{y}} = \sum_{i=0}^{2n} W_i^{(m)} \boldsymbol{\mathcal{Y}}_i \tag{7}\]\[\mathbf{P}_y = \sum_{i=0}^{2n} W_i^{(c)} (\boldsymbol{\mathcal{Y}}_i - \bar{\mathbf{y}})(\boldsymbol{\mathcal{Y}}_i - \bar{\mathbf{y}})^T \tag{8}\]

UKF アルゴリズム

予測ステップ

  1. 現在の状態推定 \((\hat{\mathbf{x}}_{k-1}, \mathbf{P}_{k-1})\) からシグマポイントを生成
  2. 状態遷移関数 \(f\) で変換: \(\boldsymbol{\chi}_{k|k-1} = f(\boldsymbol{\chi}_{k-1})\)
  3. 予測平均と共分散を計算:
\[\hat{\mathbf{x}}_{k}^{-} = \sum_i W_i^{(m)} \boldsymbol{\chi}_{i,k|k-1} \tag{9}\]\[\mathbf{P}_k^{-} = \sum_i W_i^{(c)} (\boldsymbol{\chi}_{i,k|k-1} - \hat{\mathbf{x}}_{k}^{-})(\boldsymbol{\chi}_{i,k|k-1} - \hat{\mathbf{x}}_{k}^{-})^T + \mathbf{Q} \tag{10}\]

更新ステップ

  1. 予測シグマポイントを観測関数 \(h\) で変換
  2. カルマンゲインを計算:
\[\mathbf{K}_k = \mathbf{P}_{xy} \mathbf{P}_{yy}^{-1} \tag{11}\]
  1. 状態と共分散を更新:
\[\hat{\mathbf{x}}_k = \hat{\mathbf{x}}_k^{-} + \mathbf{K}_k (\mathbf{z}_k - \hat{\mathbf{z}}_k) \tag{12}\]\[\mathbf{P}_k = \mathbf{P}_k^{-} - \mathbf{K}_k \mathbf{P}_{yy} \mathbf{K}_k^T \tag{13}\]

EKFとの比較

特徴EKFUKF
線形化方法ヤコビアン(1次近似)シグマポイント(2次精度)
ヤコビアン計算必要不要
非線形精度中程度高い
計算コスト\(O(n^2)\)\(O(n^3)\)(コレスキー分解)

Python実装

2次元の等速円運動を追跡する例です。

import numpy as np
import matplotlib.pyplot as plt

class UKF:
    def __init__(self, n, m, f, h, Q, R, alpha=1e-3, beta=2, kappa=0):
        self.n = n
        self.m = m
        self.f = f
        self.h = h
        self.Q = Q
        self.R = R

        self.lam = alpha**2 * (n + kappa) - n
        self.gamma = np.sqrt(n + self.lam)

        # 重み
        self.Wm = np.full(2 * n + 1, 1 / (2 * (n + self.lam)))
        self.Wc = np.full(2 * n + 1, 1 / (2 * (n + self.lam)))
        self.Wm[0] = self.lam / (n + self.lam)
        self.Wc[0] = self.lam / (n + self.lam) + (1 - alpha**2 + beta)

    def sigma_points(self, x, P):
        L = np.linalg.cholesky(P)
        sigmas = np.zeros((2 * self.n + 1, self.n))
        sigmas[0] = x
        for i in range(self.n):
            sigmas[i + 1] = x + self.gamma * L[:, i]
            sigmas[i + 1 + self.n] = x - self.gamma * L[:, i]
        return sigmas

    def predict(self, x, P, dt):
        sigmas = self.sigma_points(x, P)
        sigmas_pred = np.array([self.f(s, dt) for s in sigmas])

        x_pred = np.dot(self.Wm, sigmas_pred)
        P_pred = self.Q.copy()
        for i in range(2 * self.n + 1):
            d = sigmas_pred[i] - x_pred
            P_pred += self.Wc[i] * np.outer(d, d)

        return x_pred, P_pred, sigmas_pred

    def update(self, x_pred, P_pred, sigmas_pred, z):
        sigmas_z = np.array([self.h(s) for s in sigmas_pred])

        z_pred = np.dot(self.Wm, sigmas_z)
        Pzz = self.R.copy()
        Pxz = np.zeros((self.n, self.m))

        for i in range(2 * self.n + 1):
            dz = sigmas_z[i] - z_pred
            dx = sigmas_pred[i] - x_pred
            Pzz += self.Wc[i] * np.outer(dz, dz)
            Pxz += self.Wc[i] * np.outer(dx, dz)

        K = Pxz @ np.linalg.inv(Pzz)
        x_new = x_pred + K @ (z - z_pred)
        P_new = P_pred - K @ Pzz @ K.T

        return x_new, P_new

# --- 等速円運動モデル ---
def state_transition(x, dt):
    px, py, vx, vy = x
    return np.array([px + vx * dt, py + vy * dt, vx, vy])

def observation(x):
    return np.array([x[0], x[1]])

# --- シミュレーション ---
np.random.seed(42)
dt = 0.1
T = 10
steps = int(T / dt)
omega = 0.5  # 角速度

true_states = np.zeros((steps, 4))
true_states[0] = [5, 0, 0, 5 * omega]
for k in range(1, steps):
    t = k * dt
    true_states[k] = [5 * np.cos(omega * t), 5 * np.sin(omega * t),
                       -5 * omega * np.sin(omega * t),
                       5 * omega * np.cos(omega * t)]

R = np.diag([0.5**2, 0.5**2])
measurements = true_states[:, :2] + np.random.multivariate_normal(
    [0, 0], R, steps)

Q = np.diag([0.01, 0.01, 0.1, 0.1])
ukf = UKF(n=4, m=2, f=state_transition, h=observation, Q=Q, R=R)

x_est = np.array([measurements[0, 0], measurements[0, 1], 0, 2.5])
P_est = np.eye(4)
estimates = [x_est.copy()]

for k in range(1, steps):
    x_pred, P_pred, sigmas_pred = ukf.predict(x_est, P_est, dt)
    x_est, P_est = ukf.update(x_pred, P_pred, sigmas_pred, measurements[k])
    estimates.append(x_est.copy())

estimates = np.array(estimates)

# --- 可視化 ---
plt.figure(figsize=(8, 8))
plt.plot(true_states[:, 0], true_states[:, 1], 'b-', linewidth=2,
         label='True')
plt.scatter(measurements[:, 0], measurements[:, 1], c='gray', s=10,
            alpha=0.3, label='Measurements')
plt.plot(estimates[:, 0], estimates[:, 1], 'r--', linewidth=1.5,
         label='UKF estimate')
plt.xlabel('x')
plt.ylabel('y')
plt.title('UKF: Circular Motion Tracking')
plt.legend()
plt.axis('equal')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

関連記事

参考文献

  • Julier, S. J., & Uhlmann, J. K. (2004). “Unscented Filtering and Nonlinear Estimation”. Proceedings of the IEEE, 92(3), 401-422.
  • Wan, E. A., & van der Merwe, R. (2000). “The Unscented Kalman Filter for Nonlinear Estimation”. Proceedings of the IEEE Adaptive Systems for Signal Processing, Communications, and Control Symposium.
  • Simon, D. (2006). Optimal State Estimation. Wiley. Chapter 14.