使用numpy.tensordot替换嵌套循环

时间:2018-02-26 20:52:13

标签: python performance numpy

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

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)来加速这段代码,但后来我迷路了。

1 个答案:

答案 0 :(得分:1)

您可以使用jit-compiler

你的解决方案一点都不差。我唯一改变的是索引和变量循环范围。 如果你有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个数量级,并且在很大程度上优于所有其他解决方案。