matlab代码优化-将大型单元阵列中的所有3x3矩阵相乘
摘要 为了提高代码的时间效率,使用mldivide在3x3矩阵和逆3x3矩阵之间重复执行矩阵乘法 背景 在使用方向数据进行步态分析中连接到受试者下肢的传感器之间的手眼校准之前,我正在尝试实现一种矢量量化方法。。。我遵循的算法来自论文matlab代码优化-将大型单元阵列中的所有3x3矩阵相乘,matlab,optimization,rotational-matrices,Matlab,Optimization,Rotational Matrices,摘要 为了提高代码的时间效率,使用mldivide在3x3矩阵和逆3x3矩阵之间重复执行矩阵乘法 背景 在使用方向数据进行步态分析中连接到受试者下肢的传感器之间的手眼校准之前,我正在尝试实现一种矢量量化方法。。。我遵循的算法来自论文 “手眼校准的数据选择:矢量量化方法”这是您可能不需要的背景 优化代码 我希望找到一种更快的方法来解决所有可能的“相对运动”(a或B),这需要太长的时间(C和D大约有2000个元素长,因此a或B的大小将达到=2000*(2000-1)/2=1999000): 其中,和
“手眼校准的数据选择:矢量量化方法”
这是您可能不需要的背景
优化代码
我希望找到一种更快的方法来解决所有可能的“相对运动”(a或B),这需要太长的时间(C和D大约有2000个元素长,因此a或B的大小将达到=2000*(2000-1)/2=1999000
):
其中,和是四元数运算
好的,很抱歉-这就是我所有的信息和可能性 我会尝试计算和存储
inv(C{j})
,因为C{j}
多次出现在矩阵除法中。同上D{j}
。或者你的3x3矩阵是奇异的?我会尝试计算和存储inv(C{j})
,因为C{j}
在矩阵除法中多次出现。同上D{j}
。或者你的3x3矩阵是奇异的?正如我在上面所评论的,最快的方法很大程度上取决于你矩阵的属性。例如,某些算法可以从对称矩阵中获益匪浅,但如果不是对称矩阵,则速度相当慢
因此,没有进一步的信息,我只能做一些一般性的陈述,比较随机矩阵的一些方法(在矩阵求逆的情况下通常不会给出很好的比较)
根据您的MATLAB版本(R2011a中的JIT或大大改进的JIT),预先分配A
和B
可以大大提高环路性能;在循环中动态增长阵列通常效率很低
调用SpinConv
:由于这是一个外部函数(MEX或m,无所谓),JIT无法编译此循环,因此您受到解释器速度的限制。这是相当低的。如果可能的话,您可以通过简单地将SpinConv
的相关部分复制粘贴到循环体中来避免这种情况。我知道,这非常烦人(我当然希望这在未来的MATLAB版本中是自动化的),但现在这是让JIT理解循环结构并编译它的唯一方法(实际上,100或更多的因子并不少见)
说到这里,我测试了两种不同的方法:
C
和D
的LU分解,并在循环中重复使用它们n=2:n
求解系统Cn\[C{1}C{2}…C{n-1}]
,并重新排序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) )
请注意,我使用的是R2010a,因此原始方法的缓慢主要是由于未预先分配A
和B
。请注意,在这方面,更新的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\[c1c2…Cn-1]
和LU都要慢得多(尽管这取决于矩阵的性质)。此外,它无法生成allEqual==true
;有时差异很小,但通常(特别是对于近奇异矩阵和其他特殊矩阵),差异很大
正如在这里的许多其他问题中提到的那样,正如任何改进的谷歌搜索或高级线性代数书籍都会告诉你的那样,在数值应用程序中使用显式逆运算通常是缓慢的,总是不准确的,有时甚至是危险的。逆是一个非常好的理论构造,但在该理论的任何实际应用中几乎毫无用处。因此,最好使用上述其他方法之一
总之:
- 如果您可以处理无序的数据(以后可能需要更复杂的索引),那么通过串联来解决整个系统是目前最快的。诚然,我重新排序数据的方式可以改进,但我怀疑如果您需要重新排序,LU仍然会更快
- 如果情况并非如此,但矩阵适合LU分解,请使用它。若要了解情况是否如此,只需在真实数据和个人资料上使用它。您还可以尝试LU的其他输出(最显著的是,排列矩阵
,或者对于稀疏矩阵,列重新排序矩阵P
)Q
- 当然,如果QR分解更合适,请使用
。同样适用于QR
,或chol
等。稍微尝试一下不同的方法pcg
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};
这只使用内置函数,因此应该非常有效。至少它比复制粘贴spincov
要好,因为spincov
使用了很多非内置函数(null
,isequalf
,acosd
,sind
)。请注意,该方法
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
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);
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};