繁体   English   中英

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

[英]What is the fastest way to multiply with extremely sparse matrix?

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

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

行为我们提供每列中非零条目的行号。 我希望用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
我们可以提高效率吗?

任何建议都非常感谢。 谢谢

方法#1

鉴于第一个输入中每列只有一个条目,我们可以使用输入 - rowsvaluesX来使用np.bincount ,从而避免创建稀疏矩阵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.]])

方法#2

使用np.add.reduceat替换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

灵感来自这篇文章最快的方法来总结稀疏矩阵的行 ,我发现最好的方法是写入循环和端口事物到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)的两倍。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM