有效地计算稀疏数组的列方和,其中每个非零元素为1

时间:2016-05-09 13:46:31

标签: python arrays numpy scipy sparse-matrix

我有一堆SciPy compressed sparse row (CSR)格式的数据。当然大多数元素都是零,我还知道所有非零元素的值都是1.我想计算矩阵行的不同子集的和。目前我正在做以下事情:

import numpy as np
import scipy as sp
import scipy.sparse

# create some data with sparsely distributed ones
data = np.random.choice((0, 1), size=(1000, 2000), p=(0.95, 0.05))
data = sp.sparse.csr_matrix(data, dtype='int8')

# generate column-wise sums over random subsets of rows
nrand = 1000
for k in range(nrand):
    inds = np.random.choice(data.shape[0], size=100, replace=False)

    # 60% of time is spent here
    extracted_rows = data[inds]

    # 20% of time is spent here
    row_sum = extracted_rows.sum(axis=0)

最后几行存在较大计算流水线的瓶颈。正如我在代码中注释的那样,60%的时间用于从随机索引中切片数据,20%用于计算实际总和。

在我看来,我应该能够使用我对数组中数据的了解(即,稀疏矩阵中的任何非零值将为1;不存在其他值)来更有效地计算这些和。不幸的是,我无法弄清楚如何。或许只处理data.indices?我已经尝试过其他稀疏结构(例如CSC矩阵),以及首先转换为密集阵列,但这些方法都比这种CSR矩阵方法慢。

2 个答案:

答案 0 :(得分:1)

众所周知,稀疏矩阵的索引相对较慢。通过直接访问数据属性,可以解决这个问题。

但首先是一些时间。在您显示时我会使用dataind

In [23]: datad=data.A  # times at 3.76 ms per loop

In [24]: timeit row_sumd=datad[inds].sum(axis=0)
1000 loops, best of 3: 529 µs per loop

In [25]: timeit row_sum=data[inds].sum(axis=0)
1000 loops, best of 3: 890 µs per loop

In [26]: timeit d=datad[inds]
10000 loops, best of 3: 55.9 µs per loop

In [27]: timeit d=data[inds]
1000 loops, best of 3: 617 µs per loop

稀疏版本比密集版本慢,但不是很多。稀疏索引要慢得多,但它的总和要快一些。

使用矩阵乘积

完成稀疏和
def sparse.spmatrix.sum
     ....
    return np.asmatrix(np.ones((1, m), dtype=res_dtype)) * self

这表明更快的方式 - 将inds转换为适当的1s数组并乘以。

In [49]: %%timeit
   ....: b=np.zeros((1,data.shape[0]),'int8')
   ....: b[:,inds]=1
   ....: rowmul=b*data
   ....: 
1000 loops, best of 3: 587 µs per loop

这使得稀疏操作与等效密集操作一样快。 (但转换为密集的速度要慢得多)

==================

最后一次测试缺少稀疏np.asmatrix中存在的sum。但时间相似,结果相同

In [232]: timeit b=np.zeros((1,data.shape[0]),'int8'); b[:,inds]=1; x1=np.asmatrix(b)*data
1000 loops, best of 3: 661 µs per loop

In [233]: timeit b=np.zeros((1,data.shape[0]),'int8'); b[:,inds]=1; x2=b*data
1000 loops, best of 3: 605 µs per loop

一个产生一个矩阵,另一个产生一个数组。但两者都在制作一个矩阵乘积,B的第二个暗点与data的第一个相比。尽管b是一个数组,但该任务实际上是以一种不那么透明的方式委托给data及其矩阵产品。

In [234]: x1
Out[234]: matrix([[9, 9, 5, ..., 9, 5, 3]], dtype=int8)

In [235]: x2
Out[235]: array([[9, 9, 5, ..., 9, 5, 3]], dtype=int8)

b*data.A是元素乘法并引发错误; np.dot(b,data.A)有效,但速度较慢。

较新的numpy/pythonmatmul运算符。我看到了相同的时间模式:

In [280]: timeit b@dataA           # dense product
100 loops, best of 3: 2.64 ms per loop

In [281]: timeit b@data.A           # slower due to `.A` conversion
100 loops, best of 3: 6.44 ms per loop

In [282]: timeit b@data             # sparse product
1000 loops, best of 3: 571 µs per loop

np.dot也可以将操作委托给sparse,但您必须小心。我只是用np.dot(csr_matrix(b),data.A)挂了我的机器。

答案 1 :(得分:1)

以下是将data转换为密集数组并使用argpartition-based method以矢量化方式获取所有inds后的矢量化方法 -

# Number of selections as a parameter
n = 100

# Get inds across all iterations in a vectorized manner as a 2D array.
inds2D = np.random.rand(nrand,data.shape[0]).argpartition(n)[:,:n]

# Index into data with those 2D array indices. Then, convert to dense NumPy array, 
# reshape and sum reduce to get the final output 
out = np.array(data.todense())[inds2D.ravel()].reshape(nrand,n,-1).sum(1)

运行时测试 -

1)功能定义:

def org_app(nrand,n):
    out = np.zeros((nrand,data.shape[1]),dtype=int)
    for k in range(nrand):
        inds = np.random.choice(data.shape[0], size=n, replace=False)        
        extracted_rows = data[inds]    
        out[k] = extracted_rows.sum(axis=0)    
    return out


def vectorized_app(nrand,n):
    inds2D = np.random.rand(nrand,data.shape[0]).argpartition(n)[:,:n]
    return np.array(data.todense())[inds2D.ravel()].reshape(nrand,n,-1).sum(1)

时间:

In [205]: # create some data with sparsely distributed ones
     ...: data = np.random.choice((0, 1), size=(1000, 2000), p=(0.95, 0.05))
     ...: data = sp.sparse.csr_matrix(data, dtype='int8')
     ...: 
     ...: # generate column-wise sums over random subsets of rows
     ...: nrand = 1000
     ...: n = 100
     ...: 

In [206]: %timeit org_app(nrand,n)
1 loops, best of 3: 1.38 s per loop

In [207]: %timeit vectorized_app(nrand,n)
1 loops, best of 3: 826 ms per loop