Udacity CS373: Programming A Robotic Car Unit 3: Particle Filters
Udacity CS373: Programming A Robotic Car Unit 3: Particle Filters
Measurement updates
For the measurement update you computed posterior over state, given a measurement update,
that was proportional to the normalization of the probability of the measurement, given the state,
multiplied by . This is written as:
Let's flesh out this formula to show you how you used it. The distribution, , was your set
of particles; 1000 particles together represented your -- prior X. The importance weight is
21
represented by, . Technically speaking, the particles with the importance weights are a
representation of distribution. But we want to get rid of the importance weights so by resampling
you work the importance weight back into the set of particles so that the resulting particles
will represent the correct posterior.
Motion updates
In the motion update you computed a posterior over distribution one time step later, and that is the
convolution of the transition probability, multiplied by the prior. This is written as:
Similarly, let's have a look at how this formula is operating within your particle filter. Your set of
particles is written as, , and your sample from the sum, . By taking a random particle from
and applying the motion model with the noise model to generate a random particle . As
a result you get a new particle set, that is the correct distribution after the robot motion.
This math is actually the same for all of the filters we have talked about so far!
Q-16: Quiz
Answer to Q-16
2012 (2012)
The difference between the Google car and what you have learned so far, is that the Google car
follows a bicycle model and the sensor data. Instead of using landmarks like the robot, the Google
car uses a really elaborate road map. It takes a single snapshot, matches it to the map and the better
the match, the higher the score. Additional sensors, like GPS and inertial sensors, also differentiate
22
the Google car from your robot model.
Despite these differences, you do have a solid understanding of the gist of how the Google car is
able to understand where it is and where other cars are. When you build a system, you have to dive
into more elaborate systems, which is doable.
Answer Key
A-1: Answer
A-2: Answer
Remember that even though the histogram filters are discrete, they are able to represent multiple
bumps. On the other hand the Kalman filter was a single Gaussian, which is by definition unimodal.
23
A-3: Answer
The histogram's biggest disadvantage is that it scales exponentially. This is because any grid that
is defined over k-dimensions will end up having exponentially many grid cells in the number of
dimensions, which doesn't allow us to represent high dimensional grids very well. This is sufficient
for 3-dimensional robot localization programs, but becomes less useful with higher dimensions.
In contrast, the Kalman filter is quadratic. It is fully represented by a vector -- the
mean -- and the covariance matrix, which is quadratic. This makes the Kalman filter
a lot more efficient because it can operate on a state space with more dimensions.
A-4: Answer
Both histogram and Kalman filters are not exact, but are an approximation of the posterior
distribution. Histogram filters are approximate because the world is not discrete; localization for
example, is an approximate filter. Kalman filters are also approximate, they are only exact for linear
systems, however, the world is non-linear.
24
A-5: Answer
myrobot = robot()
myrobot.set(30.0, 50.0, pi/2)
myrobot = myrobot.move(-pi/2, 15.0)
print myrobot.sense()
myrobot = myrobot.move(-pi/2, 10.0)
print myrobot.sense()
[39.0, 46.0, 39.0, 46.0]
[32.0, 53.1, 47.1, 40.3]
A-6: Answer
myrobot = robot()
myrobot.set_noise(5.0, 0.1, 5.0) # here is where you add your code
myrobot.set(30.0, 50.0, pi/2)
myrobot = myrobot.move(-pi/2, 15.0)
print myrobot.sense()
myrobot = myrobot.move(-pi/2, 10.0)
print myrobot.sense()
Notice that every time you hit run you get different set of values.
A-7: Answer
N = 1000
25
p = []
for i in range(N): # iterate the loop 1000 times
x = robot() # create an object called robot
p.append(x) # append the object to growing list p
print len(p)
1000
If you try to print just p you get 1000 particles, each of which has three values associated with it, an
x-coordinate, a y-coordinate and an orientation (heading direction).
A-8: Answer
Here is one possible solution:
N = 1000
p = []
for i in range(N):
x = robot()
p.append(x)
p2 = []
for i in range(N): # go through all the particles again
p2.append(p[i].move(0.1, 5.0)) # append to list p2 the result of
motion applied to the i particle, chosen from particle set
p = p2
If you got this far, you are half way through! However, the next half is going to be tricky.
A-9: Answer
w = []
for i in range(N):
w.append(p[i].measurement_prob(Z)) # append the output of the
function measurement_prob to the i particle with the argument of the extra
measurement
print w
The results return some outputs that are highly unlikely, with exponents of -146, while others are
more likely, for example exponents of -5 -- these are the particles that are more likely to survive.
For the final step of the particle filter algorithm, you have to sample particles from p with a
probability that is proportional to a corresponding w value. Particles in p that have a large output
value should be drawn more frequently than the ones with a smaller value. How hard can that be?
A-10: Answer
To obtain the answer, you just have to normalize the
importance weights by dividing each weight by the sum of the
weights.
26
A-11: Answer
Yes because something with an importance weight of
0.1 is quite unlikely to be sampled into the next data set.
A-12: Answer
The answer is yes again, because even though the importance weight is large, it could
happen that in each of the five resampling steps you would pick one of the other
four.
27
A-13: Answer
For p3to never be drawn in the resampling phase, you would always have to draw p1, p2, p4 or p5.
These together have a 0.6 probability of being drawn (sum of them all). For five independent
samplings to draw one of those four, you get a total probability of 0.65, which is approximately
0.0777. In other words, there is about a 7.77 percent chance that p3is missing -- about a 93 percent
probability of never sampling it. Particles with small importance weights will survive at a much
lower rate than the ones with larger importance weights. This is exactly what you want to get from
the resampling step.
A-14: Answer (Resampling Wheel)
28
Represent all of the particles and importance weights in a big wheel. Each particle
occupies a slice that corresponds to its' importance weight. Particles with a bigger
weight occupy more space, where particles with a smaller weight occupy less
space.
Initially, guess a particle index, uniformly from the set of all indices. This can be written as:
index = U[1...N]
If you select w
6
, the trick is to construct a function, , that you initialize to zero,
and to which you add a uniformly drawn continuous value that sits between zero
and 2*wmax, which is the largest of the importance weights in the importance
set.
Since w5is the largest, you are going to add a random value that might
be as large as twice w
5
. Suppose that you start at the position indicated
in the picture above and add a beta that brings you to w
7
, as shown
29
below.
Now iterate the following loop; if the importance weights of the present particle doesn't suffice to
reach all the way to , then subtract from the index and increase the index by increments of one.
This is written as:
if windex <
-windex
index index +1
What you have done is moved the index to the next w and removed a section of .
By repeating this, you will eventually get to the point where beta is smaller than your w[index],
then you pick the particle associated with that index.
When we do this N times, we get N particles, and we can see that particles will be chosen in
proportion to their circumference on the circle.
30
A-15: Answer
Orientation does matter in the second step of particle filtering because the prediction is so different
for different orientations.
A-16:
Answer
31
32
Sebastian's Code: Particle Filters
from math import *
import random
landmarks = [[20.0, 20.0], [80.0, 80.0], [20.0, 80.0], [80.0, 20.0]]
world_size = 100.0
class robot:
def __init__(self):
self.x = random.random() * world_size
self.y = random.random() * world_size
self.orientation = random.random() * 2.0 * pi
self.forward_noise = 0.0;
self.turn_noise = 0.0;
self.sense_noise = 0.0;
def set(self, new_x, new_y, new_orientation):
if new_x < 0 or new_x >= world_size:
raise ValueError, 'X coordinate out of bound'
if new_y < 0 or new_y >= world_size:
raise ValueError, 'Y coordinate out of bound'
if new_orientation < 0 or new_orientation >= 2 * pi:
raise ValueError, 'Orientation must be in [0..2pi]'
self.x = float(new_x)
self.y = float(new_y)
self.orientation = float(new_orientation)
def set_noise(self, new_f_noise, new_t_noise, new_s_noise):
# makes it possible to change the noise parameters
# this is often useful in particle filters
self.forward_noise = float(new_f_noise);
self.turn_noise = float(new_t_noise);
self.sense_noise = float(new_s_noise);
def sense(self):
Z = []
for i in range(len(landmarks)):
dist = sqrt((self.x - landmarks[i][0]) ** 2 + (self.y -
landmarks[i][1]) ** 2)
dist += random.gauss(0.0, self.sense_noise)
Z.append(dist)
return Z
33
def move(self, turn, forward):
if forward < 0:
raise ValueError, 'Robot cant move backwards'
# turn, and add randomness to the turning command
orientation = self.orientation + float(turn) + random.gauss(0.0,
self.turn_noise)
orientation %= 2 * pi
# move, and add randomness to the motion command
dist = float(forward) + random.gauss(0.0, self.forward_noise)
x = self.x + (cos(orientation) * dist)
y = self.y + (sin(orientation) * dist)
x %= world_size # cyclic truncate
y %= world_size
# set particle
res = robot()
res.set(x, y, orientation)
res.set_noise(self.forward_noise, self.turn_noise, self.sense_noise)
return res
def Gaussian(self, mu, sigma, x):
# calculates the probability of x for 1-dim Gaussian with mean mu and
var. sigma
return exp(- ((mu - x) ** 2) / (sigma ** 2) / 2.0) / sqrt(2.0 * pi *
(sigma ** 2))
def measurement_prob(self, measurement):
# calculates how likely a measurement should be
prob = 1.0;
for i in range(len(landmarks)):
dist = sqrt((self.x - landmarks[i][0]) ** 2 + (self.y -
landmarks[i][1]) ** 2)
prob *= self.Gaussian(dist, self.sense_noise, measurement[i])
return prob
def __repr__(self):
return '[x=%.6s y=%.6s orient=%.6s]' % (str(self.x), str(self.y),
str(self.orientation))
def eval(r, p):
34
sum = 0.0;
for i in range(len(p)): # calculate mean error
dx = (p[i].x - r.x + (world_size/2.0)) % world_size - (world_size/
2.0)
dy = (p[i].y - r.y + (world_size/2.0)) % world_size - (world_size/
2.0)
err = sqrt(dx * dx + dy * dy)
sum += err
return sum / float(len(p))
# --------
N = 1000
T = 10
myrobot = robot()
p = []
for i in range(N):
r = robot()
#r.set_noise(0.01,0.01,1.0)
r.set_noise(0.05, 0.05, 5.0)#Sebastian's provided noise.
p.append(r)
for t in range(T):
myrobot= myrobot.move(0.1, 5.0)
Z = myrobot.sense()
p2 = []
for i in range(N):
p2.append(p[i].move(0.1, 5.0))
p = p2
w = []
for i in range(N):
w.append(p[i].measurement_prob(Z))
p3 = []
index = int(random.random() * N)
beta = 0.0
mw = max(w)
for i in range(N):
beta += random.random() * 2.0 * mw
while beta > w[index]:
35
beta -= w[index]
index = (index + 1) % N
p3.append(p[index])
p = p3
print eval(myrobot, p)
if eval(myrobot, p) > 15.0:
for i in range(N):
print '#', i, p[i]
print 'R', myrobot
36