将你的科学计算从Matlab迁移到Python?

为什么要用问句作为文章的标题呢,因为我的答案是NO!Absolutely not!

一切的一切起源于我看了一篇英文文章,说使用Python作科学计算有时比Matlab更加高效,还有那无数次见到的牛逼公式:$Python+Numpy+Scipy+Matplotlib\approx Matlab$,看的我心潮澎湃,我就寻思了,反正最近在做的那个算法用Matlab跑实验跑得不是太理想,要不咱也迁移一把,由于今天比较蛋疼,就决定开始尝试了,经过蛋疼的一天,我得出了开头那个结论:如果你有现成的Matlab算法,还是Matlab吧,如果你像我一样蛋疼,那就迁移吧。不相信,那就听我把遇到的一堆乱七八糟的事情娓娓道来吧,其中夹杂着我的一些弱弱的解决方案~~

.mat数据集文件的导入

由于现有的数据集一般都是以Matlab的格式.mat的形式出现的,Matlab可以轻松的load语句搞定,但Python就不行了,花时间将mat文件提取成普通文本文件必然更心烦。其实这点Python肯定已经想到了,解决方案就是使用Scipy提供的函数,具体如下:

1
2
import scipy as sp
dataset = sp.io.matlab.mio.loadmat(dataset_name)

其中dataset_name就是我们需要导入的.mat文件,但问题又来了,导入后返回的dataset变量是一个“字典”的数据结构,它的key就是储存的变量名称,而对应的value就是变量的内容。这个操作并不像Matlab那样直接将.mat里的变量载入workspace,那我们当然还需要进一步使dataset中存储的变量暴露出来,对应的内容赋给对应的变量名称。可以查看dataset变量的组成,发现除了我们自己的变量,还有另外三个小东西:__globals____header____version__,它们标识了.mat文件的基本信息,但我们并不需要,所以还需要去掉它们。最终,我们通过一段代码实现:

1
2
3
4
exclude = ['__globals__','__header__','__version__']
for obj in dataset.keys():
if obj not in exclude:
exec(obj + ' = dataset[" ' + obj + ' "] ')

通过exec我们就实现了类似语句data = dataset["data"]的功能。OK,到这里,第一个任务完成,撒花~~可以看到,用Matlab一句load搞定的问题,这里搞了大半天。

迁移everything

由于我搞的是代码迁移,即将原来Matlab的代码改成Python代码,本来还觉得迁移就是稍微改改,最后发现要迁移不是一点,而是 almost everything。首先一个最严重的问题:( )[ ]的问题,这恐怕是迁移过程中最频繁的一个改动,这是为什么呢?很简单,因为Matlab中取矩阵元素(即Python中所说的‘slice’切片操作)用的是小括号,而Python中用的是中括号,还有比这更fuck的事情吗,因为函数调用也是小括号,所以放弃查找替换这不切实际的念头吧,这个恐怕只能手动。别以为这样就完事了,更琐碎的东西等着你,下面的表格可以帮助你理解什么是我说的everything。

操作 Matlab Python
注释 % #
开始索引 1 0
矩阵连接 [a, b] numpy.concatenate(a, b)
循环和分支语句 未结束的条件行以,结尾结束需要end 未结束的条件行以:结尾结束不需要任何东西
产生全0全1矩阵 zeros(m, n)ones(m, n) numpy.zeros((m, n))numpy.ones((m, n))
整除问题 / 直接取准确结果,不存在整除问题 / 两遍都是整数时为整除,分子或分母需要加float强制转换
乘方 a^b a**b
矩阵向量转置 单引号 ' 搞定 没有重载 ' 操作符,需要调用 .transpose.T
结构体数组 直接{ }搞定 { } 指的是字典数据结构,没有结构体的概念,只能使用“对象列表”搞定
向量矩阵相乘 a*b 代表正常的矩阵相乘,也就是说a的列数必须和b的行数匹配的那种 a*b 代表对应元素相乘,即elementwise,a和b的维数必须相等
a.*b代表对应元素相乘,即elementwise,a和b的维数必须相等 dot(a, b)代表正常的矩阵相乘,也就是说a的列数必须和b的行数匹配的那种

OK,这么些个问题需要解决,有些还是可以接受的,毕竟两个不同的语言嘛,操作符什么的不一样还行,有的就比较恶心了。其中最让人难以理解的就是numpy的zeros和ones不明白为啥参数一定要是一个“元组”,不知道当初创造这个时怎么想的,搞的加了两层括号。而最变态的就是矩阵向量的相乘了,Matlab分的很清,单独的 * 号就是矩阵相乘,而 .* 就是对应元素相乘,而Python中单独的 * 号表示元素相乘,作用与Matlab恰恰相反,真正的矩阵乘法居然需要调用dot函数才能完成,而dot只接受两个参数,可想而知,一堆矩阵相乘的时候得多壮观啊,譬如:dot(a, dot(b, dot(c, dot(d, e) ) ) ) ,类似的变态事还有矩阵连接,本来Matlab两矩阵放一起就能连接,Numpy非要用一个函数,你用函数我忍了,你还用那么长一个函数concatenate,要是连接几个矩阵咋办,一行能写下吗?大哥,我彻底凌乱了~

后话

鉴于上述的种种让人闹心的原因,我的移植工作没有坚持下去。当然,我写下这篇文章,并没有诋毁Python在做科学计算这方面的潜力,更没有贬低Python的意思,不得不承认接触Python越多,越觉得它牛逼,我仅仅表达的是要从Matlab迁移到Python有点得不偿失,或许一开始就直接从Python开始是个不错的选择,不存在迁移,直接从头开始。但如果你想我一样,有现成的Matlab代码了,那咱就老实Matlab吧~~