Matlab:需要一些帮助,对一个看似简单的操作进行向量化处理。

3

我想优化这段Matlab代码,但目前为止我失败了。我尝试了不同的repmat、sums和cumsums组合,但所有的尝试似乎都没有给出正确的结果。我希望在这个棘手的问题上得到一些专家指导。

S=1000; T=10;
X=rand(T,S),
X=sort(X,1,'ascend');
Result=zeros(S,1);
for c=1:T-1
    for cc=c+1:T
        d=(X(cc,:)-X(c,:))-(cc-c)/T;
        Result=Result+abs(d');
    end
end

基本上,我创建了1000个包含10个随机数字的向量。对于每个向量,我都会计算其中每一对值(比如第m个和第n个)之间的差异,减去这两个值的索引差(n-m)。我对所有可能的一对数值进行求和,并将结果返回给每个向量。
希望这个解释清楚了。
非常感谢您的协助。

你可以按照 X(j,:) - j/T 进行重新分组。在你的 d 表达式中出现了两个这种形式的项。将减法操作放在循环外部进行一次,可以避免在循环内部重复执行大量的减法操作。 - Ben Voigt
3个回答

4
nchoosek(v,k)函数能够生成在v中取k个元素的所有组合。我们可以使用它来生成所有可能的索引对,然后将其用于向量化循环。在这种情况下,向量化似乎并没有实际提高性能(至少在我的机器上测试2017a版本)。也许有人会想出一种更有效的方法。
idx = nchoosek(1:T,2);
d = bsxfun(@minus,(X(idx(:,2),:) - X(idx(:,1),:)), (idx(:,2)-idx(:,1))/T);
Result = sum(abs(d),1)';

1
我认为这并不会提高性能,因为nchoosek很慢(特别是对于稍微大一点的输入),而bsxfun虽然方便但不一定快速,你可能会发现这个问题很有趣,关于何时最好使用bsxfun。话虽如此,这仍然是一个很棒的解决方案。 - Wolfie

4

至少轻松实现内部循环向量化:

Result=zeros(S,1);
for c=1:T-1
   d=(X(c+1:T,:)-X(c,:))-((c+1:T)'-c)./T;
   Result=Result+sum(abs(d),1)';
end

这里,我使用了新的自动单例展开功能。如果您使用的是旧版本的MATLAB,则需要在两个减法操作中使用bsxfun。例如,X(c+1:T,:)-X(c,:)bsxfun(@minus,X(c+1:T,:),X(c,:))相同。

在代码的这一部分中,发生的情况是,我们不再循环cc=c+1:T,而是一次性取出所有这些索引。因此,我简单地将cc替换为c+1:Td然后是一个具有多行的矩阵(第一次迭代中有9行,在每次后续迭代中少一行)。

令人惊讶的是,这比双重循环更慢,并且与Jodag的答案速度相似。

接下来,我们可以尝试改进索引。请注意,上面的代码按行从矩阵中提取数据。MATLAB按列存储数据,因此从矩阵中提取列比行更有效率。让我们转置X:

X=X';
Result=zeros(S,1);
for c=1:T-1
   d=(X(:,c+1:T)-X(:,c))-((c+1:T)-c)./T;
   Result=Result+sum(abs(d),2);
end

这比按行索引的代码快了两倍以上。

当然,同样的技巧也可以应用于问题中的代码,将其加速约50%:

X=X';
Result=zeros(S,1);
for c=1:T-1
   for cc=c+1:T
      d=(X(:,cc)-X(:,c))-(cc-c)/T;
      Result=Result+abs(d);
   end
end

从这个例子中得出的结论是,MATLAB的JIT编译器已经有了很大的改进。过去,任何类型的循环都会使代码变得极慢。今天,这并不一定是最糟糕的方法,特别是如果你只使用内置函数。


1
是的,正如你所说,现在MATLAB的JIT已经足够好了,它会自动将代码向量化,而不需要你付出额外的努力。它们让我们变得懒惰 :P - Ander Biguri
非常感谢您的帮助。我已经尝试了建议的更改,并更新了问题的结果。 - SebDL

4
更新:以下是不同方案的运行时间结果(10^5次尝试):enter image description here 因此,矩阵的转换是最有效的干预措施,而我的原始双循环实现与向量化版本相比,令人惊讶地表现最佳。然而,在我的手中(2017a),使用平均值改进仅为原始方法的16.6%(使用中位数为18.2%)。
也许还有改进的空间?

我很难决定2^(-7.8),为什么不在线性轴上绘制?在我的实验中,第二种和第三种方法大约相等,而最后两种方法的时间都是它们所基于的方法的一半。有趣的是看到这些计时从机器到机器的变化。也许还因为我使用的数组比你在问题中提到的要大?我打赌计时差异很大程度上取决于数据大小!无论如何,感谢您发布这个摘要! - Cris Luengo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接