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

时间:2014-12-09 20:46:05

标签: python performance numpy lambda sympy

我有一个输入向量列表,我需要将其用作使用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 个答案:

答案 0 :(得分: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,这意味着变量都是小写的,下划线分隔单词。