为什么将大小为 1 的维度插入 numpy 数组会使其“连续”标志无效



考虑这个数组:

In [1]: a = numpy.array([[1,2],[3,4]], dtype=numpy.uint8)
In [2]: a.strides
Out[2]: (2, 1)
In [3]: a.flat[:]
Out[3]: array([1, 2, 3, 4], dtype=uint8)
In [4]: a.flags['C_CONTIGUOUS']
Out[4]: True
In [5]: numpy.getbuffer(a)[:]
Out[5]: 'x01x02x03x04'

目前为止,一切都好。 但是,请注意,当我创建该数组的视图时会发生什么,其中插入大小为 1 的维度:

In [6]: b = a[:, numpy.newaxis, :] # Insert dimension
In [7]: b.strides
Out[7]: (2, 0, 1)
In [8]: b.flat[:]
Out[8]: array([1, 2, 3, 4], dtype=uint8)
In [9]: b.flags['C_CONTIGUOUS']
Out[9]: False
In [10]: numpy.getbuffer(b)[:]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/.../<ipython-input-28-0127b71fae43> in <module>()
----> 1 numpy.getbuffer(b)[:]
TypeError: single-segment buffer object expected

什么给? 为什么numpy认为bC_CONTIGUOUS? 肯定是吧? 还是我错过了什么?

更新:@senderle指出numpy.reshape()按预期工作:

In [11]: b = numpy.reshape(a, (2,1,2))
In [12]: b.flags['C_CONTIGUOUS']
Out[12]: True

这很奇怪,我本来希望两种情况下的观点都是相同的。

这有点神秘,在看到hpaulj的评论之前,我什至深入研究了源代码。他观察到reshape和切片会产生不同的步幅newaxis显示了导致这种行为的原因 - 回答为什么问题。

但仍然存在一个问题,即是什么促使了这种行为——为什么问题。我没有任何确凿的证据证明这一点,但我的直觉是,numpy尽其所能快速确保切片是视图,而不是副本,在这种情况下,这需要打破 c 连续性。问题在于,可能很难确定如何将正确的步幅维度添加到具有奇怪步幅的数组中;在某些情况下甚至可能是不可能的。但是,您始终可以将步幅0的维度添加到任何数组。因此,numpy不是特殊的外壳,检查c连续性和其他可能的步幅安排,而是简单地增加了步幅0维度。

这种懒惰是有道理的,因为它更简单,并且(可能)因为它更快(尽管对 c 连续性的单次检查不会花费很多时间)。我认为简单解释在这里优先 - 在这种情况下,事情会很快变得非常复杂。

另一方面,reshape需要能够生成任意形状的数组,因此它尊重连续性要求,并在必要时进行复制。