【问题标题】:Use numpy.tensordot to replace a nested loop使用 numpy.tensordot 替换嵌套循环
【发布时间】:2018-08-06 09:42:42
【问题描述】:

我有一段代码,但我想提高性能。我的代码是:

lis = []
for i in range(6):
    for j in range(6):
        for k in range(6):
            for l in range(6):
                lis[i][j] += matrix1[k][l] * (2 * matrix2[i][j][k][l] - matrix2[i][k][j][l])  
print(lis)

matrix2 是 4 维 np 数组,matrix1 是 2d 数组。

我想通过使用 np.tensordot(matrix1, matrix2) 来加速这段代码,但后来我迷路了。

【问题讨论】:

    标签: python performance numpy


    【解决方案1】:

    您可以只使用 jit 编译器

    您的解决方案一点也不差。我唯一改变的是索引和可变循环范围。 如果您有 numpy 数组和过多的循环,您可以使用编译器 (Numba),这是一件非常简单的事情。

    import numba as nb
    import numpy as np
    #The function is compiled only at the first call (with using same datatypes)
    @nb.njit(cache=True) #set cache to false if copying the function to a command window
    def almost_your_solution(matrix1,matrix2):
      lis = np.zeros(matrix1.shape,np.float64)
      for i in range(matrix2.shape[0]):
          for j in range(matrix2.shape[1]):
              for k in range(matrix2.shape[2]):
                  for l in range(matrix2.shape[3]):
                      lis[i,j] += matrix1[k,l] * (2 * matrix2[i,j,k,l] - matrix2[i,k,j,l])
    
      return lis
    

    关于代码的简单性,我更喜欢 hpaulj 的 einsum 解决方案,而不是上面显示的解决方案。在我看来,张量点解决方案并不容易理解。但这是一个品味问题。

    比较性能

    hpaulj 中我用来比较的函数:

    def hpaulj_1(matrix1,matrix2):
      matrix3 = 2*matrix2-matrix2.transpose(0,2,1,3)
      return np.einsum('kl,ijkl->ij', matrix1, matrix3)
    
    def hpaulj_2(matrix1,matrix2):
      matrix3 = 2*matrix2-matrix2.transpose(0,2,1,3)
      (matrix1*matrix3).sum(axis=(2,3))
      return np.tensordot(matrix1, matrix3, [[0,1],[2,3]])
    

    非常短的数组给出:

    matrix1=np.random.rand(6,6)
    matrix2=np.random.rand(6,6,6,6)
    
    Original solution:    2.6 ms
    Compiled solution:    2.1 µs
    Einsum solution:      8.3 µs
    Tensordot solution:   36.7 µs
    

    更大的数组给出:

    matrix1=np.random.rand(60,60)
    matrix2=np.random.rand(60,60,60,60)
    
    Original solution:    13,3 s
    Compiled solution:    18.2 ms
    Einsum solution:      115  ms
    Tensordot solution:   180  ms
    

    结论

    编译将计算速度提高了大约 3 个数量级,并且大大优于所有其他解决方案。

    【讨论】:

      【解决方案2】:

      测试设置:

      In [274]: lis = np.zeros((6,6),int)
      In [275]: matrix1 = np.arange(36).reshape(6,6)
      In [276]: matrix2 = np.arange(36*36).reshape(6,6,6,6)
      In [277]: for i in range(6):
           ...:     for j in range(6):
           ...:         for k in range(6):
           ...:             for l in range(6):
           ...:                 lis[i,j] += matrix1[k,l] * (2 * matrix2[i,j,k,l] - mat
           ...: rix2[i,k,j,l])
           ...:                 
      In [278]: lis
      Out[278]: 
      array([[-51240,  -9660,  31920,  73500, 115080, 156660],
             [ 84840, 126420, 168000, 209580, 251160, 292740],
             [220920, 262500, 304080, 345660, 387240, 428820],
             [357000, 398580, 440160, 481740, 523320, 564900],
             [493080, 534660, 576240, 617820, 659400, 700980],
             [629160, 670740, 712320, 753900, 795480, 837060]])
      

      对吗?

      我不确定 tensordot 是不是正确的工具;至少可能不是最简单的。它肯定无法处理matrix2 的差异。

      让我们从一个明显的替换开始:

      In [279]: matrix3 = 2*matrix2-matrix2.transpose(0,2,1,3)
      In [280]: lis = np.zeros((6,6),int)
      In [281]: for i in range(6):
           ...:     for j in range(6):
           ...:         for k in range(6):
           ...:             for l in range(6):
           ...:                 lis[i,j] += matrix1[k,l] * matrix3[i,j,k,l]
      

      测试正常 - 相同 lis

      现在很容易用einsum 表达这一点 - 只需复制索引

      In [284]: np.einsum('kl,ijkl->ij', matrix1, matrix3)
      Out[284]: 
      array([[-51240,  -9660,  31920,  73500, 115080, 156660],
             [ 84840, 126420, 168000, 209580, 251160, 292740],
             [220920, 262500, 304080, 345660, 387240, 428820],
             [357000, 398580, 440160, 481740, 523320, 564900],
             [493080, 534660, 576240, 617820, 659400, 700980],
             [629160, 670740, 712320, 753900, 795480, 837060]])
      

      两个轴上的元素乘积加总和也可以;和等效的tensordot(指定要对哪些轴求和)

      (matrix1*matrix3).sum(axis=(2,3))
      np.tensordot(matrix1, matrix3, [[0,1],[2,3]])
      

      【讨论】:

        猜你喜欢
        • 2019-01-24
        • 1970-01-01
        • 2016-07-14
        • 1970-01-01
        • 2021-08-09
        • 2020-06-03
        • 2017-04-02
        • 2018-05-21
        • 1970-01-01
        相关资源
        最近更新 更多