改善许多子矩阵左除运算(mldivide,\)的性能

时间:2019-06-30 13:13:36

标签: matlab performance matrix multidimensional-array least-squares

我有两个矩阵,a1a2a1是3x12000,而a2是3x4000。我想创建另一个数组3x4000,该数组是mldivide的3x3子矩阵和{{1的3x1子矩阵的左矩阵除法(\a1) }}。您可以使用for循环轻松做到这一点:

a2

但是,我想知道是否有更快的方法来做到这一点。

编辑:我知道预分配可以提高速度,我只是出于视觉目的而展示了它。

Edit2 :删除了数组的迭代增加。看来我的问题被误解了一点。我主要是想知道是否可以执行一些矩阵运算来实现我的目标,因为这可能比for循环更快,即将for ii = 1:3:12000 a = a1(:,ii:ii+2)\a2(:, ceil(ii/3)); end 调整为3x3x4000矩阵,将a1调整为3x1x4000矩阵,然后离开矩阵一键式地划分每个级别,但是,您不能将矩阵除法与3D矩阵分开。

3 个答案:

答案 0 :(得分:4)

您可以通过将a1的子矩阵放在12000x12000矩阵的对角线上来创建一个包含多个独立的子方程组的方程组,如下所示:

a1(1,1) a1(1,2) a1(1,3)    0       0      0        0       0      0       
a1(2,1) a1(2,2) a1(2,3)    0       0      0        0       0      0       
a1(3,1) a1(3,2) a1(3,3)    0       0      0        0       0      0       
   0       0       0    a1(1,4) a1(1,5) a1(1,6)    0       0      0       
   0       0       0    a1(2,4) a1(2,5) a1(2,6)    0       0      0       
   0       0       0    a1(3,4) a1(3,5) a1(3,6)    0       0      0       
   0       0       0       0       0       0    a1(1,7) a1(1,8) a1(1,9)   
   0       0       0       0       0       0    a1(2,7) a1(2,8) a1(2,9)   
   0       0       0       0       0       0    a1(3,7) a1(3,8) a1(3,9)

,然后将其除以a2(:)

可以使用kron和像这样的稀疏矩阵(source):

a1_kron = kron(speye(12000/3),ones(3));
a1_kron(logical(a1_kron)) = a1(:);
a = a1_kron\a2(:);
a = reshape(a, [3 12000/3]);

优势-速度:这比在我的PC上进行预分配的for循环快大约3-4倍。

缺点:这种方法必须考虑一个缺点:使用左除法时,Matlab会寻找求解线性方程组的最佳方法,因此,如果要求解每个子系统,独立地,将为每个子系统选择最佳方法,但是如果您将主题作为一个系统解决,则Matlab会为所有子系统一起找到最佳方法,而不是每个子系统都为最佳方法。

注意:如Stefano Manswer所示,使用一个大型方程组(使用kron和稀疏矩阵)比使用a for循环(带有预分配)仅对于很小的方程式子系统(在我的电脑上,对于等式数量<= 7),对于较大的方程式子系统而言,使用for循环更快。

比较不同的方法

我编写并运行了一个代码,比较了解决此问题的4种不同方法:

  1. for循环,没有预分配
  2. for循环,具有预分配功能
  3. kron
  4. cellfun

测试:

n = 1200000;
a1 = rand(3,n);
a2 = rand(3,n/3);

disp('Method 1: for loop, no preallocation')
tic
a_method1 = [];
for ii = 1:3:n
  a_method1 = [a_method1 a1(:,ii:ii+2)\a2(:, ceil(ii/3))];
end
toc
disp(' ')

disp('Method 2: for loop, with preallocation')
tic
a1_reshape = reshape(a1, 3, 3, []);
a_method2 = zeros(size(a2));
for i = 1:size(a1_reshape,3)
    a_method2(:,i) = a1_reshape(:,:,i) \ a2(:,i);
end
toc
disp(' ')

disp('Method 3: kron')
tic
a1_kron = kron(speye(n/3),ones(3));
a1_kron(logical(a1_kron)) = a1(:);
a_method3 = a1_kron\a2(:);
a_method3 = reshape(a_method3, [3 n/3]);
toc
disp(' ')

disp('Method 4: cellfun')
tic
a1_cells = mat2cell(a1, size(a1, 1), repmat(3 ,1,size(a1, 2)/3));
a2_cells = mat2cell(a2, size(a2, 1), ones(1,size(a2, 2)));
a_cells = cellfun(@(x, y) x\y, a1_cells, a2_cells, 'UniformOutput', 0);
a_method4 = cell2mat(a_cells);
toc
disp(' ')

结果:

Method 1: for loop, no preallocation
Elapsed time is 747.635280 seconds.

Method 2: for loop, with preallocation
Elapsed time is 1.426560 seconds.

Method 3: kron
Elapsed time is 0.357458 seconds.

Method 4: cellfun
Elapsed time is 3.390576 seconds.

比较这四种方法的结果,您会发现使用方法3-kron会得到稍微不同的结果:

disp(['sumabs(a_method1(:) - a_method2(:)): ' num2str(sumabs(a_method1(:)-a_method2(:)))])
disp(['sumabs(a_method1(:) - a_method3(:)): ' num2str(sumabs(a_method1(:)-a_method3(:)))])
disp(['sumabs(a_method1(:) - a_method4(:)): ' num2str(sumabs(a_method1(:)-a_method4(:)))])

结果:

sumabs(a_method1(:) - a_method2(:)): 0
sumabs(a_method1(:) - a_method3(:)): 8.9793e-05
sumabs(a_method1(:) - a_method4(:)): 0

答案 1 :(得分:1)

边际最大的改进是预分配输出矩阵,而不是增加它:

A1 = reshape(A1, 3, 3, []);
a = zeros(size(A2));
for i = 1:size(A1,3) 
    a(:,i) = A1(:,:,i) \ A2(:,i);
end

使用preallocate数组,如果Parallel Toolbox可用,则可以尝试parfor

编辑

此答案不再相关,因为OP改写了这个问题,以避免增长结果数组,而这是最初的主要瓶颈。

这里的问题是必须解决4000个独立的3x3线性系统。矩阵是如此之小,以至于可能会引起特别的解决方案,特别是如果该矩阵具有关于矩阵属性的一些信息(对称或不对称,条件数等)时。但是,坚持使用\ matlab运算符,加快计算速度的最佳方法是显式利用并行性,例如通过parfor命令。

Eliahu Aaron的另一个answer的稀疏矩阵解决方案确实非常聪明,但是它的速度优势并不一般,而是取决于特定的问题大小。

使用此功能,您可以探索不同的问题大小:

function [t2, t3] = sotest(n, n2)

a1 = rand(n,n*n2);
a2 = rand(n,n2);

tic
a1_reshape = reshape(a1, n, n, []);
a_method2 = zeros(size(a2));
for i = 1:size(a1_reshape,3)
    a_method2(:,i) = a1_reshape(:,:,i) \ a2(:,i);
end
t2 = toc;

tic
a1_kron = kron(speye(n2),ones(n));
a1_kron(logical(a1_kron)) = a1(:);
a_method3 = a1_kron\a2(:);
a_method3 = reshape(a_method3, [n n2]);
t3 = toc;

assert ( norm(a_method2 - a_method3, 1) / norm(a_method2, 1) < 1e-8) 

实际上,n=3的稀疏矩阵方法显然更好,但是如果增加n,则竞争性会降低

solution times

上图是通过

获得的
>> for i=1:20; [t(i,1), t(i,2)] = sotest(i, 50000); end
>> loglog(1:20, t, '*-')

我最后的评论是,使用密集\运算符的显式循环确实非常快。稀疏矩阵公式的准确性稍差,在边缘情况下可能会出现问题;并且可以肯定的是,稀疏矩阵解不是很可读。如果要解决的系统n2的数量非常大(> 1e6),则可能应该探索临时解决方案。

答案 2 :(得分:1)

您正在求解一系列N个系统,每个系统具有m个线性方程,N个系统的形式为

type

您可以将它们转换为Nm个线性方程的单个系统:

interface MessageDataMap {
    name: string;
    age: number;
    gender: 'male' | 'female';
    // ....
}

type MessageType = keyof MessageDataMap;

type Message = {
  [K in MessageType]: {
    type: K;
    data: MessageDataMap[K];
  };
}[MessageType];

但是,求解一个方程组比求解所有小的方程组要贵得多。通常,成本为O(n ^ 3),因此您从O(N m ^ 3)变为O((Nm)^ 3)。巨大的悲观。 (Eliahu proved me wrong here,显然可以利用矩阵的稀疏性。)

可以降低计算成本,但是您需要提供有关数据的保证。例如,如果矩阵A是正定的,则​​可以更廉价地求解系统。尽管如此,考虑到您要处理3x3矩阵,那里的收益将微不足道,因为这些都是非常简单的系统来解决。

如果您因为认为循环在MATLAB中本来就很慢而问这个问题,那么您应该知道,情况已不再如此,自从MATLAB在15年前获得JIT以来,情况就不再如此。如今,许多矢量化尝试都导致同样快速的代码,并且常常导致速度较慢的代码,特别是对于大数据。 (如有必要,我可以花一些时间在此处发布,以证明这一点。)

我认为一次性解决所有系统可能会减少每次调用运算符Ax = b 时MATLAB进行的检查次数。也就是说,对问题的大小和类型进行硬编码可能会始终得到改善。但是唯一的方法是编写一个MEX文件。