합동회사 코무라소프트
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.5, R = 2, 직전의 x̂ = 4, P = 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 = 5, K = 0.6 일 때 업데이트 후의 는 얼마입니까?

Q2. 이어서 예측 분산 P⁻ = 4 일 때, 업데이트 후의 분산 P 는 얼마입니까?