稀疏矩阵总和导致密集矩阵-如何强制结果稀疏?

时间:2020-06-05 14:13:56

标签: matrix scipy sparse-matrix

在scipy.sparse.csr_matrix的一个轴上求和将产生一个numpy.matrix对象。 鉴于我的稀疏矩阵确实很稀疏,所以我发现这种行为非常烦人。

这里是一个例子:

dense = [[ 0.,  0.,  0.,  0.,  0.],
         [ 1.,  0.,  0.,  0.,  0.],
         [ 0.,  0.,  0.,  0.,  0.],
         [ 0.,  0.,  0.,  0.,  0.],
         [ 2.,  0.,  4.,  0.,  0.]]


from scipy.sparse import csr_matrix
sparse = csr_matrix(dense)

print(sparse.sum(1))

结果:

matrix([[ 0.],
        [ 1.],
        [ 0.],
        [ 0.],
        [ 6.]])

如何在不对矩阵进行隐式转换为密集格式的情况下,对列求和运算强制执行稀疏? 在此示例中,我仅使用了一个小的n矩阵,但是我的矩阵更大且稀疏,因此通过密集表示会浪费大量空间。

1 个答案:

答案 0 :(得分:2)

sparse通过矩阵乘法执行求和:

In [136]: np.matrix(np.ones(M.shape[1]))@M                                      
Out[136]: matrix([[3., 0., 4., 0., 0.]])
In [137]: M@np.matrix(np.ones((M.shape[1],1)))                                  
Out[137]: 
matrix([[0.],
        [1.],
        [0.],
        [0.],
        [6.]])
In [138]: timeit M@np.matrix(np.ones((M.shape[1],1)))                           
91.5 µs ± 268 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [139]: timeit M.sum(1)                                                       
96.6 µs ± 647 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

时间是相似的。两者都产生np.matrix结果。

如果将其与2d数组相乘,我会得到一个数组结果,而且出乎意料的是更好的时间:

In [140]: timeit M@np.ones((M.shape[1],1))                                      
24.4 µs ± 1.09 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [141]: M@np.ones((M.shape[1],1))                                             
Out[141]: 
array([[0.],
       [1.],
       [0.],
       [0.],
       [6.]])

我可以将该数组放回稀疏矩阵中-但要花一些时间:

In [142]: csr_matrix(M@np.ones((M.shape[1],1)))                                 
Out[142]: 
<5x1 sparse matrix of type '<class 'numpy.float64'>'
    with 2 stored elements in Compressed Sparse Row format>
In [143]: timeit csr_matrix(M@np.ones((M.shape[1],1)))                          
391 µs ± 17.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

或者我们可以先创建一个稀疏矩阵:

In [144]: M@csr_matrix(np.ones((M.shape[1],1)))                                 
Out[144]: 
<5x1 sparse matrix of type '<class 'numpy.float64'>'
    with 2 stored elements in Compressed Sparse Row format>
In [145]: timeit M@csr_matrix(np.ones((M.shape[1],1)))                          
585 µs ± 5.28 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

即使从循环中删除提取器矩阵的创建也会导致速度降低:

In [146]: %%timeit m1 = csr_matrix(np.ones((M.shape[1],1))) 
     ...: M@m1                                                                     
227 µs ± 4.72 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

sum这样(几乎)总是增加结果的密度。每行具有至少一个非零值的矩阵比具有许多纯零行的矩阵更常见。在现实生活中,时间可能会有所不同,但是尝试使用相同的内存可能并不会给您带来多少好处。

如果我更详细地研究稀疏矩阵乘法产生的csr矩阵:

In [147]: res = M@csr_matrix(np.ones((M.shape[1],1)))                           
In [148]: res                                                                   
Out[148]: 
<5x1 sparse matrix of type '<class 'numpy.float64'>'
    with 2 stored elements in Compressed Sparse Row format>
In [149]: res.indptr                                                            
Out[149]: array([0, 0, 1, 1, 1, 2], dtype=int32)
In [150]: res.indices                                                           
Out[150]: array([0, 0], dtype=int32)

indptr数组每行(+1)都有一个值,因此此列矩阵的内存使用实际上高于密集的等效项。 res格式的相同csc会更紧凑,带有2个元素indptr

还可以直接使用indptr矩阵的indicesdatacsr属性,从本质上在行上进行迭代并对其求和创建一个新的稀疏矩阵。在某些情况下,与sparse方法相比,我们已经提高了速度。但是您必须了解数据存储,并要对整个过程保持精明。