Matlab向量化-单元格的非零矩阵行索引


10

我正在与Matlab合作。

我有一个二进制方阵。对于每一行,都有一个或多个1。我想遍历此矩阵的每一行,并返回这些1的索引,并将其存储在单元格的条目中。

我想知道是否有一种方法可以不循环遍历该矩阵的所有行,因为在Matlab中循环确实很慢。

例如我的矩阵

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有多大?又有多少1S IN的典型行?我不希望一个find循环会花费将近30s的时间来存储足够小以适合物理内存的内容。

@ftxx请查看我更新的答案,自从接受以来,我对其进行了编辑,并且性能有所改善
Wolfie

Answers:


11

该答案的底部是一些基准测试代码,因为您已表明您对性能感兴趣,而不是任意避免for循环。

实际上,我认为for循环可能是此处性能最高的选项。自从引入“ new”(2015b)JIT引擎()以来,for循环并不是固有地缓慢的-实际上,它们是在内部进行优化的。

您可以从基准测试中看到mat2cell,ThomasIsCoding 在此处提供的选项非常慢...

比较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循环慢
Luis 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基本上是变相的循环,所以这可能在两个方面都失败了:1)避免循环; 2)快速运行,正如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

2
@汉斯,您可能想看看我的答案
Wolfie

是的,由于每个单元的大小都不相同,因此无法完全向量化此问题(或者有一个我没有看到的技巧)。这只是隐藏for循环的解决方案。
obchardon

无需ind2sub[ii, jj] = find(M); accumarray(ii, jj, [], @(x){x})
路易斯·门多

@LuisMendo谢谢,我已经编辑了答案。
obchardon

2

您可以使用strfind

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

我(懒洋洋地)甚至都没有看过文档,但是使用实际string类型而不是char 会更快吗?字符串有很多优化,因此它们为什么存在……
Wolfie

@Wolfie我认为数字数组比字符串更类似于char数组,因此将数字数组转换为字符数组比转换为string更直接。
rahnema1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.