【问题标题】:Scipy sparse matrix alternative for getrow()getrow() 的 Scipy 稀疏矩阵替代方案
【发布时间】:2016-09-01 10:43:04
【问题描述】:

我正在处理大型稀疏二进制矩阵。我使用Scipy 稀疏矩阵实现对它们进行了压缩。从scipy.spatial.distance计算Jaccard distance不支持对稀疏矩阵的直接运算,所以要么:

  1. 将整个稀疏矩阵转换为密集矩阵,然后将每一行作为向量进行操作,这需要大量内存

  2. 遍历稀疏,使用getrow()抓取每一行并进行操作。

  3. 编写我们自己的实现来处理稀疏矩阵。

为了让事情更清楚,这里是示例代码:

import scipy.spatial.distance as d
import numpy as np
from scipy.sparse import csr_matrix

# benchmark performance 
X = np.random.random((3000, 3000))
# binarize
X[X > 0.3] = 0
X[X>0] = 1
mat =  csr_matrix(X)

a = np.zeros(3000)
a[4] = a[100] = a[22] =1
a = csr_matrix(a)

def jaccard_fast(v1,v2):
    common = v1.dot(v2.T)
    dis = (v1 != v2).getnnz()
    if common[0,0]:
        return 1.0-float(common[0,0])/float(common[0,0]+dis)
    else:
        return 0.0
    
def benchmark_jaccard_fast():
    for i in range(mat.shape[0]):
        jaccard_fast(mat.getrow(i),a)
        
def benchmark_jaccard_internal_todense():
    for v1,v2 in zip(mat.todense(),a.todense()):
         d.jaccard(v1,v2)
        
def benchmark_jaccard_internal_getrow():
    for i in range(mat.shape[0]):
        d.jaccard(mat.getrow(i),a)
        

print "Jaccard Fast:"
%time benchmark_jaccard_fast()
print "Jaccard Scipy (expanding to dense):"
%time benchmark_jaccard_internal_todense()
print "Jaccard Scipy (using getrow):"
%time benchmark_jaccard_internal_getrow()

jaccard_fast 是我自己的实现。在 scipy 稀疏矩阵上,我的实现似乎比内部实现要快,但是 getrow() 似乎减慢了我的实现速度。当我将 jaccard_fastscipy.spatial.distance.jaccard 进行基准测试时,结果是:

Jaccard Fast:
CPU times: user 1.28 s, sys: 0 ns, total: 1.28 s
Wall time: 1.28 s
Jaccard Scipy (expanding to dense):
CPU times: user 28 ms, sys: 8 ms, total: 36 ms
Wall time: 37.2 ms
Jaccard Scipy (using getrow):
CPU times: user 1.82 s, sys: 0 ns, total: 1.82 s
Wall time: 1.81 s

任何有关如何避免getrow 瓶颈的帮助将不胜感激。由于内存限制,我无法使用todense() 扩展我的稀疏矩阵。

【问题讨论】:

  • 我认为你的benchmark_jaccard_internal_todense 是在作弊。它没有进行完整的 3000 次迭代。 a.todense() 是 (1,3000),所以压缩它,mat 迭代最少的行数 - 即。 1.
  • benchmark_jaccard_internal_getrow 不是一个有效的测试,因为您自己承认d.jaccard 不适用于稀疏。

标签: python scipy sparse-matrix


【解决方案1】:

稀疏索引以速度较慢而著称,例如How to read/traverse/slice Scipy sparse matrices (LIL, CSR, COO, DOK) faster?

In [33]: timeit for row in mat: x=row  # sparse iteration
1 loops, best of 3: 510 ms per loop

In [35]: timeit for row in mat.todense(): x=row  # dense iteration
10 loops, best of 3: 175 ms per loop

但我发现您的d.jacard 在使用稀疏矩阵时也会变慢

In [36]: ad=a.todense()

In [37]: timeit for row in mat.todense(): d.jaccard(row,ad) # all dense
1 loops, best of 3: 734 ms per loop

In [38]: timeit for row in mat: d.jaccard(row.todense(),ad) # inner dense
1 loops, best of 3: 1.69 s per loop

In [39]: timeit for row in mat: d.jaccard(row,a) # all sparse
1 loops, best of 3: 4.61 s per loop

消除getrow 因素

In [40]: mrow=mat.getrow(0)

In [41]: mrowd=mrow.todense()

In [42]: timeit d.jaccard(mrow, a)  # one sparse row
1000 loops, best of 3: 1.32 ms per loop

In [43]: timeit d.jaccard(mrow.todense(), a.todense())  # with conversion
1000 loops, best of 3: 539 µs per loop

In [44]: timeit d.jaccard(mrowd, ad)  #  dense
10000 loops, best of 3: 173 µs per loop

=======================

我需要重新运行这些测试,因为d.jaccard 不适用于稀疏(而jaccard_fast 不适用于密集)。因此,将稀疏行迭代问题与jaccard 计算分开需要更多的工作。

我对@9​​87654331@ 做了一些修改:

def my_jaccard(mat, a):
    common = mat*a.T # sparse does the large matrix product well 
    dis=np.array([(row!=a).getnnz() for row in mat]) # iterative
    cA = common.A.ravel()
    return 1 - cA/(cA + dis)

它返回匹配 d.jaccard 在密集行上运行的值。 d.jaccardcommon 为0 的行返回1。我似乎不需要cA 测试(除非cAdis 在同一个插槽中可能都是0) .

In [141]: r=np.array([d.jaccard(row,ad) for row in mat.todense()])

In [142]: r1=my_jaccard(mat,a)

In [143]: np.allclose(r,r1)
Out[143]: True

速度只有一半。如果我可以返工dis calc 应该有类似的速度。

In [144]: timeit r=np.array([d.jaccard(row,ad) for row in mat.todense()])
1 loops, best of 3: 783 ms per loop

In [145]: timeit r1=my_jaccard(mat,a)
1 loops, best of 3: 1.29 s per loop

进一步调整计算。我屏蔽了 common 的 0 值。这有两个目的 - 它确保我们没有除以 0 的问题,它减少了 dis 迭代的次数,从而稍微提高了速度。

def my_jaccard(mat, a):
    common=mat*a.T
    cA = common.A.ravel()
    mask = cA!=0
    cA = cA[mask]
    dis = np.array([(row!=a).getnnz() for row, b in zip(mat,mask) if b])
    ret = np.ones(mat.shape[0])
    ret[mask] = 1 - cA/(cA+dis)
    return ret

这样时间会减少一点。

In [188]: timeit my_jaccard(mat,a)
1 loops, best of 3: 1.04 s per loop

===================

Python - Efficient Function with scipy sparse Matrices 的问题有重叠

在那个问题中,我比较了稀疏矩阵和 1 行矩阵,发现使用sparse.kron 复制行是复制numpy 广播的最快方法。

jaccard 中使用这个想法来计算dis 数组

def my_jaccard1(mat, a):
    common = mat*a.T
    cA = common.A.ravel()
    aM = sparse.kron(a,np.ones((mat.shape[0],1),int))
    dis = (mat!=aM).sum(1)
    ret = 1-cA/(cA+dis.A1)
    return ret    

有了这个时序显着提高(10 倍):

In [318]: timeit my_jaccard1(mat,a)
1 loops, best of 3: 97.1 ms per loop

我可以像以前一样应用掩码来防止被零除;但它实际上减慢了计算速度(到 140 毫秒)。

def my_jaccard3(mat, a):
    common = mat*a.T
    cA = common.A.ravel()
    mask = cA!=0
    cA = cA[mask]
    aM = sparse.kron(a,np.ones((len(cA),1),int))
    dis = (mat[mask,:]!=aM).sum(1)
    ret = np.ones(mat.shape[0])
    ret[mask] = 1 - cA/(cA+dis.A1) 
    return ret  

==========================

编辑 - 疑似病例测试

In [75]: x,y= np.array([1,1,0,0,1,0]), np.array([0,0,1,0,1,0])

In [76]: d.jaccard(x,y)
Out[76]: 0.75

In [78]: jaccard_fast(sparse.csr_matrix(x),sparse.csr_matrix(y))
Out[78]: 0.75

我的版本:

In [79]: my_jaccard(sparse.csr_matrix(x),sparse.csr_matrix(y))
Out[79]: array([ 0.75])
...
In [82]: my_jaccard3(sparse.csr_matrix(x),sparse.csr_matrix(y))
Out[82]: array([ 0.75])

(编辑 - 明确使用sparse.kron

【讨论】:

  • 很抱歉无法更早地检查这一点,但我认为您的解决方案不会产生与d.jaccard 相同的结果。例如,尝试使用向量 [1,1,0,0,1,0],[0,0,1,0,1,0]
  • jacard_fast 是否理解这种情况?我试图复制它(除了 1 0 开关)。
  • jacard_fast 确实得到了这个案例,我检查了。抱歉,明天我可以再次访问计算设备时,我必须调查一下。
  • 我的版本匹配;可能是我误解了你的例子。
  • 我在dis = (mat[mask,:]!=aM).sum(1) 得到一个AttributeError: 'bool' object has no attribute 'sum' 试图运行你的测试。我的 scipy 版本是 0.17.0,下面的 numpy 是 0.11.0
【解决方案2】:

试试这个:

def getrow_using_underlying_repr(a):
    for i,j in enumerate(a.indptr[:-1]):
        a.data[a.indptr[i]:a.indptr[i+1]]

比 getrow() 快 100 倍以上:

Jaccard Fast:
CPU times: user 1.41 s, sys: 0 ns, total: 1.41 s
Wall time: 1.41 s
Jaccard Scipy (expanding to dense):
CPU times: user 19.9 ms, sys: 29.8 ms, total: 49.6 ms
Wall time: 49.3 ms
Jaccard Scipy (using getrow):
CPU times: user 1.48 s, sys: 1.27 ms, total: 1.48 s
Wall time: 1.48 s
getrow_using_underlying_repr:
CPU times: user 11 µs, sys: 1e+03 ns, total: 12 µs
Wall time: 13.8 µs

要了解代码,请参阅文档中实例化的最新版本中的说明:
https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csr_matrix.html#scipy.sparse.csr_matrix
要更好地理解稀疏 CSR 的底层表示,请参阅:
https://www.geeksforgeeks.org/sparse-matrix-representations-set-3-csr/amp/?ref=rp
通过研究源码中内置的getrow()的实现,发现速度慢的原因是它没有使用底层表示,而是CSR稀疏矩阵的矩阵乘积。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-15
    • 1970-01-01
    • 2017-03-26
    • 2017-03-31
    • 2023-04-10
    • 2017-07-21
    • 2011-11-28
    相关资源
    最近更新 更多