说我有两个数组A和B。
我想以类似卷积的方式进行逐元素乘法,即,将每一列向右移动一步,例如,第1列现在是第2列,第3列现在是第1列。 这应该产生一个(2×3×3)数组(所有3种可能性为2x3矩阵)
答案 0 :(得分:4)
我们可以将A
与它自己的一个切片连接起来,然后获得那些滑动窗口。要获得这些窗口,我们可以利用基于np.lib.stride_tricks.as_strided
的scikit-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-cores
与numexpr
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)与我基于复制重塑方法的比较。
如您所见,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,我认为它更有效