【问题标题】:Cython Partial DerivativeCython 偏导数
【发布时间】:2014-11-12 00:25:50
【问题描述】:

我有一个 python 脚本,作为进化优化算法的一部分,我正在评估数千次偏导数。我已经逐行完成了配置文件,这种偏导数计算占用了大部分运行时间。我正在使用scipy.optimize.approx_fprime 计算偏导数,并尝试在 cython 中重写它,但没有取得多大成功。

逐行配置文件如下。我的scipy.optimize.approx_fprime 的cythonized 版本简称为approx_fprime

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
84                                           @profile
100      1500     14889652   9926.4     25.3      df1 = approx_fprime(inp_nom,evaluate1,epsilon)
101      1500     14939889   9959.9     25.4      df2 = scipy.optimize.approx_fprime(inp_upp,evaluate1,epsilon)

下面是我的 cython 文件。

import numpy as np
cimport numpy as np
cimport cython
@cython.boundscheck(False) # turn of bounds-checking for entire function
def approx_fprime(np.ndarray xk, f, double epsilon, *args):
    # From scipy.optimize.approx_fprime
    f0 = f(*((xk,) + args))
    cdef np.ndarray grad = np.zeros((len(xk),), float)
    cdef np.ndarray ei = np.zeros((len(xk),), float)
    cdef np.ndarray d = epsilon * ei
    for k in xrange(len(xk)):
        ei[k] = 1.0
        grad[k] = (f(*((xk + d,) + args)) - f0) / d[k]
        ei[k] = 0.0
    return grad

我已尝试放入所有相关的类型声明,并确保它与 numpy.但最终,正如他们所说,证据就在布丁中。这个版本并不比 scipy 版本快。该函数只有几个变量,所以它不是一个巨大的计算,并且可能只有一次迭代中增量改进的空间。但是,该函数会被一遍又一遍地调用,因为它用于进化优化算法,所以我期待/希望将性能提升数倍增加会有很大的回报。

那里的 cython 专家可以看看这段代码并帮助我弄清楚我是否走在正确的轨道上,或者这只是一个傻瓜的差事吗?

谢谢!

【问题讨论】:

  • 在 Cython 中成功的关键是自己做数学,而不是依赖于 numpy 对象和函数,即使它们是矢量化的。您可能需要在循环中将 f() 应用于数组的各个元素。我不是 Cython 专家,我最近学到了一些知识,以便为巨大的矩阵制作自定义距离矩阵函数。这是我的主题:stackoverflow.com/questions/25213603/… 接受的答案对我理解事情的运作方式有很大帮助。问题有所不同,但您可能仍会发现它很有用。
  • 我也不是 Cython 专家,但我知道评估数值导数(例如您在此处尝试评估)通常比评估分析导数要昂贵得多。有什么方法可以找到函数的导数并替换它们?我不知道您要解决的问题以及这是否可行。
  • 您可能会发现pyautodiff 很有帮助。它使用 Theano,它可以在 GPU 上运行,并且你将它传递给 numpy 类型。

标签: python numpy cython


【解决方案1】:

首先要注意的是,优化代码就是要找到代码中的瓶颈。通常很少有函数、循环等会消耗大部分时间。这些是优化的合适人选。所以最重要的是:使用分析器评估您的代码性能

优化python代码的第一件事是逐行检查代码并检查每一行是否创建了新对象。这是因为与简单的算术相比,创建对象的成本非常高。经验法则:尽可能避免创建对象。但请确保不要在时间紧迫的循环中创建任何新对象。

看看f*((xk + d,) + args)。这是非常好的 python 代码 - 但如果您需要高性能,则不适合。它将在循环的每一步中创建一个新的参数元组。以不创建任何对象的方式重写它可能会给您带来巨大的性能提升。

下一步是开始静态输入。确保键入循环中使用的所有内容。输入k 可能会让你受益匪浅。

之后,您可以尝试通过取消设置 boundscheck 等来进一步优化。

最重要的是:迭代地进行优化,并通过分析代码来检查性能提升。大多数时候,很难看出代码中真正的瓶颈是什么。分析会给你提示:如果优化没有给你带来太多好处,你可能错过了瓶颈。

【讨论】:

  • 添加到答案中,评估代码的快速方法是使用 cython -a <code.py> 生成 html 文件。颜色越深,运行该语句所需的时间就越长。
  • 感谢@cel 的详细回复。当您说要避免创建对象时,您能更具体一点吗?如果f 是一个函数句柄,我如何将它作为参数传递给偏导函数并在不过度创建对象的情况下使用它?
  • 好吧,优化总是一种权衡。当然,最好的办法是将你的函数f 硬编码到你的代码中,而不是调用它。但这可能不是您的选择。但是,如果函数 f 一次计算整个向量的所有偏导数,则可以限制函数的调用。另一个想法是不为f 接受额外的参数args,而是期望函数f 已经知道所有这些参数。幸运的是,在 python 中这不是一个大问题:您可以使用functools.partial 创建一个合适的函数f'
  • 不幸的是,使用partial 将再次花费您一些时间:请参阅:stackoverflow.com/questions/17388438/…。但是,我非常有信心该代码将比您目前拥有的代码快得多。不过,您将不得不尝试、基准测试,看看这是否正确。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-19
相关资源
最近更新 更多