numpy矩阵乘法到三角形/稀疏存储?

时间:2013-01-12 17:50:20

标签: python numpy combinatorics sparse-matrix matrix-multiplication

我正在处理一个非常大的稀疏矩阵乘法(matmul)问题。举个例子,我们说:

  • A是二进制(75 x 200,000)矩阵。它很稀疏,所以我使用csc进行存储。我需要进行以下matmul操作:

  • B = A.transpose()* A

  • 输出将是一个大小为200Kx200K的稀疏对称矩阵。

不幸的是,B将以方式大到存储在我的笔记本电脑上的RAM(或“核心”)中。另一方面,我很幸运,因为B有一些属性可以解决这个问题。

由于B将沿着对角线和稀疏对称,我可以使用三角矩阵(上/下)来存储matmul操作的结果,稀疏矩阵存储格式可以进一步减小尺寸。

我的问题是......可以提前告知numpy或scipy,输出存储要求会是什么样子,以便我可以使用numpy选择存储解决方案并避免“矩阵太大”运行时计算几分钟(小时)后出错?

换句话说,通过使用近似计数算法分析两个输入矩阵的内容,可以近似地估算矩阵的存储要求吗?

如果没有,我正在寻找一个强力解决方案。以下网站链接涉及地图/缩小,核外存储或matmul细分解决方案(strassens算法)的内容:

一对Map / Reduce问题细分解决方案

核心外(PyTables)存储解决方案

matmul细分解决方案:

提前感谢任何建议,评论或指导!

1 个答案:

答案 0 :(得分:2)

由于您使用的是转置矩阵的乘积,因此[m, n]处的值基本上将是原始矩阵中m列和n列的点积。

我将使用以下矩阵作为玩具示例

a = np.array([[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
              [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0],
              [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1]])
>>> np.dot(a.T, a)
array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0],
       [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
       [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0],
       [0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 2]])

形状为(3, 12),有7个非零项。它的转置产品当然是形状(12, 12),有16个非零项,其中6个在对角线上,所以它只需要存储11个元素。

您可以通过以下两种方式之一了解输出矩阵的大小:

CSR FORMAT

如果您的原始矩阵有C个非零列,则您的新矩阵最多会有C**2个非零条目,其中C位于对角线中,并且可以保证不要为零,其余的条目只需要保留一半,因此最多只有(C**2 + C) / 2个非零元素。当然,其中许多也将为零,所以这可能是一个高估。

如果您的矩阵以csr格式存储,则相应indices对象的scipy属性具有一个数组,其列索引为所有非零元素,因此您可以轻松地将上述估算计算为:

>>> a_csr = scipy.sparse.csr_matrix(a)
>>> a_csr.indices
array([ 2, 11,  1,  7, 10,  4, 11])
>>> np.unique(a_csr.indices).shape[0]
6

因此有6列具有非零条目,因此估计最多36个非零条目,超过真实16条。

CSC FORMAT

如果我们有行索引而不是非零元素的列索引,我们实际上可以做更好的估计。对于两列的点积非零,它们必须在同一行中具有非零元素。如果给定行中存在R个非零元素,则它们会向产品提供R**2个非零元素。当你对所有行求和时,你必须多次计算一些元素,所以这也是一个上限。

矩阵的非零元素的行索引位于稀疏csc矩阵的indices属性中,因此可以按如下方式计算此估计值:

>>> a_csc = scipy.sparse.csc_matrix(a)
>>> a_csc.indices
array([1, 0, 2, 1, 1, 0, 2])
>>> rows, where = np.unique(a_csc.indices, return_inverse=True)
>>> where = np.bincount(where)
>>> rows
array([0, 1, 2])
>>> where
array([2, 3, 2])
>>> np.sum(where**2)
17

这是真正的16接近!而这个估计实际上与以下相同并不是巧合:

>>> np.sum(np.dot(a.T,a),axis=None)
17

在任何情况下,以下代码都应该让您看到估算非常好:

def estimate(a) :
    a_csc = scipy.sparse.csc_matrix(a)
    _, where = np.unique(a_csc.indices, return_inverse=True)
    where = np.bincount(where)
    return np.sum(where**2)

def test(shape=(10,1000), count=100) :
    a = np.zeros(np.prod(shape), dtype=int)
    a[np.random.randint(np.prod(shape), size=count)] = 1
    print 'a non-zero = {0}'.format(np.sum(a))
    a = a.reshape(shape)
    print 'a.T * a non-zero = {0}'.format(np.flatnonzero(np.dot(a.T,
                                                                a)).shape[0])
    print 'csc estimate = {0}'.format(estimate(a))

>>> test(count=100)
a non-zero = 100
a.T * a non-zero = 1065
csc estimate = 1072
>>> test(count=200)
a non-zero = 199
a.T * a non-zero = 4056
csc estimate = 4079
>>> test(count=50)
a non-zero = 50
a.T * a non-zero = 293
csc estimate = 294