MATLAB:矩阵值函数的卷积

时间:2011-04-23 16:28:05

标签: matlab matrix optimization convolution

我编写了这段代码来执行二维矩阵值函数的一维卷积(k是我的时间索引,kend大约是10e3)。是否有更快或更简洁的方法来执行此操作,可能使用内置函数?

for k=1:kend
  C(:,:,k)=zeros(3);
  for l=0:k-1
      C(:,:,k)=C(:,:,k)+A(:,:,k-l)*B(:,:,l+1);
  end
end

3 个答案:

答案 0 :(得分:4)

新解决方案:

这是一个基于旧解决方案的新解决方案,它解决了之前给定的公式。问题中的代码实际上是对该公式的修改,其中第三维中的两个矩阵之间的重叠被重复移位(它类似于沿着数据的第三维的卷积)。我给出的先前解决方案仅计算了问题中代码的 last 迭代的结果(即k = kend)。所以,这是一个完整的解决方案,应该 比1000 {1000}的kend问题中的代码更有效:

kend = size(A,3);                         %# Get the value for kend
C = zeros(3,3,kend);                      %# Preallocate the output
Anew = reshape(flipdim(A,3),3,[]);        %# Reshape A into a 3-by-3*kend matrix
Bnew = reshape(permute(B,[1 3 2]),[],3);  %# Reshape B into a 3*kend-by-3 matrix
for k = 1:kend
  C(:,:,k) = Anew(:,3*(kend-k)+1:end)*Bnew(1:3*k,:);  %# Index Anew and Bnew so
end                                                   %#   they overlap in steps
                                                      %#   of three

即使只使用kend = 100,这个解决方案对我来说比问题中的解决方案快30倍,比纯粹的基于for循环的解决方案快4倍(这将涉及<强> 5循环!)。请注意,下面关于浮点精度的讨论仍然适用,因此正常并且预计您将看到relative floating-point accuracy的顺序上的解决方案之间存在细微差别。


OLD SOLUTION:

根据您在评论中链接的此公式:

enter image description here

看起来您实际上想要做的事情与您在问题中提供的代码不同。假设AB是3乘3乘k矩阵,结果C应为3乘3矩阵,并将链接中的公式写为a嵌套for循环的集合如下所示:

%# Solution #1: for loops
k = size(A,3);
C = zeros(3);
for i = 1:3
  for j = 1:3
    for r = 1:3
      for l = 0:k-1
        C(i,j) = C(i,j) + A(i,r,k-l)*B(r,j,l+1);
      end
    end
  end
end

现在,通过重新整形AB 可以在没有任何for循环的情况下执行此操作:

%# Solution #2: matrix multiply
Anew = reshape(flipdim(A,3),3,[]);        %# Create a 3-by-3*k matrix
Bnew = reshape(permute(B,[1 3 2]),[],3);  %# Create a 3*k-by-3 matrix
C = Anew*Bnew;                            %# Perform a single matrix multiply

您甚至可以修改问题中的代码来创建一个带有单个循环的解决方案,该循环执行3乘3子矩阵的矩阵乘法:

%# Solution #3: mixed (loop and matrix multiplication)
k = size(A,3);
C = zeros(3);
for l = 0:k-1
  C = C + A(:,:,k-l)*B(:,:,l+1);
end

现在问题是:这些方法中哪一种更快/更清洁?

嗯,“清洁”是非常主观的,老实说,我无法告诉你上面的哪些代码使得更容易理解操作正在做什么。第一个解决方案中的所有循环和变量使得跟踪正在发生的事情变得有点困难,但它清楚地反映了公式。第二种解决方案将其全部分解为简单的矩阵运算,但很难看出它与原始公式的关系。第三种解决方案似乎是两者之间的中间地带。

所以,让我们让速度成为决胜局。如果我为上述解决方案计算k的多个值,我会得到这些结果(在给定解决方案10,000次迭代所需的几秒钟内,MATLAB R2010b):

 k   |  loop  | matrix multiply |  mixed
-----+--------+-----------------+--------
 5   | 0.0915 |      0.3242     | 0.1657
 10  | 0.1094 |      0.3093     | 0.2981
 20  | 0.1674 |      0.3301     | 0.5838
 50  | 0.3181 |      0.3737     | 1.3585
 100 | 0.5800 |      0.4131     | 2.7311   * The matrix multiply is now fastest
 200 | 1.2859 |      0.5538     | 5.9280

嗯,事实证明,对于较小的k值(大约50或更小),for循环解决方案实际上胜出,再次表明for循环不像以前那样“邪恶”在旧版本的MATLAB中考虑过。在某些情况下,它们比聪明的矢量化更有效。但是,当k的值大于100时,向量化矩阵乘法解决方案开始胜出,与for-loop解决方案相比,增加k时缩放得更好。由于我不太确定的原因,混合的for-loop / matrix-multiply解决方案非常严重地

所以,如果你期望k很大,我会选择矢量化矩阵乘法解决方案。要记住的一件事是,从每个解决方案(矩阵C)得到的结果将略有不同(在floating-point precision的级别上),因为执行的加法和乘法的顺序是每种解决方案都不同,从而导致rounding errors积累的差异。简而言之,这些解决方案的结果之间的差异应该可以忽略不计,但您应该意识到这一点。

答案 1 :(得分:2)

您是否研究过Matlab的conv方法?

我无法将其与您提供的代码进行比较,因为您提供的内容让我在尝试访问A的第0个元素时出现问题。(k=1k-1=0时。)

答案 2 :(得分:1)

您是否考虑过使用FFT进行卷积?卷积运算是simply a point-wise multiplication in the frequency domain。你必须采取有限序列的一些预防措施,因为如果你不小心,你最终会得到循环卷积(但这很容易处理)。

以下是1D案例的简单示例。

>> a=rand(4,1);
>> b=rand(3,1);
>> c=conv(a,b)

c =

    0.1167
    0.3133
    0.4024
    0.5023
    0.6454
    0.3511

使用FFT

>> A=fft(a,6);
>> B=fft(b,6);
>> C=real(ifft(A.*B))

C =

    0.1167
    0.3133
    0.4024
    0.5023
    0.6454
    0.3511

M点矢量和N点矢量的卷积产生M+N-1点矢量。因此,我在使用FFT之前用零填充了每个向量ab(当我对它进行4+3-1=6点FFT时会自动处理这个。)< / p>

修改

虽然您显示的等式与循环卷积类似,但并不完全正确。所以你可以抛弃FFT方法和内置的conv*函数。要回答你的问题,这里是没有显式循环的相同操作:

dim1=3;dim2=dim1;
dim3=10;
a=rand(dim1,dim2,dim3);
b=rand(dim1,dim2,dim3);


mIndx=cellfun(@(x)(1:x),num2cell(1:dim3),'UniformOutput',false);
fun=@(x)sum(reshape(cell2mat(cellfun(@(y,z)a(:,:,y)*b(:,:,z),num2cell(x),num2cell(fliplr(x)),'UniformOutput',false)),[dim1,dim2,max(x)]),3);
c=reshape(cell2mat(cellfun(@(x)fun(x),mIndx,'UniformOutput',false)),[dim1,dim2,dim3]);
  • mIndx这里是一个单元格,i单元格包含向量1:i。这是您的l索引(正如其他人所说,请不要将l用作变量名称)。
  • 下一行是一个匿名函数,它执行卷积操作,利用k索引只是l索引翻转的事实。操作在单个电池上进行,然后组装。
  • 最后一行实际上对矩阵执行操作。

答案与循环获得的答案相同。但是,您会发现循环解决方案实际上是一个数量级更快(我的代码平均为0.007s,循环为0.0006s)。这是因为循环非常简单,而使用这种嵌套结构,会有大量的函数调用开销和重复的重塑,这会减慢它的速度。

自从循环可怕的早期以来,MATLAB的循环已经走了很长的路。当然,矢量化操作非常快;但并非所有东西都可以被矢量化,有时,循环比这种复杂的匿名函数更有效。我可以通过优化我的构造(或者采取不同的方法)来减少几十分之一,但我不打算这样做。

请记住,良好的代码应该是可读的,并且以可读性为代价的高效和次要优化不能为任何人服务。虽然我写了上面的代码,但如果我在一个月后重新访问它,我当然无法破译它的作用。你的循环代码很清晰,可读很快,我建议你坚持下去。