如何将二维数组复制到第三维,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 个这样的副本。作用于arrN=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_to2D 输入数组中简单地生成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

作为一个视图,将 N3 增加到 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)。如果您将BB.T 转置,则它与预期的输出匹配。 @Mr.F - 你是对的。这将沿第一个维度进行广播,因此B[0], B[1],... 将为您提供正确的切片,我会争辩说这比使用B[:,:,0], B[:,:,1] 等更容易输入。 它可能更容易输入,但例如,如果您使用图像数据执行此操作,则在很大程度上是不正确的,因为几乎所有算法都希望将线性代数的约定用于 2D 切片像素通道。很难想象一个应用程序从 2D 数组开始,以某种约定处理行和列,然后您希望同一事物的多个副本延伸到一个新轴,但突然您希望第一个轴将含义更改为成为新的轴心…… @Mr.F - 哦,当然。我无法猜测您将来想要使用 3D 矩阵的应用程序。话虽如此,这一切都取决于应用程序。 FWIW,我更喜欢B[:,:,i],这是我习惯的。【参考方案7】:

另一种方法是使用numpy.dstack。假设要重复矩阵anum_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

二维树状数组及(不会用到的)三维树状数组

如何将二维数组直接添加到哈希图中? [复制]

PHP:数组——二维转一维,二维转三维,将特定的数据作为键名

C语言数组为啥按行优先存储

数组再学习(一维二维三维)