【问题标题】:Fast way to turn matrix columns into unit vectors将矩阵列转换为单位向量的快速方法
【发布时间】:2019-01-15 10:22:51
【问题描述】:

我有一个矩阵:

S = [  -1.0400    4.9100    4.1000   -3.5450   -0.6600   -0.9300    4.3950   -1.0650    2.9850   -4.9800    0.2100;
   -0.5200   -4.3150   -3.0950    0.5700    4.4700    1.1500    3.1350    0.6450    0.3750   -4.9150   -2.1150; 
    5.0000    5.0000    5.0000    5.0000    5.0000    5.0000    5.0000    5.0000    5.0000    5.0000    5.0000 ];

我想将列转换为单位向量,所以我使用for 循环

for i=1:size(S,2)
    S(:,i) = S(:,i) / norm( S(:,i) );
end

有没有办法在 MATLAB 中更有效地做到这一点?

【问题讨论】:

  • 请在您的问题中包含最早的相关 MATLAB 版本,因为一些对您的问题有用的函数仅在最近的版本中引入。

标签: matlab performance matrix optimization normalize


【解决方案1】:

TLDR

如果您有 MATLAB 2016b 或更新版本,并且没有兼容性问题,我会使用

S = S ./ sqrt(sum(S.^2,1));

编辑:请参阅底部的基准以了解替代品的性能基准。


上下文

我们可以手动计算范数并按列划分。

根据定义,norm(x) = sqrt( sum( x(:).^2 ) )。我在这里使用了(:) 来表明norm 是在整个矩阵上计算的。对我们有用的是 sum 默认按列工作,因此按列标准定义如下:

nrm = sqrt( sum( x.^2 ) );

请注意,如果您的矩阵 S 有可能只有 1 行,您应该使用 nrm = sqrt(sum(x.^2,1)) 强制执行逐列求和。

现在我们有几个除法选项:

  • 隐式扩展(MATLAB R2016b 或更高版本)

    S = S ./ nrm;
    
  • 使用 bsxfun 的隐式扩展(所有 MATLAB 版本)

    S = bsxfun( @mrdivide, S, nrm );
    
  • 使用 repmat 手动扩展(所有 MATLAB 版本)

    S = S ./ repmat(nrm, size(S,1), 1);
    

如果您有 MATLAB R2017b 或更新版本,并且再次没有兼容性问题,您可以使用 vecnorm,它可以用来代替手动范数计算

S = S ./ vecnorm(S, 2, 1);

基准测试:

由于您要求性能,这里有一个简单的基准来测试这些不同方法的速度。特别是您问题中的原始循环与使用 vecnorm 或手动计算的隐式扩展。

  • 结果(使用 R2017b 运行)

             size(S):  1e3*1e2  1e5*1e3  1e3*1e6    
             Looping:  0.0005   1.0186   12.7788
     Implicit manual:  0.0001   1.1236   10.4031
    Implicit vecnorm:  0.0002   0.5774    6.8058
    
  • 结论

    • 对于相对较小的数组,所有方法都非常快,我会选择代码清晰而不是性能。
    • 如果您只想使用支持它的 MATLAB 版本,vecnorm 对于大型矩阵的速度大约是其他方法的两倍。
    • 对于1e5*1e3 阶的矩阵,循环相当于隐式展开。
  • 代码

    function benchie()
        S = rand( 1e3, 1e2 )*5;
    
        f1 = @() loopingNorm(S);
        f2 = @() implicitManual(S);
        f3 = @() implicitVecnorm(S);
    
        fprintf( 'Looping: %.4f\nImplicit manual: %.4f\nImplicit vecnorm: %.4f\n', ...
                 timeit(f1), timeit(f2), timeit(f3) );
    end
    function S = loopingNorm(S)
        for ii = 1:size(S,2)
            S(:,ii) = S(:,ii) / norm( S(:,ii) );
        end
    end
    function S = implicitManual(S)
        S = S ./ sqrt(sum(S.^2,1));
    end
    function S = implicitVecnorm(S)
        S = S ./ vecnorm( S, 2, 1 );
    end
    

【讨论】:

  • 在 2018a 及更高版本中,还有一个 normalize 函数(选择了 'norm' 方法)看起来可以完成整个工作。但这更不向后兼容......
  • @etmuse 现在是 2018 年 8 月,现在每个人都应该付费升级他们的许可证,??感谢您的信息,看起来语法应该是 S = normalize(S, 1, 'norm'),虽然我无法验证。
  • @etmuse 需要注意的一点是,normalize 在底层使用了vecnorm(它是开源的)。此外,它会自动将 NaN 视为零——在某些情况下,这可能是一种不需要的副作用。从该函数中的逻辑数量来看,我不会说它是最有效的替代方案......尽管如此,这是一个有效的建议。
  • 可能值得一提的是哪个 MATLAB 版本用于基准测试,看看 for-loop 和其他优化是如何在各种版本中引入的(换句话说,结果在一定程度上取决于本地 MATLAB 版本) .
  • @Dev-iL 公平点,我通常在 benchie 结果中包含版本,忘记但现在添加 :)
猜你喜欢
  • 2021-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-30
  • 2014-03-09
  • 1970-01-01
  • 2018-12-12
  • 2011-02-13
相关资源
最近更新 更多