【问题标题】:What is the fastest way to multiply with extremely sparse matrix?与极稀疏矩阵相乘的最快方法是什么?
【发布时间】:2018-03-09 10:40:27
【问题描述】:

我有一个非常稀疏的结构化矩阵。我的矩阵每列只有一个非零条目。但它的巨大(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 需要 178 毫秒,应用它需要 255 毫秒。所以我的问题是,鉴于我的 S 如所描述的那样,做 SX 的最佳方法是什么(其中 X 可能是稀疏的或密集的)。由于创建 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
我们能做到这一点吗?

非常感谢任何建议。谢谢

【问题讨论】:

  • 考虑到您的矩阵有多大,这些时间安排非常好。除了求助于完全不同的框架之外,我认为您无法获得更多性能。
  • @rayryeng 我觉得它有点慢,因为例如在我给出的示例中执行 X^TX 需要 450 毫秒,这等于执行 SA 所花费的时间。鉴于当 X 为 m*n 时执行 SA 是 O(mn) 并且执行 X^TX 是 O(mn^2),我发现执行 SA 不够快。我也知道我没有考虑 X 的稀疏性,但我对密集 X 有相似的数字。谢谢

标签: python numpy scipy sparse-matrix


【解决方案1】:

方法#1

鉴于在第一个输入中每列只有一个条目,我们可以使用 np.bincount 使用输入 - rows、values 和 X,从而也避免创建稀疏矩阵 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

【讨论】:

  • 谢谢。我会试试看。当 X 稀疏时,只有一件事 X.T*values 似乎在做点积而不是乘法。
  • @user1131274 建议的方法假设X 是密集的。如果您使用稀疏,一种方法是转换为密集,然后与values 相乘。所以,X.toarray().T*values 等等。
  • X 非常大以致密。还有你的 XtX 时间。我问这个的原因是因为我的 SX 时间与 XtX 相同,我觉得很奇怪,因为 XtX 是 O(nd^2) 而 SX 在理论上是 O(nd),在我们的例子中 d=2500。这就是为什么我觉得应该有更好的方法。感谢您的努力。
  • @user1131274 还有,XtX 是什么?
  • XtX 是 (X^T)*X。我也进行了编辑,添加了我的想法。我认为我们可以并行化我对输出行索引的方法。因为如果我们查看它,我们所要做的就是将 X 的行划分为输出矩阵的行。您能否看看我的方法,并告诉我是否可以在那里做些什么。我不知道 python 并行化库。谢谢
【解决方案2】:

受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 是问题中的行。在大小为 (1M* 1K)、稀疏度为 1% 且 s=10K 的 X 上,其性能是从 row_map 形成稀疏矩阵并执行 S.dot(A) 的两倍。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 这可能无济于事。 scipy 中的稀疏矩阵表示和稀疏矩阵乘法已经得到很好的优化,因此使用稀疏矩阵的未优化表示进行乘法可能需要比当前基准测试更长的时间。 (仅供参考,我没有投反对票)。
    • 不用担心任何反对票 - 如果这是答案应得的。没有一个成功的程序员会认为自我比现实更重要。
    猜你喜欢
    • 2011-04-08
    • 2014-08-25
    • 2015-04-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-04
    • 2016-02-06
    • 2017-07-21
    相关资源
    最近更新 更多