matlab代码优化 - 将大型单元阵列中的所有3x3矩阵相互相乘

时间:2013-07-15 20:20:29

标签: matlab optimization rotational-matrices

摘要

希望提高代码的时间效率,在3x3矩阵和反向3x3矩阵之间重复执行矩阵乘法 - 使用mldivide。

背景

我试图在步态分析中使用连接到受试者下肢的传感器之间的手眼校准的方向数据之前实现矢量量化方法...我所遵循的算法来自论文 "Data Selection for Hand-eye Calibration: A Vector Quantization Approach"这是您可能不需要的背景......

要优化的代码

我希望找到一种更快的方法来解决所有可能的“相对运动”(A或B),这需要太长时间(C和D长约2000个元素,因此A或B的大小将达到=2000*(2000-1)/2=1999000):

%C,D is cell array, each cell is 3x3 rotation matrix.
Nframe=length(C);
Nrel = Nframe*(Nframe-1)/2;
A=cell(Nrel,1);
B=cell(Nrel,1);
At=zeros(Nrel,4);
Bt=zeros(Nrel,4);
count = 1;
for i=1:Nframe
    for j=1:Nframe
        if j <= i
            continue;
        else
            % Main place to optimize (avoid looping?)!!
            % In DCM representation (ie. each cell is 3x3 matrix)
            A{count,1} = C{j}\C{i}; %=inv(C{i+x})*C{i}
            B{count,1} = D{j}\D{i};

            %Using the following adds to ~ 4000% to the time (according to tic toc).
            %Represent as axis-angle (ie. each cell -> 1x4 vec) - perhaps compute later
            At(count,:) = SpinConv('DCMtoEV',A{count}); %on Matlab File Exchange
            Bt(count,:) = SpinConv('DCMtoEV',B{count});
            count=count+1;
        end
    end
end

希望这是一个正确的问题,我无法找到我可以申请的先前解决方案。另外,我没有真正的经验,所以我不确定在处理大型矩阵时计算时间是否是不可避免的。

----------------------------------------------- ---------------------------------------------

*的 修改 *

矩阵属性:旋转,如下所述 - 它们“很好”,而不是单数。它们在特殊正交组中,SO(3)[转置=逆]。见http://en.wikipedia.org/wiki/Rotation_matrix#Properties_of_a_rotation_matrix

测试方法:要创建随机旋转矩阵R,请使用以下代码:

[U,S,V] = svd(randn(3,3)); 
R = U∗V';
if det(R) < 0 
    S(1 ,1) = 1; 
    S(2 ,2) = 1;
    S(3,3) = −1; 
    R = U∗S∗V’;
end

SpinConv :我只是用它来将3x3方向余弦矩阵转换为轴角度表示。它涉及更多,并且转换超过稳定所需的(首先是四元数)。这是链接:http://www.mathworks.com/matlabcentral/fileexchange/41562-spinconv/content/SpinConv.m 以下是所有需要完成的事情(不是在SpinConv中 - 只是快速实现了方法):

t = (trace(R)-1)/2;
% this is only in case of numerical errors
if t < -1,
    t = -1;
elseif t>1,
    t = 1;
end
theta = acosd(t);
if isequalf(180,theta) || isequalf(0,theta),
    axis = null(R-eye(3));
    axis = axis(:,1)/norm(axis(:,1));
else
    axis = [R(3,2) - R(2,3); R(1,3) - R(3,1); R(2,1) - R(1,2) ];
    axis = axis/(2*sind(theta));
end
At(count,:) = [-axis,theta]; %% NOTE (-ve) as noted in comments of correct answer.

* 编辑#2 * 或者,我可以使用四元数来避免使用3x3矩阵:

因此四元数是1x4向量。原始代码可以更改为(在else语句中):

A(count,:) = qnorm(qmult(qconj(C(j,:)),C(i,:)));
vec = [q(1) q(2) q(3)]/norm([q(1) q(2) q(3)]);
theta = 2*atan2(norm([q(1) q(2) q(3)]),q(4));
At(count,:)=[vec,theta];

其中qconjqmultqnorm是四元数操作。


好的,很抱歉 - 这就是我拥有的所有信息和可能性。

2 个答案:

答案 0 :(得分:3)

正如我上面评论的那样,最快的方法很大程度上取决于矩阵的属性。例如,某些算法可以从矩阵对称中受益,但如果不是,则相当慢。

因此,如果没有进一步的信息,我只能做一些一般性的陈述,并比较一些随机矩阵的方法(通常 在矩阵求逆的上下文中给出一个很好的比较)。

根据您的MATLAB版本(R2011a中的JIT大大提高了它),预分配AB可以大大提高循环性能;在循环内动态增长数组通常效率很低。

同样的问题是对SpinConv的调用:因为这是一个外部函数(MEX或m,无所谓),JIT无法编译这个循环,所以你受到了速度的限制翻译。哪个相当低。如果可能的话,只需将SpinConv的相关部分复制粘贴到循环体中即可避免这种情况。我知道,这非常烦人(我确信希望在未来的MATLAB版本中自动化),但是现在它是让JIT理解循环结构并编译它的唯一方法(实际上,100或更多的因素并不罕见)。

所以,说了这么多,我测试了两种不同的方法:

  1. 计算并存储CD的LU分解,并在循环中重复使用
  2. 解决所有Cn \ [C{1} C{2} ... C{n-1}]的系统n = 2:N,然后重新订购
  3. 在代码中:

    clc
    clear all
    
    Nframe = 500;
    
    %// Some random data
    C = cellfun(@(~)rand(3), cell(Nframe,1), 'UniformOutput', false); 
    D = cellfun(@(~)rand(3), cell(Nframe,1), 'UniformOutput', false);
    
    
    %// Your original method 
    tic
    
    count  = 1;
    for i=1:Nframe
        for j=1:Nframe
            if j <= i
                continue;
            else
                %// Main place to optimize (avoid looping?)!!
                %// In DCM representation (ie. each cell is 3x3 matrix)
                A{count,1} = C{j}\C{i}; %=inv(C{i+x})*C{i}
                B{count,1} = D{j}\D{i};
    
                count=count+1;
            end
        end
    end
    
    toc 
    A1 = A;
    
    
    
    %// First method: compute all LU decompositions and re-use them in the loop
    %// ------------------------------------------------------------------------
    
    tic
    
    %// Compute LU decompositions of all C and D
    Clu = cell(Nframe, 2);
    Dlu = cell(Nframe, 2);
    for ii = 1:Nframe
        [Clu{ii,1:2}]  = lu(C{ii});
        [Dlu{ii,1:2}]  = lu(D{ii});
    end
    
    %// improvement: pre-allocate A and B
    A = cell(Nframe*(Nframe-1)/2, 1);
    B = cell(Nframe*(Nframe-1)/2, 1);
    
    %// improvement: don't use i and j as variable names
    count  = 1;
    for ii = 1:Nframe
    
        %// improvement: instead of continue if j<=i, just use different range
        for jj = ii+1 : Nframe
    
            %// mldivide for LU is equal to backwards substitution, which is
            %// trivial and thus fast
            A{count} = Clu{jj,2}\(Clu{jj,1}\C{ii});
            B{count} = Dlu{jj,2}\(Dlu{jj,1}\D{ii});
    
            count = count+1;
    
        end
    end
    
    toc
    A2 = A;
    
    
    
    %// Second method: solve all systems simultaneously by concatenation
    %// ------------------------------------------------------------------------
    
    tic
    
    % Pre-allocate temporary matrices
    Aa = cell(Nframe-1, 1);
    Bb = cell(Nframe-1, 1);
    
    for ii = 2:Nframe   
        % Solve Cn \ [C1 C2 C3 ... Cn]
        Aa{ii-1} = C{ii}\[C{1:ii-1}];
        Bb{ii-1} = D{ii}\[D{1:ii-1}];
    end
    toc
    
    %// Compared to the order in which data is stored in one of the other
    %// methods, the order of data in Aa and Bb is different. So, we have to
    %// re-order to get the proper order back: 
    
    tic
    
    A = cell(Nframe*(Nframe-1)/2, 1);
    B = cell(Nframe*(Nframe-1)/2, 1);
    for ii = 1:Nframe-1
    
         A( (1:Nframe-ii) + (Nframe-1-(ii-2)/2)*(ii-1) ) = ...
             cellfun(@(x) x(:, (1:3) + 3*(ii-1)), Aa(ii:end), 'UniformOutput', false);
    
         B( (1:Nframe-ii) + (Nframe-1-(ii-2)/2)*(ii-1) ) = ...
             cellfun(@(x) x(:, (1:3) + 3*(ii-1)), Bb(ii:end), 'UniformOutput', false);
    end
    
    toc
    A3 = A;
    
    % Check validity of outputs
    allEqual = all( cellfun(@(x,y,z)isequal(x,y)&&isequal(x,z), A1,A2,A3) )
    

    结果:

    Elapsed time is 44.867630 seconds.  %// your original method 
    Elapsed time is 1.267333 seconds.   %// with LU decomposition
    Elapsed time is 0.183950 seconds.   %// solving en-masse by concatenation
    Elapsed time is 1.871149 seconds.   %// re-ordering the output of that
    
    allEqual = 
        1
    

    请注意,我在R2010a上,因此原始方法的缓慢主要是由于AB没有预先分配。请注意,在这方面,较新的MATLAB版本的性能会更好,但如果预先分配,性能仍将更好

    直观地(和其他人可能建议的那样),你可以计算明确的反转,

    Cinv = cellfun(@inv, C, 'UniformOutput', false);
    

    甚至

    Cinv = cellfun(@(x) [...
        x(5)*x(9)-x(8)*x(6)  x(7)*x(6)-x(4)*x(9)  x(4)*x(8)-x(7)*x(5) 
        x(8)*x(3)-x(2)*x(9)  x(1)*x(9)-x(7)*x(3)  x(7)*x(2)-x(1)*x(8)
        x(2)*x(6)-x(5)*x(3)  x(4)*x(3)-x(1)*x(6)  x(1)*x(5)-x(4)*x(2)] / ...
            (x(1)*x(5)*x(9) + x(4)*x(8)*x(3) + x(7)*x(2)*x(6) - ...
             x(7)*x(5)*x(3) - x(4)*x(2)*x(9) - x(1)*x(8)*x(6)),...
        C, 'UniformOutput', false);
    

    (这将更快,更准确),然后简单地在循环内部相乘。正如您将看到的,这明显慢于整体求解Cn\[C1 C2 ... Cn-1]和LU(尽管 取决于矩阵的性质)。此外,它失败了allEqual == true;有时差异很小,但经常(特别是对于近似奇异矩阵和其他特殊),差异是巨大

    正如在SO上的许多其他问题中所提到的,并且正如任何精致的Google搜索或高级线性代数书都会告诉您的那样,在数字应用程序中使用显式反转通常会很慢,总是不准确,有时甚至是危险的。逆是一个非常好的理论构造,但在该理论的任何实际应用中几乎没用。因此,最好使用上述其他方法之一。

    总结:

    • 如果您可以不按顺序处理数据(这可能需要在以后进行更复杂的索引),那么通过串联解决系统问题是最快的。当然,我可以改进我重新订购数据的方式,但我怀疑如果你需要重新订购,LU仍然会更快。

    • 如果不是这种情况,但您的矩阵适合LU分解,请使用它。要了解是否是这种情况,只需在真实数据和配置文件中使用它。您还可以尝试LU的其他输出(最值得注意的是置换矩阵P,或者对于稀疏矩阵,列重新排序矩阵Q)。

    • 当然,如果QR分解更合适,请使用qrcholpcg等相同。稍微尝试不同的方法。

    修改

    如你所述,所有矩阵都是SO(3)旋转矩阵。哇,这是重要的信息!!在这种情况下,逆只是转置,它比逆的任何变体快一到两个数量级。此外,您指示要将这些旋转矩阵转换为轴角度表示。然后应该将内核更改为

    A = C{ii}.'*C{jj};
    B = D{ii}.'*D{jj};
    
    [V,lam] = eig(A);
    axis  = V(:,imag(diag(lam))==0);
    theta = acos(min(max(-1, (trace(A)-1)/2), 1));
    At{count, :} = {axis theta*180/pi};
    
    [V,lam] = eig(B);
    axis  = V(:,imag(diag(lam))==0);
    theta = acos(min(max(-1, (trace(B)-1)/2), 1));
    Bt{count, :} = {axis theta*180/pi};
    

    这只使用 内置函数,所以这应该非常有效。至少它比复制粘贴SpinConv更好,因为SpinConv使用了很多非内置函数(nullisequalfacosd,{{1 }})。注意,上述方法使用特征值方法;如果使用sind函数中使用的行列式方法,只要更新它以不调用非内置函数,就可以使它更有效。

    请注意SpinConv版本的轴标识不正确;在SpinConv中计算的轴的符号与在elseif中计算的轴的符号相反。

答案 1 :(得分:1)

我会尝试计算和存储inv(C{j}),因为C{j}多次出现在矩阵除法中。同上D{j}。或者你的3x3矩阵是单数的吗?