【问题标题】:How to copy a 2D array into a 3rd dimension, N times?如何将二维数组复制到第三维,N次?
【发布时间】: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 我没听明白 - 有什么问题?

标签: python arrays numpy


【解决方案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) 并且工作正常!谢谢
          • 我想我可以提出一个查看输出以提高效率。作为一个旧帖子,人们可能会错过这一点。在此问答中添加了解决方案。
          猜你喜欢
          • 1970-01-01
          • 2023-01-11
          • 2021-12-31
          • 2021-10-18
          • 2014-09-10
          • 1970-01-01
          相关资源
          最近更新 更多