【问题标题】:How to unroll a cell array into a column vector?如何将单元格数组展开为列向量?
【发布时间】:2021-01-18 05:37:18
【问题描述】:

我有一个单元格数组,其中每个单元格都是不同大小的矩阵。我想将所有矩阵的每个元素连接成一个列向量。 所以

X1=rand(2,3);  % Total 6 elements.
X2=rand(3,4);  % Total 12 elements.
X = {X1, X2};  % Total 18 elements in a 2-cell array.

% How to unroll everything from X into one giant column vector of size 18x1 ?

% Edit: The above example only shows two matrices, X1 and X2, but there could be n such matrices in the cell array.
X = {X1, X2, ... , Xn};

我可以通过循环来做到这一点,但好奇是否有更快的方法。我查看了 cell2mat 并重塑,但无法让他们这样做(尺寸不匹配错误)。网络搜索似乎没有帮助。

这是我使用 for 循环的解决方案:

unrolled_X=[];
for i=1:length(X)
  unrolled_X = [unrolled_X; X{i}(:)];
end

编辑 2:感谢您的回答。我学到了一些关于性能的新东西。我对@HansHirse、@lucien-xhh 和@wolfie 的3 个解决方案进行了基准测试。有点意外的结果。请注意,我实际上正在运行 Octave(版本 5.2.0。)。

所以没有 cell2fun 的解决方案是最快的。其他 2 个解决方案都使用 cellfun,但惊人地接近最快,而另一个是最快的两倍。代码和结果如下。

代码


function run_benchmarks()
  X={};
  for i=1:5
    X{i}=rand(1000,1000);
  end
  
  fprintf("unroll_with_cellfun: %f\n", benchmark(@()unroll_with_cellfun(X), 100));
  fprintf("unroll_with_cellfun2: %f\n", benchmark(@()unroll_with_cellfun2(X), 100));
  fprintf("unroll_with_vertcat: %f\n", benchmark(@()unroll_with_vertcat(X), 100));
  
end

function unrolled_X = unroll_with_cellfun(X)
  unrolled_X = cell2mat(cellfun(@(x) x(:), X, 'UniformOutput', false).');
end

function unrolled_X = unroll_with_cellfun2(X)
  unrolled_X = cell2mat(cellfun(@(x) x(:).', X, 'UniformOutput', false)).';
end

function unrolled_X = unroll_with_vertcat(X)
  unrolled_X = cell(length(X),1);
  for ii = 1:length(X)
    unrolled_X{ii} = X{ii}(:);
  end
  unrolled_X = vertcat( unrolled_X{:} );
end


function elapsed_time_in_seconds = benchmark(f, N)
  % benchmark runs the function 'f' N times and returns the elapsed time in seconds.

  timeid = tic;
  for i=1:N
    output = f();
  end
  elapsed_time_in_seconds = toc(timeid);
end

结果:

octave:161> run_benchmarks
unroll_with_cellfun: 1.240324
unroll_with_cellfun2: 0.606957   <-- Close to fastest.
unroll_with_vertcat: 0.597657    <-- FASTEST

惊讶地发现 cellfun2 与最快的解决方案几乎相同,而且 cellfun 需要 2 倍的时间,即使它与 cellfun2 几乎相同。

【问题讨论】:

  • 我怀疑这两个 cellfun 答案可能会给出不同的结果,其中一个是交错的值。这将是放缓的原因。您是否仔细检查了结果?如果元胞数组中包含不同大小的数组,则交错值的解决方案将不起作用。
  • 请注意,你在测试中的 X 是一个列数组,而在 Hans 和 Lucien 的答案中它是一个行数组。
  • 另外,你在做测试之前“热身”了吗?对函数的第一次调用往往比后续调用慢很多。
  • 我刚刚在 MATLAB R2017a 中运行了您的基准测试,“cellfun2”几乎比“cellfun”慢 2 倍,“vertcat”快 2 倍以上。在 MATLAB 中,循环得到了优化,因此对 cellfun 的调用具有很大的开销。我猜添加的.' 操作会增加时间? - 但我不明白为什么“cellfun”会比 Octave 中的“cellfun2”慢。这没有任何意义。
  • 好的,我破解了 Octave(Linux 上的 v.5.2.0),并在那里运行了你的基准测试。我看到“cellfun”需要 2.28 秒,“cellfun2”需要 2.07,“vertcat”需要 2.04。 “cellfun”确实比较慢,可能是由单元阵列上的转置引起的?但这不是 2 倍,更像是 1.1 倍。另外,你必须有一台速度非常快的电脑......

标签: matlab octave


【解决方案1】:

预先分配你的循环会更有效率和更好的实践

unrolled_X = cell(length(X),1);
for ii = 1:length(X)
  unrolled_X{ii} = X{ii}(:);
end
unrolled_X = vertcat( unrolled_X{:} );

cellfun 这样的简写基本上是这个循环变相,cell2mat 在底层使用循环进行连接,但有额外的检查,所以实际上可能会导致轻微的减速。

【讨论】:

  • 感谢您提供更好的做法;以及关于 cell2mat 潜在放缓的顶部。我会在运行基准测试和@HansHirse 的回答后报告。
  • 我添加了基准测试结果作为对问题的编辑。 cellfun+cell2mat 的方式确实比较慢。但最大的缓慢可能是由于需要转置,而不是 cell2fun + cellmat 本身。
  • 转置的计算量应该非常轻,我希望它只是包裹在 cellfuncell2mat 中的额外内容以使它们变得健壮
  • 您可以通过跳过对vertcatunrolled_X{ii} = X{ii}(:).',然后是unrolled_X = [unrolled_X{:}].' 的调用来获得一点收益(在MATLAB R2017a 上
  • @Cris 很好的发现,我有点惊讶于附加行向量比附加列更快,我想可能还有一些额外的检查......也许cat(1,__) 会看到相同的加速
【解决方案2】:

您可以使用cellfun 使用anonymous function 来展平所有矩阵。然后,将修改后的单元格数组作为“列向量”输入cell2mat,即预先转置修改后的单元格数组。

这是一个示例,其中所有内容都变成了一条线(使用 MATLAB Online 测试):

X1 = rand(2, 3);
X2 = rand(3, 4);
X3 = rand(1, 5);
X = {X1, X2, X3}

unrolled_X = cell2mat(cellfun(@(x) x(:), X, 'UniformOutput', false).')

一些示例输出(使用 Octave 6.1.0 生成):

X =
{
  [1,1] =
     0.2781   0.3303   0.7424
     0.3314   0.4878   0.6254

  [1,2] =
     0.567344   0.848374   0.035421   0.171656
     0.359233   0.482265   0.327617   0.188834
     0.088272   0.771683   0.763845   0.181979

  [1,3] =
     0.9843   0.7817   0.9399   0.5453   0.3310
}

unrolled_X =

   0.278085
   0.331438
   0.330314
   0.487774
   0.742395
   0.625360
   0.567344
   0.359233
   0.088272
   0.848374
   0.482265
   0.771683
   0.035421
   0.327617
   0.763845
   0.171656
   0.188834
   0.181979
   0.984326
   0.781678
   0.939857
   0.545296
   0.331043

实际上,将cellfun 与匿名函数一起使用就像变相的循环,但它应该比循环更有效,因为您要附加到数组。

【讨论】:

    【解决方案3】:

    一种解决方案:尝试X = {[X1(:); X2(:)]},然后使用cell2mat

    两种解决方案:

    clear
    X1 = rand(2,3);  
    X2 = rand(3,4);
    X3 = rand(4,5);
    X = {X1, X2, X3};
    XX = cellfun(@(x) x(:)', X, 'UniformOutput', false);
    cell2mat(XX)
    

    【讨论】:

    • 可能有超过 2 个矩阵。我展示了两个只是为了展示这个例子。我更新了问题以表明单元格数组中可以有 n 个这样的矩阵。
    • 谢谢!我冒昧地对这些进行了基准测试,并更新了问题。
    【解决方案4】:

    如果你使用 Octave,你可以使用 cellindexmat:

    unrolled_X = vertcat(cellindexmat(X, ':'){:});
    

    【讨论】:

    • 这是 Octave 5.2.0 上最快的!
    猜你喜欢
    • 2015-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多