MATLAB: How to vector-multiply two arrays of matrices?
我有两个3维数组,举一个简单的例子,它的前两个维表示矩阵,最后一个通过参数空间计数
1 |
(但假设每个
1 2 3 4 | C = A; % pre-allocate, nan(size(A,1), size(B,2)) would be better but slower for jj = 1:size(A, 3) C(:,:,jj) = A(:,:,jj) * B(:,:,jj); end |
当然可以完成这项工作,但是如果第三维更像是1e3元素,则由于它不使用MATLAB的矢量化,因此非常慢。 那么,有没有更快的方法?
我现在进行了一些时序测试,结果证明2x2xN最快的方法是计算矩阵元素:
1 2 3 4 5 | C = A; C(1,1,:) = A(1,1,:).*B(1,1,:) + A(1,2,:).*B(2,1,:); C(1,2,:) = A(1,1,:).*B(1,2,:) + A(1,2,:).*B(2,2,:); C(2,1,:) = A(2,1,:).*B(1,1,:) + A(2,2,:).*B(2,1,:); C(2,2,:) = A(2,1,:).*B(1,2,:) + A(2,2,:).*B(2,2,:); |
在一般情况下,事实证明for循环实际上是最快的(不过请不要忘记预先分配C!)。
如果已经将结果作为矩阵的单元格数组,使用cellfun是最快的选择,它也比循环单元格元素要快:
1 |
但是,在3d数组的情况下,必须先调用num2cell(
这是我为2 x 2 x 1e4的随机集合所做的一些计时:
1 2 3 4 5 6 7 |
显式是指直接计算2 x 2矩阵元素,请参见下面的内容。
对于新的随机数组,结果是相似的,如果之前不需要
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | n = 2; m = 2; l = 1e4; A = rand(n,m,l); B = rand(m,n,l); % naive for-loop: tic %Cf = nan(n,n,l); Cf = A; for jl = 1:l Cf(:,:,jl) = A(:,:,jl) * B(:,:,jl); end; disp([' array-for: ' num2str(toc)]); % using arrayfun: tic Ca = arrayfun(@(k) A(:,:,k)*B(:,:,k), 1:size(A,3), 'UniformOutput',false); Ca = cat(3,Ca{:}); disp([' arrayfun : ' num2str(toc)]); tic Ac = num2cell(A, [1 2]); Bc = num2cell(B, [1 2]); disp([' num2cell : ' num2str(toc)]); % cell for-loop: tic Cfc = Ac; for jl = 1:l Cfc{jl} = Ac{jl} * Bc{jl}; end; disp([' cell-for : ' num2str(toc)]); % using cellfun: tic Cc = cellfun(@mtimes, Ac, Bc, 'UniformOutput', false); disp([' cellfun : ' num2str(toc)]); tic Cc = cell2mat(Cc); disp([' cell2mat : ' num2str(toc)]); tic Cm = A; Cm(1,1,:) = A(1,1,:).*B(1,1,:) + A(1,2,:).*B(2,1,:); Cm(1,2,:) = A(1,1,:).*B(1,2,:) + A(1,2,:).*B(2,2,:); Cm(2,1,:) = A(2,1,:).*B(1,1,:) + A(2,2,:).*B(2,1,:); Cm(2,2,:) = A(2,1,:).*B(1,2,:) + A(2,2,:).*B(2,2,:); disp([' explicit : ' num2str(toc)]); disp(' '); |
这是我的基准测试,用于比较@TobiasKienzler答案中提到的方法。我正在使用TIMEIT函数来获取更准确的时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | function [t,v] = matrixMultTest() n = 2; m = 2; p = 1e5; A = rand(n,m,p); B = rand(m,n,p); %# time functions t = zeros(5,1); t(1) = timeit( @() func1(A,B,n,m,p) ); t(2) = timeit( @() func2(A,B,n,m,p) ); t(3) = timeit( @() func3(A,B,n,m,p) ); t(4) = timeit( @() func4(A,B,n,m,p) ); t(5) = timeit( @() func5(A,B,n,m,p) ); %# check the results v = cell(5,1); v{1} = func1(A,B,n,m,p); v{2} = func2(A,B,n,m,p); v{3} = func3(A,B,n,m,p); v{4} = func4(A,B,n,m,p); v{5} = func5(A,B,n,m,p); assert( isequal(v{:}) ) end %# simple FOR-loop function C = func1(A,B,n,m,p) C = zeros(n,n,p); for k=1:p C(:,:,k) = A(:,:,k) * B(:,:,k); end end %# ARRAYFUN function C = func2(A,B,n,m,p) C = arrayfun(@(k) A(:,:,k)*B(:,:,k), 1:p, 'UniformOutput',false); C = cat(3, C{:}); end %# NUM2CELL/FOR-loop/CELL2MAT function C = func3(A,B,n,m,p) Ac = num2cell(A, [1 2]); Bc = num2cell(B, [1 2]); C = cell(1,1,p); for k=1:p C{k} = Ac{k} * Bc{k}; end; C = cell2mat(C); end %# NUM2CELL/CELLFUN/CELL2MAT function C = func4(A,B,n,m,p) Ac = num2cell(A, [1 2]); Bc = num2cell(B, [1 2]); C = cellfun(@mtimes, Ac, Bc, 'UniformOutput', false); C = cell2mat(C); end %# Loop Unrolling function C = func5(A,B,n,m,p) C = zeros(n,n,p); C(1,1,:) = A(1,1,:).*B(1,1,:) + A(1,2,:).*B(2,1,:); C(1,2,:) = A(1,1,:).*B(1,2,:) + A(1,2,:).*B(2,2,:); C(2,1,:) = A(2,1,:).*B(1,1,:) + A(2,2,:).*B(2,1,:); C(2,2,:) = A(2,1,:).*B(1,2,:) + A(2,2,:).*B(2,2,:); end |
结果:
1 2 3 4 5 6 7 8 |
正如我在评论中所解释的那样,一个简单的FOR循环是最好的解决方案(在最后一种情况下缺少循环展开,仅对这些小的2×2矩阵可行)。
我强烈建议您使用matlab的MMX工具箱。它可以尽可能快地乘以n维矩阵。
MMX的优点是:
对于这个问题,您只需要编写以下命令:
1 | C=mmx('mul',A,B); |
我在@Amro的答案中添加了以下功能
1 2 3 4 | %# mmx toolbox function C=func6(A,B,n,m,p) C=mmx('mul',A,B); end |
我得到了
1 2 3 4 5 6 |
我使用@Amro的代码来运行基准测试。
以我的经验,一种更快的方法是在三维矩阵上使用点乘法和求和。以下函数z_matmultiply(A,B)函数将两个具有相同深度的三维矩阵相乘。点乘法以尽可能并行的方式进行,因此您可能需要检查此函数的速度,并通过大量重复将其与其他函数进行比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | function C = z_matmultiply(A,B) [ma,na,oa] = size(A); [mb,nb,ob] = size(B); %preallocate the output as we will do a loop soon C = zeros(ma,nb,oa); %error message if the dimensions are not appropriate if na ~= mb || oa ~= ob fprintf(' z_matmultiply warning: Matrix Dimmensions Inconsistent ') else % if statement minimizes for loops by looping the smallest matrix dimension if ma > nb for j = 1:nb Bp(j,:,:) = B(:,j,:); C(:,j,:) = sum(A.*repmat(Bp(j,:,:),[ma,1]),2); end else for i = 1:ma Ap(:,i,:) = A(i,:,:); C(i,:,:) = sum(repmat(Ap(:,i,:),[1,nb]).*B,1); end end end |
一种技术是为A和B创建一个2Nx2N的稀疏矩阵并将2x2矩阵嵌入对角线。用稀疏矩阵处理乘积,并用稍微聪明的索引获取结果,然后将其整形为2x2xN。
但是我怀疑这会比简单的循环更快。