diff --git a/01_g-h_filter.ipynb b/01_g-h_filter.ipynb index 16b6857..b76e8a2 100644 --- a/01_g-h_filter.ipynb +++ b/01_g-h_filter.ipynb @@ -284,7 +284,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Try Another Scale\n", + "** Try Another Scale**\n", "\n", "Is there any way we can improve upon this result? The obvious, first thing to try is get a better sensor. Unfortunately, your co-worker informs you that she has built 10 scales, and they all operate with about the same accuracy. You have her bring out another scale, and you weigh yourself on one, and then on the other. The first scale (A) reads \"160 lbs\", and the second (B) reads \"170 lbs\". What can we conclude about your weight?\n", "\n", diff --git a/05_Kalman_Filters.ipynb b/05_Kalman_Filters.ipynb index 25052f4..5e7939a 100644 --- a/05_Kalman_Filters.ipynb +++ b/05_Kalman_Filters.ipynb @@ -1,7 +1,16 @@ { "cells": [ { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "raw", "metadata": {}, "source": [ "[Table of Contents](http://nbviewer.ipython.org/github/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/table_of_contents.ipynb)" @@ -9356,7 +9365,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#####Exercise:\n", + "### Exercise: Modify Variance Values" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "Modify the values of `movement_variance` and `sensor_variance` and note the effect on the filter and on the variance. Which has a larger effect on the value that variance converges to. For example, which results in a smaller variance:\n", "\n", " movement_variance = 40\n", @@ -19183,8 +19198,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "######Discussion\n", - "\n", + "### Discussion" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "Here we set a bad initial guess of 100. We can see that the filter never 'acquires' the signal. Note now the peak of the filter output always lags the peak of the signal by a small amount, and how the filtered signal does not come very close to capturing the high and low peaks of the input signal.\n", "\n", "If we recall the g-h filter chapter we can understand what is happening here. The structure of the g-h filter requires that the filter output chooses a value part way between the prediction and measurement. A varying signal like this one is always accelerating, whereas our process model assumes constant velocity, so the filter is mathematically guaranteed to always lag the input signal. \n", diff --git a/06_Multivariate_Kalman_Filters.ipynb b/06_Multivariate_Kalman_Filters.ipynb index 8f58dd9..e047aac 100644 --- a/06_Multivariate_Kalman_Filters.ipynb +++ b/06_Multivariate_Kalman_Filters.ipynb @@ -266,7 +266,7 @@ ] }, { - "cell_type": "markdown", + "cell_type": "raw", "metadata": {}, "source": [ "The techniques in the last chapter are very powerful, but they only work in one dimension. The gaussians represent a mean and variance that are scalars - real numbers. They provide no way to represent multidimensional data, such as the position of a dog in a field. You may retort that you could use two Kalman filters for that case, one tracks the x coordinate and the other tracks the y coordinate. That does work in some cases, but put that thought aside, because soon you will see some enormous benefits to implementing the multidimensional case.\n", @@ -16210,9 +16210,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 1:** Choose the State Variables and Set Initial Conditions\n", - "\n", - "\n", + "### **Step 1:** Choose the State Variables and Set Initial Conditions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "In the previous chapter we tracked a dog in one dimension by using a Gaussian. The mean $(\\mu)$ represented the most likely position, and the variance ($\\sigma^2$) represented the probability distribution of the position. In that problem the position is the *state* of the system, and we call $\\mu$ the *state variable*.\n", "\n", "In this chapter we will be tracking both the position and velocity of the dog, so we have two state variables. It is important to understand that this is a design choice with implications and assumptions that we are not yet prepared to explore. For example, we could optionally also track acceleration, or even jerk. For now I will only state that the decision to track position and velocity contains an assumption that acceleration is zero, which of course often not true. If the object being tracked *is* accelerating, then the performance of the filter will be suboptimal, or even diverge. However, this is a good way to introduce the concept of Kalman filtering without getting bogged down into too many details, or having to deal with large matrices.\n", @@ -16261,8 +16265,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 2:** Design the State Transition Function\n", - "\n", + "### **Step 2:** Design the State Transition Function\n", "\n", "The next step in designing a Kalman filter is telling it how to predict the next state from the current state. We do this by providing it with equations that describe the physical model of the system. For example, for our dog tracking problem we are tracking a moving object, so we just need to provide it with the Newtonian equations for motion. If we were tracking a thrown ball we would have to provide equations for how a ball moves in a gravitational field, and perhaps include the effects of things like air drag. If we were writing a Kalman filter for a rocket we would have to tell it how the rocket responds to its thrusters and main engine. A Kalman filter for a bowling ball would incorporate the effects of friction and ball rotation. You get the idea. \n", "\n", @@ -16349,7 +16352,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 3**: Design the Motion Function\n", + "### **Step 3**: Design the Motion Function\n", "\n", "The Kalman filter does not just filter data, it allows us to incorporate control inputs for systems like robots and airplanes. Consider the state transition function we wrote for the dog:\n", "\n", @@ -16368,7 +16371,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 4**: Design the Measurement Function\n", + "### **Step 4**: Design the Measurement Function\n", "\n", "The Kalman filter computes the update step in what we call *measurement space*. We mostly ignored this issue in the previous chapter because of the complication it adds. In the last chapter we tracked our dog's position using a sensor that reported his position. Computing the *residual* was easy - subtract the filter's predicted position from the measurement:\n", "\n", @@ -16414,7 +16417,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 5**: Design the Measurement Noise Matrix\n", + "### **Step 5**: Design the Measurement Noise Matrix\n", "\n", "The *measurement noise matrix* is a matrix that models the noise in our sensors as a covariance matrix. This can be admittedly a very difficult thing to do in practice. A complicated system may have many sensors, the correlation between them might not be clear, and usually their noise is not a pure Gaussian. For example, a sensor might be biased to read high if the temperature is high, and so the noise is not distributed equally on both sides of the mean. Later we will address this topic in detail. For now I just want you to get used to the idea of the measurement noise matrix so we will keep it deliberately simple.\n", "\n", @@ -16433,7 +16436,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 6**: Design the Process Noise Matrix\n", + "### **Step 6**: Design the Process Noise Matrix\n", "\n", "What is *process noise*? Consider the motion of a thrown ball. In a vacuum and with constant gravitational force it moves in a parabola. However, if you throw the ball on the surface of the earth you will also need to model factors like rotation and air drag. However, even when you have done all of that there is usually things you cannot account for. For example, consider wind. On a windy day the ball's trajectory will differ from the computed trajectory, perhaps by a significant amount. Without wind sensors, we may have no way to model the wind. The Kalman filter models this as *process noise*, and calls it $\\mathbf{Q}$.\n", "\n", @@ -18739,7 +18742,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Discussion\n", + "### Discussion\n", "\n", "As you can see, both filters produce the same results. Feel free to vary the initial guess, the measurement noise, and the process noise; so long as you make the same changes to both filters the output should be the same. This is a solid demonstration, albeit not a rigorous proof, that both filters in fact implement the same math for the 1-D case. " ] @@ -18881,7 +18884,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Exercise: Compare to a Filter That Incorporates Velocity\n", + "## Exercise: Compare to a Filter That Incorporates Velocity\n", "\n", "The last example did not use one of the fundamental insights of this chapter, unobserved variables. In this example velocity would the the unobserved variable. Write a Kalman filter that uses the state $\\mathbf{x}=\\begin{bmatrix}x & \\dot{x}\\end{bmatrix}^\\mathsf{T}$ and compare it against the filter in the last exercise which used the state $\\mathbf{x}=\\begin{bmatrix}x\\end{bmatrix}$." ] @@ -18901,7 +18904,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Solution\n", + "### Solution\n", "\n", "We've already implemented a Kalman filter for position and velocity, so I will provide the code without much comment, and then plot the result." ] @@ -19378,7 +19381,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Discussion\n", + "### Discussion\n", "\n", "The output of the filter that incorporates velocity into the state produces much better output than the filter that only tracks position - the output is much closer to a straight line. We've already discussed why unobserved variables increase the precision of the filter, so I will not repeat that explanation here. But the last exercise and this one is intended to trigger a train of thought:\n", "\n", diff --git a/08_Designing_Kalman_Filters.ipynb b/08_Designing_Kalman_Filters.ipynb index 9a6d156..69fd22e 100644 --- a/08_Designing_Kalman_Filters.ipynb +++ b/08_Designing_Kalman_Filters.ipynb @@ -474,10 +474,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "That looks correct. The slope is 1/2, as we would expect with a velocity of (2,1), and the data seems to start at near (6,4).\n", - "\n", - "##### Step 1: Choose the State Variables\n", - "\n", + "That looks correct. The slope is 1/2, as we would expect with a velocity of (2,1), and the data seems to start at near (6,4)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 1: Choose the State Variables" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "As always, the first step is to choose our state variables. We are tracking in two dimensions and have a sensor that gives us a reading in each of those two dimensions, so we know that we have the two *observed variables* $x$ and $y$. If we created our Kalman filter using only those two variables the performance would not be very good because we would be ignoring the information velocity can provide to us. We will want to incorporate velocity into our equations as well. I will represent this as\n", "\n", "$$\\mathbf{x} = \n", @@ -492,15 +502,20 @@ "\n", "It might be a good time to pause and address how you identify the unobserved variables. This particular example is somewhat obvious because we already worked through the 1D case in the previous chapters. Would it be so obvious if we were filtering market data, population data from a biology experiment, and so on? Probably not. There is no easy answer to this question. The first thing to ask yourself is what is the interpretation of the first and second derivatives of the data from the sensors. We do that because obtaining the first and second derivatives is mathematically trivial if you are reading from the sensors using a fixed time step. The first derivative is just the difference between two successive readings. In our tracking case the first derivative has an obvious physical interpretation: the difference between two successive positions is velocity. \n", "\n", - "Beyond this you can start looking at how you might combine the data from two or more different sensors to produce more information. This opens up the field of *sensor fusion*, and we will be covering examples of this in later sections. For now, recognize that choosing the appropriate state variables is paramount to getting the best possible performance from your filter. " + "Beyond this you can start looking at how you might combine the data from two or more different sensors to produce more information. This opens up the field of *sensor fusion*, and we will be covering examples of this in later sections. For now, recognize that choosing the appropriate state variables is paramount to getting the best possible performance from your filter." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### **Step 2:** Design State Transition Function" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 2:** Design State Transition Function\n", - "\n", "Our next step is to design the state transition function. Recall that the state transition function is implemented as a matrix $\\mathbf{F}$ that we multiply with the previous state of our system to get the next state, like so. \n", "\n", "$$\\mathbf{x}' = \\mathbf{Fx}$$\n", @@ -514,7 +529,7 @@ "y' &= (0*x) + (0* v_x) + (1*y) + (\\Delta t * v_y) \\\\\n", "v_y &= (0*x) + (0*v_x) + (0*y) + (1 * v_y)\n", "\\end{aligned}\n", - "$$\n" + "$$" ] }, { @@ -553,7 +568,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 3**: Design the Motion Function\n", + "### **Step 3**: Design the Motion Function" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "We have no control inputs to our robot (yet!), so this step is trivial - set the motion input $\\small\\mathbf{u}$ to zero. This is done for us by the class when it is created so we can skip this step, but for completeness we will be explicit." ] }, @@ -572,8 +593,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "##### **Step 4**: Design the Measurement Function\n", + "### **Step 4**: Design the Measurement Function" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "The measurement function defines how we go from the state variables to the measurements using the equation $\\mathbf{z} = \\mathbf{Hx}$. At first this is a bit counterintuitive, after all, we use the Kalman filter to go from measurements to state. But the update step needs to compute the residual between the current measurement and the measurement represented by the prediction step. Therefore $\\textbf{H}$ is multiplied by the state $\\textbf{x}$ to produce a measurement $\\textbf{z}$. \n", "\n", "In this case we have measurements for (x,y), so $\\textbf{z}$ must be of dimension $2\\times 1$. Our state variable is size $4\\times 1$. We can deduce the required size for $\\textbf{H}$ by recalling that multiplying a matrix of size $m\\times n$ by $n\\times p$ yields a matrix of size $m\\times p$. Thus,\n", @@ -639,8 +665,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 5**: Design the Measurement Noise Matrix\n", - "\n", + "### **Step 5**: Design the Measurement Noise Matrix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "In this step we need to mathematically model the noise in our sensor. For now we will make the simple assumption that the $x$ and $y$ variables are independent Gaussian processes. That is, the noise in x is not in any way dependent on the noise in y, and the noise is normally distributed about the mean. For now let's set the variance for $x$ and $y$ to be 5 for each. They are independent, so there is no covariance, and our off diagonals will be 0. This gives us:\n", "\n", "$$\\mathbf{R} = \\begin{bmatrix}5&0\\\\0&5\\end{bmatrix}$$\n", @@ -674,7 +705,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Step 6: Design the Process Noise Matrix\n", + "### Step 6: Design the Process Noise Matrix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "Finally, we design the process noise. We don't yet have a good way to model process noise, so for now we will assume there is a small amount of process noise, say 0.1 for each state variable. Later we will tackle this admittedly difficult topic in more detail. We have 4 state variables, so we need a $4{\\times}4$ covariance matrix:\n", "\n", "$$\\mathbf{Q} = \\begin{bmatrix}0.1&0&0&0\\\\0&0.1&0&0\\\\0&0&0.1&0\\\\0&0&0&0.1\\end{bmatrix}$$\n", @@ -709,8 +746,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 7**: Design Initial Conditions\n", - "\n", + "### **Step 7**: Design Initial Conditions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "For our simple problem we will set the initial position at (0,0) with a velocity of (0,0). Since that is a pure guess, we will set the covariance matrix $\\small\\mathbf{P}$ to a large value.\n", "$$ \\mathbf{x} = \\begin{bmatrix}0\\\\0\\\\0\\\\0\\end{bmatrix}\\\\\n", "\\mathbf{P} = \\begin{bmatrix}500&0&0&0\\\\0&500&0&0\\\\0&0&500&0\\\\0&0&0&500\\end{bmatrix}$$\n", @@ -26252,11 +26294,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Devise a way to prove that fusing the PS and wheel measurements yields a better result than using the wheel alone.\n", - "\n", - "##### Solution 1\n", - "\n", - "Force the Kalman filter to disregard the PS measurement by setting the measurement noise for the PS to a near infinite value. Re-run the filter and observe the standard deviation of the residual. " + "Devise a way to prove that fusing the PS and wheel measurements yields a better result than using the wheel alone." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Solution 1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Force the Kalman filter to disregard the PS measurement by setting the measurement noise for the PS to a near infinite value. Re-run the filter and observe the standard deviation of the residual." ] }, { @@ -26284,11 +26336,20 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Here we can see the error in the filter where the PS measurement is almost entirely ignored is greater than that where it is used.\n", - "\n", - "\n", - "##### Solution 2\n", - "\n", + "Here we can see the error in the filter where the PS measurement is almost entirely ignored is greater than that where it is used." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Solution 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "This is more work, but we can write a Kalman filter that only takes one measurement." ] }, @@ -28107,7 +28168,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Step 1: Choose the State Variables" + "### Step 1: Choose the State Variables" ] }, { @@ -28144,7 +28205,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 2:** Design State Transition Function" + "### **Step 2:** Design State Transition Function" ] }, { @@ -28184,7 +28245,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Interlude: Test State Transition" + "### Interlude: Test State Transition" ] }, { @@ -28731,7 +28792,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 3**: Design the Motion Function" + "### **Step 3**: Design the Motion Function" ] }, { @@ -28745,7 +28806,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 4**: Design the Measurement Function" + "### **Step 4**: Design the Measurement Function" ] }, { @@ -28779,7 +28840,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### **Step 5**: Design the Measurement Noise Matrix" + "### **Step 5**: Design the Measurement Noise Matrix" ] }, { @@ -28795,7 +28856,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Step 6: Design the Process Noise Matrix\n", + "### Step 6: Design the Process Noise Matrix" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "Finally, we design the process noise. As with the robot tracking example, we don't yet have a good way to model process noise. However, we are assuming a ball moving in a vacuum, so there should be no process noise. For now we will assume the process noise is 0 for each state variable. This is a bit silly - if we were in a perfect vacuum then our predictions would be perfect, and we would have no need for a Kalman filter. We will soon alter this example to be more realistic by incorporating air drag and ball spin. \n", "\n", "We have 5 state variables, so we need a $5{\\times}5$ covariance matrix:\n", @@ -28807,8 +28874,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "##### Step 7: Design the Initial Conditions\n", - "\n", + "### Step 7: Design the Initial Conditions" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "We already performed this step when we tested the state transition function. Recall that we computed the initial velocity for $x$ and $y$ using trigonometry, and set the value of $\\mathbf{x}$ with:\n", "\n", " omega = radians(omega)\n", diff --git a/Appendix_B_Symbols_and_Notations.ipynb b/Appendix_B_Symbols_and_Notations.ipynb index 5dbcdd8..cf2e3eb 100644 --- a/Appendix_B_Symbols_and_Notations.ipynb +++ b/Appendix_B_Symbols_and_Notations.ipynb @@ -264,13 +264,13 @@ "source": [ "This is just notes at this point. \n", "\n", - "### State\n", + "## State\n", "\n", "$x$ (Brookner, Zarchan, Brown)\n", "\n", "$\\underline{x}$ Gelb)\n", "\n", - "### State at step n\n", + "## State at step n\n", "\n", "$x_n$ (Brookner)\n", "\n", @@ -280,7 +280,7 @@ "\n", "\n", "\n", - "### Prediction\n", + "## Prediction\n", "\n", "$x^-$\n", "\n", @@ -347,7 +347,7 @@ "\\textbf{P}^-_{k+1} &= \\mathbf{\\phi}_k \\textbf{P}_k\\mathbf{\\phi}_k^T + \\textbf{Q}_{k} \\\\\n", "\\mathbf{P}_k &= (\\mathbf{I}-\\mathbf{K}_k\\mathbf{H}_k)\\mathbf{P}^-_k\n", "\\end{aligned}$$\n", - "## \n", + "\n", "\n", "### Zarchan\n", "\n", @@ -392,15 +392,8 @@ "\\textbf{K}_k &= \\textbf{P}^-_k\\textbf{H}_k^T\\mathbf{S}_k^{-1} \\\\\n", "\\hat{\\textbf{x}}_k &= \\hat{\\textbf{x}}^-_k +\\textbf{K}_k\\textbf{y} \\\\\n", "\\mathbf{P}_k &= (\\mathbf{I}-\\mathbf{K}_k\\mathbf{H}_k)\\mathbf{P}^-_k\n", - "\\end{aligned}$$\n", - "## \n", - "\n" + "\\end{aligned}$$" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [] } ], "metadata": { diff --git a/Introduction.ipynb b/Introduction.ipynb deleted file mode 100644 index 2cdfe56..0000000 --- a/Introduction.ipynb +++ /dev/null @@ -1,338 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[Table of Contents](http://nbviewer.ipython.org/github/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/table_of_contents.ipynb)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "