将2D数组复制到3维,N次(Python)


106

我想将一个numpy的2D数组复制到第三维。例如,给定(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)

Answers:


146

也许最干净的方法是使用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]]

话虽如此,您通常可以通过使用broadcast避免完全重复阵列。例如,假设我要添加一个(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的维度“广播”出去,给了我shape的结果(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]]

广播是一项非常强大的技术,因为它避免了在内存中创建输入数组的重复副本所涉及的额外开销。


*尽管为清楚起见,我将它们包括在内,但实际上并不需要None索引c-您也可以这样做a[..., None] + c,即(2, 2, 1)针对(3,)数组广播数组。这是因为,如果其中一个数组的尺寸小于另一个数组的尺寸,则仅两个数组的尾随尺寸需要兼容。举一个更复杂的例子:

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]。每个三维切片都是原始2D数组的副本。单看就不太明显了print(b)
2015年

None和np.newaxis有什么区别?当我测试它时,它给出了相同的结果。
monolith

1
@wedran完全相同- np.newaxis只是None
ali_m

27

另一种方法是使用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我从来没有做过基准测试……我主要是出于完整性的考虑。时间会很有趣,看看它们之间的差异。
rayryeng '16

1
我只是做了img = np.dstack([arr] * 3)并且工作正常!谢谢
thanos.a

1
图我可以提出一个可观的输出以提高效率。作为旧职位,人们可能会错过这一点。在此问答中添加了解决方案。
Divakar

1
恕我直言,这是最易读的解决方案,但是最好将它与其他方法进行比较以进行基准测试。
mrgloom

16

使用视图并获得免费的运行时!将通用n-dim数组扩展为n+1-dim

NumPy中1.10.0引入后,我们可以利用它numpy.broadcast_to来简单地生成输入数组的3D视图2D。好处将是没有额外的内存开销和几乎免费的运行时。这在数组很大且我们可以使用视图的情况下至关重要。同样,这将适用于一般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不变,并且在时序单位上两者都可以忽略不计。因此,在内存和性能上都非常有效!


3
A=np.array([[1,2],[3,4]])
B=np.asarray([A]*N)

编辑@ Mr.F,以保留尺寸顺序:

B=B.T

这会为我带来N x 2 x 2的数组,例如以的任何值B.shape打印。如果与进行转置,则它与预期输出匹配。(N, 2, 2)NBB.T
2015年

@ Mr.F-你是对的。这将沿第一个维度进行广播,因此这样做B[0], B[1],...将为您提供正确的切片,我将说这是更容易键入而不是使用B[:,:,0], B[:,:,1]等等的类
。– rayryeng

键入可能会更容易,但是例如,如果您要对图像数据执行此操作,那将是非常不正确的,因为几乎所有算法都希望将线性代数的约定用于像素通道的2D切片。很难想象有一个应用程序,其中从2D数组开始,以一定的约定处理行和列,然后又想将同一事物的多个副本扩展到新的轴上,但是突然之间,您希望第一个轴将含义更改为成为新的轴...
ely

@F先生-哦,当然。我无法猜测将来您将要使用哪些3D矩阵。话虽如此,这完全取决于应用程序。FWIW,我更喜欢B[:,:,i]我习惯的。
rayryeng 2015年

2

这是一个广播示例,可以完全满足您的要求。

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]


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]]])
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.