MATLAB:按特定行减去矩阵子集

时间:2014-05-27 01:42:38

标签: matlab matrix vectorization

以下是我想要使用的矩阵子集的示例:

  

1 3 5

     

2 3 6

     

1 1 1

     

3 5 4

     

5 5 5

     

8 8 0

这个矩阵实际上是3000 x 3。

对于前3行,我希望用这三行的第一行减去每一行。

对于后三行,我希望用这三行中的第一行减去每一行,依此类推。

因此,输出矩阵将如下所示:

  

0 0 0

     

1 0 1

     

0 -2 -4

     

0 0 0

     

2 0 1

     

5 3 -4

MATLAB中的哪些代码会为我做这个?

5 个答案:

答案 0 :(得分:12)

您也可以使用mat2cellcellfun,然后使用cell2mat完全向量化。假设我们的矩阵存储在A中,请尝试:

numBlocks = size(A,1) / 3;
B = mat2cell(A, 3*ones(1,numBlocks), 3);
C = cellfun(@(x) x - x([1 1 1], :), B, 'UniformOutput', false);
D = cell2mat(C); %//Output

第一行显示我们需要多少3 x 3块。这假设行数是3的倍数。第二行使用mat2cell来分解每个3 x 3块并将它们放入单个单元格中。然后第三行使用cellfun,以便对于我们的单元格数组中的每个单元格(3 x 3矩阵),它采用3 x 3矩阵的每一行并用第一行减去自身。这非常像@David所做的,除了我没有使用repmat来减少开销。然后第四行接受每个矩阵并将它们堆叠起来,以便我们最终获得最终矩阵。

示例(这是使用您帖子中定义的矩阵):

A = [1 3 5; 2 3 6; 1 1 1; 3 5 4; 5 5 5; 8 8 0];
numBlocks = size(A,1) / 3;
B = mat2cell(A, 3*ones(1, numBlocks), 3);
C = cellfun(@(x) x - x([1 1 1], :), B, 'UniformOutput', false);
D = cell2mat(C);

输出:

D =

 0     0     0
 1     0     1
 0    -2    -4
 0     0     0
 2     0     1
 5     3    -4

事后看来,我认为@David在性能提升方面是正确的。除非这段代码重复多次,否则我认为for循环会更有效率。无论哪种方式,我想提供另一种选择。很酷的运动!

编辑:时间和尺寸测试

由于我们之前的讨论,我决定进行时间和尺寸测试。这些测试是在具有16 GB RAM的Intel i7-4770 @ 3.40 GHz CPU上进行的,使用Windows 7 Ultimate上的MATLAB R2014a。基本上,我做了以下事情:

  • 测试#1 - 将随机种子生成器设置为1以获得再现性。我写了一个循环,循环10000次。对于循环中的每次迭代,我生成了一个随机整数3000 x 3矩阵,然后执行此处描述的每个方法。我注意到10000次循环后每种方法完成所需的时间。时间结果如下:

    1. 大卫的方法:0.092129 seconds
    2. rayryeng的方法:1.9828 seconds
    3. natan的方法:0.20097 seconds
    4. natan的bsxfun方法:0.10972 seconds
    5. Divakar的bsxfun方法:0.0689 seconds

因此,Divakar的方法是最快的,其次是David的for循环方法,紧接着是natan的bsxfun方法,接着是natan的原始kron方法,然后是树懒(又名我的。

  • 测试#2 - 我决定看看当你增加矩阵的大小时会有多快。设置如下。我做了1000次迭代,并且在每次迭代时,我每次都将矩阵行的大小增加3000。因此,迭代1由3000×3矩阵组成,下一次迭代由6000×3矩阵组成,依此类推。随机种子再次设置为1。在每次迭代时,记录完成代码所花费的时间。为了确保公平性,在处理代码开始之前,每次迭代都清除变量。因此,这是一个stem图,显示每个矩阵大小的时间。我对绘图进行了子集化,使其显示从200000 x 3到300000 x 3的时序。请注意,水平轴在每次迭代时记录行数。第一个主干为3000行,下一个为6000行,依此类推。列(当然)列保持不变。

enter image description here

我无法解释整个图表中的随机峰值....可能归因于RAM中发生的事情。但是,我非常确定我在每次迭代时清除变量以确保没有偏差。无论如何,迪瓦卡和大卫都是紧密联系在一起的。接下来是natan的bsxfun方法,然后是natan的kron方法,最后是我的方法。有趣的是看看Divakar的bsxfun方法和大卫的for方法在时间上是如何并排的。

  • 测试#3 - 我重复了我为测试#2做的事情,但是使用natan的建议,我决定采用对数比例。我做了6次迭代,从3000 x 3矩阵开始,然后将行增加10倍。因此,第二次迭代有30000 x 3,第三次迭代有300000 x 3,依此类推,直到最后一次迭代,即3e8 x 3。

我在水平轴上绘制了半对数刻度,而垂直轴仍然是线性刻度。同样,横轴表示矩阵中的行数。

enter image description here

我更改了垂直限制,因此我们可以看到大多数方法。我的方法表现很差,以至于它会将其他时间压缩到图表的下端。因此,我改变了观看限制,使我的方法脱离了图片。基本上在测试#2中看到的内容在此验证。

答案 1 :(得分:9)

以下是使用bsxfun实现此目的的另一种方式,与natan's bsxfun implementation略有不同 -

t1 = reshape(a,3,[]); %// a is the input matrix
out = reshape(bsxfun(@minus,t1,t1(1,:)),[],3); %// Desired output

答案 2 :(得分:6)

稍微短一点的矢量化方式(如果a是你的矩阵):

b=a-kron(a(1:3:end,:),ones(3,1));

让我们测试一下:

a=[1 3 5
   2 3 6
   1 1 1
   3 5 4
   5 5 5
   8 8 0]

a-kron(a(1:3:end,:),ones(3,1))

ans =
 0     0     0
 1     0     1
 0    -2    -4
 0     0     0
 2     0     1
 5     3    -4

修改

这是一个bsxfun解决方案(不太优雅,但希望更快):

a-reshape(bsxfun(@times,ones(1,3),permute(a(1:3:end,:),[2 3 1])),3,[])'

ans =

 0     0     0
 1     0     1
 0    -2    -4
 0     0     0
 2     0     1
 5     3    -4

编辑2

好吧,这让我感到好奇,因为我知道bsxfun对于更大的阵列尺寸开始效率低下。因此,我尝试使用timeit我的两个解决方案进行检查(因为它们是一个简单的衬里)。这是:

range=3*round(logspace(1,6,200));
for n=1:numel(range)
    a=rand(range(n),3);
    f=@()a-kron(a(1:3:end,:),ones(3,1));
    g=@() a-reshape(bsxfun(@times,ones(1,3),permute(a(1:3:end,:),[2 3 1])),3,[])';
    t1(n)=timeit(f);
    t2(n)=timeit(g);
end
semilogx(range,t1./t2);

enter image description here

所以我没有测试for循环和Divkar的bsxfun,但是你可以看到,对于小于3e4 kron的数组优于bsxfun,并且这在更大的数组中变化(比率为< 1意味着克朗在给定阵列大小的情况下花费的时间更少。这是在Matlab 2012a win7(i5机器)

完成的

答案 3 :(得分:4)

简单for循环。这将分别执行每个3x3块。

A=randi(5,9,3)
B=A(1:3:end,:)
for i=1:length(A(:,1))/3
    D(3*i-2:3*i,:)=A(3*i-2:3*i,:)-repmat(B(i,:),3,1)
end
D

虽然可以对此进行矢量化,但我不认为性能提升是值得的,除非你多次这样做。对于3000x3矩阵,它根本不需要很长时间。

编辑:事实上这似乎很快。我认为这是因为Matlab的JIT编译可以很好地加速简单的for循环。

答案 4 :(得分:3)

您只需使用索引即可完成此操作:

a(:) = a(:) - a(3*floor((0:numel(a)-1)/3)+1).';

当然,上面的3可以替换为任何其他数字。即使该数字不划分行数,它仍然有效。