高效的列式相关系数计算

时间:2013-10-16 10:30:15

标签: performance numpy correlation pearson-correlation

原始问题

我将大小为n的行P与大小为n×m的矩阵O的每列相关联。我精心设计了以下代码:

import numpy as np

def ColumnWiseCorrcoef(O, P):
    n = P.size
    DO = O - (np.sum(O, 0) / np.double(n))
    DP = P - (np.sum(P) / np.double(n))
    return np.dot(DP, DO) / np.sqrt(np.sum(DO ** 2, 0) * np.sum(DP ** 2))

它比天真的方法更有效:

def ColumnWiseCorrcoefNaive(O, P):
    return np.corrcoef(P,O.T)[0,1:O[0].size+1]

以下是我在英特尔核心上使用numpy-1.7.1-MKL的时间:

O = np.reshape(np.random.rand(100000), (1000,100))
P = np.random.rand(1000)

%timeit -n 1000 A = ColumnWiseCorrcoef(O, P)
1000 loops, best of 3: 787 us per loop
%timeit -n 1000 B = ColumnWiseCorrcoefNaive(O, P)
1000 loops, best of 3: 2.96 ms per loop

现在的问题是:你能为这个问题建议一个更快的代码版本吗?挤出额外的20%将会很棒。

2017年5月更新

经过一段时间后,我回到了这个问题,重新运行并扩展了任务和测试。

  1. 使用einsum,我已将代码扩展到P不是行而是矩阵的情况。因此,任务是将所有O列与P的所有列相关联。

  2. 对于如何通过科学计算常用的不同语言解决同一问题感到好奇,我在MATLAB,Julia和R中实现了它(在其他人的帮助下),MATLAB和Julia是最快的,他们有一个专门的例程来计算列式相关性。 R也有一个专门的例程,但是最慢的。

  3. 在当前版本的numpy(来自Anaconda的1.12.1)中,einsum仍然胜过我使用的专用功能。

  4. 所有脚本和时间均可在https://github.com/ikizhvatov/efficient-columnwise-correlation处获得。

1 个答案:

答案 0 :(得分:4)

我们可以为此引入np.einsum;但是,您的milage可能vary取决于您的编译以及它是否使用SSE2。替换求和操作的额外einsum调用可能看起来无关紧要,但是numpy ufuncs在einsum执行时直到numpy 1.8才使用SSE2,我们可以避免一些if语句。

在使用intel mkl blas的opteron核心上运行它我得到一个奇怪的结果,因为我希望dot调用占用大部分时间。

def newColumnWiseCorrcoef(O, P):
    n = P.size
    DO = O - (np.einsum('ij->j',O) / np.double(n))
    P -= (np.einsum('i->',P) / np.double(n))
    tmp = np.einsum('ij,ij->j',DO,DO)
    tmp *= np.einsum('i,i->',P,P)          #Dot or vdot doesnt really change much.
    return np.dot(P, DO) / np.sqrt(tmp)

O = np.reshape(np.random.rand(100000), (1000,100))
P = np.random.rand(1000)

old = ColumnWiseCorrcoef(O,P)
new = newColumnWiseCorrcoef(O,P)

np.allclose(old,new)
True

%timeit ColumnWiseCorrcoef(O,P)
100 loops, best of 3: 1.52ms per loop

%timeit newColumnWiseCorrcoef(O,P)
1000 loops, best of 3: 518us per loop

再次使用带有intel mkl的英特尔系统运行此程序,我得到更合理/预期的内容:

%timeit ColumnWiseCorrcoef(O,P)
1000 loops, best of 3: 524 µs per loop

%timeit newColumnWiseCorrcoef(O,P)
1000 loops, best of 3: 354 µs per loop

再次在英特尔机器上用更大的东西:

O = np.random.rand(1E5,1E3)
P = np.random.rand(1E5)

%timeit ColumnWiseCorrcoef(O,P)
1 loops, best of 3: 1.33 s per loop

%timeit newColumnWiseCorrcoef(O,P)
1 loops, best of 3: 791 ms per loop