关于 ndarrays 的形状不可知切片

Posted

技术标签:

【中文标题】关于 ndarrays 的形状不可知切片【英文标题】:On shape-agnostic slicing of ndarrays 【发布时间】:2014-04-27 13:19:30 【问题描述】:

在这篇文章中,我使用术语 slice 来指代 n 维数组 A 的子数组 B_i,这样 size(B_i, d)是 1,对于某个维度 dAsize(A, d) 这样的切片组成,沿维度 d 连接。

例如,如果ndims(A) 是6,d 是3,那么形式的表达式

A(:, :, i, :, :, :)

1:size(A, d) 中的i 表示构成A 的所有切片(沿维度d)。

A(:, :, i, :, :, :) 这样的表达式的问题在于,它不能象征性地推广到在维数不同于 6 的数组中沿维数不同于 3 的切片。例如,要获得 @ 987654336@ 沿维度 2 的切片,需要一个不同的表达式,A(:, i, :, :, :, :)。这意味着此类表达式在不知道要从中提取切片的某个数组的形状的代码中是无用的。

下面的函数是我的 matlab-noob 尝试实现与形状无关的切片。 (名称slice 已经被占用,因此我将函数称为hslice,是hyperslice 的缩写。)该函数的策略是将输入数组重新整形为合适的3-d 数组,沿重新整形后取所需切片数组的 second 维度,并将结果重塑为原始输入数组的切片形状。

function out = hslice(ndarray, d, i)
    sz = size(ndarray);
    pfx = sz(1:d-1);    % dimensions before d
    sfx = sz(d+1:end);  % dimensions after d

    tmp = reshape(ndarray, prod(pfx), sz(d), prod(sfx));
    out = reshape(tmp(:, i, :), [pfx 1 sfx]);
end

是否有内置的,或者至少是更有效的方法来实现相同的结果(以与形状无关的方式)?

【问题讨论】:

为什么你认为你的方法效率不高?您是否尝试过将其与显式切片进行比较? 我其实很喜欢你的,因为在切片之前没有重写任何数据。 @Shai:我主要希望为此提供一些低级的内置(并且可能非常有效)功能;另外,由于我对 MATLAB 中的高效功能知之甚少,因此我的假设是我的任何实现都可能效率低下(尽管这次我很幸运)。 IMO,分析(而不仅仅是在 MATLAB 中)很棘手。在这种情况下,I 可以想到的任何分析方案(作为 MATLAB 菜鸟)都需要将显式切片表达式放在函数中,这可能会显着扭曲比较。 IOW,我不相信 可以对此进行任何分析。 是的,Matlab 的性能很难掌握,因为操作成本可能相差很大,所以O(n) 分析通常不如弄清楚如何用快速构建的 Matlab 来“矢量化”东西有用-ins。但是,相对而言,您使用的所有这些索引、重塑和复制操作都非常便宜。在函数中这样做很好:因为您正在创建一个新数组,所以不可能进行就地优化,并且写时复制意味着在函数之间传递数组很便宜。您的方法应该可以正常运行。 哦,是的 - reshape() 特别快,因为它实际上并没有重新排列内存中的原始原始数据:它只是扭曲了 Matlab 内部标题中的“维度”信息数据结构(“mxArray”),并保留指向原始基础原始数据的指针。因此,您在此处的实现只是对原始数据进行一次提取/复制,就像静态 A(:, :, i, :, ...) 索引表达式一样。 【参考方案1】:

是的。您可以使用取消引用的元胞数组和“逗号分隔列表”之间的等价性,并且可以使用 char ':' 作为索引来动态构造任意维度的 A(:, :, :, i, :, ...) 调用。

function out = slice(A, ix, dim)

subses = repmat(':', [1 ndims(A)]);
subsesdim = ix;
out = A(subses:);

这将完全概括,并将执行与原始静态 A(:, :, i, :, ...) 表达式完全相同的“切片”索引操作,除了设置这些字符串的开销之外。

或者,如果您愿意,您可以使用sprintfA(:, :, i, :, ...) 构造为字符串,然后在其上调用eval()。但我想尽可能避免eval

请注意,您的原始实现使用的是快速操作,并且应该执行得很好,大约和这个一样快。我只是发布这个,因为我认为它非常易读,确实回答了你最初提出的问题,并且它可以应用于其他有用的东西。

分配给切片

您也可以使用相同的下标单元格技术作为左值分配给数组的切片。但是,您不能直接重用 slice 函数,因为它返回数组的提取子集,而不是左值引用。所以你可以创建一个非常相似的函数来完成赋值。

function A = slice_assign(A, ix, dim, B)
%SLICE_ASSIGN Assign new values to a "slice" of A

subses = repmat(':', [1 ndims(A)]);
subsesdim = ix;
A(subses:) = B;

在实践中,您可能还需要一个仅返回元胞数组中计算索引的函数,这样您就可以随身携带这些索引并重复使用它们进行赋值和引用。

function out = slice_subs(A, ix, dim)

subses = repmat(':', [1 ndims(A)]);
subsesdim = ix;
out = subses;

【讨论】:

非常聪明,而且速度很快。 谁知道你能做到A(':',':',2,':')?我只想重申这个想法有多酷。 @chappjc:谢谢!查看subsrefsubsasgnsubsindex 的规格和文档。它们是覆盖用户定义类的(). 行为的钩子,您可以从它们的签名和设计中推断出很多信息。我就是这样学会了这个技巧的。 (但请注意,如果要与其他操作保持一致,它们可能很难以完全正确的方式使用。这是高级魔法。)“外部接口”文档也可以为您提供一些其他想法。 其实我是从Amro's old post找到的。这促使我在整个代码库中搜索 'builtin'! P.S.如果您要覆盖subsref,您可以为普通类不支持的索引类型定义行为。例如。你可以让 支持任意字符串输入而不仅仅是':',或者让. 将数字作为参数。我不久前使用它使 成为内存中关系类的 RESTRICT 操作,而. 成为支持按名称或位置索引列的 PROJECT。例如。 selection = r''WHERE trade_date > today & price < 14'';.【参考方案2】:

您可以尝试permutesetdiff 将该维度移动到一致的位置:

function out = hslice(ndarray, d, i)
subdims = setdiff(1:ndims(ndarray),d);
sz = size(ndarray);
outsz = sz(subdims);
order = [d subdims];
ndarray = permute(ndarray,order);
out = reshape(ndarray(i,:),outsz);
end

例如:

d = 3; i = 2;
nd = randi(23,3,3,3,2);
out = hslice(nd,d,i);   % out = squeeze(nd(:,:,i,:)) for d=3

但是,数据是在切片之前重写的,而不是问题中的代码。所以,我实际上会选择OP!

【讨论】:

您可以考虑来回使用shiftdim 使setdiff 冗余。 是的 - setdiff 是一个 M 代码函数(与“内置”相反),旨在用于两个输入都具有多个成员的较大数据集。它有足够的开销,以至于在这里使用它可能太慢了。当输入之一是标量或较小时,ismember 也是如此。使用subdims = 1:ndims(ndarray); subdims(d) = [];find(x == y) 可能更合适。 @AndrewJanke 感谢在这种情况下替代setdiff。尽管如此,permute 可能比setdiff 慢一个数量级,除非numel(ndarray) 非常小。这就是为什么我倾向于使用在实际切片操作之前不会移动任何数据的解决方案(例如yours)。 非常好;我完全没有接受permute 的费用。我可能只是在小范围内对setdiff 过敏,因为它过去经常让我绊倒。

以上是关于关于 ndarrays 的形状不可知切片的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用数组作为索引会改变多维 ndarray 的形状?

无法将 NumPy 数组转换为张量(不支持的对象类型 numpy.ndarray)错误

Python机器学习入门——科学计算库(Numpy)

Python科学计算库Numpy

线性模型: numpy.ndarray 大小已更改,可能表示二进制不兼容。预期来自 C 标头的 88,从 PyObject 得到 80”

MXNet中NDArray中的广播运算条件