我正在写一些适度的性能关键代码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会不会选择这些纯函数并根据需要为我缓存它们? 我什么都不担心?
答案 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