我有两个三维数组,前两个维度表示矩阵,最后一个维数通过参数空间计算,作为一个简单的示例
A = repmat([1,2; 3,4], [1 1 4]);
(但假设每个A(:,:,j)
j
不同。如何轻松执行两个此类矩阵数组j
和A
的每B
矩阵乘法?
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的矢量化。那么,有更快的方法吗?
答案 0 :(得分:6)
我现在做了一些时序测试,2x2xN的最快方法是计算矩阵元素:
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是最快的选择,它也比循环遍历单元格元素更快:
C = cellfun(@mtimes, A, B, 'UniformOutput', false);
但是,必须先为{-3}}(Ac = num2cell(A, [1 2])
)和cell2mat
调用3d数组的情况会浪费太多时间。
这是我为2 x 2 x 1e4的随机集合做的一些时间:
array-for: 0.057112
arrayfun : 0.14206
num2cell : 0.079468
cell-for : 0.033173
cellfun : 0.025223
cell2mat : 0.010213
explicit : 0.0021338
显式是指使用2 x 2矩阵元素的直接计算,请参见下文。
对于新的随机数组,结果类似,如果之前不需要cellfun
,num2cell
是最快的,并且对2x2xN没有限制。对于在第三维上循环的一般3d数组确实是最快的选择。这是时间码:
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(' ');
答案 1 :(得分:4)
这是我的基准测试,比较@TobiasKienzler答案中提到的方法。我正在使用TIMEIT函数来获得更准确的时间。
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
结果:
>> [t,v] = matrixMultTest();
>> t
t =
0.63633 # FOR-loop
1.5902 # ARRAYFUN
1.1257 # NUM2CELL/FOR-loop/CELL2MAT
1.0759 # NUM2CELL/CELLFUN/CELL2MAT
0.05712 # Loop Unrolling
正如我在评论中所解释的那样,一个简单的FOR循环是最好的解决方案(在最后一种情况下短于loop unwinding,这对于这些小的2乘2矩阵是可行的。)
答案 2 :(得分:3)
我强烈建议您使用matlab的。它可以尽可能快地乘以n维矩阵。
MMX 的优点是:
对于此问题,您只需编写此命令:
%# mmx toolbox
function C=func6(A,B,n,m,p)
C=mmx('mul',A,B);
end
我在@ Amro的回答中添加了以下功能
n=2,m=2,p=1e5
我得到 1.6571 # FOR-loop
4.3110 # ARRAYFUN
3.3731 # NUM2CELL/FOR-loop/CELL2MAT
2.9820 # NUM2CELL/CELLFUN/CELL2MAT
0.0244 # Loop Unrolling
0.0221 # MMX toolbox <===================
的结果:
class School
has_many :school_tags, dependent: :destroy
has_many :tags, through: :school_tags
end
class Tag
has_many :school_tags, dependent: :destroy
has_many :schools, through: :school_tags
end
class SchoolTag < ActiveRecord::Base
belongs_to :tag, foreign_key: 'tag_id'
belongs_to :school, foreign_key: 'school_id'
validates_uniqueness_of :tag_id, scope: :school_id, message: 'has already been assigned to this school'
end
我使用@ Amro的代码来运行基准测试。
答案 3 :(得分:1)
一种技术是创建一个2Nx2N稀疏矩阵,并在对角线上嵌入2x2矩阵,用于A和B.使用稀疏矩阵进行乘积,并使用稍微聪明的索引获取结果并将其重新整形为2x2xN。
但我怀疑这比简单的循环更快。
答案 4 :(得分:1)
根据我的经验,更快的方法是在三维矩阵上使用点乘法和求和。以下函数,函数z_matmultiply(A,B)将两个具有相同深度的三维矩阵相乘。点乘法以尽可能平行的方式完成,因此您可能需要检查此函数的速度,并在大量重复时将其与其他函数进行比较。
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('\n z_matmultiply warning: Matrix Dimmensions Inconsistent \n')
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