位置を求めるカルマンフィルタ

基礎的なことを再考してみたくて。ノートに書くと失くすので、ここに書いてみよう。

## メモ概要
- 位置をスムージングさせるカルマンフィルタを、最も単純な例題として考えてみる。
- 誤差のベイズ推定として導出する。
- １次元で、状態も観測も位置とする
- 初期状態の推定についてのふるまいをシミュレーションでみてみたい

-------------------------------------------------------

## 1次元の運動の位置の定式化

### 誤差の分散の時間変化

時刻$t_n$の位置を$x_n$とあらわすことにする。時刻$t_n$から時刻$t_{n+1}$にかけての移動量を$u_n$とする。

```math
x_{n+1}=x_n + u_n
```
つぎに、位置と移動量について、真値からの誤差を考える。誤差は位置の真値$x^*$と推定値$\hat{x}$の差で定義する。

```math
\delta x = x^* - \hat{x} \\
\delta u = u^* - \hat{u} \\
```

これから

```math
\delta x_{n+1} = \delta x_n + \delta u_n
```

を得る。この誤差の分散は、位置と移動量が無相関であると仮定すると

```math
V[\delta x_{n+1}] = V[\delta x_n] + V[\delta u_n]
```

と書ける。$x$の分散を$P$、$u$の分散を$Q$と書くことにすると

```math
P_{n+1} = P_{n} + Q_n
```

となる。今、初期の位置$x_0$と誤差の分散$P_0$、移動量の分散$Q$はモデルで与えられているとする。$P_n$は時間とともに、移動量の曖昧性$Q$が蓄積されてどんどん大きくなっていく。

-------------------------------------------------

### ベイズ推定による状態と誤差共分散の更新

次に、状態変数の位置が$x_n$であるとき、位置について観測$y_n$を得たとする。観測には誤差$\epsilon$があり、この誤差は

```math
y = x + \epsilon \\
\epsilon \sim N(0, R)
```

に従うとする。$N$は正規分布を表す。

今、$x_n$は観測$y_n$を得る前であるとすると、そのときの誤差の確率分布は、

```math
P(\delta x) = N(0, P)
```

であるとする。この$x$のとき、位置$y$を観測する確率は、$\Delta y = y - x$として

```math
P(\Delta y | \delta x) = N(0,R)
```

である。では、$y$を観測した後で最も尤もらしい$\delta x$を求めるには、

```math
\hat{\delta x} = \underset{\delta x}{\textrm{argmax}} P(\delta x|\Delta y) 
```

である。 ベイズの定理(Bayes's theorem) より

```math
P(\delta x|\Delta y) = \frac{P(\Delta y | \delta x) P(\delta x)}{P(\Delta y)}
```

であるので、

```math
\begin{align}
\log P(\delta x | y) &= -\frac{1}{2\pi} \left[ \frac{(\Delta y - \delta x)^2}{R} + \frac{(\delta x)^2}{P}  \right ] + const. \\
 &= -\frac{1}{2\pi} (R^{-1} + P^{-1})\left(\delta x - \frac{R^{-1} y}{R^{-1}+P^{-1}} \right)^2 + const. 
\end{align}
```

から

```math
 \hat{\delta x} = \frac{P}{P+R} \Delta y
```

を得る。また、このときの事後確率を最大化したときの事後分布$P(\delta x|\Delta y)$の分散は、

```math
\hat{P}^{-1} = R^{-1} + P^{-1} 
```
と得る。これを用いて状態変数$x$を更新したいので、つまり$K=\frac{P}{P+R}$とおいて、状態変数と誤差の分散の更新式を得る。

```math
  x \leftarrow x + K(y-x) \\
  P \leftarrow P(1-K)
```

### モデルによる観測値の生成 (simulation)

次に、この運動モデルと誤差モデルによって生成される観測値を計算してみる。

- 運動は初期値 $x_0=x(t_0)$ と制御項 $u_n=u^*(t_n)$ の真値から生成できる。ただし、実際は運動方程式$d/dt x(t) = v(t)$で記述されている運動を$u_n=v(t_n)\Delta t$で離散化していると考える。
 
- 実際に観測される制御項には正規分布に従うノイズが乗っている 

```math
 u_n=u_n^*+\epsilon \\
 \epsilon \sim N(0,Q)
```

ウィナー過程(確率過程)あるいはノイズ密度から$Q$を計算することができるが、それはまた別の機会にまとめます。

- 観測は真値に対してノイズが載ったものです。

```math
 y_n=x_n^*+\epsilon_y \\
 \epsilon_y \sim N(0,R)
```
これでシミュレーション用のデータが作れるはずです。指定するのは、初期値と制御項です。微分方程式で言えば、状態変数の時間微分に相当する部分の関数です。ノイズはそれぞれ性器乱数を用いて生成できます。

# シミュレーション

以上で定式化できているので、あとは実装するだけです。

### データの生成

設定するのは

- 離散化の条件としてサンプリングレート $F_s$ [1/sec]とデータ長さ T [sec]: 時刻は$t_n=n*dT$となる。$dT = 1/F_s$
- 初期条件 (初期位置) $x_0$ [m]
- 移動量 $u_n$ [m] for all n in 0

生成してみると、、、

```python
import pandas as pd
import numpy as np

FS, T=4, 100.0# USER SET
x_init = 0.0 # INIITIAL POSITION 
Q = 0.1 #Variance of delta position (control term)
R = 1.0 #Variance of measuremnt noise
dt = 1.0 / float(FS)
df = pd.DataFrame([], columns=["ts", "u_true", "x_true", "u", "y", "x", "P", "P_pri", "K"])
df.ts = np.linspace(0.0, T, int(T*FS))
df.u_true = np.sin(2*np.pi / float(T) * df.ts) # USE SETTING
df.x_true = np.cumsum(df.u_true) * dt
df.u = df.u_true + Q * np.random.randn(df.ts.size)
df.y = df.x_true + R * np.random.randn(df.ts.size)

import matplotlib.pyplot as plt
%matplotlib inline 
plt.close('all')
import seaborn as sns
sns.set_style("whitegrid")
fig, axes = plt.subplots(2,1, sharex=True)
axes[0].plot(df.ts, df.u_true, '*', df.ts, df.u, ".")
axes[0].set_ylabel('u[m]')
axes[0].legend(("true", "sim"))
axes[1].plot(df.ts, df.x_true, '*', df.ts, df.y, ".")
axes[1].legend(("true", "sim"))
axes[1].set_xlabel('time [s]')
axes[1].set_ylabel('x[m]')
```

![20181027_fig1.png](https://qiita-image-store.s3.amazonaws.com/0/294040/f117a047-9a6b-486d-80ff-a9c91ad2fe28.png)



### Kalman filter simulation

適当な位置の初期値を与えて実行してみると、、、、

```python

#
# Kalman filter
#
x = -100.0
P = 1.0
for idx, row in df.iterrows():
    #
    # Prediction
    #
    if idx > 0:
        P = P + Q
        x = x + df.loc[idx-1,"u"]
    #
    # filtering
    #
#    x, P, y = df.x[idx], df.P_pri[idx], df.y[idx]
    K = P / (P + R)
    x = x + K * (df.y[idx] - x)
    P = P * (1.0 - K)
    df.loc[idx,"x"], df.loc[idx, "P"], df.loc[idx, "K"] = x, P, K
    
fig, axes = plt.subplots(2,1, sharex=True)
axes[0].plot(df.ts, df.x, '-*', df.ts, df.y, ".")
axes[0].set_ylabel('x[m]')
axes[0].legend(("est", "obs"))

axes[1].plot(df.ts, df.K)
axes[1].set_xlabel('time[s]')

plt.show()
```

![20181027_fig2.png](https://qiita-image-store.s3.amazonaws.com/0/294040/beade5df-8b52-efb0-2efb-90a976579e57.png)


正解位置に寄っていく。