【问题标题】:Select entries of matrix based on given locations根据给定位置选择矩阵条目
【发布时间】:2018-07-31 03:34:42
【问题描述】:

我有以下矩阵(MxN,其中 M ≤ N):

0.8147    0.9134    0.2785    0.9649
0.9058    0.6324    0.5469    0.1576
0.1270    0.0975    0.9575    0.9706

从每一行中,我想分别选择以下列条目(每行一个):

idx = [ 3  1  4 ];

这意味着我们将元素保留在 (1,3)、(2,1) 和 (3,4) 中,而数组的其余部分应该为零。

对于上面的示例,我会得到以下输出:

     0         0    0.2785         0
0.9058         0         0         0
     0         0         0    0.9706

我目前使用循环生成它,当矩阵大小较大时它会变慢。

谁能推荐一种更高效的方法?

【问题讨论】:

    标签: matlab performance matrix optimization matrix-indexing


    【解决方案1】:

    在其他答案/cmets 中有一些关于性能的讨论。这是一个简单(构造良好)for 循环可以很好地完成工作的情况之一,对性能基本上没有影响

    % For some original matrix 'm', and column indexing array 'idx':
    x = zeros( size(m) ); % Initialise output of zeros
    for ii = 1:numel(idx) % Loop over indices
        % Assign the value at the column index for this row
        x( ii, idx(ii) ) = m( ii, idx(ii) );     
    end
    

    此代码可读性强且速度快。为了证明“快速”的合理性,我为所有 4 个当前答案的方法编写了以下基准测试代码,在 MATLAB R2017b 上运行。这是输出图。

    • 对于“小”矩阵,最多 2^5 列和 2^4 行:

    • 对于“大”矩阵,最多 2^15 列和 2^14 行(使用和不使用 bsxfun 解决方案的相同图,因为它会破坏缩放比例):

    第一个情节可能有点误导。虽然结果一致(因为性能排名慢速是bsxfun 然后是sub2ind 然后是手动索引然后循环),y 轴是 10^(-5) 秒,所以基本上你使用哪种方法并不重要!

    第二张图显示,对于大型矩阵,这些方法基本上是等效的,除了 bsxfun 这很糟糕(这里没有显示,但它需要更多的内存)。

    我会选择更清晰的循环,它可以让您更灵活,并且您会在 2 年后准确地记住它在您的代码中所做的事情。


    基准代码:

    function benchie() 
        K = 5;                      % Max loop variable
        T = zeros( K, 4 );          % Timing results
        for k = 1:K
            M = 2^(k-1); N = 2^k;   % size of matrix
            m = rand( M, N );       % random matrix
            idx = randperm( N, M ); % column indices
    
            % Define anonymous functions with no inputs for timeit, and run
            f1 = @() f_sub2ind( m, idx ); T(k,1) = timeit(f1);
            f2 = @() f_linear( m, idx );  T(k,2) = timeit(f2);
            f3 = @() f_loop( m, idx );    T(k,3) = timeit(f3);   
            f4 = @() f_bsxfun( m, idx );  T(k,4) = timeit(f4);   
        end
        % Plot results
        plot( (1:K)', T, 'linewidth', 2 );
        legend( {'sub2ind', 'linear', 'loop', 'bsxfun'} );
        xlabel( 'k, where matrix had 2^{(k-1)} rows and 2^k columns' );
        ylabel( 'function time (s)' )
    end
    
    function f_sub2ind( m, idx )
        % Using the in-built sub2ind to generate linear indices, then indexing
        lin_idx = sub2ind( size(m), 1:numel(idx), idx );
        x = zeros( size(m) );
        x( lin_idx ) = m( lin_idx );
    end
    function f_linear( m, idx )
        % Manually calculating linear indices, then indexing
        lin_idx = (1:numel(idx)) + (idx-1)*size(m,1);
        x = zeros( size(m) );
        x( lin_idx ) = m( lin_idx );
    end
    function f_loop( m, idx )
        % Directly indexing in a simple loop
        x = zeros( size(m) );
        for ii = 1:numel(idx)
            x( ii, idx(ii) ) = m( ii, idx(ii) );
        end
    end
    function f_bsxfun( m, idx )
        % Using bsxfun to create a logical matrix of desired elements, then masking
        % Since R2016b, can use 'x = ( (1:size(m,2)) == idx(:) ) .* m;'
        x = bsxfun(@eq, 1:size(m,2), idx(:)).*m;
    end
    

    【讨论】:

    • 很好的建议、讨论和总结,谢谢@Wolfie
    【解决方案2】:

    您可以使用sub2ind 函数将条目索引转换为线性索引。

    当使用线性索引时,matlab 将矩阵视为一个长列向量。

    org_mat=[0.8147    0.9134    0.2785    0.9649
    0.9058    0.6324    0.5469    0.1576
    0.1270    0.0975    0.9575    0.9706];
    entries=[3,1,4];
    
    linear_entries=sub2ind(size(org_mat),1:length(entries),entries);
    new_mat=zeros(size(org_mat));
    new_mat(linear_entries)=org_mat(linear_entries);
    

    【讨论】:

    • 我非常感谢这个解决方案,感谢@RadioJava,我也指出这是一个有用的答案。
    【解决方案3】:

    TL;DR - 这是我的建议:

    nI = numel(idx);
    sz = size(m); 
    x = sparse( 1:nI, idx, m(sub2ind( size(m), 1:numel(idx), idx )), sz(1), sz(2), nI);
    

    帖子的其余部分讨论了为什么它会更好。


    看到所需的输出矩阵主要由零组成,这实际上要求使用sparse matrices!这不仅应该提高性能(尤其是对于较大的矩阵),而且应该对内存更加友好。

    我要给Wolfie's benchmark添加两个函数:

    function x = f_sp_loop( m, idx )
      nI = numel(idx);
      sz = size(m); 
      x = spalloc( sz(1), sz(2), nI ); % Initialize a sparse array.
      for indI = 1:nI
          x( indI, idx(indI) ) = m( indI, idx(indI) ); % This generates a warning (inefficient)
      end
    end
    
    function x = f_sp_sub2ind( m, idx )
      nI = numel(idx);
      sz = size(m); 
      x = sparse( 1:nI, idx, m(sub2ind( size(m), 1:numel(idx), idx )), sz(1), sz(2), nI);
    end
    

    本质上的区别在于,我们没有将输出预分配为一个零数组,而是作为一个稀疏数组。基准1的结果如下:

    ...这提出了一个问题 - sparse 方法为什么要快一个数量级

    要回答这个问题,我们应该查看基准函数内部的实际运行时分布,我们可以从profiler 中获得。要获得更多信息,我们可以make the profiler output memory consumption info,使用profile('-memory','on')。在运行一个较短版本的基准2 之后,它只运行k 的最高值,我们得到:

    所以我们可以总结几件事:

    1. 运行时的绝大部分时间都花在分配和释放内存上,这就是算法看起来几乎具有相同性能的原因。因此,如果我们减少内存分配,我们会直接节省时间(sparse!)。
    2. 即使sub2indloop 方法看起来相同,我们仍然可以在两者之间建立一个“赢家”(见下图紫色框)-sub2indsub2ind 是 32 毫秒,而循环是 41 毫秒。
    3. 稀疏循环方法的速度并不令人意外,正如mlint 警告我们的那样:

      说明

      代码分析器检测到可能很慢的稀疏数组的索引模式。更改稀疏数组的非零模式的赋值可能会导致此错误,因为此类赋值会导致相当大的开销。

      建议的操作

      如果可能,使用sparse 构建稀疏数组,如下所示,并且不要使用索引赋值(例如C(4) = B)来构建它们:

      1. 创建单独的索引和值数组。
      2. 调用 sparse 来组装索引和值数组。

      如果您必须使用索引分配来构建稀疏数组,您可以通过首先使用 spalloc 预分配稀疏数组来优化性能。

      如果代码只更改已经非零的数组元素,那么开销是合理的。按照Adjust Code Analyzer Message Indicators and Messages 中的说明隐藏此消息。

      有关详细信息,请参阅“Constructing Sparse Matrices”。

    4. 结合了两全其美的方法,即sparse 的内存节省和sub2ind 的矢量化,似乎是运行时间仅为 3 毫秒的最佳方法!

    1 图表制作代码:

    function q51605093()
        K = 15;                     % Max loop variable
        T = zeros( K, 4 );          % Timing results
        for k = 1:K
            M = 2^(k-1); N = 2^k;   % size of matrix
            m = rand( M, N );       % random matrix
            idx = randperm( N, M ); % column indices
    
            % Define anonymous functions with no inputs, for timeit, and run
            f = cell(4,1);
            f{1} = @() f_sub2ind( m, idx ); 
            f{2} = @() f_loop( m, idx );   
            f{3} = @() f_sp_loop( m, idx );
            f{4} = @() f_sp_sub2ind( m, idx );
            T(k,:) = cellfun(@timeit, f);
    
            if k == 5 % test equality during one of the runs
              R = cellfun(@feval, f, 'UniformOutput', false);
              assert(isequal(R{:}));
            end
        end
        % Plot results
        figure();
        semilogy( (1:K).', T, 'linewidth', 2 ); grid on; xticks(0:K);
        legend( {'sub2ind', 'loop', 'sp\_loop', 'sp\_sub2ind'}, 'Location', 'NorthWest' );
        xlabel( 'k, where matrix had 2^{(k-1)} rows and 2^k columns' );
        ylabel( 'function time (s)' )    
    end
    
    function x = f_sub2ind( m, idx )
        % Using the in-built sub2ind to generate linear indices, then indexing
        lin_idx = sub2ind( size(m), 1:numel(idx), idx );
        x = zeros( size(m) );
        x( lin_idx ) = m( lin_idx );
    end
    
    function x = f_loop( m, idx )
        % Directly indexing in a simple loop
        x = zeros( size(m) );
        for ii = 1:numel(idx)
            x( ii, idx(ii) ) = m( ii, idx(ii) );
        end
    end
    
    function x = f_sp_loop( m, idx )
      nI = numel(idx);
      sz = size(m); 
      x = spalloc( sz(1), sz(2), nI ); % Initialize a sparse array.
      for indI = 1:nI
          x( indI, idx(indI) ) = m( indI, idx(indI) ); % This generates a warning (inefficient)
      end
    end
    
    function x = f_sp_sub2ind( m, idx )
      nI = numel(idx);
      sz = size(m); 
      x = sparse( 1:nI, idx, m(sub2ind( size(m), 1:numel(idx), idx )), sz(1), sz(2), nI);
    end
    

    2 分析代码:

    function q51605093_MB()
        K = 15;                 % Max loop variable
        M = 2^(K-1); N = 2^K;   % size of matrix
        m = rand( M, N );       % random matrix
        idx = randperm( N, M ); % column indices
    
        % Define anonymous functions with no inputs, for timeit, and run
        f = cell(4,1);
        f{1} = f_sub2ind( m, idx ); 
        f{2} = f_loop( m, idx );   
        f{3} = f_sp_loop( m, idx );
        f{4} = f_sp_sub2ind( m, idx );
    
    %     assert(isequal(f{:}));
    end
    
    ... the rest is the same as above
    

    【讨论】:

    • 鉴于稀疏矩阵不像普通矩阵那样“友好”,为了完整性,可能值得在最后调用full。这会重新增加内存分配,但在稀疏分配之后使用它可能比从一开始就处理完整矩阵更有效?
    • 我非常感谢这些建议,感谢@Dev-iL,我认为这是一个有用的答案。
    【解决方案4】:

    没有bsxfun没有聚会

    m 为输入矩阵,idx 为具有列索引的向量。您可以从idx 构建一个逻辑掩码,并按元素乘以m,如下所示:

    result = bsxfun(@eq, 1:size(m,2), idx(:)).*m;
    

    【讨论】:

    • 这意味着(尤其是在 R2016b 之后)这是迄今为止最优雅的解决方案,result = ((1:size(m,2)) == idx(:)) .* m
    • @Wolfies 最慢的:-P
    • 刚刚添加到我的benchmark,它对于小矩阵来说相当可比,所以我喜欢它的整洁性,但对于大矩阵来说它很差!
    • 我非常感谢这个简单的解决方案,感谢@Luis,我也指出这是一个有用的答案。
    【解决方案5】:

    这应该比sub2ind快:

    m = [0.8147, 0.9134, 0.2785, 0.9649;
    0.9058, 0.6324, 0.5469, 0.1576;
    0.1270, 0.0975, 0.9575,   0.9706];
    
    n=[3,1,4];
    
    linear = (1:length(n)) + (n-1)*size(m,1);
    new_m = zeros(size(m));
    new_m(linear) = m(linear);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-01
      • 2017-12-24
      • 1970-01-01
      • 1970-01-01
      • 2014-11-27
      • 1970-01-01
      相关资源
      最近更新 更多