多维数组的逐元素矩阵乘法
Element-wise matrix multiplication for multi-dimensional array
我想在 MATLAB 中实现逐分量矩阵乘法,可以使用 Python 中的 numpy.einsum
来完成,如下所示:
import numpy as np
M = 2
N = 4
I = 2000
J = 300
A = np.random.randn(M, M, I)
B = np.random.randn(M, M, N, J, I)
C = np.random.randn(M, J, I)
# using einsum
D = np.einsum('mki, klnji, lji -> mnji', A, B, C)
# naive for-loop
E = np.zeros(M, N, J, I)
for i in range(I):
for j in range(J):
for n in range(N):
E[:,n,j,i] = B[:,:,i] @ A[:,:,n,j,i] @ C[:,j,i]
print(np.sum(np.abs(D-E))) # expected small enough
到目前为止,我使用 i
、j
和 n
的 for 循环,但我不想,至少 [=14= 的 for 循环].
选项 1:从 MATLAB 调用 numpy
假设您的系统已设置 according to the documentation,并且安装了 numpy 包,您可以(在 MATLAB 中)执行以下操作:
np = py.importlib.import_module('numpy');
M = 2;
N = 4;
I = 2000;
J = 300;
A = matpy.mat2nparray( randn(M, M, I) );
B = matpy.mat2nparray( randn(M, M, N, J, I) );
C = matpy.mat2nparray( randn(M, J, I) );
D = matpy.nparray2mat( np.einsum('mki, klnji, lji -> mnji', A, B, C) );
哪里可以找到 matpy
here。
选项 2:原生 MATLAB
这里最重要的部分是正确排列,因此我们需要跟踪我们的尺寸。我们将使用以下顺序:
I(1) J(2) K(3) L(4) M(5) N(6)
现在,我将解释如何获得正确的排列顺序(让我们以 A
为例):einsum
期望维度顺序为 mki
,这根据我们的编号是 5 3 1
。这告诉我们A
的第1st维需要是第5th,第2nd 需要是 3rd 而 3rd 需要是 1st (在短 1->5, 2->3, 3->1
)。这也意味着“无源维度”(意味着那些没有原始维度成为它们的维度;在本例中为 2 4 6)应该是单例的。使用 ipermute
这写起来真的很简单:
pA = ipermute(A, [5,3,1,2,4,6]);
在上面的例子中,1->5
意味着我们先写5
,其他两个维度也是如此(产生[5,3,1])。那么我们只要在最后加上单例(2,4,6)就可以得到[5,3,1,2,4,6]
。最后:
A = randn(M, M, I);
B = randn(M, M, N, J, I);
C = randn(M, J, I);
% Reference dim order: I(1) J(2) K(3) L(4) M(5) N(6)
pA = ipermute(A, [5,3,1,2,4,6]); % 1->5, 2->3, 3->1; 2nd, 4th & 6th are singletons
pB = ipermute(B, [3,4,6,2,1,5]); % 1->3, 2->4, 3->6, 4->2, 5->1; 5th is singleton
pC = ipermute(C, [4,2,1,3,5,6]); % 1->4, 2->2, 3->1; 3rd, 5th & 6th are singletons
pD = sum( ...
permute(pA .* pB .* pC, [5,6,2,1,3,4]), ... 1->5, 2->6, 3->2, 4->1; 3rd & 4th are singletons
[5,6]);
(请参阅 post 底部关于 sum
的注释。)
另一种在 MATLAB 中实现的方法 as mentioned by @AndrasDeak 如下:
rD = squeeze(sum(reshape(A, [M, M, 1, 1, 1, I]) .* ...
reshape(B, [1, M, M, N, J, I]) .* ...
... % same as: reshape(B, [1, size(B)]) .* ...
... % same as: shiftdim(B,-1) .* ...
reshape(C, [1, 1, M, 1, J, I]), [2, 3]));
另请参阅:squeeze
, reshape
, permute
, ipermute
, shiftdim
。
下面是一个完整的示例,显示测试这些方法是否等效:
function q55913093
M = 2;
N = 4;
I = 2000;
J = 300;
mA = randn(M, M, I);
mB = randn(M, M, N, J, I);
mC = randn(M, J, I);
%% Option 1 - using numpy:
np = py.importlib.import_module('numpy');
A = matpy.mat2nparray( mA );
B = matpy.mat2nparray( mB );
C = matpy.mat2nparray( mC );
D = matpy.nparray2mat( np.einsum('mki, klnji, lji -> mnji', A, B, C) );
%% Option 2 - native MATLAB:
%%% Reference dim order: I(1) J(2) K(3) L(4) M(5) N(6)
pA = ipermute(mA, [5,3,1,2,4,6]); % 1->5, 2->3, 3->1; 2nd, 4th & 6th are singletons
pB = ipermute(mB, [3,4,6,2,1,5]); % 1->3, 2->4, 3->6, 4->2, 5->1; 5th is singleton
pC = ipermute(mC, [4,2,1,3,5,6]); % 1->4, 2->2, 3->1; 3rd, 5th & 6th are singletons
pD = sum( permute( ...
pA .* pB .* pC, [5,6,2,1,3,4]), ... % 1->5, 2->6, 3->2, 4->1; 3rd & 4th are singletons
[5,6]);
rD = squeeze(sum(reshape(mA, [M, M, 1, 1, 1, I]) .* ...
reshape(mB, [1, M, M, N, J, I]) .* ...
reshape(mC, [1, 1, M, 1, J, I]), [2, 3]));
%% Comparisons:
sum(abs(pD-D), 'all')
isequal(pD,rD)
运行上面我们得到的结果确实是等价的:
>> q55913093
ans =
2.1816e-10
ans =
logical
1
请注意,这两种调用方法 sum
是在最近的版本中引入的,因此如果您的 MATLAB 比较旧,您可能需要更换它们:
S = sum(A,'all') % can be replaced by ` sum(A(:)) `
S = sum(A,vecdim) % can be replaced by ` sum( sum(A, dim1), dim2) `
根据评论中的要求,这里有一个比较方法的基准:
function t = q55913093_benchmark(M,N,I,J)
if nargin == 0
M = 2;
N = 4;
I = 2000;
J = 300;
end
% Define the arrays in MATLAB
mA = randn(M, M, I);
mB = randn(M, M, N, J, I);
mC = randn(M, J, I);
% Define the arrays in numpy
np = py.importlib.import_module('numpy');
pA = matpy.mat2nparray( mA );
pB = matpy.mat2nparray( mB );
pC = matpy.mat2nparray( mC );
% Test for equivalence
D = cat(5, M1(), M2(), M3());
assert( sum(abs(D(:,:,:,:,1) - D(:,:,:,:,2)), 'all') < 1E-8 );
assert( isequal (D(:,:,:,:,2), D(:,:,:,:,3)));
% Time
t = [ timeit(@M1,1), timeit(@M2,1), timeit(@M3,1)];
function out = M1()
out = matpy.nparray2mat( np.einsum('mki, klnji, lji -> mnji', pA, pB, pC) );
end
function out = M2()
out = permute( ...
sum( ...
ipermute(mA, [5,3,1,2,4,6]) .* ...
ipermute(mB, [3,4,6,2,1,5]) .* ...
ipermute(mC, [4,2,1,3,5,6]), [3,4]...
), [5,6,2,1,3,4]...
);
end
function out = M3()
out = squeeze(sum(reshape(mA, [M, M, 1, 1, 1, I]) .* ...
reshape(mB, [1, M, M, N, J, I]) .* ...
reshape(mC, [1, 1, M, 1, J, I]), [2, 3]));
end
end
在我的系统上这导致:
>> q55913093_benchmark
ans =
1.3964 0.1864 0.2428
这意味着 2nd 方法更可取(至少对于默认输入大小而言)。
我想在 MATLAB 中实现逐分量矩阵乘法,可以使用 Python 中的 numpy.einsum
来完成,如下所示:
import numpy as np
M = 2
N = 4
I = 2000
J = 300
A = np.random.randn(M, M, I)
B = np.random.randn(M, M, N, J, I)
C = np.random.randn(M, J, I)
# using einsum
D = np.einsum('mki, klnji, lji -> mnji', A, B, C)
# naive for-loop
E = np.zeros(M, N, J, I)
for i in range(I):
for j in range(J):
for n in range(N):
E[:,n,j,i] = B[:,:,i] @ A[:,:,n,j,i] @ C[:,j,i]
print(np.sum(np.abs(D-E))) # expected small enough
到目前为止,我使用 i
、j
和 n
的 for 循环,但我不想,至少 [=14= 的 for 循环].
选项 1:从 MATLAB 调用 numpy
假设您的系统已设置 according to the documentation,并且安装了 numpy 包,您可以(在 MATLAB 中)执行以下操作:
np = py.importlib.import_module('numpy');
M = 2;
N = 4;
I = 2000;
J = 300;
A = matpy.mat2nparray( randn(M, M, I) );
B = matpy.mat2nparray( randn(M, M, N, J, I) );
C = matpy.mat2nparray( randn(M, J, I) );
D = matpy.nparray2mat( np.einsum('mki, klnji, lji -> mnji', A, B, C) );
哪里可以找到 matpy
here。
选项 2:原生 MATLAB
这里最重要的部分是正确排列,因此我们需要跟踪我们的尺寸。我们将使用以下顺序:
I(1) J(2) K(3) L(4) M(5) N(6)
现在,我将解释如何获得正确的排列顺序(让我们以 A
为例):einsum
期望维度顺序为 mki
,这根据我们的编号是 5 3 1
。这告诉我们A
的第1st维需要是第5th,第2nd 需要是 3rd 而 3rd 需要是 1st (在短 1->5, 2->3, 3->1
)。这也意味着“无源维度”(意味着那些没有原始维度成为它们的维度;在本例中为 2 4 6)应该是单例的。使用 ipermute
这写起来真的很简单:
pA = ipermute(A, [5,3,1,2,4,6]);
在上面的例子中,1->5
意味着我们先写5
,其他两个维度也是如此(产生[5,3,1])。那么我们只要在最后加上单例(2,4,6)就可以得到[5,3,1,2,4,6]
。最后:
A = randn(M, M, I);
B = randn(M, M, N, J, I);
C = randn(M, J, I);
% Reference dim order: I(1) J(2) K(3) L(4) M(5) N(6)
pA = ipermute(A, [5,3,1,2,4,6]); % 1->5, 2->3, 3->1; 2nd, 4th & 6th are singletons
pB = ipermute(B, [3,4,6,2,1,5]); % 1->3, 2->4, 3->6, 4->2, 5->1; 5th is singleton
pC = ipermute(C, [4,2,1,3,5,6]); % 1->4, 2->2, 3->1; 3rd, 5th & 6th are singletons
pD = sum( ...
permute(pA .* pB .* pC, [5,6,2,1,3,4]), ... 1->5, 2->6, 3->2, 4->1; 3rd & 4th are singletons
[5,6]);
(请参阅 post 底部关于 sum
的注释。)
另一种在 MATLAB 中实现的方法 as mentioned by @AndrasDeak 如下:
rD = squeeze(sum(reshape(A, [M, M, 1, 1, 1, I]) .* ...
reshape(B, [1, M, M, N, J, I]) .* ...
... % same as: reshape(B, [1, size(B)]) .* ...
... % same as: shiftdim(B,-1) .* ...
reshape(C, [1, 1, M, 1, J, I]), [2, 3]));
另请参阅:squeeze
, reshape
, permute
, ipermute
, shiftdim
。
下面是一个完整的示例,显示测试这些方法是否等效:
function q55913093
M = 2;
N = 4;
I = 2000;
J = 300;
mA = randn(M, M, I);
mB = randn(M, M, N, J, I);
mC = randn(M, J, I);
%% Option 1 - using numpy:
np = py.importlib.import_module('numpy');
A = matpy.mat2nparray( mA );
B = matpy.mat2nparray( mB );
C = matpy.mat2nparray( mC );
D = matpy.nparray2mat( np.einsum('mki, klnji, lji -> mnji', A, B, C) );
%% Option 2 - native MATLAB:
%%% Reference dim order: I(1) J(2) K(3) L(4) M(5) N(6)
pA = ipermute(mA, [5,3,1,2,4,6]); % 1->5, 2->3, 3->1; 2nd, 4th & 6th are singletons
pB = ipermute(mB, [3,4,6,2,1,5]); % 1->3, 2->4, 3->6, 4->2, 5->1; 5th is singleton
pC = ipermute(mC, [4,2,1,3,5,6]); % 1->4, 2->2, 3->1; 3rd, 5th & 6th are singletons
pD = sum( permute( ...
pA .* pB .* pC, [5,6,2,1,3,4]), ... % 1->5, 2->6, 3->2, 4->1; 3rd & 4th are singletons
[5,6]);
rD = squeeze(sum(reshape(mA, [M, M, 1, 1, 1, I]) .* ...
reshape(mB, [1, M, M, N, J, I]) .* ...
reshape(mC, [1, 1, M, 1, J, I]), [2, 3]));
%% Comparisons:
sum(abs(pD-D), 'all')
isequal(pD,rD)
运行上面我们得到的结果确实是等价的:
>> q55913093
ans =
2.1816e-10
ans =
logical
1
请注意,这两种调用方法 sum
是在最近的版本中引入的,因此如果您的 MATLAB 比较旧,您可能需要更换它们:
S = sum(A,'all') % can be replaced by ` sum(A(:)) `
S = sum(A,vecdim) % can be replaced by ` sum( sum(A, dim1), dim2) `
根据评论中的要求,这里有一个比较方法的基准:
function t = q55913093_benchmark(M,N,I,J)
if nargin == 0
M = 2;
N = 4;
I = 2000;
J = 300;
end
% Define the arrays in MATLAB
mA = randn(M, M, I);
mB = randn(M, M, N, J, I);
mC = randn(M, J, I);
% Define the arrays in numpy
np = py.importlib.import_module('numpy');
pA = matpy.mat2nparray( mA );
pB = matpy.mat2nparray( mB );
pC = matpy.mat2nparray( mC );
% Test for equivalence
D = cat(5, M1(), M2(), M3());
assert( sum(abs(D(:,:,:,:,1) - D(:,:,:,:,2)), 'all') < 1E-8 );
assert( isequal (D(:,:,:,:,2), D(:,:,:,:,3)));
% Time
t = [ timeit(@M1,1), timeit(@M2,1), timeit(@M3,1)];
function out = M1()
out = matpy.nparray2mat( np.einsum('mki, klnji, lji -> mnji', pA, pB, pC) );
end
function out = M2()
out = permute( ...
sum( ...
ipermute(mA, [5,3,1,2,4,6]) .* ...
ipermute(mB, [3,4,6,2,1,5]) .* ...
ipermute(mC, [4,2,1,3,5,6]), [3,4]...
), [5,6,2,1,3,4]...
);
end
function out = M3()
out = squeeze(sum(reshape(mA, [M, M, 1, 1, 1, I]) .* ...
reshape(mB, [1, M, M, N, J, I]) .* ...
reshape(mC, [1, 1, M, 1, J, I]), [2, 3]));
end
end
在我的系统上这导致:
>> q55913093_benchmark
ans =
1.3964 0.1864 0.2428
这意味着 2nd 方法更可取(至少对于默认输入大小而言)。