在大scipy稀疏矩阵上快速列访问

时间:2016-12-06 14:23:49

标签: python performance numpy scipy cython

我正在使用scipy的csc稀疏矩阵,目前代码中的主要瓶颈是类似于以下内容的

for i in range(multiply_cols.shape[0]):
   F = F - factor*values[i]*mat.getcol(multiply_cols[i])

我正在使用的矩阵非常大,通常大于10**6x10**6并且我不想将它们转换为密集矩阵。事实上,我有一个限制,总是有csc格式的矩阵。我的尝试表明,转换为coo_matrixlil_matrix也无法获得回报。

以下是我使用csccsrcoo进行的基本尝试:

n=1000
sA = csc_matrix(np.random.rand(n,n))
F = np.random.rand(n,1)
multiply_cols = np.unique(np.random.randint(0,int(0.6*n),size=n))
values = np.random.rand(multiply_cols.shape[0])

def foo1(mat,F,values,multiply_cols):
    factor = 0.75
    for i in range(multiply_cols.shape[0]):
        F = F - factor*values[i]*mat.getcol(multiply_cols[i])

def foo2(mat,F,values,multiply_cols):
    factor = 0.75
    mat = mat.tocsr()
    for i in range(multiply_cols.shape[0]):
        F = F - factor*values[i]*mat.getcol(multiply_cols[i])

def foo3(mat,F,values,multiply_cols):
    factor = 0.75
    mat = mat.tocoo()
    for i in range(multiply_cols.shape[0]):
        F = F - factor*values[i]*mat.getcol(multiply_cols[i])

def foo4(mat,F,values,multiply_cols):
    factor = 0.75
    mat = mat.tolil()
    for i in range(multiply_cols.shape[0]):
        F = F - factor*values[i]*mat.getcol(multiply_cols[i])

然后我得到他们的时间:

In [41]: %timeit foo1(sA,F,values,multiply_cols)
10 loops, best of 3: 133 ms per loop

In [42]: %timeit foo2(sA,F,values,multiply_cols)
1 loop, best of 3: 999 ms per loop

In [43]: %timeit foo3(sA,F,values,multiply_cols)
1 loop, best of 3: 6.38 s per loop

In [44]: %timeit foo4(sA,F,values,multiply_cols)
1 loop, best of 3: 45.1 s per loop

所以肯定coo_matrixlil_matrix不是一个好选择。有没有人知道更快的方法。检索underlyng indptrindicesdata是否有自定义cython解决方案是一个不错的选择?

2 个答案:

答案 0 :(得分:1)

那么你可以使用一种矢量化方法,使用稀疏矩阵的切片列与values的矩阵乘法,就像这样 -

F -= (mat[:,multiply_cols]*values*factor)[:,None]

<强>基准

似乎foo1是问题中列出的最快的一个。所以,让我们来对付那个提议的方法。

功能定义 -

def foo1(mat,F,values,multiply_cols):
    factor = 0.75
    outF = F.copy()
    for i in range(multiply_cols.shape[0]):
        outF -= factor*values[i]*mat.getcol(multiply_cols[i])
    return outF

def foo_vectorized(mat,F,values,multiply_cols):
    factor = 0.75 
    return F - (mat[:,multiply_cols]*values*factor)[:,None]

对稀疏性较大的集合进行计时和验证 -

In [242]: # Setup inputs
     ...: n = 3000
     ...: mat = csc_matrix(np.random.randint(0,3,(n,n))) #Sparseness with  0s
     ...: F = np.random.rand(n,1)
     ...: multiply_cols = np.unique(np.random.randint(0,int(0.6*n),size=n))
     ...: values = np.random.rand(multiply_cols.shape[0])
     ...: 

In [243]: out1 = foo1(mat,F,values,multiply_cols)

In [244]: out2 = foo_vectorized(mat,F,values,multiply_cols)

In [245]: np.allclose(out1, out2)
Out[245]: True

In [246]: %timeit foo1(mat,F,values,multiply_cols)
1 loops, best of 3: 641 ms per loop

In [247]: %timeit foo_vectorized(mat,F,values,multiply_cols)
10 loops, best of 3: 40.3 ms per loop

In [248]: 641/40.3
Out[248]: 15.905707196029779

我们有 15x+ 加速!

答案 1 :(得分:1)

我找到了

Sparse matrix slicing using list of int

sparse矩阵的列(或行)索引本质上是一个矩阵乘法任务 - 构造一个具有1和0的正确混合的稀疏矩阵,并乘以。行(和列)总和也用乘法完成。

这个功能实现了这个想法。 M是1列稀疏矩阵,values位中有multiply_cols

def wghtsum(sA, values, multiply_cols):
   cols = np.zeros_like(multiply_cols)
   M=sparse.csc_matrix((values,(multiply_cols,cols)),shape=(sA.shape[1],1))
   return (sA*M).A

测试:

In [794]: F1=wghtsum(sA,values,multiply_cols)
In [800]: F2=(sA[:,multiply_cols]*values)[:,None]  # Divaker's
In [802]: np.allclose(F1,F2)
Out[802]: True

@Divakar's解决方案相比节省了适度的时间:

In [803]: timeit F2=(sA[:,multiply_cols]*values)[:,None]
100 loops, best of 3: 18.3 ms per loop
In [804]: timeit F1=wghtsum(sA,values,multiply_cols)
100 loops, best of 3: 6.57 ms per loop

=======

创建的

sA是密集的 - 它是密集随机数组的稀疏表达。 sparse.rand可用于创建具有定义稀疏度的稀疏随机矩阵。

在测试foo1 getcol时,我遇到了问题:

In [818]: sA.getcol(multiply_cols[0])
...
TypeError: an integer is required
In [819]: sA.getcol(multiply_cols[0].item())
Out[819]: 
<1000x1 sparse matrix of type '<class 'numpy.float64'>'
    with 1000 stored elements in Compressed Sparse Column format>
In [822]: sA[:,multiply_cols[0]]
Out[822]: 
<1000x1 sparse matrix of type '<class 'numpy.float64'>'
    with 1000 stored elements in Compressed Sparse Column format>

我怀疑这是由scipy版本差异引起的。

In [821]: scipy.__version__
Out[821]: '0.17.0'

这个问题确实在0.18中消失了;但我找不到相关问题/ pullrequest。