Matlabベクトル化-セルに対するゼロでない行列行インデックス


10

私はMatlabと協力しています。

バイナリ正方行列があります。各行には、1の1つ以上のエントリがあります。この行列の各行を調べて、それらの1のインデックスを返し、セルのエントリに格納します。

Matlabではforループが本当に遅いので、この行列のすべての行をループしないでこれを行う方法があるかどうか疑問に思っていました。

たとえば、私の行列

M = 0 1 0
    1 0 1
    1 1 1 

そして、最終的に、私は次のようなものが欲しい

A = [2]
    [1,3]
    [1,2,3]

A細胞もそうです。

より迅速に結果を計算する目的で、forループを使用せずにこの目標を達成する方法はありますか?


結果を速くしたいforですか、それともループを回避したいですか?この問題について、MATLABの最新バージョンでは、forループが最速の解決策であると強く思います。パフォーマンスの問題がある場合、古いアドバイスに基づいてソリューションの間違った場所を探していると思います。
ウィル

@結果を速くしたいのですが。私のマトリックスは非常に大きいです。実行時間は、私のコンピュータでforループを使用して約30秒です。速度を上げることができる巧妙なベクトル化操作やmapReduceなどがあるかどうかを知りたいです。
ftxx

1
できないと思います。ベクトル化は、正確に記述されたベクトルと行列に対して機能しますが、結果として、異なる長さのベクトルが可能になります。したがって、私の仮定は、常にいくつかの明示的なループまたはのような変装ループがあることですcellfun
HansHirse

@ftxxどれくらい大きい?そして1、典型的な行にはいくつありますか?find物理メモリに収まるほど小さいものについては、ループが30秒に近いものを取るとは思いません。
ウィル

@ftxx更新された回答を参照してください。マイナーなパフォーマンスの改善で受け入れられたので編集しました
Wolfie

回答:


11

forループを回避するのではなく、パフォーマンスに関心があることを明確にしたため、この回答の下部にはいくつかのベンチマークコードがあります。

実際、forループはおそらくここで最もパフォーマンスの高いオプションだと思います。"新しい"(2015b)JITエンジンが導入された(ソース)以来、forループは本質的に低速ではありません。実際、ループは内部で最適化されています。

ベンチマークから、mat2cellThomasIsCodingが提供するオプションが非常に遅いことがわかります ...

比較1

スケールを明確にするためにその行を削除すると、私のsplitapply方法はかなり遅くなります。obchardonのaccumarrayオプションは少し優れていますが、最も速い(そして比較可能な)オプションはarrayfun(Thomasによって示唆されているように)またはforループを使用しています。これarrayfunは基本的forにほとんどのユースケースの変装ループであることに注意してください。したがって、これは驚くべきタイではありません。

比較2

forコードを読みやすくして最高のパフォーマンスを得るには、ループを使用することをお勧めします。

編集

ループが最速のアプローチであると想定した場合、findコマンドを中心にいくつかの最適化を行うことができます。

具体的には

  • 確認しMた論理。以下のプロットが示すように、これは比較的小さなの場合は速くなる可能性Mがありますが、大きなの場合は型変換のトレードオフで遅くなりますM

  • を使用する代わりに、論理Mを使用して配列1:size(M,2)にインデックスを付けfindます。これにより、ループの最も遅い部分(findコマンド)が回避され、型変換のオーバーヘッドを上回り、最も速いオプションになります。

最高のパフォーマンスを得るための私の推奨は次のとおりです。

function A = f_forlooplogicalindexing( M )
    M = logical(M);
    k = 1:size(M,2);
    N = size(M,1);
    A = cell(N,1);
    for r = 1:N
        A{r} = k(M(r,:));
    end
end

これを以下のベンチマークに追加しました。ループスタイルのアプローチの比較を以下に示します。

比較3

ベンチマークコード:

rng(904); % Gives OP example for randi([0,1],3)
p = 2:12; 
T = NaN( numel(p), 7 );
for ii = p
    N = 2^ii;
    M = randi([0,1],N);

    fprintf( 'N = 2^%.0f = %.0f\n', log2(N), N );

    f1 = @()f_arrayfun( M );
    f2 = @()f_mat2cell( M );
    f3 = @()f_accumarray( M );
    f4 = @()f_splitapply( M );
    f5 = @()f_forloop( M );
    f6 = @()f_forlooplogical( M );
    f7 = @()f_forlooplogicalindexing( M );

    T(ii, 1) = timeit( f1 ); 
    T(ii, 2) = timeit( f2 ); 
    T(ii, 3) = timeit( f3 ); 
    T(ii, 4) = timeit( f4 );  
    T(ii, 5) = timeit( f5 );
    T(ii, 6) = timeit( f6 );
    T(ii, 7) = timeit( f7 );
end

plot( (2.^p).', T(2:end,:) );
legend( {'arrayfun','mat2cell','accumarray','splitapply','for loop',...
         'for loop logical', 'for loop logical + indexing'} );
grid on;
xlabel( 'N, where M = random N*N matrix of 1 or 0' );
ylabel( 'Execution time (s)' );

disp( 'Done' );

function A = f_arrayfun( M )
    A = arrayfun(@(r) find(M(r,:)),1:size(M,1),'UniformOutput',false);
end
function A = f_mat2cell( M )
    [i,j] = find(M.');
    A = mat2cell(i,arrayfun(@(r) sum(j==r),min(j):max(j)));
end
function A = f_accumarray( M )
    [val,ind] = ind2sub(size(M),find(M.'));
    A = accumarray(ind,val,[],@(x) {x});
end
function A = f_splitapply( M )
    [r,c] = find(M);
    A = splitapply( @(x) {x}, c, r );
end
function A = f_forloop( M )
    N = size(M,1);
    A = cell(N,1);
    for r = 1:N
        A{r} = find(M(r,:));
    end
end
function A = f_forlooplogical( M )
    M = logical(M);
    N = size(M,1);
    A = cell(N,1);
    for r = 1:N
        A{r} = find(M(r,:));
    end
end
function A = f_forlooplogicalindexing( M )
    M = logical(M);
    k = 1:size(M,2);
    N = size(M,1);
    A = cell(N,1);
    for r = 1:N
        A{r} = k(M(r,:));
    end
end

1
すでに見て賛成しています。:-)まだルイスを待っています。彼はそのために黒のMATLABマジックを持っています。
HansHirse

@Hans Hahaええ、彼の通常のトリック(暗黙の拡張、巧妙なインデックス付けなど)は通常、物事を行列として保持しますが、ここでのボトルネックはセルに集約されます
Wolfie

1
これらの時間はのスパース性に強く依存していることに注意してくださいM。たとえば、要素の5%しか入力されていないM = randi([0,20],N) == 20;場合、forループがはるかに遅くなり、arrayfunメソッドが優先されます。
ウィル

@HansHirse :-)私のアプローチがあったであろうaccumarrayせずにind2sub、それはより遅いforループ
ルイスMendo

2

arrayfun以下のように試すことができます。M

A = arrayfun(@(r) find(M(r,:)),1:size(M,1),'UniformOutput',false)

A =
{
  [1,1] =  2
  [1,2] =

     1   3

  [1,3] =

     1   2   3

}

または(による遅いアプローチmat2cell

[i,j] = find(M.');
A = mat2cell(i,arrayfun(@(r) sum(j==r),min(j):max(j)))

A =
{
  [1,1] =  2
  [2,1] =

     1
     3

  [3,1] =

     1
     2
     3

}

1
これarrayfunは基本的に変装ループですが、OP
Wolfie

2

編集:私はベンチマークを追加しました、結果はforループがよりも効率的accumarrayであることを示しています


あなたは使用することができますfindaccumarray

[c, r] = find(A');
C = accumarray(r, c, [], @(v) {v'});

列ごとにグループ化されるA'ため、行列は転置findされます()。

例:

A = [1 0 0 1 0
     0 1 0 0 0
     0 0 1 1 0
     1 0 1 0 1];

%  Find nonzero rows and colums
[c, r] = find(A');

%  Group row indices for each columns
C = accumarray(r, c, [], @(v) {v'});

% Display cell array contents
celldisp(C)

出力:

C{1} = 
     1     4

C{2} = 
     2

C{3} =
     3     4

C{4} = 
     1     3     5

基準:

m = 10000;
n = 10000;

A = randi([0 1], m,n);

disp('accumarray:')
tic
[c, r] = find(A');
C = accumarray(r, c, [], @(v) {v'});
toc
disp(' ')

disp('For loop:')
tic
C = cell([size(A,1) 1]);
for i = 1:size(A,1)
    C{i} = find(A(i,:));
end
toc

結果:

accumarray:
Elapsed time is 2.407773 seconds.

For loop:
Elapsed time is 1.671387 seconds.

forループはaccumarray... よりも効率的です。


これは、obchardonによってすでに提案されている方法とほぼ同じです。
Wolfie

はい、私は少し遅かったです、私が投稿した後に私は彼の答えを見ました。
エリアフアーロン

2

accumarrayの使用:

M = [0 1 0
     1 0 1
     1 1 1];

[val,ind] = find(M.');

A = accumarray(ind,val,[],@(x) {x});

1
OctaveおよびMATLAB Onlineでの実行時間は、次のような単純なforループの約2倍ですMM{I} = find(M(I, :))
HansHirse


ええ、各セルのサイズは同じではないので、この問題は完全にベクトル化することはできません(または私が見たことがないトリックがあります)。これはforループを隠す唯一のソリューションです。
オブチャードン

必要なしind2sub[ii, jj] = find(M); accumarray(ii, jj, [], @(x){x})
ルイスメンド

@LuisMendoありがとう、私の回答を編集しました。
オブチャードン

2

strfindを使用できます。

A = strfind(cellstr(char(M)), char(1));

私は(怠惰に)ドキュメントを調べたこともありませんが、これstringは文字ではなく実際の型を使用した方が速いでしょうか?文字列には多くの最適化があり、それがなぜ存在するのか...
Wolfie

@Wolfie数値配列は文字列よりも文字配列に似ていると思うので、数値配列の文字配列への変換は、文字列への変換よりも簡単なはずです。
rahnema1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.