稀疏矩阵的Numpy Elementwise外积

时间:2019-06-07 11:15:09

标签: python numpy sparse-matrix matrix-multiplication elementwise-operations

我想在python中将三个(或四个)大型2D数组进行元素逐个外部乘积(值将float32舍入为两位小数)。它们都具有相同数量的行“ n”,但是具有不同数量的列“ i”,“ j”,“ k”。
结果数组的形状应为(n,i * j * k)。然后,我想对结果的每一列求和,最后得到一维形状为(i * j * k)的一维数组。

np.shape(a) = (75466, 10)
np.shape(b) = (75466, 28)
np.shape(c) = (75466, 66)

np.shape(intermediate_result) = (75466, 18480)
np.shape(result) = (18480)

感谢ruankesi and divakar,我得到了一段有效的代码:

# Multiply first two matrices
first_multi = a[...,None] * b[:,None]
# could use np.einsum('ij,ik->ijk',a,b), which is slightly faster
ab_fills = first_multi.reshape(a.shape[0], a.shape[1]*b.shape[1])

# Multiply the result with the third matrix
second_multi = ab_fills[..., None] * c[:,None]
abc_fills = second_multi.reshape(ab_fills.shape[0], ab_fills.shape[1] * c.shape[1])

# Get the result: sum columns and get a 1D array of length 10*28*66 = 18 480
result = np.sum(abc_fills, axis = 0)

问题1 :性能

这大约需要3秒钟,但是我必须重复多次此操作,并且某些矩阵甚至更大(以行数计)。这是可以接受的,但是使其更快将是不错的。

问题2 :我的矩阵稀疏

例如,“ a”实际上包含0的70%。我尝试玩scipy csc_matrix,但实际上无法获得工作版本。 (要在此处获得按元素分类的外部乘积,我需要转换为3D矩阵,而scipy sparse_matrix不支持该矩阵)

问题3 :内存使用情况

如果我尝试同时使用第4个矩阵,则会遇到内存问题。


我想将这段代码转换为sparse_matrix可以节省大量内存,并且可以通过忽略众多0值来加快计算速度。 真的吗?如果可以,有人可以帮我吗?
当然,如果您有更好的实施建议,我也很感兴趣。我不需要任何中间结果,只需要最终的一维结果。
我停留在这部分代码上已经有几个星期了,我疯了!

谢谢!



在Divakar回答后编辑

方法1:
一种很好的衬板,但出人意料地比原始方法要慢(?)。
在我的测试数据集上,方法#1每个循环花费4.98 s±3.06 ms(optimize = True时无加速)
最初的分解方法每个循环耗时3.01 s±16.5 ms


方法2:
太好了,谢谢!多么令人印象深刻的加速!
每个循环62.6 ms±233 µs


关于numexpr,我尽量避免对外部模块的需求,并且我不打算使用多核/线程。这是一项“令人费解的”可并行化任务,需要分析成千上万个对象,我将在生产过程中将列表分散到可用的CPU上。我将尝试进行内存优化。
作为对numexpr的一个简短尝试,它限制了1个线程并执行1个乘法,没有numexpr的运行时间为40毫秒,而使用numexpr的运行时间为52毫秒。
再次感谢!

1 个答案:

答案 0 :(得分:0)

方法1

我们可以使用np.einsum一次性进行总和减少-

result = np.einsum('ij,ik,il->jkl',a,b,c).ravel()

另外,通过将其设置为optimize以使用BLAS,在np.einsum中使用True标志。

方法2

我们可以使用broadcasting来执行第一步,正如在发布的代码中也提到的那样,然后将张量矩阵乘法与np.tensordot-

def broadcast_dot(a,b,c):
    first_multi = a[...,None] * b[:,None]
    return np.tensordot(first_multi,c, axes=(0,0)).ravel()

我们还可以使用numexpr module来支持多核处理,并获得更高的内存效率来获得first_multi。这为我们提供了一种经过修改的解决方案,例如-

import numexpr as ne

def numexpr_broadcast_dot(a,b,c):
    first_multi = ne.evaluate('A*B',{'A':a[...,None],'B':b[:,None]})
    return np.tensordot(first_multi,c, axes=(0,0)).ravel()

具有给定数据集大小的随机浮动数据的计时-

In [36]: %timeit np.einsum('ij,ik,il->jkl',a,b,c).ravel()
4.57 s ± 75.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [3]: %timeit broadcast_dot(a,b,c)
270 ms ± 103 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [4]: %timeit numexpr_broadcast_dot(a,b,c)
172 ms ± 63.8 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

仅凭numexpr给人一种改善的感觉-

In [7]: %timeit a[...,None] * b[:,None]
80.4 ms ± 2.64 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [8]: %timeit ne.evaluate('A*B',{'A':a[...,None],'B':b[:,None]})
25.9 ms ± 191 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

在将此解决方案扩展到更多输入时,这应该是实质性的。