【问题标题】:Understanding the differences between similar numpy flattening techniques了解类似 numpy 展平技术之间的差异
【发布时间】:2019-03-31 07:58:33
【问题描述】:

我正在完成 Coursera 上 deeplearning.ai 证书第一门课程第 2 周的家庭作业。

首要任务之一是展平图像(209、64、64、3)。你可以通过三种方式来做到这一点(或者我认为):

  1. X.reshape(X.shape[0],-1).T
  2. X.flatten().reshape(12288, 209)
  3. X.reshape(12288, 209)

在这个练习中,我发现只有选项一可以正确地重塑图像,但我不知道为什么。任何帮助将非常感激。

【问题讨论】:

  • X.reshape(X.shape[0],-1).T == X.reshape(-1, X.shape[0]) 和 hereX.flatten().reshape(12288, 209) 你先展平,然后再整形为非展平形式,最后一个也是如此。其中哪些产生了预期的结果?
  • 是一张图片还是 209 张图片?只有 1 尊重 (64,64) 块中元素的顺序。

标签: python image numpy reshape


【解决方案1】:

首先,我们注意到我们可以将reshape 视为将一个数组“拉”成一长串元素,然后通过按特定顺序填充轴来“重新堆叠”它们。考虑以下数组:

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 个元素,重复此过程直到没有更多元素。

现在,请注意“拉出”步骤类似于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] 等于209X 的第一个轴的长度)。因此,1. 等价于X.reshape(209, -1).T(-1 是推断最后一个轴的简写,.T 转置一个数组)。

因此,两者的不同之处不在于它们的形状,而在于元素放置到轴中的顺序。 2. 和 3. 从同一点开始,由第一行中的元素组成的扁平数组,然后是第二行,然后是第三行,依此类推。所以(0, 0)包含第一个原始元素,然后是(0, 1)(0, 2)...

另一方面,通过在 1. 中执行重塑和 然后 转置,元素的这种线性顺序不再受到尊重。相反,首先填充列,这样(0, 0) 包含第一个原始元素,然后是(1, 0),依此类推。

【讨论】:

    【解决方案2】:

    所有三个操作的最终结果将是相同的。这三种方法只是实现相同结果的三种不同方法。但应该有一些收获,对吧?就在这里。

    1. X.reshape(X.shape[0],-1).T:当你将-1 作为轴传递给reshape 操作时,你说的是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 将采用所有其他维度,并采用第二维度的形状的乘积。
    2. X.flatten().reshape(12288, 209):这里说的是Yes, I know the shape of the ndarray I want,但不是直接重塑它,而是先将flattened 取出,然后重新排列元素。李>
    3. 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
    
    
    

    【讨论】:

    • 这是错误的。你在每个数组上调用all,如果它们的所有元素都是非零的,它将返回True,因此你正在打印(True == True == True == True)的结果。你想要(a == b).all() and (b == c).all() and (c == d).all()
    • 你为什么在测试中变平了?为什么不使用np.allclose
    【解决方案3】:

    (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, ...])
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-23
      • 1970-01-01
      • 2016-10-28
      • 2011-09-02
      • 2012-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多