Numpy 将非连续数组的连续部分视为更大尺寸的 dtype

Numpy view contiguous part of non-contiguous array as dtype of bigger size

我试图从超长字符数组生成一个三元组数组(即连续的三个字母组合):

# data is actually load from a source file
a = np.random.randint(0, 256, 2**28, 'B').view('c')

由于复制效率不高(并且会产生缓存未命中等问题),我直接使用stride tricks生成了trigram:

tri = np.lib.stride_tricks.as_strided(a, (len(a) - 2, 3), a.strides * 2)

这会生成一个形状为 (2**28 - 2, 3) 的三元组列表,其中每一行都是一个三元组。现在我想将 trigram 转换为字符串列表(即 S3),以便 numpy 更“合理地”显示它(而不是单个字符)。

tri = tri.view('S3')

它给出了例外:

ValueError: To change to a dtype of a different size, the array must be C-contiguous

我理解通常数据应该是连续的以创建一个有意义的视图,但是这个数据在“它应该在的地方”是连续的:每三个元素都是连续的。

所以我想知道如何view非连续np.ndarray中的连续部分作为更大尺寸的数据类型?更“标准”的方式会更好,同时也欢迎 hackish 方式。 shapestride 似乎可以自由设置 np.lib.stride_tricks.as_strided,但我不能强制 dtype 是什么,这就是问题所在。

编辑

非连续数组可以通过简单的切片来制作。例如:

np.empty((8, 4), 'uint32')[:, :2].view('uint64')

将抛出与上面相同的异常(虽然从内存的角度来看我应该能够做到这一点)。这种情况比我上面的例子更常见。

如果您可以访问从中派生出非连续数组的连续数组,通常应该可以解决此限制。

比如你的八卦可以这样得到:

>>> a = np.random.randint(0, 256, 2**28, 'B').view('c')
>>> a
array([b')', b'\xf2', b'\xf7', ..., b'\xf4', b'\xf1', b'z'], dtype='|S1')
>>> np.lib.stride_tricks.as_strided(a[:0].view('S3'), ((2**28)-2,), (1,))
array([b')\xf2\xf7', b'\xf2\xf7\x14', b'\xf7\x14\x1b', ...,
       b'\xc9\x14\xf4', b'\x14\xf4\xf1', b'\xf4\xf1z'], dtype='|S3')

事实上,这个例子表明我们所需要的只是内存缓冲区底部的连续 "stub" 用于视图转换,因为之后,因为 as_strided 不做很多检查我们基本上是免费的为所欲为。

看来我们总是可以通过切片到大小为 0 的数组来获得这样的存根。对于你的第二个例子:

>>> X = np.empty((8, 4), 'uint32')[:, :2]
>>> np.lib.stride_tricks.as_strided(X[:0].view(np.uint64), (8, 1), X.strides)
array([[140133325248280],
       [             32],
       [       32083728],
       [       31978800],
       [              0],
       [       29686448],
       [             32],
       [       32362720]], dtype=uint64)

从 numpy 1.23.0 开始,您将能够完全按照自己的意愿行事,而无需跳过额外的环节。我已将 PR#20722 添加到 numpy 以解决几乎这个确切的问题。这个想法是,如果你的新 dtype 小于当前的,你可以毫无问题地扩展一个单元或连续的轴。如果新的数据类型更大,你可以缩小一个连续的轴。

随着更新,您的代码开箱即用:

>>> a = np.random.randint(0, 256, 2**28, 'B').view('c')
>>> a
array([b'\x19', b'\xf9', b'\r', ..., b'\xc3', b'\xa3', b'{'], dtype='|S1')
>>> tri = np.lib.stride_tricks.as_strided(a, (len(a)-2,3), a.strides*2)
>>> tri.view('S3')
array([[b'\x9dB\xeb'],
       [b'B\xebU'],
       [b'\xebU\xa4'],
       ...,
       [b'-\xcbM'],
       [b'\xcbM\x97'],
       [b'M\x97o']], dtype='|S3')

数组必须具有单位维度或在最后一个轴上是连续的,这在您的情况下是正确的。


我还添加了 PR#20694 to introduce slicing to the np.char 模块。如果该 PR 按原样被接受,您将能够:

>>> np.char.slice_(a.view(f'U{len(a)}'), step=1, chunksize=3)