计算Vandermonde矩阵的有效方法

时间:2018-01-14 01:45:34

标签: python arrays performance numpy linear-algebra

我正在为一个相当大的1D数组计算Vandermonde matrix。这样做的自然而干净的方法是使用np.vander()。但是,我发现这是约。 比基于列表推导的方法慢2.5倍

In [43]: x = np.arange(5000)
In [44]: N = 4

In [45]: %timeit np.vander(x, N, increasing=True)
155 µs ± 205 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

# one of the listed approaches from the documentation
In [46]: %timeit np.flip(np.column_stack([x**(N-1-i) for i in range(N)]), axis=1)
65.3 µs ± 235 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [47]: np.all(np.vander(x, N, increasing=True) == np.flip(np.column_stack([x**(N-1-i) for i in range(N)]), axis=1))
Out[47]: True

我正在尝试了解瓶颈的位置以及原生np.vander()的实施速度慢〜 2.5x 的原因。

效率对我的实施至关重要。所以,也欢迎更快的替代品!

2 个答案:

答案 0 :(得分:9)

广播取幂怎么样?

%timeit (x ** np.arange(N)[:, None]).T
43 µs ± 348 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

完整性检查 -

np.all((x ** np.arange(N)[:, None]).T == np.vander(x, N, increasing=True))
True

这里需要注意的是,只有当输入数组x的{​​{1}} dtype时,才能实现此加速。正如@Warren Weckesser在一篇评论中所指出的那样,广播指数对于浮点阵列的速度变慢了。

至于为什么int速度慢,请查看source code -

np.vander

除了你的功能之外,这个功能必须满足更多的用例,所以它使用了更通用的计算方法,这种方法可靠,但速度较慢(我特别指向x = asarray(x) if x.ndim != 1: raise ValueError("x must be a one-dimensional array or sequence.") if N is None: N = len(x) v = empty((len(x), N), dtype=promote_types(x.dtype, int)) tmp = v[:, ::-1] if not increasing else v if N > 0: tmp[:, 0] = 1 if N > 1: tmp[:, 1:] = x[:, None] multiply.accumulate(tmp[:, 1:], out=tmp[:, 1:], axis=1) return v )。

感兴趣的是,我找到了另一种计算Vandermonde矩阵的方法,最后得出结论:

multiply.accumulate

它做同样的事情,但速度慢得多。答案在于操作是广播的,但效率低下

另一方面,对于%timeit x[:, None] ** np.arange(N) 150 µs ± 230 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) 数组,这实际上最终会表现最佳。

答案 1 :(得分:4)

以下是一些方法,其中一些方法(在我的计算机上)比目前发布的方法快得多。

我认为最重要的观察是,它在很大程度上取决于你想要多少度数。指数(我认为是小整数指数的特殊情况)只对小指数范围有意义。指数越多,基于乘法的方法就越好。

我想强调一个基于multiply.accumulate的方法(ma),它类似于numpy的内置方法,但更快(而不是因为我在检查上吝啬 - {{ 1}},numpy-no-checks演示了这一点)。除了最小的指数范围以外,它对我来说实际上是最快的。

由于我不明白的原因,numpy实现做了三件我认识最慢且不必要的事情:(1)它产生了很多基本向量的副本。 (2)它使它们不连续。 (3)我相信强制缓冲就地积累。

我想提到的另一件事是,小范围的最快(nnc基本上是out_e_1的手动版本)的速度最快,速度减慢了两倍以上通过简单的预防措施促进更大的dtype(ma可能有点用词不当)。

基于广播的方法称为safe_e_1,其中bc_*表示广播轴(b表示基数,e表示exp)'作弊'意味着结果是不连续的。

计时(最好的3个):

*

代码:

rep=100 n_b=5000 n_e=4 b_tp=<class 'numpy.int32'> e_tp=<class 'numpy.int32'>
vander                0.16699657 ms
bc_b                  0.09595204 ms
bc_e                  0.07959786 ms
ma                    0.10755240 ms
nnc                   0.16459018 ms
out_e_1               0.02037535 ms
out_e_2               0.02656622 ms
safe_e_1              0.04652272 ms
safe_e_2              0.04081079 ms
cheat bc_e_cheat            0.04668466 ms
rep=100 n_b=5000 n_e=8 b_tp=<class 'numpy.int32'> e_tp=<class 'numpy.int32'>
vander                0.25086462 ms
bc_b             apparently failed
bc_e             apparently failed
ma                    0.15843041 ms
nnc                   0.24713077 ms
out_e_1          apparently failed
out_e_2          apparently failed
safe_e_1              0.15970622 ms
safe_e_2              0.19672418 ms
bc_e_cheat       apparently failed
rep=100 n_b=5000 n_e=4 b_tp=<class 'float'> e_tp=<class 'numpy.int32'>
vander                0.16225773 ms
bc_b                  0.53315020 ms
bc_e                  0.56200830 ms
ma                    0.07626799 ms
nnc                   0.16059748 ms
out_e_1               0.03653416 ms
out_e_2               0.04043702 ms
safe_e_1              0.04060494 ms
safe_e_2              0.04104209 ms
cheat bc_e_cheat            0.52966076 ms
rep=100 n_b=5000 n_e=8 b_tp=<class 'float'> e_tp=<class 'numpy.int32'>
vander                0.24542852 ms
bc_b                  2.03353578 ms
bc_e                  2.04281270 ms
ma                    0.11075758 ms
nnc                   0.24212880 ms
out_e_1               0.14809043 ms
out_e_2               0.19261359 ms
safe_e_1              0.15206112 ms
safe_e_2              0.19308420 ms
cheat bc_e_cheat            1.99176601 ms