[翻译][学习卡尔曼与贝叶斯滤波器——基于Python实践][前言]
译者:王伟韵
QQ : 352707983
项目链接:Github
注:这是一个互动式教程,博客只能显示静态页面。需要得到完整功能请下载项目并按照指导运行
这是一本卡尔曼滤波器和贝叶斯滤波器入门教材。这本书使用Jupyter Notebook编写,因此你可以在浏览器中阅读这本书,还可以运行和修改书中的代码并实时查看结果。还有比这更好的学习方法吗?
所有传感器都有噪声。这个世界充满了各种我们想要测量和跟踪的数据与事件,但我们并不能依靠传感器来得到完美准确的信息。我车上的GPS显示高度,每次我经过同一个位置,它报告的高度略有不同。如果我使用厨房秤称同一个物体的重量两次,会得到不同的读数。
在简单的情况下,解决方案是显而易见的,如果测量仪器给出的读数只是稍有不同,我可以多读几次然后取其平均值,或者我可以使用更高精度的测量仪器。但是,但传感器噪声很大,或者在某些环境下采集数据比较困难时,我们该怎么做呢?我们可能想尝试跟踪一架低空飞行器的飞行轨迹,也可能想给无人机设计一个自动驾驶仪,亦或者想确保农场的拖拉机能够毫无遗漏地播种整个田地。我的工作与计算机视觉有关,需要跟踪图像中的运动物体,而计算机视觉算法会产生充满噪声且不太可靠的结果。
这本书教你如何解决这些滤波问题。我使用了许多不同的算法,但它们均基于贝叶斯概率。简单地说,贝叶斯概率根据过去的信息来判断未来可能发生的事。
如果我让你说出我车子现在的方向,你可能会一脸懵逼。你最多只能提供一个介于1到360°之间的数字,那么只有1/360的机会是正确的。现在假设我告诉你,2秒前我车子的航向是243°,而2秒内车子的方向变化不太可能会很大,因此你便能得出一个更精确的预测。你用过去的信息更准确地推断出当前或未来的信息。
这个世界也是充满噪声的。上面的预测有助于你做出更好的估计,但它也会受到噪声的影响。我可能会为了一条狗而突然刹车,或者绕着一个坑洞转弯。路上的强风和结冰是影响我汽车行驶路径的外部因素。在控制相关文献中,我们称这些为噪声,尽管你可能不会这么认为。
关于贝叶斯概率还不止这些,但可能你已经了解其主要概念了。知识是不确定的,我们根据现象的强度来改变我们的置信度。卡尔曼与贝叶斯滤波器将我们对系统如何运行的嘈杂且有限的认知和同样嘈杂且有限的传感器测量结合起来,以得到对系统状态的最优估计。我们的原则是永远不要丢弃信息。
假设我们正在追踪一个物体,传感器告诉我们它突然改变方向了。是真的改变了,还只是传感器数据有噪声?这得看情况而定,如果这时一架喷气式战斗机,我们会非常倾向于相信它的确发生了一个瞬时机动,而如果这是一辆直线轨道上的货运列车,那我们会降低数据的可信度,同时我们会进一步根据传感器的精度来修正它的置信度。也就是说,置信度取决于我们对正在跟踪的系统的认知程度以及传感器特性。
卡尔曼滤波器最初是由Rudolf Emil Kálmán发明的,用数学上的最优方法来解决这类问题。它最初用于阿波罗登月任务,从那时起,它就被广泛用于各种领域。飞机、潜艇和巡航导弹上都使用了卡尔曼滤波器。它们被华尔街用于跟踪金融市场,还被用于机器人、物联网传感器和实验室仪器中。化工厂用它来控制和监测化学反应,医学成像中用于去除心脏信号中的噪声。在涉及传感器和时间序列数据的应用中,通常都会运用到卡尔曼滤波器或者相似方法。
我是一名软件工程师,在航空航天领域工作了近20年,所以我经常会在卡尔曼滤波器上碰壁,但却从未实现过一个,毕竟它们是出了名的困难。这个理论很优美,但是如果你在信号处理、控制理论、概率和统计以及制导和控制理论等方面还没有比较好的训练的话,学习起来就很困难。随着我开始使用计算机视觉来解决跟踪问题,自己来实现卡尔曼滤波器的需求也变得迫切了。
这个领域并不缺少优秀的教科书,如Grewal和Andrew的《Kalman Filtering》。但是,如果不了解一些必要的背景知识,那坐下来直接阅读这些书籍只会让你感到沮丧和厌烦。一般来说,前几章会概述需要数年学习的大学数学课程内容,轻描淡写地让你参考有关微积分的教科书,并在几个简短的段落中展示需要整个学期来学习的结论。它们是高年级本科或研究生课程的教科书,也是研究人员和专业人士的宝贵参考资料,但对于更一般的读者来说,确实学习起来会很困难。引入的数学符号没有注释,不同的文档中可能会使用不同的单词和变量名来表示同一个概念,并且这些书中几乎没有实例或解决一些实际问题。我经常发现自己能看懂每一个单词并了解那些数学定义,却完全不能理解这些单词和数学公式试图描述的真实世界现象是什么,我会反复思考:“但这些是什么意思呢?”。以下是曾经让我困惑的典型例子:
x ^ k = Φ k x ^ k − 1 + G k u k − 1 + K k [ z k − H Φ k x ^ k − 1 − H G k u k − 1 ] P k ∣ k = ( I − K k H k ) cov ( x k − x ^ k ∣ k − 1 ) ( I − K k H k ) T + K k cov ( v k ) K k T \hat{x}_{k} = \Phi_{k}\hat{x}_{k-1} + G_k u_{k-1} + K_k [z_k - H \Phi_{k} \hat{x}_{k-1} - H G_k u_{k-1}] \\ \mathbf{P}_{k\mid k} = (I - \mathbf{K}_k \mathbf{H}_{k})\textrm{cov}(\mathbf{x}_k - \hat{\mathbf{x}}_{k\mid k-1})(I - \mathbf{K}_k \mathbf{H}_{k})^{\text{T}} + \mathbf{K}_k\textrm{cov}(\mathbf{v}_k )\mathbf{K}_k^{\text{T}} x^k=Φkx^k−1+Gkuk−1+Kk[zk−HΦkx^k−1−HGkuk−1]Pk∣k=(I−KkHk)cov(xk−x^k∣k−1)(I−KkHk)T+Kkcov(vk)KkT
然而,当我终于理解卡尔曼滤波器后,才意识到这些基本概念是非常简单的。如果你熟悉一些简单的概率定理,并且对如何融合不确定事物有一定直觉,那便能理解卡尔曼滤波器的概念。卡尔曼滤波器以困难著称,但是抛弃很多专业术语后,我愈发清晰看到它本质和数学上的美丽,于是我爱上了它。
更多的困难出现在我尝试开始去理解这些数学和理论的时候,一本书或者一篇论文会去陈述事实并提供图表以证明,但不幸的是,我仍然不清楚这个陈述为什么是正确的,又或者我无法重现其图表。或有时我想知道“这是否成立如果R=0?”,作者可能会提供一些比较高层次的伪代码以至于无法轻易实现。有些书提供Matlab代码, 但可惜我没有使用这种昂贵商业软件所需要的许可证。另外 ,还有很多书在每一章末尾提供了许多有用的练习,如果你想要自己实现卡尔曼滤波器,那么便要去理解这些没有答案的练习。如果你在课堂上使用这本书,那可能关系不大,但对于独立读者来说这便很糟糕了,我讨厌作者对我隐瞒信息,大概他是为了避免学生在课堂上作弊吧。
所有的这些都会阻碍学习。我想在屏幕上追踪一张图像,或者为我的Arduino项目写一些代码,因此我想知道这些书中的图表是如何产生的,并且想选择一些与作者所提供的不一样的参数。我想仿真运行,看看滤波器在信号中加入更多噪声时是如何表现的。在每天的代码中都有成千上万使用卡尔曼滤波器的机会,而这些相当直观简单的项目也是火箭科学家和学者诞生的源头。
我写这本书便是为了满足所有上述需求。但如果你在设计军用雷达,这并不能成为你唯一的参考资料,你更需要的是去一所很棒的STEM学校攻读硕士或博士学位。这本书提供给那些需要滤波或平滑一些数据的爱好者,好奇者和正在工作的工程师。如果你是一个爱好者,这本书应该能提供你所需要的一切。如果你想深入专研卡尔曼滤波器,那你还需要学习更多,我意图介绍足够的概念和数学基础,让你更容易去学习教科书和论文。
这本书是交互式的,虽然你也可以把它当成静态内容直接在线阅读,但我强烈建议你按照预期来使用它。基于Jupyter Notebook,让我可以在书中任何地方组织文本、数学公式、Python代码和其输出。本书中每一个数据和图表都是由notebook中的Python生成的。想成倍修改某个参数值?只需要更改参数的值,然后按CTRL-ENTER,便会生成新的图表或者打印输出。
这本书有练习,但也都有答案。我信任你。如果你只是需要一个答案,那就直接阅读答案。而如果你想更深刻理解这些知识,那么在这之前先尝试去实现那些练习吧。因为这本书具有交互性,你可以直接在本书中输入并运行你的答案,而不必要迁移到其它不一样的环境,也不用在开始前导入大量内容。
这本书是免费的。我曾经花了数千美元去购买卡尔曼相关书籍,这对于那些手头拮据的学生来说几乎是不可能的事情。我从诸如Python之类的免费软件和Allen B. Downey [1]的那些免费书籍中获益颇多,是时候回报了。因此,这本书是免费的,它托管在Github的免费服务器上,并且只用到了免费和开源的软件如IPython和MathJax。
这本书托管在Github上,你可以通过点击章节名字来阅读任何一章。Github只能静态呈现Jupyter Notebooks,你可以阅读所有内容,但无法运行和修改代码。
这个项目的GitHub链接如下
https://github.com/rlabbe/Kalman-and-Bayesian-Filters-in-Python
binder提供notebooks在线交互服务,所以你可以直接在浏览器上修改和运行代码,而不必下载这本书或者安装Jupyter。使用下面的链接在binder上获取这本书:
http://mybinder.org/repo/rlabbe/Kalman-and-Bayesian-Filters-in-Python
nbviewer网站可以将任何一本Notebook转换为静态格式。我发现它比Github提供的转换器要更好一些,只是用起来有点不太方便。它可以直接接入Github,任何我在Github上的修改都可以被nbviewer所同步。
你可以使用下面的链接在nbviewer上获取这本书:
http://nbviewer.ipython.org/github/rlabbe/Kalman-and-Bayesian-Filters-in-Python/blob/master/table_of_contents.ipynb
我会定期通过notebooks生成这本书的一个PDF版本,你可以通过这个链接访问:
https://drive.google.com/file/d/0By_SW19c1BfhSVFzNHc0SjduNzg/view?usp=sharing
不管怎样,这本书设计成了交互式的,我也建议以这种形式来使用它。做成这样花费了一番功夫,但这是很值得的。如果你在电脑上安装了IPython和一些支持库,那可以把这本书clone下来,你将能够自己运行书中的所有代码。你可以执行实验,观察滤波器对于不同数据的表现,等等。我发现这种即时反馈不仅很重要而且会让人充满动力,你不用去怀疑“如果会怎样”,放手去试吧!
安装说明见安装附录,见此处。
安装软件后,你可以进入到安装目录并在终端中通过命令行运行juptyer notebook
jupyter notebook
它将会打开一个浏览器窗口,显示根目录的内容。这本书分成许多章,每个章节都命名为xx-name.ipynb,其中xx是章节号,.ipynb是Notebook文件的扩展名。要阅读第二章,请单击第二章的链接,浏览器将会打开那个子目录。每个子目录中将会有一个或多个IPython Notebooks(所有notebooks都有.ipynb文件扩展名)。章节内容在notebook中,与章节名称同名。每章中还会有许多实现相关功能如生成显示动画的supporting notebooks,每个用户没必要去阅读这些,但如果你对于动画是如何制作的比较好奇,那就看一眼吧。
诚然,对于一本书来说这个界面过于繁琐。我正在跟随其它几个项目的脚步,这些项目将Jupyter Notebook重新设计以生成完整的一本书。我觉得这些繁琐的事情会有巨大的回报——当你读一本书的时候,不必下载一个单独的代码库并在一个IDE中运行它,所有的代码和文档都会在同一个地方。如果你想修改代码,你可以立即这么做并即时看到修改的效果。你可以修复一个你发现的bug,然后推送到我的仓库中,这样全世界的人们都能受益。当然,你也永远不会遇到我在传统书籍中一直需要面临的问题——书籍和其代码彼此不同步,你需要绞尽脑汁去判断哪个来源更可靠些。
首先,简单介绍一下如何使用Jupyter Notebooks来阅读这本书。这本书是交互式的,如果你想运行示例代码,尤其是想观察波形动画时,则需要运行代码单元。我不能教会你Jupyter Notebooks的一切。然而有一些地方可能会让读者产生困惑,你可以访问http://jupyter.org/获取详细文档。
首先,你必须要运行最顶层的代码单元, 即带有注释 #format the book
的那个,就在最上方。这一步不仅设置了一些你不用关心的格式,还加载了一些必要模块,进行了一些有关绘图和打印的全局设置。所以,必须要运行这个单元,除非你只是单纯地被动阅读。import from __future__
可以让 Python 2.7 像 Python 3.X 那样工作。整数的除法将会返回一个 float
(3/10 == 0.3
) ,而不是一个 int
(3/10 == 0
),并且打印函数需要加上括弧: print(3)
, 非 print 3
. 这一行
%matplotlib inline
会让绘图显示在notebook的范围内。Matplotlib是一个绘图包,下面将会描述。出于某种理由让我无法理解为什么Jupyter Notebooks的默认设置会在一个额外的窗口中进行绘图。
%matplotlib
中的百分比符号用于 IPython magic - 这是核心实现的一些命令而不是Python语言中的一部分。还有很多有用的魔法命令(magic commands),你可以在这里了解到更多: http://ipython.readthedocs.io/en/stable/interactive/magics.html
在单元格里运行代码很容易,单击它使其被选中(会出现一个围绕它的外框),然后按下 CTRL-Enter。
其次,单元格必须按顺序运行。我将问题分解到数个单元格中,如果你尝试跳过并直接运行第十个代码单元格,那几乎可以肯定它不会工作。如果你还没有运行任何内容,只需要从单元格 菜单中选择 运行所有单元格 ,这是确保一切能顺利工作的最简单方式。
一旦全部单元格被运行,你便可以经常进行跳转并以不同的顺序重新运行各个单元格,但有时候可能会出现问题,我正在尝试解决,不过还有一个折衷的办法。举个例子,我在单元格10中定义一个变量,然后在单元格11和12中运行修改该变量的代码,如果你返回并再次运行单元格11,变量已经在单元格12中被赋值,然而代码所期望的是这个变量应该是在单元格10中被赋值。所以你如果不按照顺序运行单元格,有时候会得到奇怪的结果。我的建议是再回退一步,重新运行单元格以恢复到正确的状态。这比较烦人,但Jupyter notebooks的互动功能依然弥盖了这个不足。更好的是,你可以再Github上提交一个issue,这样我就知道这个问题并修复它了!
最后,已经有一些读者报告了在某些浏览器中动画绘图功能的问题,我还不能复现这个。在本书中的某些部分,我使用了%matplotlib notebook
magic 以实现交互式绘图。如果这些绘图在你那里无法生效,请尝试将其更改读取 %matplotlib inline
。 你将会失去绘图动画,不过这样似乎在所有平台和浏览器上都能工作。
Scipy是一个开源的数学软件集,其中包含了提供数组对象、线性代数、随机数等功能的NumPy。Matplotlib给NumPy数组提供绘图功能。Scipy的模块复制了NumPy的一些功能,同时添加了优化、图像处理等功能。
为了保持我对这本书投入的精力是可控的,我选择假设你了解如何使用Python编程,并且熟悉这些packages。尽管如此,我还是会花一些时间来说明每一个的特性,实际上你必须去寻找其它外部资源来学习更多细节。SciPy的主页,https://scipy.org ,是一个完美的起点,尽管你可能很快会想去搜索相关的教程或视频。
Python的发行版中并不默认包含NumPy,SciPy以及Matplotlib,如果你还没安装这些,请参见安装附录。
我在书中通篇使用了NumPy的数组元素类型,所以现在让我们来了解一下它们。我会教够你足够多的知识以便开始学习这本书,如果你想更深入,请参考NumPy的文档。
numpy.array
实现了单维或者多维数组,它的类型是numpy.ndarray
,简称为ndarray。你可以用任何类似列表的对象来构造它。下面从一个列表中构造一个一维数组:
import numpy as np
x = np.array([1, 2, 3])
print(type(x))
x
array([1, 2, 3])
使用import numpy as np
已经成为了一个行业标准。
你也可以使用元组:
x = np.array((4,5,6))
x
array([4, 5, 6])
使用嵌套括号创建多维数组:
x = np.array([[1, 2, 3],
[4, 5, 6]])
print(x)
[[1 2 3]
[4 5 6]]
你可以创建三维或者更高维的数组,但这里没有这个需求,所以我便不再赘述。
默认情况下,数组使用列表中变量的数据类型,如果有多种类型,则它将选择能够最精确表示所有变量的类型。例如,如果列表包含 int
和 float
这两种类型,那么数组的数据类型将是 float
,不过你仍可以使用dtype参数来指定具体类型。
x = np.array([1, 2, 3], dtype=float)
print(x)
[1. 2. 3.]
可以使用数组下标来访问数组元素:
x = np.array([[1, 2, 3],
[4, 5, 6]])
print(x[1,2])
6
你可以使用切片(slices)来访问某一列或某一行。 冒号 (:) 作为下标,可以用于表示那一行或者列的所有数据,所以x[:,0]
返回一个包含第一列(0指定第一列)所有数据的数组:
x[:, 0]
array([1, 4])
我们可以用这种方式获取第二行:
x[1, :]
array([4, 5, 6])
获取第二行的最后两个元素:
x[1, 1:]
array([5, 6])
与Python的列表一样,可以使用负索引来引用数组的尾部,-1表示最后一个索引,因此获得第二行(最后一行)最后两个元素的另一种方法为:
x[-1, -2:]
array([5, 6])
可以使用 +
运算符来执行矩阵加法,但矩阵乘法需要 dot
方法或函数, *
运算符表示的是元素乘法( element-wise multiplication),而不是你想要的那种线性代数中的相乘。
x = np.array([[1., 2.],
[3., 4.]])
print('加法:\n', x + x)
print('\元素乘法\n', x * x)
print('\乘法\n', np.dot(x, x))
print('\dot 也是 np.array 的一个成员\n', x.dot(x))
加法:
[[2. 4.]
[6. 8.]]
元素乘法
[[ 1. 4.]
[ 9. 16.]]
乘法
[[ 7. 10.]
[15. 22.]]
dot 也是 np.array 的一个成员
[[ 7. 10.]
[15. 22.]]
Python 3.5 为矩阵乘法引入了 @
运算符。
x @ x
[[ 7.0 10.0]
[ 15.0 22.0]]
只有在使用Python 3.5+时,这个乘法运算符才有效。所以,在这本书里我不会用它,尽管相比np.dot(x, x)
而言我更喜欢这个符号。
你可以使用.T
得到矩阵转置, 并使用numpy.linalg.inv
对矩阵求逆。 SciPy 包也提供了求逆函数。
import scipy.linalg as linalg
print('transpose\n', x.T)
print('\nNumPy ninverse\n', np.linalg.inv(x))
print('\nSciPy inverse\n', linalg.inv(x))
transpose
[[1. 3.]
[2. 4.]
NumPy ninverse
[[-2. 1. ]
[ 1.5 -0.5]
SciPy inverse
[[-2. 1. ]
[ 1.5 -0.5]]
还有许多辅助函数,如 zeros
用来构造全零矩阵(所有元素为0), ones
构造全一矩阵,而 eye
可以构造单位矩阵。如果需要多维数组,请用元组指定形状。
print('zeros\n', np.zeros(7)
print('\nzeros(3x2)\n', np.zeros((3, 2)
print('\neye\n', np.eye(3))
zeros
[0. 0. 0. 0. 0. 0. 0.]
zeros(3x2)
[[0. 0.]
[0. 0.]
[0. 0.]
eye
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
我们有创建等差数据的函数。 arange
的工作原理与Python的 range
函数非常相似, 除了返回值为NumPy数组。linspace
稍有区别,你可以使用linspace(start, stop, num)
调用它,其中 num
是所需数组的长度。
np.arange(0, 2, 0.1)
array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1. , 1.1,
1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9])
np.linspace(0, 2, 20)
array([0. , 0.105, 0.211, 0.316, 0.421, 0.526, 0.632, 0.737, 0.842,
0.947, 1.053, 1.158, 1.263, 1.368, 1.474, 1.579, 1.684, 1.789,
1.895, 2. ])
现在让我们来绘制一些数据,大多数情况下都是比较简单的。 Matplotlib 包含了一个绘图库 pyplot
,将其import为plt
是一个行业标准。import后,使用列表或数组作为参数,调用 plt.plot
可以绘制出这些数字。如果你多次调用,它将绘制多个曲线,利用不同颜色加以区分。
import matplotlib.pyplot as plt
a = np.array([6, 3, 5, 2, 4, 1])
plt.plot([1, 4, 2, 5, 3, 6])
plt.plot(a)"
输出[
是因为 plt.plot
会返回刚创建的对象。通常我们不想看到这个,所以我在最后一个绘图命令中加入了一个 ;
来禁止其输出。
默认情况下plot
假定X坐标轴按1递增,你可以通过同时传入x和y来设置自己的X坐标轴
我希望你创建一个由10个元素组成的NumPy数组,其中每个元素的值为1/10。有数种方法可以做到这一点,请尽可能多的实现你能想到的方法。
这里有三种实现方法,第一种是我想让你了解的那个。我使用了 ‘/’ 运算符将数组的所有元素除以10,我们会很快使用这个方式将一个数组的单位从米转换为公里。
print(np.ones(10) / 10.)
print(np.array([.1, .1, .1, .1, .1, .1, .1, .1, .1, .1])
print(np.array([.1]*10))
[0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1]
[0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1]
[0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1 0.1]
这个我还没有提到过,numpy.asarray()
函数可以将其传入的参数转换为ndarray(如果它原先还不是的话),如果是,则数据不变。这是编写一个可以接受Python列表或ndarrays参数的函数的一种简便方式,而且如果本身类型已经是ndarray,那它是非常高效的,因为没有创建新的东西。
def one_tenth(x):
x = np.asarray(x)
return x / 10.
print(one_tenth([1, 2, 3])) # I work!
print(one_tenth(np.array([4, 5, 6]))) # so do I!
[0.1 0.2 0.3]
[0.4 0.5 0.6]
我正在编写一个名为FilterPy的开源贝叶斯滤波python库,上面给出了安装说明。
这本书的特定代码与书存放在一起,位于子目录 /kf_book 中。它包含了格式化书籍的代码,还包含名为 xxx_internal.py 的Python文件,我使用这些文件存储对特定章节有效的函数。这允许我能够隐藏那些你可能不太感兴趣的Python代码——我可能生成了一个波形图表,但我希望你能够关注图表的内容,而不是我如何使用Python生成它的方法。如果你对其非常好奇,那只需去浏览源代码即可。
一些章节介绍了对本书其余部分有用的函数,这些函数最初是在Notebook本身中定义的,但是代码也存储在 /kf_book 的Python文件中,如果后续章节需要,那可以导入这些文件。我编写文档来注明这些函数首次被调用的地方,不过这仍是一项正在进行的工作。我尽量避免这么做,因为这样总是会让我面临目录中的代码与书中的代码不同步的问题。然而,Jupyter Notebook并没有给我们提供一种方法来引用其它notebooks中的代码单元,所以这是我知道的在notebooks中共享函数的唯一技巧。
有一个名为 /experiments的未注明目录,这是我在将代码放入书中之前编写和测试代码的地方。里面有一些有趣的东西,你可以随意看看。随着这本书的发展,我计划创建一些实例和项目,其中很多材料最终都会出现在那里。小测试最终都会被删除,如果你只是对读这本书感兴趣,可以放心地忽略这个目录。
目录 /kf_book 中有一个包含书籍样式指南的css文件,Jupyter Notebook的默认外观非常简单,我参考了一些书籍的例子,如Probabilistic Programming and Bayesian Methods for Hackers [2],我也深受Lorena Barba教授的精彩著作的影响,该书见此处 [3]。本书的外观及样式方面的成果都归功于这些项目。
大多数卡尔曼滤波和其它工程类教科书都是由数学家或学者编撰的,甚少涉及到程序,即使有,其质量也比较低下。以Paul Zarchan的《Fundamentals of Kalman Filtering》为例,这是一本很棒并值得你收藏的书,是为数不多的为每个例子和图表提供代码的书之一。但使用的代码是Frotran,除了调用MATMUL之类的函数外没有任何子程序。卡尔曼滤波器在整本书中被重复实现,同样的清单中将仿真和滤波代码混到了一起,让人很难区分它们。有一些章节使用稍有区别的方式来实现相同的滤波器,并使用粗体文本突出显示更改的几行。如果需要用到Runge Kutta算法,它会被直接嵌入到代码中,没有任何注释。
存在着更好的方法。如果我想执行调用被我称之为 ode45
的Runge Kutta算法,我不会直接在代码中嵌入Runge Kutta,我不想多次重复实现Runge Kutta并多次调试它。如果我真的找到了一个bug,我可以只修复一次,并确保它现在可以在我所有不同的项目中正常工作。而且这么做使得代码可读性更好,事实上我很少关心Runge Kutta的实现。
这是一本关于卡尔曼滤波的教科书,你可能会说我们只关心卡尔曼滤波器的实现。这是事实,但是只需要大约10行代码用于滤波器的执行,实现数学的代码相当简单,卡尔曼滤波器需要做的大部分工作是设计输入数学模型中的矩阵。
可能还存在一个缺点,即执行滤波的方程隐藏在函数中,不过可以说这只是教学文本中的一个缺失。或者说,我想让你学会如何在现实世界中使用卡尔曼滤波器,而不只是到处复制粘贴已经写好的算法。
我使用Python类。但我主要使用类来组织滤波器所需的数据,而不是实现如继承之类的面向对象特性(OO)。例如,KalmanFilter
类存储了名为 x
, P
, R
, Q
, S
, y
, K
的矩阵和向量。 我见过卡尔曼滤波器的过程实现库,不过它们要求程序员维护所有这些矩阵。这对于一个玩具程序来说或许不坏,但是编写大量卡尔曼滤波器时,你将不会喜欢必须要管理这些矩阵和其它相关数据。在我自己的工作中偶尔会派生出这些类,且发现它很方便,但我不想强行安利OO,据我所知很多人反感这么做。