実装読解 — 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̂⁻(メソッド内のローカル変数)P⁻(メソッド内のローカル変数)x̂(フィルタの状態)P(フィルタの状態)理解チェック 1 — 1 ループを手で追う
Q = 0.5、R = 2、直前の x̂ = 4、P = 1、観測 z = 7 の 1 ループ分を、実装の順序で追います。
Q1. 1 行目 x_pred = self.x_hat を実行したあと、予測値 x̂⁻ はいくつですか。
定常モデルなので x̂⁻ = x̂ = 4。
Q2. 2 行目 P_pred = self.P + self.Q を実行したあと、予測分散 P⁻ はいくつですか。
P⁻ = 1 + 0.5 = 1.5。
Q3. 3 行目 K = P_pred / (P_pred + self.R) を実行したあと、カルマンゲインはいくつですか。
K = 1.5 / (1.5 + 2) ≈ 0.4286。
Q4. 4 行目 self.x_hat = x_pred + K * (z - x_pred) を実行したあと、更新後の推定値 x̂ はいくつですか。
観測と予測の差は 7 − 4 = 3、x̂ ≈ 4 + 0.4286 × 3 ≈ 5.2857。
Q5. 5 行目 self.P = (1.0 - K) * P_pred を実行したあと、更新後の分散 P はいくつですか。
P ≈ (1 − 0.4286) × 1.5 ≈ 0.8571。
読み解くときのポイント
- 予測の 2 行と更新の 3 行をまず分ける
Kが何に依存しているか確認する- 最後に分散
Pがちゃんと更新されているか確かめる
理解チェック 2 — コードの意味を読む
実装の各行が、式のどの部分を表しているかを言葉で説明します。
Q1. 次のうち、「観測を取り込んだあとで不確かさを減らす」処理に対応する行はどれですか。
self.P = (1.0 - K) * P_pred が、観測を取り込んだあとに不確かさを減らす行です。
Q2. 実装を誤って K = self.R / (P_pred + self.R) と書いてしまった場合、どのような問題が生じますか。
本来は R が大きいほど観測を疑うべきですが、式を逆にすると大きい R で観測を強く採用してしまいます。
理解チェック 3 — イノベーションが 0 のケース
観測が予測と一致している特殊ケースでの挙動を確認します。
Q1. 予測値 x̂⁻ = 5、観測 z = 5、K = 0.6 のとき、更新後の x̂ はいくつですか。
イノベーションは 5 − 5 = 0 なので、推定値は x̂⁻ = 5 のまま動きません。
Q2. 続けて予測分散 P⁻ = 4 のとき、更新後の分散 P はいくつですか。
P = (1 − 0.6) × 4 = 1.6。推定値は動かなくても、観測を取り込んだので不確かさは減少します。