性能和缓存的Numpy Pure函数



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

所以函数是计算sigmoid(X),另一个是计算其导数(梯度)。Sigmoid具有这样的属性:对于
y=Sigmoid(x),dy/dx=y(1-y)
在numpy的python中,这看起来像:

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是否会发现这些都是纯函数,并酌情为我缓存它们
我什么都不担心吗

这些函数已经存在于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是S型函数。它可用作scipy.stats.logisticcdf方法,但cdf最终调用expit,因此使用该方法没有意义。您可以使用pdf方法来计算sigmoid函数的导数,也可以使用开销较小但"滚动自己的"更快的_pdf方法:

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

定时(x的长度为1001):

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

但它能更好地处理尾部:

In [103]: sigmoid_grad_sech2(-500)
Out[103]: 7.1245764067412855e-218
In [104]: sigmoid_grad_sech2(500)
Out[104]: 7.1245764067412855e-218

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

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个S形(即每个25纳秒)。在Python中查找一个字典的速度比这慢,更不用说所有其他代码来实现内存化了。

我能想到的唯一优化方法是为numpy编写一个sigmoid ufunc,它基本上会在C中实现操作。这样,你就不必对整个数组执行sigmoid中的每个操作,即使numpy做得很快。

如果你想记住这个过程,我会把代码包装在一个函数中,并用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/用于具有相同语法的记忆库。

您可以通过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

我大部分同意Warren Weckesser和他上面的回答。但对于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    

相关内容

  • 没有找到相关文章

最新更新