Numpy Pure函数用于性能,缓存

时间:2014-01-14 04:32:01

标签: python numpy optimization memoization blas

我正在写一些适度的性能关键代码numpy。 此代码将位于最内部循环中,计算的运行时间以小时为单位。 快速计算表明,在计算的某些变体中,此代码将执行大约10 ^ 12次。

因此函数是计算sigmoid(X)而另一个函数是计算它的导数(gradient)。 Sigmoid具有以下性质: y = sigmoid(x),dy / dx = y(1-y)
在python for numpy中,这看起来像:

sigmoid = vectorize(lambda(x): 1.0/(1.0+exp(-x)))
grad_sigmoid = vectorize(lambda (x): sigmoid(x)*(1-sigmoid(x)))

可以看出,两种功能都是纯粹的(没有副作用), 所以他们是备忘录的理想候选人, 至少在短期内,我有一些担心缓存每次对sigmoid的调用:存储10 ^ 12个浮点数,这需要几TB的RAM。

有优惠的方法吗? python会不会选择这些纯函数并根据需要为我缓存它们? 我什么都不担心?

4 个答案:

答案 0 :(得分:30)

这些功能已经存在于scipy中。 sigmoid函数以scipy.special.expit形式提供。

In [36]: from scipy.special import expit

expit与矢量化sigmoid函数进行比较:

In [38]: x = np.linspace(-6, 6, 1001)

In [39]: %timeit y = sigmoid(x)
100 loops, best of 3: 2.4 ms per loop

In [40]: %timeit y = expit(x)
10000 loops, best of 3: 20.6 µs per loop

expit也比自己实施公式更快:

In [41]: %timeit y = 1.0 / (1.0 + np.exp(-x))
10000 loops, best of 3: 27 µs per loop

逻辑分布的CDF是sigmoid函数。它作为cdf的{​​{1}}方法提供,但scipy.stats.logistic最终会调用cdf,因此使用该方法毫无意义。您可以使用expit方法计算sigmoid函数的导数,或者pdf方法,其开销较小,但“滚动自己”更快:

_pdf

时间(x的长度为1001):

In [44]: def sigmoid_grad(x):
   ....:     ex = np.exp(-x)
   ....:     y = ex / (1 + ex)**2
   ....:     return y

如果要使用远离尾部的值,请注意您的实现。指数函数很容易溢出。 In [45]: from scipy.stats import logistic In [46]: %timeit y = logistic._pdf(x) 10000 loops, best of 3: 73.8 µs per loop In [47]: %timeit y = sigmoid_grad(x) 10000 loops, best of 3: 29.7 µs per loop logistic._cdf的快速实施更加强大:

sigmoid_grad

使用In [60]: sigmoid_grad(-500) /home/warren/anaconda/bin/ipython:3: RuntimeWarning: overflow encountered in double_scalars import sys Out[60]: 0.0 In [61]: logistic._pdf(-500) Out[61]: 7.1245764067412855e-218 sech**2)的实施比上述1/cosh**2慢一点:

sigmoid_grad

但它更好地处理尾巴:

In [101]: def sigmoid_grad_sech2(x):
   .....:     y = (0.5 / np.cosh(0.5*x))**2
   .....:     return y
   .....: 

In [102]: %timeit y = sigmoid_grad_sech2(x)
10000 loops, best of 3: 34 µs per loop

答案 1 :(得分:5)

只是扩展我的评论,这里是你的sigmoid到vectorize和直接使用numpy之间的比较:

In [1]: x = np.random.normal(size=10000)

In [2]: sigmoid = np.vectorize(lambda x: 1.0 / (1.0 + np.exp(-x)))

In [3]: %timeit sigmoid(x)
10 loops, best of 3: 63.3 ms per loop

In [4]: %timeit 1.0 / (1.0 + np.exp(-x))
1000 loops, best of 3: 250 us per loop

正如你所看到的,vectorize不仅使它慢得多,事实是你可以在250微秒内计算10000个sigmoids(即每个25纳秒)。 Python中的单个字典查找比这慢,更不用说所有其他代码来实现memoization。

我能想到的优化这个的唯一方法就是为numpy写一个sigmoid ufunc,它基本上会在C中实现这个操作。那样,你就不必在sigmoid中做每个操作了整个阵列,尽管numpy确实很快。

答案 2 :(得分:1)

如果您想要记住这个过程,我会将该代码包装在一个函数中并用functools.lru_cache(maxsize=n)进行修饰。尝试使用maxsize值来查找适合您的应用程序的大小。为获得最佳结果,请使用{2}的幂maxsize参数。

from functools import lru_cache

lru_cache(maxsize=8096)
def sigmoids(x):
    sigmoid = vectorize(lambda(x): 1.0/(1.0+exp(-x)))
    grad_sigmoid = vectorize(lambda (x): sigmoid(x)*(1-sigmoid(x)))
    return sigmoid, grad_sigmoid

如果您使用的是2.7(我希望您使用的是numpy),您可以查看https://pypi.python.org/pypi/repoze.lru/以获得具有相同语法的memoization库。

您可以通过pip安装它:pip install repoze.lru

from repoze.lru import lru_cache

lru_cache(maxsize=8096)
def sigmoids(x):
    sigmoid = vectorize(lambda(x): 1.0/(1.0+exp(-x)))
    grad_sigmoid = vectorize(lambda (x): sigmoid(x)*(1-sigmoid(x)))
    return sigmoid, grad_sigmoid

答案 3 :(得分:0)

大多数情况下,我同意Warren Weckesser及其答案above。 但对于sigmoid的衍生物,可以使用以下内容:

In [002]: def sg(x):
     ...: s = scipy.special.expit(x)
     ...: return s * (1.0 - s) 

时序:

In [003]: %timeit y = logistic._pdf(x)
10000 loops, best of 3: 45 µs per loop

In [004]: %timeit y = sg(x)
10000 loops, best of 3: 20.4 µs per loop

唯一的问题是准确性:

In [005]: sg(37)
Out[005]: 0.0

In [006]: logistic._pdf(37)
Out[006]: 8.5330476257440658e-17