2.4.3 让代码更快

一旦我们识别出瓶颈,我们需要让相关的代码跑得更快。

2.4.3.1 算法优化

第一件要看的事情是算法优化:有没有计算量更小的方法或者更好的方法?

从更高的视角来看这个问题,对算法背后的数学有一个很好的理解会有帮助。但是,寻找到像将计算或内存分配移到循环外这样的简单改变,来带来巨大的收益,通常很困难。

2.4.3.1.1 SVD的例子

在上面的两个例子中,SVD - 奇异值分解 - 花费了最多的时间。确实,这个算法的计算成本大概是输入矩阵大小的$n^3$。

但是,在这些例子中,我们并不是使用SVD的所有输出,而只是它第一个返回参数的前几行。如果我们使用scipy的svd实现,我们可以请求一个这个SVD的不完整版本。注意scipy中的线性代数实现比在numpy中更丰富,应该被优选选用。

In [20]:

  1. %timeit np.linalg.svd(data)
  1. 1 loops, best of 3: 4.12 s per loop

In [21]:

  1. from scipy import linalg
  2. %timeit linalg.svd(data)
  1. 1 loops, best of 3: 3.65 s per loop

In [22]:

  1. %timeit linalg.svd(data, full_matrices=False)
  1. 10 loops, best of 3: 70.5 ms per loop

In [23]:

  1. %timeit np.linalg.svd(data, full_matrices=False)
  1. 10 loops, best of 3: 70.3 ms per loop

接下来我们可以用这个发现来优化前面的代码

In [24]:

  1. import demo

In [27]:

  1. %timeit demo.test()
  1. 1 loops, best of 3: 3.65 s per loop

In [28]:

  1. import demo_opt

In [29]:

  1. %timeit demo_opt.test()
  1. 10 loops, best of 3: 81.9 ms per loop

真实的非完整版SVD,即只计算前十个特征向量,可以用arpack来计算,可以在scipy.sparse.linalg.eigsh找到。

计算线性代数

对于特定的算法,许多瓶颈会是线性代数计算。在这种情况下,使用正确的方法来解决正确的问题是关键。例如一个对称矩阵中的特征向量问题比通用矩阵中更好解决。同样,更普遍的是,你可以避免矩阵逆转,使用一些成本更低(在数字上更可靠)的操作。

了解你的计算线性代数。当有疑问时,查找scipy.linalg,并且用%timeit来试一下替代方案。