脾气暴躁,可以有效地将一个矩阵插入另一个矩阵吗?

时间:2018-11-20 20:21:54

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

我试图通过以下方法使两个向量的外部乘积更有效:删除零元素,进行外部乘积,然后将所得矩阵扩大为零行或插入零矩阵。 (使用scipy来对矩阵进行稀疏处理实际上是行不通的,因为转换成本很高,而且我会一遍又一遍地这样做。)

import numpy
dim = 100
vec = np.random.rand(1, dim)
mask = np.flatnonzero(vec > 0.8)
vec_sp = vec[:, mask]
mat_sp = vec_sp.T * vec_sp # This is faster than dot product
# Enlarge matrix or insert into zero matrix

由于它是两个向量的外积,我知道原始矩阵中的零行和零列,它们是mask变量中的索引。要看到这个,

a = np.array(((1,0,2,0))).reshape(1,-1)
a.T * a
>> array([[1, 0, 2, 0],
       [0, 0, 0, 0],
       [2, 0, 4, 0],
       [0, 0, 0, 0]])

我尝试了两种不同的解决方案:一种使用numpy的insert方法,并将方法追加到mat_sp变量中。整个过程变成了循环,而且速度很慢。

for val in mask:
    if val < mat_sp.shape[0]:
        mat_sp = np.insert(mat_sp, val, values=0, axis=1)
        mat_sp = np.insert(mat_sp, val, values=0, axis=0)
    else:
        mat_sp = np.append(mat_sp, values=np.zeros((mat_sp.shape[0], 1)), axis=1)
        mat_sp = np.append(mat_sp, values=np.zeros((1, mat_sp.shape[1])), axis=0)

另一种方法是创建大小为dim x dim的零矩阵,然后通过两个for循环从掩码中创建一个巨型索引向量。然后使用索引向量将矩阵乘法插入零矩阵。但是,这也超级慢。

由于稀疏矩阵乘积需要花费非稀疏时间的2/3,因此任何可以有效解决问题的想法或见解都将是非常重要的。


使用@hjpaul的示例,我们得到以下比较代码

import numpy as np
dims = 400

def test_non_sparse():
    vec = np.random.rand(1, dims)
    a = vec.T * vec

def test_sparse():  
    vec = np.random.rand(1, dims)
    idx = np.flatnonzero(vec>0.75)
    oprod = vec[:,idx].T * vec[:,idx]
    vec_oprod = np.zeros((dims, dims))
    vec_oprod[idx[:,None], idx] = oprod


if __name__ == '__main__':
    import timeit
    print('Non sparse:',timeit.timeit("test_non_sparse()", setup="from __main__ import test_non_sparse", number=10000))
    print('Sparse:',timeit.timeit("test_sparse()", setup="from __main__ import test_sparse", number=10000))

根据矢量的大小和零的数量,代码可以提供一定的改进。大于300的尺寸和大约70%的零将适度提高速度,并随零元素和尺寸的数量而增加。如果矩阵和蒙版一次又一次地相同,则肯定有可能获得更大的加速。

(我做逻辑索引的错误是用idx而不是idx[:,None]

1 个答案:

答案 0 :(得分:1)

将一个矩阵插入另一个矩阵的最快方法是使用索引。

使用外部产品进行说明:

In [94]: a = np.array(((1,0,2,0)))
In [95]: idx = np.where(a>0)[0]
In [96]: idx
Out[96]: array([0, 2])
In [97]: a1 = a[idx]

凝聚数组的外部乘积:

In [98]: a2 = a1[:,None]*a1
In [99]: a2
Out[99]: 
array([[1, 2],
       [2, 4]])

设置结果数组,并使用块索引来确定a2值的位置:

In [100]: res = np.zeros((4,4),int)
In [101]: res[idx[:,None], idx] = a2
In [102]: res
Out[102]: 
array([[1, 0, 2, 0],
       [0, 0, 0, 0],
       [2, 0, 4, 0],
       [0, 0, 0, 0]])

未压缩数组的直接外部:

In [103]: a[:,None]*a
Out[103]: 
array([[1, 0, 2, 0],
       [0, 0, 0, 0],
       [2, 0, 4, 0],
       [0, 0, 0, 0]])
In [104]: np.outer(a,a)
Out[104]: 
array([[1, 0, 2, 0],
       [0, 0, 0, 0],
       [2, 0, 4, 0],
       [0, 0, 0, 0]])

如果a是2d(n,1),则此外部可以写为np.dot(a.T,a)dot涉及一个和,在这种情况下为1维。

我认为a必须非常稀疏,才能从这项额外的索引工作中受益。对于稀疏的稀疏矩阵,我发现即使在预制矩阵的情况下,稀疏度也只有1%左右才能具有任何速度优势。


In [105]: from scipy import sparse
In [106]: A = sparse.csr_matrix(a)
In [107]: A
Out[107]: 
<1x4 sparse matrix of type '<class 'numpy.int64'>'
    with 2 stored elements in Compressed Sparse Row format>
In [108]: A.A
Out[108]: array([[1, 0, 2, 0]], dtype=int64)
In [109]: A.T*A           # sparse matrix product, dot
Out[109]: 
<4x4 sparse matrix of type '<class 'numpy.int64'>'
    with 4 stored elements in Compressed Sparse Column format>
In [110]: _.A
Out[110]: 
array([[1, 0, 2, 0],
       [0, 0, 0, 0],
       [2, 0, 4, 0],
       [0, 0, 0, 0]], dtype=int64)