【问题标题】:Speed optimization with matix vector multiplication in Python在 Python 中使用矩阵向量乘法进行速度优化
【发布时间】:2013-06-27 02:22:31
【问题描述】:

我想在python中优化以下代码:

for imode in N.arange(3*natom): #Loop on perturbation (6 for 2 atoms)
  for ikpt in N.arange(nkpt):
    for iband in N.arange(nband):    
      for iatom1 in N.arange(natom):
        for iatom2 in N.arange(natom):
          for idir1 in N.arange(0,3):
            for idir2 in N.arange(0,3):    
              fan_corrQ[imode,ikpt,iband] += EIG2D[ikpt,iband,idir1,iatom1,idir2,iatom2]*\
                  displ_red_FAN2[imode,iatom1,iatom2,idir1,idir2]
              ddw_corrQ[imode,ikpt,iband] += ddw_save[ikpt,iband,idir1,iatom1,idir2,iatom2]*\
                  displ_red_DDW2[imode,iatom1,iatom2,idir1,idir2]

如您所见,我想对我的多空间 python 数组的一些索引进行求和。 我想要类似的东西:

for imode in N.arange(3*natom): #Loop on perturbation (6 for 2 atoms)
  for ikpt in N.arange(nkpt):
    for iband in N.arange(nband):
      fan_corrQ[imode,ikpt,iband] = N.dot(EIG2D[ikpt,iband,:,:,:,:],displ_red_FAN2.T[imode,:,:,:,:])
      ddw_corrQ[imode,ikpt,iband] = N.dot(ddw_save[ikpt,iband,:,:,:,:],displ_red_DDW2.T[imode,:,:,:,:])

当然,我有一个问题是不能乘以相同的索引,所以我重新定义了它。我还必须指出,我正在处理复数:

displ_red_DDW2 = N.zeros((3*natom,3,natom,3,natom),dtype=complex)

我还在 ipython 中尝试了一个小的虚拟程序来测试它:

import numpy as N
atom =2
displ_red_FAN2 = N.zeros((3*natom,3,natom,3,natom),dtype=complex)
EIG2D = N.zeros((216,12,3,2,3,2))

所以我有 displ_red_FAN2.shape = (6, 3, 2, 3, 2) 和 EIG2D.shape = (216, 12, 3, 2, 3, 2)

所以如果我这样做:

N.dot(EIG2D[1,1,:,:,:,:],displ_red_FAN2[1,:,:,:,:].T).shape

它应该给出 (3,2,3,2) 但它给出的是 (3, 2, 3, 2, 3, 3) ???然后当乘法完成时,我想我将不得不做一些求和来降低维度。

任何帮助都会很棒!

干杯!

塞缪尔

【问题讨论】:

    标签: python arrays optimization numpy matrix


    【解决方案1】:

    你可以使用np.tensordot()

    fan_corrQ = np.tensordot(displ_red_FAN2, EIG2D, axes = ([3,1,4,2],[2,3,4,5]))
    ddw_corrQ = np.tensordot(displ_red_DDW2, ddw_save, axes = ([3,1,4,2],[2,3,4,5]))
    

    这给出了与您当前方法相同的结果,并且速度大约为 9 times

    关于您的其他问题。 np.dot() 对应于 ND-array 的作用是 summing over the last axis of the first array and the second-to-last axis of the second array

    【讨论】:

    • 感谢您的帮助,我认为您不需要交换轴。 fan_corrQ = displ_red_FAN2.dot(EIG2D) # [3*natom,natom,natom,idir1,nkpt,nband,idir1,iatom1,iatom2] fan_corrQ = N.sum(fan_corrQ,axis=8) fan_corrQ = N.sum(fan_corrQ, axis=7) fan_corrQ = N.sum(fan_corrQ,axis=6) fan_corrQ = N.sum(fan_corrQ,axis=3) fan_corrQ = N.sum(fan_corrQ,axis=2) fan_corrQ = N.sum(fan_corrQ,axis= 1)似乎工作。但它没有给出与以前相同的结果。我们想要对我们想要减少的所有 4 维进行乘法。
    • 我希望这行得通...如果不行,那么肯定有一种方法可以在没有 python for 循环的情况下执行您想要的操作...
    • 它现在从维度的角度工作,但不是从物理的角度来看:p 也许通过在我们想要的每个维度上制作多个点积?
    • @sponce 我更新了答案。有一个np.tensordot 而不是多个dot...
    • @sgpc 这对您的答案来说是一个非常好的更新。仅供参考 @sponce:您应该对我们的两个解决方案进行基准测试 - np.tensordot 在您的情况下可能会稍微快一些。这是因为np.einsum 非常通用(例如,输入数组的数量可变)使得优化对 BLAS 库的底层调用变得更加困难。
    【解决方案2】:

    我认为您可以通过使用 Einstein 求和 (np.einsum) 大大简化这一过程。语法可能有点难以理解,所以我稍微简化了变量和索引的名称:

    # arrays
    EIG2D           --> A
    displ_red_FAN2  --> B
    fan_corrQ       --> C
    
    # indices
    ikpt    --> i
    iband   --> j
    idir1   --> k
    iatom1  --> l
    idir2   --> m
    iatom2  --> n
    imode   --> o
    

    np.einsum 采用逗号分隔的下标列表,每个下标指代 对应输入数组的维度。每当索引重复时,它 在输出中求和。您还可以在 通过给出输出索引来输出。

    在你的情况下,我认为:

    ...
    fan_corrQ[imode,ikpt,iband] += EIG2D[ikpt,iband,idir1,iatom1,idir2,iatom2]*\
        displ_red_FAN2[imode,iatom1,iatom2,idir1,idir2]
    ...
    

    应该简化为:

    C = np.einsum('ijklmn,olnkm->oij',A,B)
    

    您应该尝试一下,并确保我没有犯任何错误!与sgpc 的回答一样,在内存要求方面也有类似的注意事项。

    【讨论】:

    • 先生,你成就了我的一天!!!它工作得很好,对我(物理学家)来说更容易理解。有关信息,代码从“for”循环到 einsum 的时间从 40 秒变为 5 秒 :)
    • 很高兴听到这个消息 - 我最近才发现 np.einsum 我自己,从那时起它就震撼了我的世界
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-11
    • 2021-02-06
    • 2020-07-13
    • 2019-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多