MATLAB:如何对两个矩阵进行向量乘运算?

MATLAB: How to vector-multiply two arrays of matrices?

我有两个3维数组,举一个简单的例子,它的前两个维表示矩阵,最后一个通过参数空间计数

1
A = repmat([1,2; 3,4], [1 1 4]);

(但假设每个jA(:,:,j)不同)。 如何轻松地对两个这样的矩阵数组AB进行per- j矩阵乘法?

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
C = cellfun(@mtimes, A, B, 'UniformOutput', false);

但是,在3d数组的情况下,必须先调用num2cell(Ac = num2cell(A, [1 2]))和cell2mat会浪费太多时间。

这是我为2 x 2 x 1e4的随机集合所做的一些计时:

1
2
3
4
5
6
7
 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矩阵元素,请参见下面的内容。
对于新的随机数组,结果是相似的,如果之前不需要num2cell并且对2x2xN没有限制,则cellfun是最快的。对于一般的3D阵列,在三维上循环确实是最快的选择。这是时间代码:

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
>> [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循环是最好的解决方案(在最后一种情况下缺少循环展开,仅对这些小的2×2矩阵可行)。


我强烈建议您使用matlab的MMX工具箱。它可以尽可能快地乘以n维矩阵。

MMX的优点是:

  • 这个用起来很简单。
  • 乘以n维矩阵(实际上它可以乘以2维矩阵的数组)
  • 它还执行其他矩阵运算(转置,二次乘法,Chol分解等)
  • 它使用C编译器和多线程计算来加快速度。
  • 对于这个问题,您只需要编写以下命令:

    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

    我得到了n=2,m=2,p=1e5的结果:

    1
    2
    3
    4
    5
    6
        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  <===================

    我使用@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。

    但是我怀疑这会比简单的循环更快。