【问题标题】:Concatenating views in NumPy在 NumPy 中连接视图
【发布时间】:2021-01-14 18:19:28
【问题描述】:

通过切片/索引索引NumPy 数组创建了一个轻量级视图(不复制数据)并允许分配给原始数组的元素。即

import numpy as np
a = np.array([1, 2, 3, 4, 5])
a[2:4] = [6, 7]
print(a)
# [1 2 6 7 5]

但是多个视图怎么样,我如何连接它们以创建一个更大的视图,仍然分配给原始的第一个数组。例如。虚函数concatenate_views(...):

a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
concatenate_views((a[1:3], a[4:6], a[7:9])) = [11, 12, 13, 14, 15, 16]
print(a)
# should print [1 11 12 4 13 14 7 15 16 10]

当然,我可以为它查看的每个视图创建一个索引列表,只需将切片转换为索引,然后连接这些索引。这样,我将获得连接视图的所有索引,并可以使用这些索引来创建组合视图。但这不是我想要的。我希望 NumPy 保留切片表示的概念,因为所有切片都可能很长,并且将这些切片转换和存储为索引效率低下。我希望 NumPy 了解所有连接视图的底层切片,以便在内部实现切片范围的简单循环。

也可以很好地概括这个问题。不仅连接视图,还允许形成任意的切片/索引操作树,例如连接视图,然后应用一些切片,然后索引,然后切片,然后再次连接。还有N维切片/索引。 IE。所有花哨的东西都可以用单个未连接的视图来完成。

级联视图的要点只是效率。当然,我们可以通过整数索引的 N 维数组(坐标,如网格网格)来表示任何视图或切片操作,然后可以使用该数组来制作源数组的视图。但是,如果 numpy 可以保留切片源集而不是整数数组的概念,那么首先它将是轻量级的(内存消耗少得多),其次不是从内存中读取索引 numpy 可以更有效地循环(迭代)每个切片在C++ 循环中。

通过连接视图,我希望能够以有效的方式将任何 numpy 操作(如 np.mean(...))应用于组合视图。

下面描述了基于 2D 示例的 N-D 切片的连接视图的完整过程:

    1 Step described below:
    
    2D array slicing using 3 slices for each axis
    
    a,b,c - sizes of "slices" along axis 0
    d,e,f - sizes of "slices" along axis 1
    
    Each "slice" - is either slice(start, stop, step) or 1D array of integer indexes
            
      d e f
     .......
    a.0.1.2.
     .......
    b.3.4.5.
     .......
    c.6.7.8.
     .......

    Above 0 1 2 3 4 5 6 7 8 mean not a single integer but some 2D sub-array.
    Dots (`.`) also mean some 2D sub-arrays.
    
    Sub-views shapes:
    0:(a, d), 1:(a, e), 2:(a, f)
    3:(b, d), 4:(b, e), 5:(b, f)
    6:(c, d), 7:(c, e), 8:(c, f)

    Final aggregated (concatenated) view shape:
    ((a + b + c), (d + e + f))
    containing 2D array
    012
    345
    678
    
    There can be more than one Steps, each next Step applies new sequence of slicing
    to the final view obtained on previous Step. Each Step has different set of sizes
    of slices and different amount of slices per each dimension.
    In general each next Step reduces number of total elements, except the case
    when slices or indexes overlap then you may get more elements but with duplicates.

【问题讨论】:

  • 那些总是等间隔的切片吗?
  • @Divakar 它可以简化原因的解决方案。但我想要一些通用功能。这允许形成任何切片树。 IE。将切片应用于切片。将索引应用于切片等。与单个未连接视图可以完成的相同花哨的东西。还有 N-D 切片。
  • 然后,只需创建一个初始化为零的布尔掩码,并迭代地为这些切片分配 True。最后做a[mask] = new_values
  • 在我上面的问题中,我提到了这种情况,说我真的希望 NumPy 保留源切片的概念,而不是转换为普通索引或布尔值,因为数组可能非常大,而且索引不仅消耗大量内存,但处理速度也很慢。如果 NumPy 保持原始切片,那么它可以在内部组织切片范围内的可能递归循环。所以切片允许在 NumPy 中使用快速和轻量级的算法。
  • 掩码没有整数索引那么糟糕,与等长的 int 数组相比,布尔掩码的内存开销只有 1/8。不要在没有尝试对您的情况有效的情况下就下结论。最后,如果你有长切片,你可能想简单地迭代和分配。

标签: python arrays numpy reference slice


【解决方案1】:

您可以使用np.r_ 连接切片对象并分配回索引数组:

a[np.r_[1:3, 4:6, 7:9]] = [11, 12, 13, 14, 15, 16]

print(a)
array([ 1, 11, 12,  4, 13, 14,  7, 15, 16, 10])

更新

根据您的更新,我认为您可能需要以下内容:

from itertools import islice

it = iter([11, 12, 13, 14, 15, 16])
for s in slice(1,3), slice(4,6), slice(7,9):
    a[s] = list(islice(it, s.stop-s.start))

print(a)
array([ 1, 11, 12,  4, 13, 14,  7, 15, 16, 10])

【讨论】:

  • 我用大段更新了我的问题,说我不想将切片转换为索引以提高效率,以便 numpy 了解底层切片并在源切片范围内创建简单的内部循环。跨度>
  • 也可以制作切片树吗? IE。应用切片切片。还有 N-D 切片。等等所有可以与常规未连接视图一起使用的花哨的东西。
  • 更新@arty 让我知道这是否是你所追求的
  • 是的,这是一个不错的方法,基本上,如果我有一个切片/索引操作树,那么我可以一个一个地手动应用这个切片。当还结合使用索引和切片时,这仍然可能效率低下,而不仅仅是单独切片。算法也应该以某种方式考虑切片。
  • @Arty。您想要的所有东西都在一个操作中编码:将两个视图组合到另一个视图中。然后,该视图本身就是一个数组,您可以做任何您想做的事情:重塑、索引或与其他切片视图组合。
【解决方案2】:

只有在 dtypes、strides 和 offsets 方面是连续的时,您才能连接视图。这是一种检查方法。这种方式可能是不完整的,但它说明了它的要点。基本上,如果视图共享一个基础,并且步幅和偏移量对齐以便它们在同一个网格上,则可以连接。

本着 TDD 的精神,我将使用以下示例:

x = np.arange(24).reshape(4, 6)

我们(或至少我)希望以下内容可以连接:

a, b = x[:, :4], x[:, 4:]        # Basic case
a, b = x[:, :4:2], x[:, 4::2]    # Strided
a, b = x[:, :4:2], x[:, 2::2]    # Strided overlapping
a, b = x[1:2, 1:4], x[2:4, 1:4]  # Stacked

# Completely reshaped:
a, b = x.ravel()[:12].reshape(3, 4), x.ravel()[12:].reshape(3, 4)
# Equivalent to
a, b = x[:2, :].reshape(3, 4), x[2:, :].reshape(3, 4)

我们确实希望以下内容可以连接:

a, b = x, np.arange(12).reshape(2, 6)   # Buffer mismatch
a, b = x[0, :].view(np.uint), x[1:, :]  # Dtype mismatch
a, b = x[:, ::2], x[:, ::3]             # Stride mismatch
a, b = x[:, :4], x[:, 4::2]             # Stride mismatch
a, b = x[:, :3], x[:, 4:]               # Overlap mismatch
a, b = x[:, :4:2], x[:, 3::2]           # Overlap mismatch
a, b = x[:-1, :-1], x[1:, 1:]           # Overlap mismatch
a, b = x[:-1, :4], x[:, 4:]             # Shape mismatch

以下可以被解释为可连接的,但在这种情况下不会:

a, b = x, x[1:-1, 1:-1]

这个想法是一切(dtype、strides、offsets)都必须完全匹配。视图之间只允许一个轴偏移量不同,只要它距另一个视图的边缘不超过一个步幅即可。唯一可能的例外是当一个视图完全包含在另一个视图中时,但我们将在这里忽略这种情况。如果我们对偏移量和步幅使用数组操作,则泛化到多个维度应该非常简单。

def cat_slices(a, b):
    if a.base is not b.base:
        raise ValueError('Buffer mismatch')
    if a.dtype != b.dtype:  # I don't thing you can use `is` here in general
        raise ValueError('Dtype mismatch')

    sa = np.array(a.strides)
    sb = np.array(b.strides)

    if (sa != sb).any():
        raise ValueError('Stride mismatch')

    oa = np.byte_bounds(a)[0]
    ob = np.byte_bounds(b)[0]

    if oa > ob:
        a, b = b, a
        oa, ob = ob, oa

    offset = ob - oa

    # Check if you can get to `b` from a by moving along exactly one axis
    # This part works consistently for arrays with internal overlap
    div = np.zeros_like(sa)
    mod = np.ones_like(sa)  # Use ones to auto-flag divide-by zero
    np.divmod(offset, sa, where=sa.astype(bool), out=(div, mod))

    zeros = np.flatnonzero((mod == 0) & (div >= 0) & (div <= a.shape))

    if not zeros.size:
        raise ValueError('Overlap mismatch')

    axis = zeros[0]

    check_shape = np.equal(a.shape, b.shape)
    check_shape[axis] = True

    if not check_shape.all():
        raise ValueError('Shape mismatch')

    shape = list(a.shape)
    shape[axis] = b.shape[axis] + div[axis]

    start = np.byte_bounds(a)[0] - np.byte_bounds(a.base)[0]

    return np.ndarray(shape, dtype=a.dtype, buffer=a.base, offset=start, strides=a.strides)

此函数无法处理的一些事情:

  • 合并标志
  • 广播
  • 处理彼此完全包含但具有多轴偏移的数组
  • 负增长

但是,您可以检查它是否返回了上述所有情况的预期视图(和错误)。在更适合生产的版本中,我可以设想这种增强 np.concatenate,因此对于失败的情况,它只会复制数据而不是引发错误。

【讨论】:

  • 非常好的一段代码。绝对像这样的东西应该包含在numpy 库中,至少包含在一些 util 子目录中。为此,numpy 不需要在其底层表示中引入任何新内容。但这仅部分解决了我的任务,只是兼容视图的子案例。我可能已经明白,我的任务在一般情况下是无法解决的。我真正想要解决的是下一个更一般的问题 - 假设我们有任何巨大的切片/索引混合树,切片可以第二次应用于切片+索引等。
  • @Arty。就像任何树一样,您将链接操作。除非你举一个具体的例子,否则很难讨论。
  • 所以我希望这棵树在某个地方被 numpy 记住为某个复杂视图的属性,以便稍后在需要遍历该视图的所有元素的任何操作中只对这个进行嵌套循环树元素,因此迭代原始数组的元素,而无需将此树转换为纯整数索引数组。对切​​片树进行迭代可能会或可能不会更有效,尽管在许多情况下它可能比转换索引数组更有效,而且还节省内存。
  • 看起来你想要这样的东西,但是在 tensorflow 中
  • 首先,操作并不总是可链接的,想象一下应用一些聚合函数,比如计算平均值。您必须自己计算 Tree 中每个切片的均值,并通过某些公式手动组合该均值。而不是 numpy 正确地为你做这件事。其次,您的树总体上可能很大,并且具有非常小的元素,例如范围内元素很少的切片或多个整数索引。那么在纯 Python 循环中自己迭代会比 numpy 的 C++ 效率低。
猜你喜欢
  • 1970-01-01
  • 2016-03-22
  • 1970-01-01
  • 2011-10-10
  • 2013-01-11
  • 2014-05-09
  • 1970-01-01
  • 2020-08-02
  • 1970-01-01
相关资源
最近更新 更多