繁体   English   中英

Numpy中的矩阵反转速度

[英]Matrix inversion speed in Numpy

我一直在阅读“使用 Scikit-Learn 和 TensorFlow 进行机器学习实践”一书。 在第 4 章(第 110 页)中,它说:反转这样的矩阵的计算复杂度通常约为 O(n^2.4) 到 O(n^3)(取决于实现)。 换句话说,如果将特征数量翻倍,则计算时间大约乘以 2^2.4 = 5.3 到 2^3 = 8。

我在 Python 中测试了上述内容:

import numpy as np
import timeit
nsize = 100
A = np.random.rand(nsize, nsize)
x = np.random.rand(nsize, 1)
b = A.dot(x)
# np.linalg.inv(A) or np.linalg.inv(A).dot(b) - not a big difference in time
timeit.timeit('np.linalg.inv(A)','import numpy as np\nfrom __main__ import A, b', number=10000)

nsize=100大约需要 2.5 秒, nsize=200大约需要 8.5 秒。 计算成本似乎增加了 3.4 倍(8.5/2.5),远低于作者提到的范围。 有人可以解释导致这种“加速”的原因吗?

Numpy 使用 LAPACK 的 LU 分解(正如@Murali 在评论中指出的那样)。 LU 分解通常由 OpenBLAS 在大多数平台(或行为类似的英特尔 MKL)上完成。 OpenBLAS 使用dgemm调用(矩阵乘法)来加快 LU 分解。 dgemm是用于大型矩阵的最优化的 BLAS 原语之一。 它利用多线程和 SIMD 指令,因此速度非常快。 话虽如此,在小型矩阵上,使用多个线程会带来很大的开销(创建线程、分配工作和等待结果需要时间)。 对于更大的矩阵,这种开销往往可以忽略不计。 对于具有更小矩阵的 SIMD 指令也是如此:SIMD 指令在计算相对较大的数组时速度很快,但与标量指令相比,它们具有较高的延迟(对于小矩阵可以更好地流水线化)。 循环展开也会导致相同的效果(对于非常非常小的矩阵)以及 CPU 缓存。 除此之外,Numpy 还进行了一些类型检查和内部工作(以便在其他一些情况下优化内部循环),这需要几微秒。 所有这些因素的结合导致代码对于小矩阵非常低效,而对于大矩阵则更快。 这种开销在测量的加速中引入了偏差。

在我的机器上,对于时间为 100 的矩阵, dgemm调用需要 19% 的时间, dtrsm需要 11%, dlaswp需要 7%(其他调用需要的时间可以忽略不计,rest 在等待或内核中丢失)。 对于大小为 200 的矩阵, dgemm调用占 29%, dtrsm 12%, dlaswp 7%。 对于大小为 1000 的矩阵, dgemm调用占 57%, dtrsm 5%, dlaswp 6%。 正如我们所看到的,大部分时间都浪费在大小为 100 的矩阵上,而大部分时间被 BLAS 操作用于大小为 1000 的矩阵。

请注意,LU 分解很难并行化。 这是一个活跃的研究领域(持续了几十年)。 此外,遗憾的是,最好的并行化方法(通常基于任务)还没有在 OpenBLAS 等库中使用。 AFAIK,MKL 使用了一点。 State 的艺术实现速度更快,特别是对于小矩阵,因为dgemm针对大矩阵进行了适当优化。

可以使用OMP_NUM_THREADS=1在 OpenBLAS 中禁用多线程。 这样,对于大小为 100 的矩阵,我得到了 151 µs,对于大小为 200 的矩阵,我得到了 800 µs。应该考虑 Numpy 开销的约 4 µs,以及其他不恒定的开销,如页面错误、分配、SIMD 指令的延迟、缓存效果,分支预测等。话虽如此,因子达到 5.44(没有 Numpy 开销)。 对于 200 VS 400,它是 5.67。 而对于 400 VS 800,它甚至是 6.8。 该因子仍未接近 8(除了其他开销)的主要原因是 OpenBLAS 未针对使用 1 个线程运行 LU 分解进行优化:内部参数设置为次优,在这种情况下算法不是最优的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM