2.4.3 让代码更快
一旦我们识别出瓶颈,我们需要让相关的代码跑得更快。
2.4.3.1 算法优化
第一件要看的事情是算法优化:有没有计算量更小的方法或者更好的方法?
从更高的视角来看这个问题,对算法背后的数学有一个很好的理解会有帮助。但是,寻找到像将计算或内存分配移到循环外这样的简单改变,来带来巨大的收益,通常很困难。
2.4.3.1.1 SVD的例子
在上面的两个例子中,SVD - 奇异值分解 - 花费了最多的时间。确实,这个算法的计算成本大概是输入矩阵大小的$n^3$。
但是,在这些例子中,我们并不是使用SVD的所有输出,而只是它第一个返回参数的前几行。如果我们使用scipy的svd
实现,我们可以请求一个这个SVD的不完整版本。注意scipy中的线性代数实现比在numpy中更丰富,应该被优选选用。
In [20]:
%timeit np.linalg.svd(data)
1 loops, best of 3: 4.12 s per loop
In [21]:
from scipy import linalg
%timeit linalg.svd(data)
1 loops, best of 3: 3.65 s per loop
In [22]:
%timeit linalg.svd(data, full_matrices=False)
10 loops, best of 3: 70.5 ms per loop
In [23]:
%timeit np.linalg.svd(data, full_matrices=False)
10 loops, best of 3: 70.3 ms per loop
接下来我们可以用这个发现来优化前面的代码:
In [24]:
import demo
In [27]:
%timeit demo.test()
1 loops, best of 3: 3.65 s per loop
In [28]:
import demo_opt
In [29]:
%timeit demo_opt.test()
10 loops, best of 3: 81.9 ms per loop
真实的非完整版SVD,即只计算前十个特征向量,可以用arpack来计算,可以在scipy.sparse.linalg.eigsh
找到。
计算线性代数
对于特定的算法,许多瓶颈会是线性代数计算。在这种情况下,使用正确的方法来解决正确的问题是关键。例如一个对称矩阵中的特征向量问题比通用矩阵中更好解决。同样,更普遍的是,你可以避免矩阵逆转,使用一些成本更低(在数字上更可靠)的操作。
了解你的计算线性代数。当有疑问时,查找scipy.linalg
,并且用%timeit
来试一下替代方案。