带移位列的矩阵元素明智乘法

时间:2019-09-28 15:53:40

标签: python numpy matrix linear-algebra matrix-multiplication

说我有两个数组A和B。

按元素的乘法定义如下: enter image description here

我想以类似卷积的方式进行逐元素乘法,即,将每一列向右移动一步,例如,第1列现在是第2列,第3列现在是第1列。 这应该产生一个(2×3×3)数组(所有3种可能性为2x3矩阵)

enter image description here

4 个答案:

答案 0 :(得分:4)

我们可以将A与它自己的一个切片连接起来,然后获得那些滑动窗口。要获得这些窗口,我们可以利用基于np.lib.stride_tricks.as_stridedscikit-image's view_as_windows。然后,将这些窗口与B相乘以得到最终输出。 More info on use of as_strided based view_as_windows

因此,我们将有一个像这样的矢量化解决方案-

In [70]: from skimage.util.shape import view_as_windows

In [71]: A1 = np.concatenate((A,A[:,:-1]),axis=1)

In [74]: view_as_windows(A1,A.shape)[0]*B
Out[74]: 
array([[[1, 0, 3],
        [0, 0, 6]],

       [[2, 0, 1],
        [0, 0, 4]],

       [[3, 0, 2],
        [0, 0, 5]]])

我们还可以将multi-coresnumexpr module一起用于broadcasted-multiplication的最后一步,这在较大的数组上应该更好。因此,对于示例情况,它将是-

In [53]: import numexpr as ne

In [54]: w = view_as_windows(A1,A.shape)[0]

In [55]: ne.evaluate('w*B')
Out[55]: 
array([[[1, 0, 3],
        [0, 0, 6]],

       [[2, 0, 1],
        [0, 0, 4]],

       [[3, 0, 2],
        [0, 0, 5]]])

在大型数组上比较两种建议方法的时间-

In [56]: A = np.random.rand(500,500)
    ...: B = np.random.rand(500,500)

In [57]: A1 = np.concatenate((A,A[:,:-1]),axis=1)
    ...: w = view_as_windows(A1,A.shape)[0]

In [58]: %timeit w*B
    ...: %timeit ne.evaluate('w*B')
1 loop, best of 3: 422 ms per loop
1 loop, best of 3: 228 ms per loop

寻求最佳的基于跨步的方法

如果您真的想从基于步幅视图的方法中获得最大收益,请采用原始的基于np.lib.stride_tricks.as_strided的方法,以避免view_as_windows的功能开销-

def vaw_with_as_strided(A,B):
    A1 = np.concatenate((A,A[:,:-1]),axis=1)
    s0,s1 = A1.strides
    S = (A.shape[1],)+A.shape
    w = np.lib.stride_tricks.as_strided(A1,shape=S,strides=(s1,s0,s1))
    return w*B

与基于@Paul Panzer's array-assignment的数组相比,交叉似乎位于19x19形状的数组上-

In [33]: n = 18
    ...: A = np.random.rand(n,n)
    ...: B = np.random.rand(n,n)

In [34]: %timeit vaw_with_as_strided(A,B)
    ...: %timeit pp(A,B)
10000 loops, best of 3: 22.4 µs per loop
10000 loops, best of 3: 21.4 µs per loop

In [35]: n = 19
    ...: A = np.random.rand(n,n)
    ...: B = np.random.rand(n,n)

In [36]: %timeit vaw_with_as_strided(A,B)
    ...: %timeit pp(A,B)
10000 loops, best of 3: 24.5 µs per loop
10000 loops, best of 3: 24.5 µs per loop

因此,对于小于19x19的任何事物,array-assignment似乎更好,而对于大于opaque_paint的事物,应该采用基于跨步的方法。

答案 1 :(得分:2)

view_as_windows / as_strided上做一个注释。这些功能很简洁,知道它们具有相当明显的恒定开销是很有用的。这是@Divakar基于view_as_windows的解决方案(vaw)与我基于复制重塑方法的比较。

enter image description here

如您所见,vaw在中小型操作数上不是很快,并且仅在数组大小为30x30时才发光。

代码:

from simple_benchmark import BenchmarkBuilder, MultiArgument
import numpy as np
from skimage.util.shape import view_as_windows

B = BenchmarkBuilder()

@B.add_function()
def vaw(A,B):
    A1 = np.concatenate((A,A[:,:-1]),axis=1)
    w = view_as_windows(A1,A.shape)[0]
    return w*B

@B.add_function()
def pp(A,B):
    m,n = A.shape
    aux = np.empty((n,m,2*n),A.dtype)
    AA = np.concatenate([A,A],1)
    aux.reshape(-1)[:-n].reshape(n,-1)[...] = AA.reshape(-1)[:-1]
    return aux[...,:n]*B

@B.add_arguments('array size')
def argument_provider():
    for exp in range(4, 16):
        dim_size = int(1.4**exp)
        a = np.random.rand(dim_size,dim_size)
        b = np.random.rand(dim_size,dim_size)
        yield dim_size, MultiArgument([a,b])

r = B.run()
r.plot()

import pylab
pylab.savefig('vaw.png')

答案 2 :(得分:1)

运行for循环以获取列数,并使用np.roll()绕轴= 1,移动列并进行矩阵乘法。

请参考此参考文献中被接受的answer。 希望这会有所帮助。

答案 3 :(得分:0)

我实际上可以从阵列的两侧填充2列(以获得2x5阵列) 并以'b'为内核运行conv2,我认为它更有效