首页 文章

提高性能 - 应用于numpy数组的每一行的符号函数

提问于
浏览
1

我有一个输入向量列表,我需要将其用作使用SymPy生成的函数列表的输入 . 在实际应用中,输入向量的数量约为100k,并且有~5M组符号函数 . 这是我的代码中的瓶颈,所以我试图加快速度 .

我已经通过使用Sympy的lambdify来创建基于numpy的lambda函数做了很大的改进,但是我不禁想到有一种方法可以对此进行向量化并将for循环变为numpy / C而不是python .

我最初认为numpy.apply_along_axis()会有所帮助,但它仍然在python中进行循环 .

这是我现在正在做的简化版本:

import time
import sympy as sp
import numpy as np

#Input for performance testing
# sampleSize = 200000
# inputVector = [1.2, -0.33]

# inputArray = np.array(inputVector*np.ones((sampleSize,1)))


#This array would have ~100k rows in actual data set
inputArray = [[-.333, -.558],
              [-.454, -.367],
              [-.568, -.678]]                    


start = time.time()


#These are the equations of motion of a mechanical system. Each row represents 
#a unique arrangement of components. There may be a better way to handle this, 
#but I haven't understood the system well enough to do so yet.

#This array would have ~5M rows in actual data set
symEqns = [['(R_1 - 1)/(R_0 - 1)', '0', '-R_1 + 1',     '(R_1 - 1)/(R_0*R_1 - 1)','1'],
           ['R_1/R_0',             '0', '-1/(R_0 - 1)', '(R_1 - 1)/(R_0 - 1)',    '1']]

for eqnSet in symEqns:
  #Create lambda functions 
  lambdaFuncs = []
  for eqn in eqnSet:
    func = sp.lambdify(['R_0', 'R_1'], eqn, 'numpy')

    #This is ~5x slower, due to use of pure python vs. numpy ??
    # func = lambda R_0, R_1: eval(eqn)  

    lambdaFuncs.append(func)

  #Evaluate each lambda func for each input set

  # in my actual code, this is a parameter of an object. forgot to store it in my example code
  outputList = []   
  for row in inputArray:
    results = []
    for func in lambdaFuncs:
      results.append(func(*row))
    outputList.append(results)

end = time.time()
print "\nTotal Time Elapsed: {:d}:{:0>5.2f}".format(int((end-start)/60), (end-start)%60)

如果它有帮助,我还可以构建评估以独立计算每个函数,为每个函数创建一列结果 . 这是一个在这种情况下评估块的例子(使用for循环进行说明,我想使用numpy进行矢量化评估):

#Evaluate each lambda func for each input set
  outputList = []
  for func in lambdaFuncs:
    results = []
    for row in inputArray:
      results.append(func(*row))
    outputList.append(results)

[编辑]为了将来参考,这是我改进的这个问题的工作示例代码 . 我已经从Oliver的响应中调整了一些内容,主要是允许可变长度的输入向量:

import time
import sympy as sp
import numpy as np

# This array would have ~100k rows in actual data set
input_array = np.array([[-.333, -.558],
              [-.454, -.367],
              [-.568, -.678]])



#This array would have ~5M rows in actual data set (generated via Sympy linear algebraic solns)
sym_eqns = [['(R_1 - 1)/(R_0 - 1)', '0', '-R_1 + 1',     '(R_1 - 1)/(R_0*R_1 - 1)','1'],
           ['R_1/R_0',             '0', '-1/(R_0 - 1)', '(R_1 - 1)/(R_0 - 1)',    '1']]

for eqn_set in sym_eqns:
  output_list = []
  for eqn in eqn_set:
    func = sp.lambdify(['R_0', 'R_1'], eqn, 'numpy')
    results = func(*[input_array[:,n] for n in range(input_array.shape[1])])  
    output_list.append(results)

1 回答

  • 2

    没有实际的方程式很难做出任何具体的时间,但是有一些关于你的代码的建议 .

    首先,我们来谈谈方程式:

    • 如果总是有一列零和一列,为什么还要费心去评估?

    • 方程似乎是对称的:你的 symEqns[0][0] == symEqns[1][3] . 再次,为什么评估?

    这些方程的起源是什么?我看到 R_1 - 1 是一个相当普遍的因素 . 也许你原来的问题要容易解决 .

    其次,我们来谈谈循环 . 你可以从4中删除一个循环结构:

    这个:

    for eqnSet in symEqns:
      lambdaFuncs = []
      for eqn in eqnSet:
        func = sp.lambdify(['R_0', 'R_1'], eqn, 'numpy')
        lambdaFuncs.append(func)
    
      # This seems out of place in your example code: whenever a
      # new row of functions is being processed, you lose all the 
      # data from outputList, because you're not storing it anywhere.
      outputList = [] 
      for row in inputArray:
        results = []
        for func in lambdaFuncs:
          results.append(func(*row))
        outputList.append(results)
    

    可以改成这个:

    outputlist = [] # Better position for outputList
    for eqnSet in symEqns:
      for eqn in eqnSet:
        func = sp.lambdify(['R_0', 'R_1'], eqn, 'numpy')
        for row in inputArray:
            results = []
            results.append(func(*row))
        outputList.append(results)
    

    除非你真的需要存储所有lambdified numpy函数,我非常怀疑 .

    你可以通过实现lambdified函数就像numpy函数一样工作来摆脱另一个循环结构:它们也是矢量化的 .

    >>> for row in inputArray:
    ...     print(f(*row)),
    1.16879219805 0.940165061898 1.07015306122
    
    >>> arr = np.array(inputArray)
    >>> f(arr[:,0], arr[:,1])
    array([ 1.1687922 ,  0.94016506,  1.07015306])
    

    相同的输出,没有for循环 . 这将使您的四重换环降至:

    input_data = np.array(inputArray)
    outputlist = [] # Better position for outputList
    for eqnSet in symEqns:
        for eqn in eqnSet:
            func = sp.lambdify(['R_0', 'R_1'], eqn, 'numpy')
            outputList.append(func(input_data[:,0], input_data[:,1]))
    

    这会快得多,因为现在基本上你只是循环遍历sympy函数列表,而不是遍及数据(现在是连续的,因此具有缓存优势)或lambdified sympy函数列表 . 如果您在应用这些技术后可以在注释中添加一些计时结果,那将是非常好的 .

    另外,一个温和的提示:在python编程语言中,大多数程序员都遵循PEP8 coding style,这意味着变量都是小写的,下划线分隔单词 .

相关问题