【问题标题】:Improving Performance - Symbolic function applied to each row of numpy array提高性能 - 符号函数应用于 numpy 数组的每一行
【发布时间】:2014-12-10 21:07:17
【问题描述】:

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

【问题讨论】:

  • 最好添加您的改进版本作为答案。

标签: python performance numpy lambda sympy


【解决方案1】:

如果没有实际的方程式,很难做出任何具体的时间安排,但是有一些关于您的代码的建议。

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

  • 如果总是有一列零和一,为什么还要进行评估?
  • 方程中似乎存在对称性:您的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)

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

你可以摆脱另一个循环结构,通过实现你的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 循环。 这将使您的四重 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 函数列表。一旦你应用了这些技术,如果你能在 cmets 中添加一些计时结果,那就太好了。

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

【讨论】:

  • 非常感谢您的彻底回复。简短的回答,对于一个小样本案例,计算时间从 ~ 18 秒到 ~ 2.8 秒
  • 我更新了我的问题,做了一些澄清。另外,感谢您对编码风格的推动。我是一个自学成才的黑客,一直在努力与最佳实践保持一致,这是我还没有改掉的习惯。我会努力的:)。
猜你喜欢
  • 1970-01-01
  • 2018-01-08
  • 2021-06-11
  • 1970-01-01
  • 2018-01-18
  • 1970-01-01
  • 1970-01-01
  • 2014-01-31
  • 1970-01-01
相关资源
最近更新 更多