공부

Kalman Filter 공부했다. - 2 차원 이동 실습

Swimming_Kim 2018. 12. 28. 23:28

지난 시간에 1차원에 대한 칼만 필터를 설계해 보았었다. 그 코드를 잠시 보자면


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
measurements = [1.2.3.]
 
= matrix([[0.], [0.]]) # initial state (location and velocity)
= matrix([[1000.0.], [0.1000.]]) # initial uncertainty
= matrix([[0.], [0.]]) # external motion
= matrix([[1.1.], [01.]]) # next state function
= matrix([[1.0.]]) # measurement function
= matrix([[1.]]) # measurement uncertainty
= matrix([[1.0.], [0.1.]]) # identity matrix
 
def kalman_filter(x, P):
    for n in range(len(measurements)):
        # measurement update
        z = matrix([[measurements[n]]])
        y = z - ( H * x )
        S = ( H * P * H.transpose() ) + R
        K = P * H.transpose() * S.inverse()
 
        # prediction
        x = x + (K * y)
        P = (I - K * H) * P
 
        x = ( F * x ) + u
        P = F * P * F.transpose()
 
        print('x', x, 'P', P)
    return (x,P)
cs


 위와 같았는데, 칼만 필터에 대한 설명은 지난 번에 하였으므로, 이번에는 각 Matrix들이 왜 저렇게 생겼는지 알아보고, 차원을 높여서 2차원에서 칼만 필터를 설계해 본다.


 주의 깊게 봐야 할 Matrix로 P, F, H 정도가 있다. P는 두 벡터의 불확실성에 대한 행렬로 칼만 필터를 처음 실행할 시점에선 불확실성을 높게 1000으로 잡아 주었고, 위치와 속도 둘 사이의 불확실성에는 서로 관계가 없으므로 대각 성분에만 값을 넣어준 것이 보인다. 


 H는 벡터에서 측정 값만을 뽑아내는 행렬인데, 우리는 위치 x에 대해서만 측정을 하므로 [1, 0]

이런 모양을 하고 있게 된다.


 F는 state를 나타내는 벡터로 사실 이것을 설명하기 위해서 이 글을 작성하는 것이기도 하다.



  이는 약간의 물리 지식이 필요하기도 하다. 속도를 가진 어떤 물체의 위치를 알고 싶다고 할 때


거리 = 시간 * 속도


라는 놀라운 사실을 알고 있어야 한다. 그래서 속도(X_dot)를 통해 0.1초 후의 어떠한 물체의 위치(X)를 알고 싶다고 한다면, X = X + 0.1*X_dot 이 되는 것이다. - 다만, 칼만 필터를 사용하면서 우리는 속도의 값을 직접적으로 받을 수 없었다는 것을 기억하자.


그래서 위 그림은 2차원 (x,y)인 상황에서 x와 x_dot(x 방향으로의 속도), y와 y_dot(y방향으로의 속도)를 하나의 벡터로 나타낼 때 다음 time_step(=0.1)에서의 상태를 나타내는 새로운 행렬 F 을 나타내 보라는 문제이다. 



 일단 수식들을 먼저 정리해 보자. time_step(=0.1)전의 상태들에 대해서는 각 변수들에 _1을 붙이도록 하겠다. x-1, y-1이렇게 말이다. 


x = x-1 + 0.1 * x_dot

x_dot = x_dot-1

y = y-1 + 0.1 * y_dot

y_dot = y_dot-1


 자, 이렇게 모든 변수들에 대해서 식이 완성되었으므로 F행렬의 빈칸을 채울 수 있게 되었다. 그럼 나머지 행렬들에 대해서는 앞서 말하였으니, 실질적인 코드를 채울 수 있게 되었다.


1
2
3
4
5
6
7
8
9
10
11
dt = 0.1
 
= matrix([[initial_xy[0]], [initial_xy[1]], [0.], [0.]]) # initial state (location and velocity)
= matrix([[0.], [0.], [0.], [0.]]) # external motion
 
 
=  matrix([[0.0.0.0.], [0.0.0.0.], [0.0.1000.0.], [0.0.0.1000.]])# initial uncertainty: 0 for positions x and y, 1000 for the two velocities
=  matrix([[1.0.0.10.], [0.1.0.0.1], [0.0.1.0.], [0.0.0.1.]])# next state function: generalize the 2d version to 4d
=  matrix([[1.0.0.0.], [0.1.0.0.]])# measurement function: reflect the fact that we observe x and y but not the two velocities
=  matrix([[0.10.], [0.0.1]])# measurement uncertainty: use 2x2 matrix with 0.1 as main diagonal
=  matrix([[1.0.0.0.], [0.1.0.0. ], [0.0.1.0.], [0.0.0.1.]])# 4d identity matrix
cs


짠! x-y좌표에서의 칼만 필터가 구현이 되었다!!



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# Fill in the matrices P, F, H, R and I at the bottom
#
# This question requires NO CODING, just fill in the 
# matrices where indicated. Please do not delete or modify
# any provided code OR comments. Good luck!
 
from math import *
 
class matrix:
    
    # implements basic operations of a matrix class
    
    def __init__(self, value):
        self.value = value
        self.dimx = len(value)
        self.dimy = len(value[0])
        if value == [[]]:
            self.dimx = 0
    
    def zero(self, dimx, dimy):
        # check if valid dimensions
        if dimx < 1 or dimy < 1:
            raise ValueError
        else:
            self.dimx = dimx
            self.dimy = dimy
            self.value = [[0 for row in range(dimy)] for col in range(dimx)]
    
    def identity(self, dim):
        # check if valid dimension
        if dim < 1:
            raise ValueError
        else:
            self.dimx = dim
            self.dimy = dim
            self.value = [[0 for row in range(dim)] for col in range(dim)]
            for i in range(dim):
                self.value[i][i] = 1
    
    def show(self):
        for i in range(self.dimx):
            print (self.value[i])
        print(' ')
    
    def __add__(self, other):
        # check if correct dimensions
        if self.dimx != other.dimx or self.dimy != other.dimy:
            raise ValueError
        else:
            # add if correct dimensions
            res = matrix([[]])
            res.zero(self.dimx, self.dimy)
            for i in range(self.dimx):
                for j in range(self.dimy):
                    res.value[i][j] = self.value[i][j] + other.value[i][j]
            return res
    
    def __sub__(self, other):
        # check if correct dimensions
        if self.dimx != other.dimx or self.dimy != other.dimy:
            raise ValueError
        else:
            # subtract if correct dimensions
            res = matrix([[]])
            res.zero(self.dimx, self.dimy)
            for i in range(self.dimx):
                for j in range(self.dimy):
                    res.value[i][j] = self.value[i][j] - other.value[i][j]
            return res
    
    def __mul__(self, other):
        # check if correct dimensions
        if self.dimy != other.dimx:
            raise ValueError
        else:
            # subtract if correct dimensions
            res = matrix([[]])
            res.zero(self.dimx, other.dimy)
            for i in range(self.dimx):
                for j in range(other.dimy):
                    for k in range(self.dimy):
                        res.value[i][j] += self.value[i][k] * other.value[k][j]
            return res
    
    def transpose(self):
        # compute transpose
        res = matrix([[]])
        res.zero(self.dimy, self.dimx)
        for i in range(self.dimx):
            for j in range(self.dimy):
                res.value[j][i] = self.value[i][j]
        return res
    
    # Thanks to Ernesto P. Adorio for use of Cholesky and CholeskyInverse functions
    
    def Cholesky(self, ztol=1.0e-5):
        # Computes the upper triangular Cholesky factorization of
        # a positive definite matrix.
        res = matrix([[]])
        res.zero(self.dimx, self.dimx)
        
        for i in range(self.dimx):
            S = sum([(res.value[k][i])**2 for k in range(i)])
            d = self.value[i][i] - S
            if abs(d) < ztol:
                res.value[i][i] = 0.0
            else:
                if d < 0.0:
                    raise ValueError
                res.value[i][i] = sqrt(d)
            for j in range(i+1, self.dimx):
                S = sum([res.value[k][i] * res.value[k][j] for k in range(self.dimx)])
                if abs(S) < ztol:
                    S = 0.0
                try:
                   res.value[i][j] = (self.value[i][j] - S)/res.value[i][i]
                except:
                   raise ValueError
        return res
    
    def CholeskyInverse(self):
        # Computes inverse of matrix given its Cholesky upper Triangular
        # decomposition of matrix.
        res = matrix([[]])
        res.zero(self.dimx, self.dimx)
        
        # Backward step for inverse.
        for j in reversed(range(self.dimx)):
            tjj = self.value[j][j]
            S = sum([self.value[j][k]*res.value[j][k] for k in range(j+1, self.dimx)])
            res.value[j][j] = 1.0/tjj**2 - S/tjj
            for i in reversed(range(j)):
                res.value[j][i] = res.value[i][j] = -sum([self.value[i][k]*res.value[k][j] for k in range(i+1, self.dimx)])/self.value[i][i]
        return res
    
    def inverse(self):
        aux = self.Cholesky()
        res = aux.CholeskyInverse()
        return res
    
    def __repr__(self):
        return repr(self.value)
 
 
########################################
 
def filter(x, P):
    for n in range(len(measurements)):
        
        # prediction
        x = (F * x) + u
        P = F * P * F.transpose()
        
        # measurement update
        Z = matrix([measurements[n]])
        y = Z.transpose() - (H * x)
        S = H * P * H.transpose() + R
        K = P * H.transpose() * S.inverse()
        x = x + (K * y)
        P = (I - (K * H)) * P
    
    print('x= ')
    x.show()
    print('P= ')
    P.show()
 
########################################
 
print("### 4-dimensional example ###")
 
measurements = [[5.10.], [6.8.], [7.6.], [8.4.], [9.2.], [10.0.]]
initial_xy = [4.12.]
 
# measurements = [[1., 4.], [6., 0.], [11., -4.], [16., -8.]]
# initial_xy = [-4., 8.]
 
# measurements = [[1., 17.], [1., 15.], [1., 13.], [1., 11.]]
# initial_xy = [1., 19.]
 
dt = 0.1
 
= matrix([[initial_xy[0]], [initial_xy[1]], [0.], [0.]]) # initial state (location and velocity)
= matrix([[0.], [0.], [0.], [0.]]) # external motion
 
#### DO NOT MODIFY ANYTHING ABOVE HERE ####
#### fill this in, remember to use the matrix() function!: ####
 
=  matrix([[0.0.0.0.], [0.0.0.0.], [0.0.1000.0.], [0.0.0.1000.]])# initial uncertainty: 0 for positions x and y, 1000 for the two velocities
=  matrix([[1.0.0.10.], [0.1.0.0.1], [0.0.1.0.], [0.0.0.1.]])# next state function: generalize the 2d version to 4d
=  matrix([[1.0.0.0.], [0.1.0.0.]])# measurement function: reflect the fact that we observe x and y but not the two velocities
=  matrix([[0.10.], [0.0.1]])# measurement uncertainty: use 2x2 matrix with 0.1 as main diagonal
=  matrix([[1.0.0.0.], [0.1.0.0. ], [0.0.1.0.], [0.0.0.1.]])# 4d identity matrix
 
###### DO NOT MODIFY ANYTHING HERE #######
 
filter(x, P)
 
cs


 짚고 넘어가야 할 부분이 조금 있는데


= matrix([[0.], [0.], [0.], [0.]]) # external motion


 예제에서는 모두 그냥 0행렬이었던 이 U가 무슨의미일까?? 이는 외부의 힘, 그래서 물리적으로 는 곧 "가속도"를 의미한다. 지금은 그저 물체의 위치를 보면서 속도와 다음 위치를 예측하는 것일 뿐 외부의 가속도는 알 수가 없으므로 0으로 취급했던 것이다.


=  matrix([[0.0.0.0.], [0.0.0.0.], [0.0.1000.0.], [0.0.0.1000.]])


불확실성을 나타내는 행렬 P, 여기에서 비대각성분(off_diagonal)은 전부 0으로 되어 있다 이것이 무슨 의미를 가지고 있는 것이고 왜 0으로 설정해 준 것일까?? 


 어떠한 두 변수가 서로 관계를 가진다(속도와 위치처럼)는 것을 숫자로 표현한 것을 Covariance라고 한다. 이것이 높을 수록 서로 연관이 크다는 것이다. 그런 의미에서 대각 성분은 자기 자신과의 관계를 뜻한다.


 위에서는 비대각 성분들이 모두 0이었다. 하지만, 우리는 이 변수들이 서로 영향이 있다는 것을 알고 있다. 그래서 칼만 필터가 진행이 될 수록 이 비대각 성분들은 값을 보이기 시작할 것이다. 그리고 속도 성분들에 대해서도 1000이라는 큰 불확실성이 점차 작아질 것이라는 것을 볼 수 있다.