我正在用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.logistic
的cdf
方法,但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