将3D矩阵与2D矩阵相乘

时间:2009-11-16 22:33:44

标签: matlab matrix vectorization matrix-multiplication

假设我有一个 AxBxC 矩阵X和一个 BxD 矩阵Y

是否有非循环方法可以将 C AxB 矩阵中的每一个与Y相乘?

10 个答案:

答案 0 :(得分:16)

作为个人偏好,我喜欢我的代码尽可能简洁易读。

这就是我要做的,虽然它不符合你的'无循环'要求:

for m = 1:C

    Z(:,:,m) = X(:,:,m)*Y;

end

这导致 A x D x C 矩阵 Z

当然,您可以使用Z = zeros(A,D,C);预先分配Z以加快速度。

答案 1 :(得分:15)

您可以使用函数NUM2CELL在一行中执行此操作,将矩阵X分解为单元格数组,并CELLFUN在单元格中运行:

Z = cellfun(@(x) x*Y,num2cell(X,[1 2]),'UniformOutput',false);

结果Z 1-by-C 单元格数组,其中每个单元格包含 A-by-D 矩阵。如果您希望Z成为 A-by-D-by-C 矩阵,则可以使用CAT函数:

Z = cat(3,Z{:});



注意:我的旧解决方案使用MAT2CELL代替NUM2CELL,而不是简洁:

[A,B,C] = size(X);
Z = cellfun(@(x) x*Y,mat2cell(X,A,B,ones(1,C)),'UniformOutput',false);

答案 2 :(得分:8)

这是一个单行解决方案(如果你想分成第三维,则为两个):

A = 2;
B = 3;
C = 4;
D = 5;

X = rand(A,B,C);
Y = rand(B,D);

%# calculate result in one big matrix
Z = reshape(reshape(permute(X, [2 1 3]), [A B*C]), [B A*C])' * Y;

%'# split into third dimension
Z = permute(reshape(Z',[D A C]),[2 1 3]);

因此现在:Z(:,:,i)包含X(:,:,i) * Y

的结果

<强>解释

以上看起来可能令人困惑,但这个想法很简单。 首先,我从X的第三维开始,沿着第一个暗点进行垂直连接:

XX = cat(1, X(:,:,1), X(:,:,2), ..., X(:,:,C))

...难点在于C是变量,因此您无法使用 cat vertcat 来概括该表达式。接下来,我们将其乘以Y

ZZ = XX * Y;

最后,我将其拆分为第三维:

Z(:,:,1) = ZZ(1:2, :);
Z(:,:,2) = ZZ(3:4, :);
Z(:,:,3) = ZZ(5:6, :);
Z(:,:,4) = ZZ(7:8, :);

所以你可以看到它只需要一个矩阵乘法,但你必须重塑矩阵之前和之后。

答案 3 :(得分:5)

我正在接近完全相同的问题,着眼于最有效的方法。我看到大约有三种方法,没有使用外部库(即mtimesx):

  1. 循环切入3D矩阵
  2. repmat-and-permute wizardry
  3. cellfun multiplication
  4. 我最近比较了所有三种方法,看看哪种方法最快。我的直觉是(2)将成为胜利者。这是代码:

    % generate data
    A = 20;
    B = 30;
    C = 40;
    D = 50;
    
    X = rand(A,B,C);
    Y = rand(B,D);
    
    % ------ Approach 1: Loop (via @Zaid)
    tic
    Z1 = zeros(A,D,C);
    for m = 1:C
        Z1(:,:,m) = X(:,:,m)*Y;
    end
    toc
    
    % ------ Approach 2: Reshape+Permute (via @Amro)
    tic
    Z2 = reshape(reshape(permute(X, [2 1 3]), [A B*C]), [B A*C])' * Y;
    Z2 = permute(reshape(Z2',[D A C]),[2 1 3]);
    toc
    
    
    % ------ Approach 3: cellfun (via @gnovice)
    tic
    Z3 = cellfun(@(x) x*Y,num2cell(X,[1 2]),'UniformOutput',false);
    Z3 = cat(3,Z3{:});
    toc
    

    这三种方法都产生了相同的输出(p!),但令人惊讶的是,循环速度最快:

    Elapsed time is 0.000418 seconds.
    Elapsed time is 0.000887 seconds.
    Elapsed time is 0.001841 seconds.
    

    请注意,从一个试验到另一个试验,时间可能会有很大变化,有时(2)出现的时间最慢。数据量越大,这些差异就越大。但是很多更大的数据,(3)节拍(2)。循环方法仍然是最好的。

    % pretty big data...
    A = 200;
    B = 300;
    C = 400;
    D = 500;
    Elapsed time is 0.373831 seconds.
    Elapsed time is 0.638041 seconds.
    Elapsed time is 0.724581 seconds.
    
    % even bigger....
    A = 200;
    B = 200;
    C = 400;
    D = 5000;
    Elapsed time is 4.314076 seconds.
    Elapsed time is 11.553289 seconds.
    Elapsed time is 5.233725 seconds.
    

    但是如果循环维度比其他维度大得多,则循环方法可以慢于(2)。

    A = 2;
    B = 3;
    C = 400000;
    D = 5;
    Elapsed time is 0.780933 seconds.
    Elapsed time is 0.073189 seconds.
    Elapsed time is 2.590697 seconds.
    

    所以(2)在这个(可能是极端的)情况下以一个重要因素获胜。可能没有一种方法在所有情况下都是最佳的,但循环仍然相当不错,在许多情况下最好。在可读性方面也是最好的。绕开!

答案 4 :(得分:1)

不。有几种方法,但它总是以循环形式出现,直接或间接。

为了取悦我的好奇心,你为什么还要这样呢?

答案 5 :(得分:1)

要回答问题以提高可读性,请参阅:

  • ndmult,ajuanpi(Juan Pablo Carbajal),2013,GNU GPL

输入

  • 2个数组
  • 暗淡

实施例

 nT = 100;
 t = 2*pi*linspace (0,1,nT)’;

 # 2 experiments measuring 3 signals at nT timestamps
 signals = zeros(nT,3,2);
 signals(:,:,1) = [sin(2*t) cos(2*t) sin(4*t).^2];
 signals(:,:,2) = [sin(2*t+pi/4) cos(2*t+pi/4) sin(4*t+pi/6).^2];

 sT(:,:,1) = signals(:,:,1)’;
 sT(:,:,2) = signals(:,:,2)’;
   G = ndmult (signals,sT,[1 2]);

来源

原始来源。我添加了内联评论。

function M = ndmult (A,B,dim)
  dA = dim(1);
  dB = dim(2);

  # reshape A into 2d
  sA = size (A);
  nA = length (sA);
  perA = [1:(dA-1) (dA+1):(nA-1) nA dA](1:nA);
  Ap = permute (A, perA);
  Ap = reshape (Ap, prod (sA(perA(1:end-1))), sA(perA(end)));

  # reshape B into 2d
  sB = size (B);
  nB = length (sB);
  perB = [dB 1:(dB-1) (dB+1):(nB-1) nB](1:nB);
  Bp = permute (B, perB);
  Bp = reshape (Bp, sB(perB(1)), prod (sB(perB(2:end))));

  # multiply
  M = Ap * Bp;

  # reshape back to original format
  s = [sA(perA(1:end-1)) sB(perB(2:end))];
  M = squeeze (reshape (M, s));
endfunction

答案 6 :(得分:1)

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

MMX 的优点是:

  1. 使用简单
  2. 乘以 n维矩阵(实际上它可以乘以2-D矩阵的数组)
  3. 执行其他矩阵运算(转置,二次乘法,Chol分解等)
  4. 它使用 C编译器多线程计算来加速。
  5. 对于此问题,您只需编写此命令:

    C=mmx('mul',X,Y);
    

    这是所有可能方法的基准。有关详细信息,请参阅此question

        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  <===================
    

答案 7 :(得分:0)

我会考虑递归,但这是你可以做的唯一其他非循环方法

答案 8 :(得分:0)

你可以“展开”循环,即在循环中按顺序写出所有乘法

答案 9 :(得分:0)

我想分享我对以下问题的回答:

1)制作两个张量(任意价)的张量积;

2)使两个张量沿任意维度收缩。

这是我执行第一和第二个任务的子例程:

1)张量积:

function [C] = tensor(A,B)
   C = squeeze( reshape( repmat(A(:), 1, numel(B)).*B(:).' , [size(A),size(B)] ) );
end

2)收缩: 这里的A和B是分别沿着第i维和第j维收缩的张量。当然,这些尺寸的长度应相等。对此不做任何检查(这会使代码模糊),但除此之外效果很好。

   function [C] = tensorcontraction(A,B, i,j)
      sa = size(A);
      La = length(sa);
      ia = 1:La;
      ia(i) = [];
      ia = [ia i];

      sb = size(B);
      Lb = length(sb);
      ib = 1:Lb;
      ib(j) = [];
      ib = [j ib];

      % making the i-th dimension the last in A
      A1 = permute(A, ia);
      % making the j-th dimension the first in B
      B1 = permute(B, ib);

      % making both A and B 2D-matrices to make use of the
      % matrix multiplication along the second dimension of A
      % and the first dimension of B
      A2 = reshape(A1, [],sa(i));
      B2 = reshape(B1, sb(j),[]);

      % here's the implicit implication that sa(i) == sb(j),
      % otherwise - crash
      C2 = A2*B2;

      % back to the original shape with the exception
      % of dimensions along which we've just contracted
      sa(i) = [];
      sb(j) = [];
      C = squeeze( reshape( C2, [sa,sb] ) );
   end

有批评家吗?