合同会社小村ソフト
第 6 章

実装読解 — 1 次元の最小実装を追う

Python / C の最小実装を、式・変数名・役割で照らし合わせながら 1 ループ追えるようにする。

ここまでの内容をコードに落とすと、1 次元のカルマンフィルタは非常に短く書けます。大事なのは「行数」ではなく、各行がどの式に対応しているかを追えることです。

Python 最小実装

状態(x_hat, P)とパラメータ(Q, R)はクラスにまとめます。global を使ってモジュール変数を書き換えると、複数のフィルタを同時に動かせず、テストもしにくくなるため避けます。

class KalmanFilter1D:
    def __init__(self, x_hat=0.0, P=10.0, Q=0.05, R=2.0):
        self.x_hat = x_hat
        self.P = P
        self.Q = Q
        self.R = R

    def step(self, z):
        x_pred = self.x_hat
        P_pred = self.P + self.Q

        K = P_pred / (P_pred + self.R)
        self.x_hat = x_pred + K * (z - x_pred)
        self.P = (1.0 - K) * P_pred
        return self.x_hat, self.P

(1.0 - K) と書いているのは、整数で計算されないようにするためです。Python では 1 - K でも K が float なら結果は float になるため動作上は同じですが、C など他言語に移植したときの挙動を揃える意図で 1.0 と明示しています。

C 最小実装

C 版も状態とパラメータを構造体にまとめ、関数はその構造体を受け取って書き換える形にします。グローバル変数は使いません。

typedef struct {
    double x_hat;
    double P;
    double Q;
    double R;
} KalmanFilter1D;

double kf_step(KalmanFilter1D *kf, double z) {
    double x_pred = kf->x_hat;
    double P_pred = kf->P + kf->Q;

    double K = P_pred / (P_pred + kf->R);
    kf->x_hat = x_pred + K * (z - x_pred);
    kf->P = (1.0 - K) * P_pred;
    return kf->x_hat;
}

使用例: KalmanFilter1D kf = { .x_hat = 0.0, .P = 10.0, .Q = 0.05, .R = 2.0 }; と初期化してから、観測ごとに kf_step(&kf, z) を呼びます。

式と変数の対応

x_pred
予測値 x̂⁻(メソッド内のローカル変数)
P_pred
予測分散 P⁻(メソッド内のローカル変数)
K
カルマンゲイン(メソッド内のローカル変数)
self.x_hat / kf->x_hat
更新後の推定 (フィルタの状態)
self.P / kf->P
更新後の分散 P(フィルタの状態)

理解チェック 1 — 1 ループを手で追う

Q = 0.5R = 2、直前の x̂ = 4P = 1、観測 z = 7 の 1 ループ分を、実装の順序で追います。

Q1. 1 行目 x_pred = self.x_hat を実行したあと、予測値 x̂⁻ はいくつですか。

Q2. 2 行目 P_pred = self.P + self.Q を実行したあと、予測分散 P⁻ はいくつですか。

Q3. 3 行目 K = P_pred / (P_pred + self.R) を実行したあと、カルマンゲインはいくつですか。

Q4. 4 行目 self.x_hat = x_pred + K * (z - x_pred) を実行したあと、更新後の推定値 はいくつですか。

Q5. 5 行目 self.P = (1.0 - K) * P_pred を実行したあと、更新後の分散 P はいくつですか。

読み解くときのポイント

  1. 予測の 2 行と更新の 3 行をまず分ける
  2. K が何に依存しているか確認する
  3. 最後に分散 P がちゃんと更新されているか確かめる

理解チェック 2 — コードの意味を読む

実装の各行が、式のどの部分を表しているかを言葉で説明します。

Q1. 次のうち、「観測を取り込んだあとで不確かさを減らす」処理に対応する行はどれですか。

Q2. 実装を誤って K = self.R / (P_pred + self.R) と書いてしまった場合、どのような問題が生じますか。

理解チェック 3 — イノベーションが 0 のケース

観測が予測と一致している特殊ケースでの挙動を確認します。

Q1. 予測値 x̂⁻ = 5、観測 z = 5K = 0.6 のとき、更新後の はいくつですか。

Q2. 続けて予測分散 P⁻ = 4 のとき、更新後の分散 P はいくつですか。