Python中2种稀疏矩阵的逐行乘法的特殊类型

时间:2014-10-02 01:23:22

标签: python matrix scipy sparse-matrix

我正在寻找:在Python中实现一种特殊的乘法运算方式,用于恰好采用scipy稀疏(csr)格式的矩阵。这是一种特殊的乘法,不是矩阵乘法,也不是Kronecker multiplication也不是Hadamard又称pointwise乘法,并且在scipy.sparse中似乎没有任何内置支持。

所需操作:输出的每一行应包含两个输入矩阵中相应行元素的每个乘积的结果。因此,从两个大小相同的矩阵开始,每个矩阵的维度 m n ,结果的维度 m n ^ 2 < / em>的。

看起来像这样:

graphic of desired multiplication operation

Python代码:

import scipy.sparse
A = scipy.sparse.csr_matrix(np.array([[1,2],[3,4]]))
B = scipy.sparse.csr_matrix(np.array([[0,5],[6,7]]))

# C is the desired product of A and B. It should look like:
C = scipy.sparse.csr_matrix(np.array([[0,5,0,10],[18,21,24,28]]))

这样做有什么好方法?我试过看看stackoverflow以及其他地方,到目前为止没有运气。到目前为止,听起来我最好的选择是在for循环中逐行操作,但这听起来很可怕,因为我的输入矩阵有几百万行和几千列,大多数稀疏

3 个答案:

答案 0 :(得分:5)

在您的示例中,Ckron

的第一行和最后一行
In [4]: A=np.array([[1,2],[3,4]])
In [5]: B=np.array([[0,5],[6,7]])
In [6]: np.kron(A,B)
Out[6]: 
array([[ 0,  5,  0, 10],
       [ 6,  7, 12, 14],
       [ 0, 15,  0, 20],
       [18, 21, 24, 28]])
In [7]: np.kron(A,B)[[0,3],:]
Out[7]: 
array([[ 0,  5,  0, 10],
       [18, 21, 24, 28]])

kron包含与np.outer相同的值,但它们的顺序不同。

对于大型密集阵列,einsum可能提供良好的速度:

np.einsum('ij,ik->ijk',A,B).reshape(A.shape[0],-1)

sparse.kronnp.kron

做同样的事情
As = sparse.csr_matrix(A); Bs ...
sparse.kron(As,Bs).tocsr()[[0,3],:].A

sparse.kron是用Python编写的,所以如果进行不必要的计算,你可能会修改它。

迭代解决方案似乎是:

sparse.vstack([sparse.kron(a,b) for a,b in zip(As,Bs)]).A

作为迭代,我不希望它比削减完整kron更快。但是,如果没有深入研究sparse.kron的逻辑,那可能是我能做的最好的事情。

vstack使用bmat,因此计算结果为:

sparse.bmat([[sparse.kron(a,b)] for a,b in zip(As,Bs)])

但是bmat相当复杂,所以进一步简化这一过程并不容易。

np.einsum解决方案无法轻松扩展为稀疏 - 不存在sparse.einsum,中间产品为3d,稀疏无法处理。


sparse.kron使用coo格式,这对于处理行没有好处。但是,按照这个功能的精神,我已经找到了一个迭代csr格式矩阵行的函数。与kronbmat一样,我构建datarowcol数组,并从中构建coo_matrix。这反过来可以转换为其他格式。

def test_iter(A, B):
    m,n1 = A.shape
    n2 = B.shape[1]
    Cshape = (m, n1*n2)
    data = np.empty((m,),dtype=object)
    col =  np.empty((m,),dtype=object)
    row =  np.empty((m,),dtype=object)
    for i,(a,b) in enumerate(zip(A, B)):
        data[i] = np.outer(a.data, b.data).flatten()
        #col1 = a.indices * np.arange(1,a.nnz+1) # wrong when a isn't dense
        col1 = a.indices * n2   # correction
        col[i] = (col1[:,None]+b.indices).flatten()
        row[i] = np.full((a.nnz*b.nnz,), i)
    data = np.concatenate(data)
    col = np.concatenate(col)
    row = np.concatenate(row)
    return sparse.coo_matrix((data,(row,col)),shape=Cshape)

使用这些小的2x2矩阵以及较大的矩阵(例如A1=sparse.rand(1000,2000).tocsr()),这比使用bmat的版本快3倍。对于足够大的矩阵,它比密集的einsum版本(可能有内存错误)更好。

答案 1 :(得分:1)

执行此操作的非最佳方法是每行分别为kron:

def my_mult(A, B):
    nrows = A.shape[0]
    prodrows = []
    for i in xrange(0, nrows):
        Arow = A.getrow(i)
        Brow = B.getrow(i)
        prodrow = scipy.sparse.kron(Arow,Brow)
        prodrows.append(prodrow)
    return scipy.sparse.vstack(prodrows)

这比@ hpaulj的解决方案here的性能差大约3倍,可以通过运行以下代码看出:

A=scipy.sparse.rand(20000,1000, density=0.05).tocsr()
B=scipy.sparse.rand(20000,1000, density=0.05).tocsr()

# Check memory
%memit C1 = test_iter(A,B)
%memit C2 = my_mult(A,B)

# Check time
%timeit C1 = test_iter(A,B)
%timeit C2 = my_mult(A,B)

# Last but not least, check correctness!
print (C1 - C2).nnz == 0

<强>结果:

hpaulj的方法:

peak memory: 1993.93 MiB, increment: 1883.80 MiB
1 loops, best of 3: 6.42 s per loop

这种方法:

peak memory: 2456.75 MiB, increment: 1558.78 MiB
1 loops, best of 3: 18.9 s per loop

答案 2 :(得分:0)

hpauj对我的另一篇文章的回答:

How do i create interacting sparse matrix?

def test_iter2(A, B): 
    m,n1 = A.shape 
    n2 = B.shape[1] 
    Cshape = (m, n1*n2) 
    data = [] 
    col =  [] 
    row =  [] 
    for i in range(A.shape[0]): 
        slc1 = slice(A.indptr[i],A.indptr[i+1]) 
        data1 = A.data[slc1]; ind1 = A.indices[slc1] 
        slc2 = slice(B.indptr[i],B.indptr[i+1])  
        data2 = B.data[slc2]; ind2 = B.indices[slc2]  
        data.append(np.outer(data1, data2).ravel()) 
        col.append(((ind1*n2)[:,None]+ind2).ravel()) 
        row.append(np.full(len(data1)*len(data2), i)) 
    data = np.concatenate(data) 
    col = np.concatenate(col) 
    row = np.concatenate(row) 
    return sparse.coo_matrix((data,(row,col)),shape=Cshape) 

速度提高了6倍。

In [536]: S0=sparse.random(200,200, 0.01, format='csr')                                                   
In [537]: S1=sparse.random(200,200, 0.01, format='csr')                                                   
In [538]: timeit test_iter(S0,S1)                                                                         
42.8 ms ± 1.7 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [539]: timeit test_iter2(S0,S1)                                                                        
6.94 ms ± 27 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)