8

我有一个 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])

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

有没有更好的办法 ?文档中对我来说没有什么明显的。

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

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

4 回答 4

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
于 2012-06-29T17:37:20.247 回答
3

@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
于 2012-07-02T22:47:43.470 回答
1

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

>>> sum(my_sparse_matrices[1:],my_sparse_matrices[0]).todense()
于 2012-06-29T00:22:09.337 回答
1

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

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

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

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

于 2012-06-29T14:31:05.050 回答