了解类似的 numpy 展平技术之间的差异
Understanding the differences between similar numpy flattening techniques
我正在完成 Coursera deeplearning.ai 证书第一门课程第 2 周的家庭作业。
首要任务之一是拼合图像 (209, 64, 64, 3)。您可以通过三种方式(或者我认为如此)来做到这一点:
- X.reshape(X.shape[0],-1).T
- X.flatten().reshape(12288, 209)
- X.reshape(12288, 209)
在这个练习中,我发现只有选项一可以正确地重塑图像,但我不知道为什么。任何帮助将非常感激。
所有三个操作的最终结果将是相同的。这三种方法只是实现相同结果的三种不同方式。但是应该有一些收获,对吧?就在这里。
- X.reshape(X.shape[0],-1).T:当您将
-1
作为轴传递给重塑时操作,你说的是Hey, here is my array. I am giving you the first dimension(X.shape[0] in this case), figure out yourself what the second dimesnion should be!
。因为 reshape
只是排列元素的另一种方式,所以 numpy
将采用所有其他维度,并采用第二个维度的形状的乘积。
- X.flatten().reshape(12288, 209):在这里你说的是
Yes, I know the shape of the ndarray I want
但不是直接重塑它,你首先 flattened
它出来然后重新排列元素。
- X.reshape(12288, 209):这与第二个选项相同,但知道你没有做
redundant
flatten
重塑你的 ndarray 的操作。
还有什么?:花费的时间
a = np.random.rand(2,3,4)
%timeit d = a.reshape(a.shape[0], -1)
382 ns ± 8.94 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit b = a.flatten().reshape(2,12)
963 ns ± 11.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit c = a.reshape(2,12)
272 ns ± 4.61 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
print(b.shape, c.shape, d.shape)
(2, 12) (2, 12) (2, 12)
print((a.flatten()==b.flatten()).all())
True
print((a.flatten()==c.flatten()).all())
True
print((a.flatten()==d.flatten()).all())
True
首先,我们注意到我们可以将 reshape
视为 "pulling" 一个数组,其中包含一长串元素,然后 "restacking" 通过按特定顺序填充坐标轴来 "restacking" 它们.考虑以下数组:
array = np.arange(48).reshape(6, 4, 2)
此数组将包含从 0 到 47 的元素,形状为 (6, 4, 2)
。这种形状可以简单地解释为元素放置在每个轴中的顺序。
例如:
>>> print(array[0, :, :])
[[0 1]
[2 3]
[4 5]
[6 7]]
第一个轴的长度是48 / 4 / 2 = 8
,因此这个切片必须有8个元素。由于它是 first 轴,它仅由 运行 顺序的源的前 8 个元素组成。
接下来,我们需要决定这 8 个元素将如何填充其他 2 个轴。这8个元素可以认为是组成了一个自己的子数组,形状为(4, 2)
。由于必须首先填充第一个轴(在子数组中),我们希望它包含 运行 顺序的元素对:
>>> for i in range(array.shape[1]):
... print(array[0, i, :])
[0 1]
[2 3]
[4 5]
[6 7]
将此与最后一个轴进行对比:
>>> for i in range(array.shape[2]):
... print(array[0, :, i])
[0 2 4 6]
[1 3 5 7]
第二个切片 array[1, :, :]
将包含 下一个 8 个元素,或 8 到 15 个,重复此过程,直到没有更多元素为止。
现在,请注意 "pulling out" 步骤类似于 flatten()
。因此,2 和 3 相同:
也就不足为奇了
X = np.random.rand(209, 64, 64, 3)
print(X.flatten().reshape(12288, 209) == X.reshape(12288, 209)).all(axis=None)
输出:
True
与 1. 粗略比较一下,就会发现 1. 是奇数。请注意 X.shape[0]
等于 209
(X
的第一个轴的长度)。因此,1.相当于X.reshape(209, -1).T
(-1是shorthand推断最后一个轴,.T
转置一个数组)。
因此,两者的不同之处不在于它们的形状,而是在于元素放置在轴中的顺序。 2. 和 3. 从同一点开始,一个展平的数组由第一行的元素组成,然后是第二行,然后是第三行,依此类推。因此,(0, 0)
包含第一个原始元素,然后是(0, 1)
、(0, 2)
...
另一方面,通过执行 1. 中的整形和 then 转置,不再遵守元素的线性顺序.相反,首先填充列,这样 (0, 0)
包含第一个原始元素,然后是 (1, 0)
,依此类推。
(209, 64, 64, 3)
看起来像图像数组的形状,每组 209 张图像 (64,64,3)。重塑应该使这些图像元素保持在一起并按顺序排列。
用一个更小的例子来说明:
In [845]: arr = np.arange(24).reshape(4,2,3)
In [846]: arr
Out[846]:
array([[[ 0, 1, 2],
[ 3, 4, 5]],
[[ 6, 7, 8],
[ 9, 10, 11]],
[[12, 13, 14],
[15, 16, 17]],
[[18, 19, 20],
[21, 22, 23]]])
In [847]: arr[1]
Out[847]:
array([[ 6, 7, 8],
[ 9, 10, 11]])
天真的重塑:
In [848]: x = arr.reshape(6,4)
In [849]: x
Out[849]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]])
In [850]: x[:,1]
Out[850]: array([ 1, 5, 9, 13, 17, 21])
选择一列会产生与 Out[847]
不同的一组数字。 [6,7,8]
现在分为第 2 行和第 3 行。[1,5,9...]
是从 arr
.
中抽取的
重塑后转置:(4,2,3)=>(4,(2*3))=>(4,6)=>(6,4):
In [851]: x = arr.reshape(4,6).T
In [852]: x
Out[852]:
array([[ 0, 6, 12, 18],
[ 1, 7, 13, 19],
[ 2, 8, 14, 20],
[ 3, 9, 15, 21],
[ 4, 10, 16, 22],
[ 5, 11, 17, 23]])
In [853]: x[:,1]
Out[853]: array([ 6, 7, 8, 9, 10, 11])
In [855]: x[:,1].reshape(2,3)
Out[855]:
array([[ 6, 7, 8],
[ 9, 10, 11]])
形式上reshape
只是要求元素总数不变。但如此处所示,维度的子组也应保持不变,(4,2,3) => (4,6)
或 (8,3)
,而不是 (6,4)
。否则你可能会重新组合值。
只需重塑和转置,x
仍然是 view
,与 arr
共享数据缓冲区。但是 order
是不同的。进一步重塑(例如ravel
)可能会产生一个副本。
In [859]: arr.__array_interface__['data']
Out[859]: (36072624, False)
In [860]: x.__array_interface__['data']
Out[860]: (36072624, False)
In [861]: x.ravel()
Out[861]:
array([ 0, 6, 12, 18, 1, 7,...])
In [862]: x.ravel(order='F')
Out[862]:
array([ 0, 1, 2, 3, 4, 5, ...])
我正在完成 Coursera deeplearning.ai 证书第一门课程第 2 周的家庭作业。
首要任务之一是拼合图像 (209, 64, 64, 3)。您可以通过三种方式(或者我认为如此)来做到这一点:
- X.reshape(X.shape[0],-1).T
- X.flatten().reshape(12288, 209)
- X.reshape(12288, 209)
在这个练习中,我发现只有选项一可以正确地重塑图像,但我不知道为什么。任何帮助将非常感激。
所有三个操作的最终结果将是相同的。这三种方法只是实现相同结果的三种不同方式。但是应该有一些收获,对吧?就在这里。
- X.reshape(X.shape[0],-1).T:当您将
-1
作为轴传递给重塑时操作,你说的是Hey, here is my array. I am giving you the first dimension(X.shape[0] in this case), figure out yourself what the second dimesnion should be!
。因为reshape
只是排列元素的另一种方式,所以numpy
将采用所有其他维度,并采用第二个维度的形状的乘积。 - X.flatten().reshape(12288, 209):在这里你说的是
Yes, I know the shape of the ndarray I want
但不是直接重塑它,你首先flattened
它出来然后重新排列元素。 - X.reshape(12288, 209):这与第二个选项相同,但知道你没有做
redundant
flatten
重塑你的 ndarray 的操作。
还有什么?:花费的时间
a = np.random.rand(2,3,4)
%timeit d = a.reshape(a.shape[0], -1)
382 ns ± 8.94 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit b = a.flatten().reshape(2,12)
963 ns ± 11.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit c = a.reshape(2,12)
272 ns ± 4.61 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
print(b.shape, c.shape, d.shape)
(2, 12) (2, 12) (2, 12)
print((a.flatten()==b.flatten()).all())
True
print((a.flatten()==c.flatten()).all())
True
print((a.flatten()==d.flatten()).all())
True
首先,我们注意到我们可以将 reshape
视为 "pulling" 一个数组,其中包含一长串元素,然后 "restacking" 通过按特定顺序填充坐标轴来 "restacking" 它们.考虑以下数组:
array = np.arange(48).reshape(6, 4, 2)
此数组将包含从 0 到 47 的元素,形状为 (6, 4, 2)
。这种形状可以简单地解释为元素放置在每个轴中的顺序。
例如:
>>> print(array[0, :, :])
[[0 1]
[2 3]
[4 5]
[6 7]]
第一个轴的长度是48 / 4 / 2 = 8
,因此这个切片必须有8个元素。由于它是 first 轴,它仅由 运行 顺序的源的前 8 个元素组成。
接下来,我们需要决定这 8 个元素将如何填充其他 2 个轴。这8个元素可以认为是组成了一个自己的子数组,形状为(4, 2)
。由于必须首先填充第一个轴(在子数组中),我们希望它包含 运行 顺序的元素对:
>>> for i in range(array.shape[1]):
... print(array[0, i, :])
[0 1]
[2 3]
[4 5]
[6 7]
将此与最后一个轴进行对比:
>>> for i in range(array.shape[2]):
... print(array[0, :, i])
[0 2 4 6]
[1 3 5 7]
第二个切片 array[1, :, :]
将包含 下一个 8 个元素,或 8 到 15 个,重复此过程,直到没有更多元素为止。
现在,请注意 "pulling out" 步骤类似于 flatten()
。因此,2 和 3 相同:
X = np.random.rand(209, 64, 64, 3)
print(X.flatten().reshape(12288, 209) == X.reshape(12288, 209)).all(axis=None)
输出:
True
与 1. 粗略比较一下,就会发现 1. 是奇数。请注意 X.shape[0]
等于 209
(X
的第一个轴的长度)。因此,1.相当于X.reshape(209, -1).T
(-1是shorthand推断最后一个轴,.T
转置一个数组)。
因此,两者的不同之处不在于它们的形状,而是在于元素放置在轴中的顺序。 2. 和 3. 从同一点开始,一个展平的数组由第一行的元素组成,然后是第二行,然后是第三行,依此类推。因此,(0, 0)
包含第一个原始元素,然后是(0, 1)
、(0, 2)
...
另一方面,通过执行 1. 中的整形和 then 转置,不再遵守元素的线性顺序.相反,首先填充列,这样 (0, 0)
包含第一个原始元素,然后是 (1, 0)
,依此类推。
(209, 64, 64, 3)
看起来像图像数组的形状,每组 209 张图像 (64,64,3)。重塑应该使这些图像元素保持在一起并按顺序排列。
用一个更小的例子来说明:
In [845]: arr = np.arange(24).reshape(4,2,3)
In [846]: arr
Out[846]:
array([[[ 0, 1, 2],
[ 3, 4, 5]],
[[ 6, 7, 8],
[ 9, 10, 11]],
[[12, 13, 14],
[15, 16, 17]],
[[18, 19, 20],
[21, 22, 23]]])
In [847]: arr[1]
Out[847]:
array([[ 6, 7, 8],
[ 9, 10, 11]])
天真的重塑:
In [848]: x = arr.reshape(6,4)
In [849]: x
Out[849]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]])
In [850]: x[:,1]
Out[850]: array([ 1, 5, 9, 13, 17, 21])
选择一列会产生与 Out[847]
不同的一组数字。 [6,7,8]
现在分为第 2 行和第 3 行。[1,5,9...]
是从 arr
.
重塑后转置:(4,2,3)=>(4,(2*3))=>(4,6)=>(6,4):
In [851]: x = arr.reshape(4,6).T
In [852]: x
Out[852]:
array([[ 0, 6, 12, 18],
[ 1, 7, 13, 19],
[ 2, 8, 14, 20],
[ 3, 9, 15, 21],
[ 4, 10, 16, 22],
[ 5, 11, 17, 23]])
In [853]: x[:,1]
Out[853]: array([ 6, 7, 8, 9, 10, 11])
In [855]: x[:,1].reshape(2,3)
Out[855]:
array([[ 6, 7, 8],
[ 9, 10, 11]])
形式上reshape
只是要求元素总数不变。但如此处所示,维度的子组也应保持不变,(4,2,3) => (4,6)
或 (8,3)
,而不是 (6,4)
。否则你可能会重新组合值。
只需重塑和转置,x
仍然是 view
,与 arr
共享数据缓冲区。但是 order
是不同的。进一步重塑(例如ravel
)可能会产生一个副本。
In [859]: arr.__array_interface__['data']
Out[859]: (36072624, False)
In [860]: x.__array_interface__['data']
Out[860]: (36072624, False)
In [861]: x.ravel()
Out[861]:
array([ 0, 6, 12, 18, 1, 7,...])
In [862]: x.ravel(order='F')
Out[862]:
array([ 0, 1, 2, 3, 4, 5, ...])