【问题标题】:Use of Taylor series to speed up computation使用泰勒级数加速计算
【发布时间】:2017-03-20 22:34:04
【问题描述】:

在数学中,Taylor series 对于使用小次数多项式获得函数的近似值很重要。

我想看看这样的近似值有何帮助,例如为了加快计算速度。让我们使用著名的泰勒级数:

log(1+x) = x + 0.5 * x^2 + (error term)

从道德上讲,计算 2 次多项式的值应该比计算 log 快​​得多。

因此有一个代码来测试这个:

import numpy, time

def f(t):
    return t + 0.5 * t ** 2
f = numpy.vectorize(f)  

s = time.time()
for i in range(100):
    x = numpy.random.rand(100000) 
    numpy.log(1 + x)
print time.time() - s          # 0.556999921799 seconds

s = time.time()
for i in range(100):
    x = numpy.random.rand(100000)
    f(x)
print time.time() - s          # arghh! 4.81500005722 seconds

为什么多项式法比实际对数慢10倍?我的预期正好相反。

PS:这道题大概在SO和math.SE中间。

【问题讨论】:

  • 你看过 numpy 是如何计算日志的吗? numpy 的 log() 本身很有可能已经优化了
  • 使用numpy.log(1 + x),您正在使用以实际矢量化方式运行的 NumPy 数组编程,而使用 np.vectorize 作为doc 状态:"The vectorize function is provided primarily for convenience, not for performance"。因此,等效的方法是在替换 f(x) 时直接使用 xx + 0.5 * x ** 2
  • 泰勒级数对于远离展开点的参数会有收敛问题。这适用于所有推断。
  • @duffymo 好吧,这对于所有的推断都是不正确的。多项式在任何时候都非常接近“足够长”的泰勒展开式。
  • 通过霍纳规则计算多项式几乎总是更好。在你的情况下 t*(1.0+0.5*t)。您使用了幂运算符 **,这可能会很慢。

标签: python numpy math scipy taylor-series


【解决方案1】:

使用 numpy 的矢量化操作几乎总是比在您自己的代码中尝试的任何优化都快。正如@Divakar 所提到的,vectorize 实际上只是编写 for 循环的一种便捷方式,因此您的代码将比 numpy 的本机代码慢。

用标准python代码替换numpy的优化例程表明你的方法速度差不多。

import math, numpy, time


def f(t):
    return t + 0.5 * t ** 2

x = numpy.random.rand(1000000)

s = time.time()
for num in x:
    math.log(1 + num)
print (time.time() - s  )  

s = time.time()
for num in x:
    f(num)
print (time.time() - s)      

结果:

1.1951053142547607
1.3485901355743408

逼近只是稍微慢一些,但求幂非常昂贵。将t ** 2 替换为t*t 可以带来很好的改进,并且近似值略优于python 的log

1.1818947792053223
0.8402454853057861

编辑:另外,由于这里的重要教训是优化的科学库几乎在一周中的任何一天都将胜过手动编码的解决方案,这是使用 numpy 的矢量化操作的泰勒级数近似,这是迄今为止最快的。注意唯一大的变化是vectorize没有在逼近函数上调用,所以默认使用numpy的向量化操作。

import numpy, time

def f(t):
    return t + 0.5 * t ** 2

x = numpy.random.rand(1000000)
s = time.time()
numpy.log(1 + x)
print (time.time() - s)

s = time.time()
x = numpy.random.rand(100000)
f(x)
print (time.time() - s  )

结果:

0.07202601432800293
0.0019881725311279297

你知道了,矢量化近似比 numpy 的矢量化 log 快一个数量级。

【讨论】:

  • 呃,最重要的是,numpy.log 将在 C 中实现,而您的函数需要 Python 解释器。
  • @Max,没错。当比较相等时(使用 numpy 的矢量化函数进行对数和近似),函数会快得多。
  • 读起来很有趣。结论:Numpy 和 Python 级别太高/太优化,无法对 2 阶泰勒展开如何比 log 更快进行基准测试。我会用纯 C 再做一次,看看会发生什么。
【解决方案2】:

使用 Python+Numpy,它可能到处都进行了优化,因此不可能真正对 log(1+x)x + 0.5 * x^2 进行基准测试。 所以我转向了 C++。

结果:

每次操作记录的时间:19.57 ns
对数的 2 阶泰勒展开的每次操作时间:3.73 ns

大概是 x5 倍!


#include <iostream>
#include <math.h>
#include <time.h>
#define N (1000*1000*100)
#define NANO (1000*1000*1000)

int main()
{
  float *x = (float*) malloc(N * sizeof(float));
  float y;
  float elapsed1, elapsed2;
  clock_t begin, end;
  int i;

  for (i = 0; i < N; i++) 
    x[i] = (float) (rand() + 1) / (float)(RAND_MAX);

  begin = clock();
  for (i = 0; i < N; i++) 
    y = logf(x[i]);
  end = clock();
  elapsed1 = float(end - begin) / CLOCKS_PER_SEC / N * NANO;

  begin = clock();
  for (i = 0; i < N; i++) 
    y = x[i] + 0.5 * x[i] * x[i];  
  end = clock();
  elapsed2 = float(end - begin) / CLOCKS_PER_SEC / N * NANO;

  std::cout << "Time per operation with log: " << elapsed1 << " ns\n";  
  std::cout << "Time per operation with order-2 Taylor epansion: " << elapsed2 << " ns";

  free(x);

}

【讨论】:

  • 它可以在 python 中进行基准测试,问题是您将 numpy 的优化代码与原始 python 进行比较。当比较基本 python 和基本 python,或 numpy 实现到 numpy 时,近似的速度是显而易见的。
  • 是的,但是比较“基本 python 日志”和“基本 python 泰勒扩展”可以得到你的 1.1818947792053223 / 0.8402454853057861 比率约为 1.4。这是相当低的,我期望更多的 x5 或 x10 比率,因此 C/C++ 版本可以看到更多发生的事情......
  • 是的,解释器的开销并不令人惊讶。 numpy 版本显示 0.07202601432800293 vs 0.0019881725311279297,这就是你想要显示的。
  • 是的,是的。谢谢。
猜你喜欢
  • 2015-05-15
  • 1970-01-01
  • 2017-09-27
  • 2020-03-01
  • 1970-01-01
  • 2016-04-26
  • 1970-01-01
  • 2015-11-23
  • 2014-03-27
相关资源
最近更新 更多