web-dev-qa-db-ja.com

3D行列と2D行列を乗算します

AxBxC行列XBxD行列Yがあるとします。

[〜#〜] c [〜#〜]AxB行列のそれぞれにYを掛けることができる非ループメソッドはありますか?

25
Jacob

これは、関数 NUM2CELL を使用して1行で実行し、行列Xをセル配列に分割します [〜#〜] cellfun [〜#〜] =セル間で動作する:

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

結果のZは、1-by-Cセル配列であり、各セルにはAが含まれます。 -by-D行列。 ZA-by-D-by-C行列にしたい場合は、 [ 〜#〜] cat [〜#〜] 関数:

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



注:私の古いソリューションは NUM2CELL の代わりに MAT2CELL を使用しましたが、これはそれほど簡潔ではありませんでした:

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

個人的な好みとして、私は自分のコードができるだけ簡潔で読みやすいものであることが好きです。

これが私がしたであろうことですが、それはあなたの「ループなし」の要件を満たしていません:

_for m = 1:C

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

end
_

これにより、 A x D x C マトリックス Z

そしてもちろん、Z = zeros(A,D,C);を使用して、いつでもZを事前に割り当てて処理を高速化できます。

17
Zaid

これが1行のソリューションです(3次元に分割する場合は2つ):

_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の3番目の次元を取得し、最初の薄暗い部分に沿って垂直方向の連結を行います。

_XX = cat(1, X(:,:,1), X(:,:,2), ..., X(:,:,C))
_

...難しさは、Cが変数であるため、catまたはvertcatを使用してその式を一般化できないことでした。次に、これにYを掛けます。

_ZZ = XX * Y;
_

最後に、それを3番目の次元に分割し直します。

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

したがって、1つの行列乗算のみが必要であることがわかりますが、前後に行列を再形成する必要があります。

8
Amro

私は、最も効率的な方法を目指して、まったく同じ問題に取り組んでいます。外部ライブラリを使用する以外に、私が見ているアプローチはおおよそ3つあります(つまり、 mtimesx ):

  1. 3Dマトリックスのスライスをループします
  2. repmat-and-permuteウィザードリー
  3. cellfun乗算

最近、3つの方法すべてを比較して、どれが最も速いかを確認しました。私の直感では、(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

3つのアプローチすべてで同じ出力が生成されましたが(おい!)、驚くべきことに、ループが最速でした。

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

時間は試行ごとに大きく異なる可能性があり、(2)が最も遅くなる場合があることに注意してください。これらの違いは、データが大きいほど劇的になります。しかし、muchより大きなデータでは、(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.

ただし、ループされた次元が他の次元よりもはるかに大きい場合、ループメソッドcanは(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)この(おそらく極端な)ケースでは、大きな要因で勝ちます。すべての場合に最適なアプローチはないかもしれませんが、それでもループはかなり良好であり、多くの場合に最適です。読みやすさの点でも最高です。ループアウェイ!

5
Nolan Conaway

読みやすさのためにの質問に答えるには、以下を参照してください。

  • 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
1
user649198

いいえ。いくつかの方法がありますが、それは常に直接または間接のループで発生します。

私の好奇心を喜ばせるために、なぜとにかくそれが欲しいのですか?

1
Rook

Matlabの MMXツールボックス を使用することを強くお勧めします。 n次元の行列をできるだけ速く乗算できます。

[〜#〜] mmx [〜#〜]の利点は次のとおりです。

  1. 使いやすいです。
  2. 乗算n次元行列(実際には2次元行列の配列を乗算できます)
  3. 他の行列演算(転置、2次乗算、Chol分解など)を実行します
  4. Cコンパイラマルチスレッド計算を使用して高速化します。

この問題については、次のコマンドを記述する必要があります。

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

これは、考えられるすべての方法のベンチマークです。詳細については、これを参照してください 質問

    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  <===================
1
Ali Mirzaei

再帰だと思いますが、それがあなたができる唯一の他の非ループメソッドです

0
Kevin

ループを「展開」することができます。つまり、ループで発生するすべての乗算を順番に書き出します。

0
µBio

以下の問題に対する私の答えを共有したいと思います。

1)2つのテンソル(任意の価数)のテンソル積を作成します。

2)任意の次元に沿って2つのテンソルを収縮させます。

これが最初と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

批評家はいますか?

0
Dan