关于 np.tile 和 numpy 广播的困惑
Confusion regarding np.tile and numpy broadcasting
假设我有一个二维 numpy
数组 A
,形状为 (m, n)
。我想创建一个形状为 (m, n, k)
的 3D 数组 B
,这样 B[:, :, l]
是任何切片 l
的 A
的副本。我可以想到两种方法来做到这一点:
np.tile(A, (m, n, k))
或
np.repeat(A[:, :, np.newaxis], k, axis=-1)
第一种方法似乎更简单,但我在 np.tile
的文档中提到:
Note: Although tile may be used for broadcasting, it is strongly
recommended to use numpy's broadcasting operations and functions.
为什么会这样,这也是 np.repeat
的问题吗?
我的另一个担心是,如果 m == n == k
,那么 np.tile()
是否会造成关于哪个轴被增强的混淆?
综上所述,我有两个问题:
- 为什么
np.tile
不是首选,并且 m == n == k
在某些情况下会导致意外行为?
- 以上两种方式,哪种方式在时间和内存方面效率更高?有没有比这两种方法更清洁或更有效的方法?
In [100]: A = np.arange(12).reshape(3,4)
使用重复在末尾添加新维度:
In [101]: B = np.repeat(A[:,:,np.newaxis], 2, axis=-1)
In [102]: B.shape
Out[102]: (3, 4, 2)
使用平铺和重复在开头添加新维度:
In [104]: np.tile(A, (2,1,1)).shape
Out[104]: (2, 3, 4)
In [105]: np.repeat(A[None,:,:], 2, axis=0).shape
Out[105]: (2, 3, 4)
如果我们在最后一个维度上用平铺指定 2 次重复,它会给出不同的形状
In [106]: np.tile(A, (1,1,2)).shape
Out[106]: (1, 3, 8)
请注意 tile
所说的关于在前面加上重复元组的维度大于形状。
但是,如果您按照评论中的描述在计算中使用了扩展数组,则无需进行完整的重复复制。可以使用正确形状的临时视图,利用 broadcasting
.
In [107]: A1=np.arange(12).reshape(3,4)
In [108]: A2=np.arange(8).reshape(4,2)
In [109]: A3=A1[:,:,None] + A2[None,:,:]
In [110]: A3.shape
Out[110]: (3, 4, 2)
In [111]: A3
Out[111]:
array([[[ 0, 1],
[ 3, 4],
[ 6, 7],
[ 9, 10]],
[[ 4, 5],
[ 7, 8],
[10, 11],
[13, 14]],
[[ 8, 9],
[11, 12],
[14, 15],
[17, 18]]])
与None
(np.newaxis
),数组视图为(3,4,1)和(1,4,2)形,一起广播为(3,4, 2).我可以在第二种情况下省略 None
,因为广播会自动添加。但尾随 None
是必需的。
In [112]: (A1[:,:,None] + A2).shape
Out[112]: (3, 4, 2)
添加一维数组(最后一维):
In [113]: (A1[:,:,None] + np.array([1,2])[None,None,:]).shape
Out[113]: (3, 4, 2)
In [114]: (A1[:,:,None] + np.array([1,2])).shape
Out[114]: (3, 4, 2)
两个基本广播步骤:
- 根据需要添加尺寸 1 尺寸作为开始(自动
[None,....]
)
- 将所有大小为 1 的维度扩展到共享大小
这组计算说明了这一点:
In [117]: np.ones(2) + np.ones(3)
ValueError: operands could not be broadcast together with shapes (2,) (3,)
In [118]: np.ones(2) + np.ones((1,3))
ValueError: operands could not be broadcast together with shapes (2,) (1,3)
In [119]: np.ones(2) + np.ones((3,1))
Out[119]:
array([[2., 2.],
[2., 2.],
[2., 2.]])
In [120]: np.ones((1,2)) + np.ones((3,1))
Out[120]:
array([[2., 2.],
[2., 2.],
[2., 2.]])
缺少中间维度
In [126]: np.repeat(A[:,None,:],2,axis=1)+np.ones(4)
Out[126]:
array([[[ 1., 2., 3., 4.],
[ 1., 2., 3., 4.]],
[[ 5., 6., 7., 8.],
[ 5., 6., 7., 8.]],
[[ 9., 10., 11., 12.],
[ 9., 10., 11., 12.]]])
有更多 'advanced' 的选择(但不一定更快):
In [127]: np.broadcast_to(A[:,None,:],(3,2,4))+np.ones(4)
你说你想扩展一个 shape-(m, n)
数组和一个 shape-(n, k)
数组,将它们都扩展为 shape (m, n, k)
并将它们相加。在那种情况下,您根本不需要物理扩展您的阵列;对齐轴和广播将正常工作:
A = something of shape (m, n)
B = something of shape (n, k)
C = A[..., np.newaxis] + B
这不需要复制 A
和 B
中的数据,并且应该 运行 比涉及物理副本的任何操作都快得多。
假设我有一个二维 numpy
数组 A
,形状为 (m, n)
。我想创建一个形状为 (m, n, k)
的 3D 数组 B
,这样 B[:, :, l]
是任何切片 l
的 A
的副本。我可以想到两种方法来做到这一点:
np.tile(A, (m, n, k))
或
np.repeat(A[:, :, np.newaxis], k, axis=-1)
第一种方法似乎更简单,但我在 np.tile
的文档中提到:
Note: Although tile may be used for broadcasting, it is strongly
recommended to use numpy's broadcasting operations and functions.
为什么会这样,这也是 np.repeat
的问题吗?
我的另一个担心是,如果 m == n == k
,那么 np.tile()
是否会造成关于哪个轴被增强的混淆?
综上所述,我有两个问题:
- 为什么
np.tile
不是首选,并且m == n == k
在某些情况下会导致意外行为? - 以上两种方式,哪种方式在时间和内存方面效率更高?有没有比这两种方法更清洁或更有效的方法?
In [100]: A = np.arange(12).reshape(3,4)
使用重复在末尾添加新维度:
In [101]: B = np.repeat(A[:,:,np.newaxis], 2, axis=-1)
In [102]: B.shape
Out[102]: (3, 4, 2)
使用平铺和重复在开头添加新维度:
In [104]: np.tile(A, (2,1,1)).shape
Out[104]: (2, 3, 4)
In [105]: np.repeat(A[None,:,:], 2, axis=0).shape
Out[105]: (2, 3, 4)
如果我们在最后一个维度上用平铺指定 2 次重复,它会给出不同的形状
In [106]: np.tile(A, (1,1,2)).shape
Out[106]: (1, 3, 8)
请注意 tile
所说的关于在前面加上重复元组的维度大于形状。
但是,如果您按照评论中的描述在计算中使用了扩展数组,则无需进行完整的重复复制。可以使用正确形状的临时视图,利用 broadcasting
.
In [107]: A1=np.arange(12).reshape(3,4)
In [108]: A2=np.arange(8).reshape(4,2)
In [109]: A3=A1[:,:,None] + A2[None,:,:]
In [110]: A3.shape
Out[110]: (3, 4, 2)
In [111]: A3
Out[111]:
array([[[ 0, 1],
[ 3, 4],
[ 6, 7],
[ 9, 10]],
[[ 4, 5],
[ 7, 8],
[10, 11],
[13, 14]],
[[ 8, 9],
[11, 12],
[14, 15],
[17, 18]]])
与None
(np.newaxis
),数组视图为(3,4,1)和(1,4,2)形,一起广播为(3,4, 2).我可以在第二种情况下省略 None
,因为广播会自动添加。但尾随 None
是必需的。
In [112]: (A1[:,:,None] + A2).shape
Out[112]: (3, 4, 2)
添加一维数组(最后一维):
In [113]: (A1[:,:,None] + np.array([1,2])[None,None,:]).shape
Out[113]: (3, 4, 2)
In [114]: (A1[:,:,None] + np.array([1,2])).shape
Out[114]: (3, 4, 2)
两个基本广播步骤:
- 根据需要添加尺寸 1 尺寸作为开始(自动
[None,....]
) - 将所有大小为 1 的维度扩展到共享大小
这组计算说明了这一点:
In [117]: np.ones(2) + np.ones(3)
ValueError: operands could not be broadcast together with shapes (2,) (3,)
In [118]: np.ones(2) + np.ones((1,3))
ValueError: operands could not be broadcast together with shapes (2,) (1,3)
In [119]: np.ones(2) + np.ones((3,1))
Out[119]:
array([[2., 2.],
[2., 2.],
[2., 2.]])
In [120]: np.ones((1,2)) + np.ones((3,1))
Out[120]:
array([[2., 2.],
[2., 2.],
[2., 2.]])
缺少中间维度
In [126]: np.repeat(A[:,None,:],2,axis=1)+np.ones(4)
Out[126]:
array([[[ 1., 2., 3., 4.],
[ 1., 2., 3., 4.]],
[[ 5., 6., 7., 8.],
[ 5., 6., 7., 8.]],
[[ 9., 10., 11., 12.],
[ 9., 10., 11., 12.]]])
有更多 'advanced' 的选择(但不一定更快):
In [127]: np.broadcast_to(A[:,None,:],(3,2,4))+np.ones(4)
你说你想扩展一个 shape-(m, n)
数组和一个 shape-(n, k)
数组,将它们都扩展为 shape (m, n, k)
并将它们相加。在那种情况下,您根本不需要物理扩展您的阵列;对齐轴和广播将正常工作:
A = something of shape (m, n)
B = something of shape (n, k)
C = A[..., np.newaxis] + B
这不需要复制 A
和 B
中的数据,并且应该 运行 比涉及物理副本的任何操作都快得多。