【问题标题】:Create block diagonal numpy array from a given numpy array从给定的 numpy 数组创建块对角 numpy 数组
【发布时间】:2015-11-03 20:20:58
【问题描述】:

我有一个列数和行数相等的二维 numpy 数组。我想将它们排列成一个更大的阵列,在对角线上有较小的阵列。应该可以指定起始矩阵在对角线上的频率。例如:

a = numpy.array([[5, 7], 
                 [6, 3]])

因此,如果我希望这个数组在对角线上 2 次,则所需的输出将是:

array([[5, 7, 0, 0], 
       [6, 3, 0, 0], 
       [0, 0, 5, 7], 
       [0, 0, 6, 3]])

3次:

array([[5, 7, 0, 0, 0, 0], 
       [6, 3, 0, 0, 0, 0], 
       [0, 0, 5, 7, 0, 0], 
       [0, 0, 6, 3, 0, 0],
       [0, 0, 0, 0, 5, 7],
       [0, 0, 0, 0, 6, 3]])

有没有一种快速的方法可以使用 numpy 方法和任意大小的起始数组(仍然考虑起始数组具有相同的行数和列数)来实现这一点?

【问题讨论】:

    标签: python arrays numpy


    【解决方案1】:

    方法#1

    numpy.kron的经典案例-

    np.kron(np.eye(r,dtype=int),a) # r is number of repeats
    

    示例运行 -

    In [184]: a
    Out[184]: 
    array([[1, 2, 3],
           [3, 4, 5]])
    
    In [185]: r = 3 # number of repeats
    
    In [186]: np.kron(np.eye(r,dtype=int),a)
    Out[186]: 
    array([[1, 2, 3, 0, 0, 0, 0, 0, 0],
           [3, 4, 5, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 1, 2, 3, 0, 0, 0],
           [0, 0, 0, 3, 4, 5, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 1, 2, 3],
           [0, 0, 0, 0, 0, 0, 3, 4, 5]])
    

    方法 #2

    另一个高效的diagonal-viewed-array-assignment -

    def repeat_along_diag(a, r):
        m,n = a.shape
        out = np.zeros((r,m,r,n), dtype=a.dtype)
        diag = np.einsum('ijik->ijk',out)
        diag[:] = a
        return out.reshape(-1,n*r)
    

    示例运行 -

    In [188]: repeat_along_diag(a,3)
    Out[188]: 
    array([[1, 2, 3, 0, 0, 0, 0, 0, 0],
           [3, 4, 5, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 1, 2, 3, 0, 0, 0],
           [0, 0, 0, 3, 4, 5, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 1, 2, 3],
           [0, 0, 0, 0, 0, 0, 3, 4, 5]])
    

    【讨论】:

    • 如果你需要在对角线上插入 x 个不同的矩阵,你会怎么做?我有 80 个不同的矩阵,需要制作成块对角矩阵。
    • @Will.Evo 所有 80 个相同的形状?
    • 是的,形状都一样
    • @Will.Evo 嗯,这更适合作为一个新问题。我认为需要一些基于掩码的方法,这与这里的简单案例不同。
    • 如果您有很多矩阵 a、b、c...,则使用 scipy.linalg.block_diag(a,b,c,...),另请参阅 Prokhozhii 答案。
    【解决方案2】:
    import numpy as np
    from scipy.linalg import block_diag
    
    a = np.array([[5, 7], 
                  [6, 3]])
    
    n = 3
    
    d = block_diag(*([a] * n))
    
    d
    
    array([[5, 7, 0, 0, 0, 0],
           [6, 3, 0, 0, 0, 0],
           [0, 0, 5, 7, 0, 0],
           [0, 0, 6, 3, 0, 0],
           [0, 0, 0, 0, 5, 7],
           [0, 0, 0, 0, 6, 3]], dtype=int32)
    

    但看起来 np.kron 解决方案对于小 n 来说要快一些。

    %timeit np.kron(np.eye(n), a)
    12.4 µs ± 95.7 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    
    %timeit block_diag(*([a] * n))
    19.2 µs ± 34.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
    

    但是,例如,对于 n = 300,block_diag 更快:

    %timeit block_diag(*([a] * n))
    1.11 ms ± 32.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    
    %timeit np.kron(np.eye(n), a)
    4.87 ms ± 31 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    【讨论】:

      【解决方案3】:

      对于矩阵的特殊情况,简单的切片比numpy.kron()(最慢)要快得多,并且与基于numpy.einsum() 的方法(来自@Divakar 回答)基本相同。 与scipy.linalg.block_diag() 相比,它在较小的arr 上表现更好,在某种程度上与块重复数无关。

      请注意,block_diag_view() 在较小输入上的性能可以通过 Numba 轻松进一步提高,但对于较大输入,性能会更差。

      使用 Numba,如果重复次数很少,完全显式循环和并行化 (block_diag_loop_jit()) 将再次获得与 block_diag_einsum() 相似的结果。

      总体而言,性能最高的解决方案是block_diag_einsum()block_diag_view()

      import numpy as np
      import scipy as sp
      import numba as nb
      
      import scipy.linalg
      
      
      NUM = 4
      M = 9
      
      
      def block_diag_kron(arr, num=NUM):
          return np.kron(np.eye(num), arr)
      
      
      def block_diag_einsum(arr, num=NUM):
          rows, cols = arr.shape
          result = np.zeros((num, rows, num, cols), dtype=arr.dtype)
          diag = np.einsum('ijik->ijk', result)
          diag[:] = arr
          return result.reshape(rows * num, cols * num)
      
      
      def block_diag_scipy(arr, num=NUM):
          return sp.linalg.block_diag(*([arr] * num))
      
      
      def block_diag_view(arr, num=NUM):
          rows, cols = arr.shape
          result = np.zeros((num * rows, num * cols), dtype=arr.dtype)
          for k in range(num):
              result[k * rows:(k + 1) * rows, k * cols:(k + 1) * cols] = arr
          return result
      
      
      @nb.jit
      def block_diag_view_jit(arr, num=NUM):
          rows, cols = arr.shape
          result = np.zeros((num * rows, num * cols), dtype=arr.dtype)
          for k in range(num):
              result[k * rows:(k + 1) * rows, k * cols:(k + 1) * cols] = arr
          return result
      
      
      @nb.jit(parallel=True)
      def block_diag_loop_jit(arr, num=NUM):
          rows, cols = arr.shape
          result = np.zeros((num * rows, num * cols), dtype=arr.dtype)
          for k in nb.prange(num):
              for i in nb.prange(rows):
                  for j in nb.prange(cols):
                      result[i + (rows * k), j + (cols * k)] = arr[i, j]
          return result
      

      NUM = 4 的基准:

      NUM = 400 的基准:


      绘图是从this template 使用以下附加代码生成的:

      def gen_input(n):
          return np.random.randint(1, M, (n, n))
      
      
      def equal_output(a, b):
          return np.all(a == b)
      
      
      funcs = block_diag_kron, block_diag_scipy, block_diag_view, block_diag_jit
      
      
      input_sizes = tuple(int(2 ** (2 + (3 * i) / 4)) for i in range(13))
      print('Input Sizes:\n', input_sizes, '\n')
      
      
      runtimes, input_sizes, labels, results = benchmark(
          funcs, gen_input=gen_input, equal_output=equal_output,
          input_sizes=input_sizes)
      
      
      plot_benchmarks(runtimes, input_sizes, labels, units='ms')
      

      (已编辑以包括基于 np.einsum() 的方法和另一个具有显式循环的 Numba 版本。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-30
        • 2017-06-07
        • 2012-06-05
        • 1970-01-01
        • 1970-01-01
        • 2023-03-12
        相关资源
        最近更新 更多