与极稀疏矩阵相乘的最快方法是什么?

时间:2017-09-27 20:07:47

标签: python numpy scipy sparse-matrix

我有一个非常稀疏的结构矩阵。我的矩阵每列只有一个非零条目。但它的巨大(10k * 1M)并以下面的形式给出(例如uisng随机值)

rows = np.random.randint(0, 10000, 1000000)
values = np.random.randint(0,10,1000000)

其中rows为我们提供了每列中非零条目的行号。我希望用S快速矩阵乘法,我现在正在跟随 - 我将这个形式转换为稀疏矩阵(S)并做S.dot(X)与矩阵X(可以是稀疏或密集)的乘法。

S=scipy.sparse.csr_matrix( (values, (rows, scipy.arange(1000000))), shape = (10000,1000000))

对于大小为1M * 2500且nnz(X)= 8M的X,创建S需要178ms,应用它需要255 ms。所以我的问题是这是什么是做SX的最佳方式(其中X可能是稀疏的或密集的),因为我的S如上所述。因为创建S本身非常耗时,所以我想到了一些特别的东西。我确实尝试使用循环创建一些东西,但它甚至没有关闭 简单的循环过程看起来像这样

SX = np.zeros((rows.size,X.shape[1])) for i in range(X.shape[0]): SX[rows[i],:]+=values[i]*X[i,:] return SX
我们可以提高效率吗?

非常感谢任何建议。感谢

3 个答案:

答案 0 :(得分:3)

方法#1

鉴于第一个输入中每列只有一个条目,我们可以使用np.bincountrowsvaluesX来使用S,从而避免创建稀疏矩阵def sparse_matrix_mult(rows, values, X): nrows = rows.max()+1 ncols = X.shape[1] nelem = nrows * ncols ids = rows + nrows*np.arange(ncols)[:,None] sums = np.bincount(ids.ravel(), (X.T*values).ravel(), minlength=nelem) out = sums.reshape(ncols,-1).T return out -

In [746]: import numpy as np
     ...: from scipy.sparse import csr_matrix
     ...: import scipy as sp
     ...: 

In [747]: np.random.seed(1234)
     ...: m,n = 3,4
     ...: rows = np.random.randint(0, m, n)
     ...: values = np.random.randint(2,10,n)
     ...: X = np.random.randint(2, 10, (n,5))
     ...: 

In [748]: S = csr_matrix( (values, (rows, sp.arange(n))), shape = (m,n))

In [749]: S.dot(X)
Out[749]: 
array([[42, 27, 45, 78, 87],
       [24, 18, 18, 12, 24],
       [18,  6,  8, 16, 10]])

In [750]: sparse_matrix_mult(rows, values, X)
Out[750]: 
array([[ 42.,  27.,  45.,  78.,  87.],
       [ 24.,  18.,  18.,  12.,  24.],
       [ 18.,   6.,   8.,  16.,  10.]])

示例运行 -

np.add.reduceat

方法#2

使用np.bincount替换def sparse_matrix_mult_v2(rows, values, X): nrows = rows.max()+1 ncols = X.shape[1] scaled_ar = X*values[:,None] sidx = rows.argsort() rows_s = rows[sidx] cut_idx = np.concatenate(([0],np.flatnonzero(rows_s[1:] != rows_s[:-1])+1)) sums = np.add.reduceat(scaled_ar[sidx],cut_idx,axis=0) out = np.empty((nrows, ncols),dtype=sums.dtype) row_idx = rows_s[cut_idx] out[row_idx] = sums return out -

In [149]: m,n = 1000, 100000
     ...: rows = np.random.randint(0, m, n)
     ...: values = np.random.randint(2,10,n)
     ...: X = np.random.randint(2, 10, (n,2500))
     ...: 

In [150]: S = csr_matrix( (values, (rows, sp.arange(n))), shape = (m,n))

In [151]: %timeit csr_matrix( (values, (rows, sp.arange(n))), shape = (m,n))
100 loops, best of 3: 16.1 ms per loop

In [152]: %timeit S.dot(X)
1 loop, best of 3: 193 ms per loop

In [153]: %timeit sparse_matrix_mult(rows, values, X)
1 loop, best of 3: 4.4 s per loop

In [154]: %timeit sparse_matrix_mult_v2(rows, values, X)
1 loop, best of 3: 2.81 s per loop

运行时测试

我无法按照问题中提到的尺寸运行它,因为那些对我来说太大了。因此,在减少的数据集上,这是我得到的 -

numpy.dot

因此,所提议的方法似乎并没有超出X的性能,但它们应该在内存效率方面做得很好。

适用于稀疏X

对于稀疏from scipy.sparse import find def sparse_matrix_mult_sparseX(rows, values, Xs): # Xs is sparse nrows = rows.max()+1 ncols = Xs.shape[1] nelem = nrows * ncols scaled_vals = Xs.multiply(values[:,None]) r,c,v = find(scaled_vals) ids = rows[r] + c*nrows sums = np.bincount(ids, v, minlength=nelem) out = sums.reshape(ncols,-1).T return out ,我们需要进行一些修改,如下面列出的修改方法所列 -

cat

答案 1 :(得分:1)

受到这篇文章Fastest way to sum over rows of sparse matrix的启发,我发现最好的方法是编写循环并将内容移植到numba。这是

`

@njit
def sparse_mul(SX,row,col,data,values,row_map):
    N = len(data)
    for idx in range(N):
        SX[row_map[row[idx]],col[idx]]+=data[idx]*values[row[idx]]
    return SX
X_coo=X.tocoo()
s=row_map.max()+1
SX = np.zeros((s,X.shape[1]))
sparse_mul(SX,X_coo.row,X_coo.col,X_coo.data,values,row_map)`

这里的row_map是问题中的行。在X大小(1M * 1K),1%稀疏度和s = 10K时,它的性能是从row_map形成稀疏矩阵并执行S.dot(A)的两倍。

答案 2 :(得分:0)

我记得,Knuth TAOP谈到将稀疏矩阵表示为(对于你的app)非零值的链表。也许是这样的?然后按每个维度遍历链表而不是整个数组。