如何将二维数组复制到第三维,N次?
Posted
技术标签:
【中文标题】如何将二维数组复制到第三维,N次?【英文标题】:How to copy a 2D array into a 3rd dimension, N times? 【发布时间】:2015-11-17 06:19:45 【问题描述】:我想将一个 numpy 二维数组复制到第三维。例如,给定 2D numpy 数组:
import numpy as np
arr = np.array([[1, 2], [1, 2]])
# arr.shape = (2, 2)
将其转换为 3D 矩阵,在新维度中具有 N 个这样的副本。作用于arr
和N=3
,输出应该是:
new_arr = np.array([[[1, 2], [1,2]],
[[1, 2], [1, 2]],
[[1, 2], [1, 2]]])
# new_arr.shape = (3, 2, 2)
【问题讨论】:
只需 new_arr = np.array([a]*3) 其中 a = [[1,2],[1,2]] 对我有用。 @JStrahl:如果 a 的形状为 (r,c),则np.array([a]*3)
会生成一个形状为 (3,r,c) 的数组,而我假设 OP 想要 (r,c, 3).
@mins OP 想要 (3, r, c),如 new_arr.shape = (3,2,2) 所示。所以这行得通。
作为一个询问如何复制和粘贴并使用 [[1, 2], [1, 2]] 作为示例的问题,这很糟糕。我希望我能投反对票。
@Symbol1 我没听明白 - 有什么问题?
【参考方案1】:
Einops 提供了一种更方便、更易读的方式来编写重复:
y = einops.repeat(x, 'i j -> 3 i j')
此代码在二维 numpy 数组中再添加一个长度为 3 的轴。
【讨论】:
【参考方案2】:现在也可以使用np.tile 来实现,如下所示:
import numpy as np
a = np.array([[1,2],[1,2]])
b = np.tile(a,(3, 1,1))
b.shape
(3,2,2)
b
array([[[1, 2],
[1, 2]],
[[1, 2],
[1, 2]],
[[1, 2],
[1, 2]]])
【讨论】:
【参考方案3】:使用视图并获得免费的运行时!将通用 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
对计时没有任何改变,而且两者在计时单位上都可以忽略不计。因此,在内存和性能上都很高效!
【讨论】:
【参考方案4】:可能最干净的方法是使用np.repeat
:
a = np.array([[1, 2], [1, 2]])
print(a.shape)
# (2, 2)
# indexing with np.newaxis inserts a new 3rd dimension, which we then repeat the
# array along, (you can achieve the same effect by indexing with None, see below)
b = np.repeat(a[:, :, np.newaxis], 3, axis=2)
print(b.shape)
# (2, 2, 3)
print(b[:, :, 0])
# [[1 2]
# [1 2]]
print(b[:, :, 1])
# [[1 2]
# [1 2]]
print(b[:, :, 2])
# [[1 2]
# [1 2]]
话虽如此,您通常可以使用broadcasting 避免完全重复您的数组。例如,假设我想添加一个(3,)
向量:
c = np.array([1, 2, 3])
到a
。我可以在第三维复制a
的内容3 次,然后在第一维和第二维复制c
的内容两次,这样我的两个数组都是(2, 2, 3)
,然后计算它们的总和。但是,这样做更简单、更快捷:
d = a[..., None] + c[None, None, :]
这里,a[..., None]
的形状为 (2, 2, 1)
,c[None, None, :]
的形状为 (1, 1, 3)
*。当我计算总和时,结果会沿着尺寸 1 的尺寸“广播”出来,给我一个形状为 (2, 2, 3)
的结果:
print(d.shape)
# (2, 2, 3)
print(d[..., 0]) # a + c[0]
# [[2 3]
# [2 3]]
print(d[..., 1]) # a + c[1]
# [[3 4]
# [3 4]]
print(d[..., 2]) # a + c[2]
# [[4 5]
# [4 5]]
广播是一种非常强大的技术,因为它避免了在内存中创建输入数组的重复副本所涉及的额外开销。
* 尽管我为了清楚起见将它们包括在内,但实际上并不需要 c
中的 None
索引 - 您也可以使用 a[..., None] + c
,即针对 (3,)
数组广播 (2, 2, 1)
数组。这是因为如果其中一个数组的维数比另一个数组少,那么只有两个数组的 尾随 维数需要兼容。举一个更复杂的例子:
a = np.ones((6, 1, 4, 3, 1)) # 6 x 1 x 4 x 3 x 1
b = np.ones((5, 1, 3, 2)) # 5 x 1 x 3 x 2
result = a + b # 6 x 5 x 4 x 3 x 2
【讨论】:
要验证这确实给出了正确的结果,您还可以打印出b[:,:,0]
、b[:,:,1]
和b[:,:,2]
。每个第三维切片都是原始二维数组的副本。这并不像 print(b)
那样明显。
None 和 np.newaxis 有什么区别?当我测试它时,它给出了相同的结果。
@wedran 它们完全一样——np.newaxis
只是None
的别名【参考方案5】:
这是一个完全符合要求的广播示例。
a = np.array([[1, 2], [1, 2]])
a=a[:,:,None]
b=np.array([1]*5)[None,None,:]
然后b*a
是期望的结果,(b*a)[:,:,0]
产生array([[1, 2],[1, 2]])
,这是原始的a
,(b*a)[:,:,1]
也是如此。
【讨论】:
【参考方案6】:A=np.array([[1,2],[3,4]])
B=np.asarray([A]*N)
编辑@Mr.F,以保留维度顺序:
B=B.T
【讨论】:
这会为我生成一个 N x 2 x 2 数组,例如对于N
的任何值,B.shape
打印 (N, 2, 2)
。如果您将B
与B.T
转置,则它与预期的输出匹配。
@Mr.F - 你是对的。这将沿第一个维度进行广播,因此B[0], B[1],...
将为您提供正确的切片,我会争辩说这比使用B[:,:,0], B[:,:,1]
等更容易输入。
它可能更容易输入,但例如,如果您使用图像数据执行此操作,则在很大程度上是不正确的,因为几乎所有算法都希望将线性代数的约定用于 2D 切片像素通道。很难想象一个应用程序从 2D 数组开始,以某种约定处理行和列,然后您希望同一事物的多个副本延伸到一个新轴,但突然您希望第一个轴将含义更改为成为新的轴心……
@Mr.F - 哦,当然。我无法猜测您将来想要使用 3D 矩阵的应用程序。话虽如此,这一切都取决于应用程序。 FWIW,我更喜欢B[:,:,i]
,这是我习惯的。【参考方案7】:
另一种方法是使用numpy.dstack
。假设要重复矩阵a
num_repeats
次:
import numpy as np
b = np.dstack([a]*num_repeats)
诀窍是将矩阵 a
包装到一个包含单个元素的列表中,然后使用 *
运算符将该列表中的元素复制 num_repeats
次。
例如,如果:
a = np.array([[1, 2], [1, 2]])
num_repeats = 5
这会在第三维中重复[1 2; 1 2]
的数组5 次。验证(在 IPython 中):
In [110]: import numpy as np
In [111]: num_repeats = 5
In [112]: a = np.array([[1, 2], [1, 2]])
In [113]: b = np.dstack([a]*num_repeats)
In [114]: b[:,:,0]
Out[114]:
array([[1, 2],
[1, 2]])
In [115]: b[:,:,1]
Out[115]:
array([[1, 2],
[1, 2]])
In [116]: b[:,:,2]
Out[116]:
array([[1, 2],
[1, 2]])
In [117]: b[:,:,3]
Out[117]:
array([[1, 2],
[1, 2]])
In [118]: b[:,:,4]
Out[118]:
array([[1, 2],
[1, 2]])
In [119]: b.shape
Out[119]: (2, 2, 5)
最后我们可以看到矩阵的形状是2 x 2
,在第三维有5个切片。
【讨论】:
这与reshape
相比如何?快点?给出相同的结构?它绝对更整洁。
@AnderBiguri 我从来没有做过基准测试......我把它放在这里主要是为了完整性。计时并查看差异会很有趣。
我刚刚做了 img = np.dstack([arr] * 3) 并且工作正常!谢谢
我想我可以提出一个查看输出以提高效率。作为一个旧帖子,人们可能会错过这一点。在此问答中添加了解决方案。以上是关于如何将二维数组复制到第三维,N次?的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode - 2022 - 将一维数组变成二维数组 - Java