在Python / Numpy中矢量化牛顿方法

时间:2015-05-12 13:07:12

标签: python numpy

我试图弄清楚Python / Numpy是否是开发我的数字软件的可行替代方案,该软件已经在C ++中提供。为了在Python / Numpy中获得性能,需要“向量化”代码。但事实证明,一旦我摆脱了非常简单的例子,我就很难对代码进行矢量化(我不是在讨论SIMD指令,而是在没有循环的情况下使用“高效的Numpy代码”)。这是一个我希望在Python / Numpy中有效获得的算法。

  1. 创建一个包含以下内容的numpy数组:1.0,1.0 + 1 / n,1.0 + 2 / n,...,2.0
  2. 对于数组中的每个u,使用牛顿方法计算x ^ 2 - u的根,在| dx |时停止< = 1.0e-7。将结果存储在数组结果中。
  3. 求结果数组的所有元素
  4. 这是我想加速的Python算法

    import numpy as np
    
    n = 1000000
    data = np.arange(1.0, 2.0, 1.0 / n)
    
    def newton(u):
      x = 2.0
      while True:
        f = x**2 - u
        df_dx = 2 * x
        dx = f / df_dx
        if (abs(dx) <= 1.0e-7):
          break
        x -= dx
      return x
    
      result = map(newton, data)
    
      print result[n - 1]
    

    以下是C ++ 11中的算法版本

    #include <iostream>
    #include <vector>
    #include <cmath>
    
    int main (int argc, char const *argv[]) {
      auto n = std::size_t{100000000};
    
      auto v = std::vector<double>(n + 1);
      for(size_t k = 0; k < v.size(); ++k) {
        v[k] = 1.0 + static_cast<double>(k) / n;
      }
    
      auto result = std::vector<double>(n + 1);
      for(size_t k = 0; k < v.size(); ++k) {
        auto x = double{2.0};
        while(true) {
          auto f = double{x * x - v[k]};
          auto df_dx = double{2 * x};
          auto dx = double{f / df_dx};
          if (std::abs(dx) <= 1.0e-7) {
            break;
          }
          x -= dx;
        }
        result[k] = x;
      }
    
      auto somme = double{0.0};
      for(size_t k = 0; k < result.size(); ++k) {
        somme += result[k];
      }
    
      std::cout << somme << std::endl;
      return 0;
    }
    

    在我的机器上运行需要2.9秒。有没有办法制作一个快速的Python / Numpy算法来做同样的事情(我愿意得到的东西慢不到5倍)。

    感谢。

3 个答案:

答案 0 :(得分:2)

您可以有效地使用numpy执行第1步:

1.0 + np.arange(n + 1) / n

但是我认为您需要使用np.vectorize()方法将x反馈到计算值中,并且它不是一个有效的函数(基本上是python循环的包装器)。如果您可以使用scipy,那么内置的方法可能会执行您想要的http://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.optimize.newton.html

编辑:考虑到这一点后,我跟进了@ ev-br的观点并尝试了一些替代方案。掩码使用了太多的处理但是abs()。max()非常快,因此折衷可能是将问题分成块&#34;在数组的第一维和迭代方向。以下在我的低功耗笔记本电脑上做得不是太差(<20s) - 肯定比np.vectorize()或我能找到的任何scipy解决系统快得多。 (如果我将m设置得太大,它就会耗尽某些东西(记忆?)并完全停止!)

n = 100000000
m = 5000000

block = 3
u = 1.0 + np.arange(n + 1) / n
x = np.full(u.shape, 2.0)
dx = np.ones(u.shape)

for i in range(0, n, m):
  while np.abs(dx[i:i+m]).max() > 1.0e-7:
    for j in range(block):
      dx[i:i+m] = (x[i:i+m] ** 2 - u[i:i+m]) / (2 * x[i:i+m])
      x[i:i+m] -= dx[i:i+m]

答案 1 :(得分:1)

这是一个玩具示例。请注意,通常矢量化意味着编写代码就好像您正在操纵数字,并让numpy发挥其魔力:

>>> import numpy as np
>>> a = np.array([1., 2., 3.])
>>> def f(x):
...    return x**2 - a, 2.*x    # function and derivative
>>>
>>> def newt(f, x0):
...    x = np.asarray(x0)
...    for _ in range(5):    # hardcode the number of iterations (I know)
...        v, dv = f(x)
...        x -=  v / dv
...    return x
>>> 
>>> newt(f, [1., 1., 1.])
array([ 1.        ,  1.41421356,  1.73205081])

如果这是一个性能瓶颈,那么这不太可能与手写的C ++代码竞争:首先,你要用所有开销来操作python对象;然后numpy可能会在引擎盖下做一堆数组分配。

一个经常可行的策略是首先在python / numpy中编写内容,然后将瓶颈转移到已编译的代码中 - 例如由Cython包装的Cython或C ++。在这种特殊情况下,因为你已经有了C ++代码,所以用Cython包装它可能是最容易的,但是YMMV。

答案 2 :(得分:0)

我不打算将小代码片段作为解决方案,但这里有一些东西可以让你开始。我有一种强烈的怀疑,你只是在python中声明这样一个数组而没有花费太多时间,所以我会帮助你。

就平方根来说,请添加你的示例python代码,我会看到从那时起我可以帮助优化的内容。在我的例子中,使用默认的numpy函数/方法找到了根和和。

def summing():
    n = 1000000
    ar = np.arange(0, n)
    ar = ar/float(n)
    ar = ar + np.ones(n)
    sqrt = np.sqrt(ar)
    return np.sum(ar)

简而言之,要获得起始阵列,最好使用“解决方法”。

  • 使用值“[1,2,3,.... n]
  • 初始化数组ar
  • ar除以n。这会让我们成为1/n, 2/n ...成员
  • 添加一个相同维度的数组,其中只包含数字1.0 这让我们得到了我们追求的完整数组[ 1., 1.000001, 1.000002, ..., 1.999998, 1.999999])。如果我理解你的话。
  • 找到平方根,求和

平均10个连续执行时间为0.018786秒。