我正在用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)))
可以看出,这两个函数都是纯函数(没有副作用),因此它们是理想的记忆选择,至少就短期而言,我担心缓存每次对S型信号的调用:存储10 ^ 12个浮点数将占用数TB的RAM。
是否有优化此方法的好方法? python是否会发现它们是纯函数,并在适当时为我缓存它们? 我有什么担心的吗?
这些功能已经存在于scipy中。乙状结肠功能可用scipy.special.expit。
scipy.special.expit
In [36]: from scipy.special import expit
与expit向量化S型函数比较:
expit
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形函数。它可以作为的cdf方法使用scipy.stats.logistic,但cdf最终会调用expit,因此使用该方法毫无意义。您可以使用该pdf方法来计算Sigmoid函数的导数,也可以使用_pdf开销较小的方法,但是“自己滚动”的方法更快:
cdf
scipy.stats.logistic
pdf
_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:
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:
sech**2
1/cosh**2
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