【问题标题】:Numpy Pure Functions for performance, caching用于性能、缓存的 Numpy 纯函数
【发布时间】:2014-02-02 01:48:11
【问题描述】:

我正在用 numpy. 此代码将位于最内层循环中,计算的运行时间以小时为单位。 快速计算表明,在某些计算变体中,此代码将被执行大约 10^12 次。

所以这个函数是计算 sigmoid(X) 和另一个计算它的导数(梯度)。 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 是否会发现这些是纯函数并酌情为我缓存它们?
我什么都不担心吗?

【问题讨论】:

  • 请注意,1/(1+np.exp(-x)) 已经接受 numpy 数组作为输入,而且速度非常快(我猜记忆化根本没有帮助)。通过使用vectorize,它会变得更慢,因为它是使用慢 for 循环实现的。
  • x 的大小是多少?
  • @M4rtini,不同,但我一直假设我的 tumb 计算规则,x 通常是长度为 100 的向量。

标签: python numpy optimization memoization blas


【解决方案1】:

我主要同意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    

【讨论】:

    【解决方案2】:

    这些函数已经存在于 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 函数。它可以作为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**21/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
    

    【讨论】:

    • 当你说“你可以使用pdf方法计算sigmoid函数的导数,或者开销较小的_pdf方法”overhead是什么意思 究竟是什么意思?您是否建议受保护的方法更快,因为它执行的代码更少?
    • @Nicholas 是的,._pdf() 对参数的错误检查较少。它也不使用分布的loc(位置)和scale 参数——它们在.pdf() 方法中处理。 .pdf() 最终调用._pdf() 进行实际计算。
    【解决方案3】:

    只是扩展我的评论,这里是你的 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 个 sigmoid(即每个 25 纳秒)。 Python 中的单个字典查找比这要慢,更不用说所有其他代码来获得记忆。

    我能想到的唯一优化方法是为 numpy 编写一个 sigmoid ufunc,它基本上将在 C 中实现操作。这样,您就不必对 sigmoid 中的每个操作执行整个数组,尽管 numpy 的速度非常快。

    【讨论】:

    • 用现有的训练 RBM 的代码自己测试了它:矢量化:1 个循环,最好的 3:每个循环 5.44 秒 nonverctorised:1 个循环,最好的 3:每个循环 4.75 秒,带 def 的非矢量化而不是 lambda:1 个循环,最好的 3 个:每个循环 4.53 秒 不是很具体的数字,因为它只做了一个循环,但我认为是指示性的。因此,对于这样一个微小的变化,一个坚实的加速。
    【解决方案4】:

    如果你想记住这个过程,我会将该代码包装在一个函数中并用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
    

    【讨论】:

    • 我要补充一点,functools.lru_cache 是在 python 3.2 中引入的 :)
    • 好电话——我添加了一个 2.7 替代我的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-07
    • 2016-03-15
    相关资源
    最近更新 更多