使用视图并获得免费的运行时!将通用 n-dim 数组扩展为 n+1-dim
在NumPy 1.10.0 中引入,我们可以利用numpy.broadcast_to 在2D 输入数组中简单地生成3D 视图。好处是没有额外的内存开销和几乎免费的运行时间。在数组很大并且我们可以使用视图的情况下,这将是必不可少的。此外,这也适用于通用 n-dim 案例。
我会使用 stack 代替 copy,因为读者可能会将其与创建内存副本的数组复制混淆。
沿第一轴堆叠
如果我们想沿第一个轴堆叠输入arr,使用np.broadcast_to 创建3D 视图的解决方案是-
np.broadcast_to(arr,(3,)+arr.shape) # N = 3 here
沿第三/最后一个轴堆叠
要沿第三轴堆叠输入arr,创建3D 视图的解决方案是-
np.broadcast_to(arr[...,None],arr.shape+(3,))
如果我们确实需要内存副本,我们可以随时在此处附加.copy()。因此,解决方案将是 -
np.broadcast_to(arr,(3,)+arr.shape).copy()
np.broadcast_to(arr[...,None],arr.shape+(3,)).copy()
以下是这两种情况的堆叠工作方式,并显示了示例情况下的形状信息 -
# Create a sample input array of shape (4,5)
In [55]: arr = np.random.rand(4,5)
# Stack along first axis
In [56]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[56]: (3, 4, 5)
# Stack along third axis
In [57]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[57]: (4, 5, 3)
相同的解决方案可以将n-dim 输入扩展到n+1-dim 沿第一个轴和最后一个轴的视图输出。让我们探索一些更暗淡的情况 -
3D输入案例:
In [58]: arr = np.random.rand(4,5,6)
# Stack along first axis
In [59]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[59]: (3, 4, 5, 6)
# Stack along last axis
In [60]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[60]: (4, 5, 6, 3)
4D输入案例:
In [61]: arr = np.random.rand(4,5,6,7)
# Stack along first axis
In [62]: np.broadcast_to(arr,(3,)+arr.shape).shape
Out[62]: (3, 4, 5, 6, 7)
# Stack along last axis
In [63]: np.broadcast_to(arr[...,None],arr.shape+(3,)).shape
Out[63]: (4, 5, 6, 7, 3)
等等。
时间
让我们使用一个大样本 2D 案例并获取时间并验证输出是 view。
# Sample input array
In [19]: arr = np.random.rand(1000,1000)
让我们证明所提出的解决方案确实是一个视图。我们将沿第一个轴使用堆叠(沿第三个轴堆叠的结果非常相似)-
In [22]: np.shares_memory(arr, np.broadcast_to(arr,(3,)+arr.shape))
Out[22]: True
让我们来看看它实际上是免费的 -
In [20]: %timeit np.broadcast_to(arr,(3,)+arr.shape)
100000 loops, best of 3: 3.56 µs per loop
In [21]: %timeit np.broadcast_to(arr,(3000,)+arr.shape)
100000 loops, best of 3: 3.51 µs per loop
作为一个视图,将 N 从 3 增加到 3000 对计时没有任何改变,而且两者在计时单位上都可以忽略不计。因此,在内存和性能上都很高效!