Python 具有稀疏矩阵的numpy元素外积

Python 具有稀疏矩阵的numpy元素外积,python,numpy,sparse-matrix,matrix-multiplication,elementwise-operations,Python,Numpy,Sparse Matrix,Matrix Multiplication,Elementwise Operations,我想用python实现三(或四)个大型2D数组的元素级外积(值被四舍五入到2位小数)。它们的行数“n”相同,但列数“i”、“j”、“k”不同 结果数组的形状应为(n,i*j*k)。然后,我想对结果的每一列求和,得到一个1D形状数组(I*j*k) 多亏了,我得到了一段有效的代码: # Multiply first two matrices first_multi = a[...,None] * b[:,None] # could use np.einsum('ij,ik->ijk',a,b)

我想用python实现三(或四)个大型2D数组的元素级外积(值被四舍五入到2位小数)。它们的行数“n”相同,但列数“i”、“j”、“k”不同
结果数组的形状应为(n,i*j*k)。然后,我想对结果的每一列求和,得到一个1D形状数组(I*j*k)

多亏了,我得到了一段有效的代码:

# 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”包含70%的0。我试着玩scipy csc_matrix,但真的无法得到一个工作版本。(为了获得元素级外积,我通过转换到3D矩阵,这在scipy sparse_矩阵中不受支持)

问题3:内存使用 如果我尝试使用第四个矩阵,我会遇到内存问题。


我认为,将此代码转换为稀疏_矩阵将节省大量内存,并通过忽略大量的0值来加快计算速度。 这是真的吗?如果是,有人能帮我吗?
当然,如果您对更好的实施有任何建议,我也非常感兴趣。我不需要任何中间结果,只需要最终1D结果。
这几周我都被这部分代码卡住了,我快发疯了!

非常感谢。



在Divakar的回答后编辑 方法#1:
非常好的一条直线,但比原来的方法慢得出奇(?)。
在我的测试数据集上,方法#1每循环需要4.98 s±3.06 ms(优化=真时无加速)
最初的分解方法每次循环耗时3.01s±16.5ms


方法#2:
非常好,谢谢!多么令人印象深刻的加速!
62.6 ms±233µs/回路


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

方法#1

我们可以使用一次完成总和缩减-

result = np.einsum('ij,ik,il->jkl',a,b,c).ravel()
另外,在
np.einsum
中将
optimize
标志设置为
True
以使用BLAS,从而使用该标志

方法#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()
我们还可以使用支持多核处理并实现更好的内存效率的。这给了我们一个修改后的解决方案,就像这样-

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)

当将此解决方案扩展到更高数量的输入时,这应该是非常重要的。

我认为
optimize=True
不会启用BLAS,而只是在计算之前优化收缩路径。@NilsWerner-Hmm,这解释了为什么它并不总是有用。好消息,谢谢!发布的解决方案对你有用吗?太好了,太神奇了。非常感谢。我正在用更长的答案编辑这篇文章。
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)