有效地积累稀疏的scipy矩阵的集合

时间:2012-06-28 23:22:45

标签: python optimization numpy scipy sparse-matrix

我有一个O(N)NxN scipy.sparse.csr_matrix的集合,每个稀疏矩阵都有N个元素集的顺序。我想将所有这些矩阵一起添加到一个常规的NxN numpy数组中。 (N大约为1000)。矩阵内非零元素的排列使得得到的和肯定不稀疏(实际上几乎没有剩余零元素)。

目前我正在做

reduce(lambda x,y: x+y,[m.toarray() for m in my_sparse_matrices])

虽然有效,但有点慢:当然,在那里进行的零点无意义处理绝对是可怕的。

有更好的方法吗?我docs中没有任何明显的东西。

更新:根据user545424的建议,我尝试了对稀疏矩阵求和的替代方案,并将稀疏矩阵求和到密集矩阵上。下面的代码显示了在相当的时间内运行的所有方法(关于四核i7的amd64 Debian / Squeeze上的Python 2.6.6)

import numpy as np
import numpy.random
import scipy
import scipy.sparse
import time

N=768
S=768
D=3

def mkrandomsparse():
    m=np.zeros((S,S),dtype=np.float32)
    r=np.random.random_integers(0,S-1,D*S)
    c=np.random.random_integers(0,S-1,D*S)
    for e in zip(r,c):
        m[e[0],e[1]]=1.0
    return scipy.sparse.csr_matrix(m)

M=[mkrandomsparse() for i in xrange(N)]

def plus_dense():
    return reduce(lambda x,y: x+y,[m.toarray() for m in M])

def plus_sparse():
    return reduce(lambda x,y: x+y,M).toarray()

def sum_dense():
    return sum([m.toarray() for m in M])

def sum_sparse():
    return sum(M[1:],M[0]).toarray()

def sum_combo():  # Sum the sparse matrices 'onto' a dense matrix?
    return sum(M,np.zeros((S,S),dtype=np.float32))

def benchmark(fn):
    t0=time.time()
    fn()
    t1=time.time()
    print "{0:16}:  {1:.3f}s".format(fn.__name__,t1-t0)

for i in xrange(4):
    benchmark(plus_dense)
    benchmark(plus_sparse)
    benchmark(sum_dense)
    benchmark(sum_sparse)
    benchmark(sum_combo)
    print

并退出

plus_dense      :  1.368s
plus_sparse     :  1.405s
sum_dense       :  1.368s
sum_sparse      :  1.406s
sum_combo       :  1.039s

虽然通过弄乱N,S,D参数可以得到一种方法或者另一种方法提前2倍左右......但是没有什么比你希望从中看到的数量级改进考虑到零添加的数量,应该可以跳过。

4 个答案:

答案 0 :(得分:4)

如果您的矩阵非常稀疏,我认为我已经找到了将速度加快~10倍的方法。

In [1]: from scipy.sparse import csr_matrix

In [2]: def sum_sparse(m):
   ...:     x = np.zeros(m[0].shape)
   ...:     for a in m:
   ...:         ri = np.repeat(np.arange(a.shape[0]),np.diff(a.indptr))
   ...:         x[ri,a.indices] += a.data
   ...:     return x
   ...: 

In [6]: m = [np.zeros((100,100)) for i in range(1000)]

In [7]: for x in m:
   ...:     x.ravel()[np.random.randint(0,x.size,10)] = 1.0
   ...:     

        m = [csr_matrix(x) for x in m]

In [17]: (sum(m[1:],m[0]).todense() == sum_sparse(m)).all()
Out[17]: True

In [18]: %timeit sum(m[1:],m[0]).todense()
10 loops, best of 3: 145 ms per loop

In [19]: %timeit sum_sparse(m)
100 loops, best of 3: 18.5 ms per loop

答案 1 :(得分:2)

@ user545424已经发布了可能是最快的解决方案。有同样精神的东西,更具可读性和速度...... nonzero() 有各种有用的应用程序。

def sum_sparse(m):
        x = np.zeros(m[0].shape,m[0].dtype)
        for a in m:
            # old lines
            #ri = np.repeat(np.arange(a.shape[0]),np.diff(a.indptr))
            #x[ri,a.indices] += a.data
            # new line
            x[a.nonzero()] += a.data
        return x

答案 2 :(得分:1)

在转换为密集矩阵之前,你不能将它们加在一起吗?

>>> sum(my_sparse_matrices[1:],my_sparse_matrices[0]).todense()

答案 3 :(得分:1)

这不是一个完整的答案(我也希望看到更详细的答案),但是你可以通过不创造中间结果来获得两个或更多改进的简单因素:

def sum_dense():
    return sum([m.toarray() for m in M])

def sum_dense2():
    return sum((m.toarray() for m in M))

在我的机器(YMMV)上,这导致计算速度最快。通过将求和放在()而不是[]中,我们在求和之前构造生成器而不是整个列表。