【问题标题】:How can I speed up Cython code to compute conditional log likelihood of dirichlet?如何加快 Cython 代码以计算 dirichlet 的条件对数似然性?
【发布时间】:2015-07-26 15:47:23
【问题描述】:

我有一个函数可以计算狄利克雷分布的条件(在第 k 个 alpha 上)对数似然。我用 Cython 编写并编译了它,但我的代码调用它大约 12M 次,这似乎是瓶颈,所以我希望加快速度。

cimport numpy as np
import numpy as np
import math
DTYPE = np.float64
ctypedef np.float64_t DTYPE_t

def logFullConAlphaK(np.ndarray p,np.ndarray alpha, np.int k):
    assert p.dtype == np.float64 and alpha.dtype == np.float64
    cdef double t1=sum(np.log(p))
    cdef DTYPE_t y=((alpha[k-1]-1)*t1)-np.log(alpha[k-1])+(p.shape[0]*
                   (math.lgamma(sum(alpha))- math.lgamma(alpha[k-1])))
return y

我将 Cython 编译成我在代码中使用的 .pyd 文件。有什么想法可以加快速度吗?

谢谢

【问题讨论】:

  • 在要求代码优化时,请始终包含示例数据并为您的代码提供时序。这使其他人更容易帮助您。也就是说,在高性能 numpy 代码中使用 np.sum 而不是内置的 sum

标签: python performance numpy optimization cython


【解决方案1】:

1) 通过声明输入数组和p.shape[0] 的数据类型和维度:

def logFullConAlphaK(np.ndarray[DTYPE_t, ndim=1] p,
                     np.ndarray[DTYPE_t, ndim=1] alpha, int k):

    ...
    cdef int tmp
    tmp = p.shape[0]

2) 通过使用 C 函数而不是模块 math 中的 Python 函数:

cdef extern from "math.h":
    double log(double x) nogil

3) 使用 NumPy 的np.ndarray.sum() 方法

4) 使用 Cython 指令来避免一些开销

总共:

#cython: wraparound=False
#cython: boundscheck=False
#cython: cdivision=True
#cython: nonecheck=False

import math

cimport numpy as np
import numpy as np

cdef extern from "math.h":
    double log(double x) nogil    

DTYPE = np.float64
ctypedef np.float64_t DTYPE_t

def logFullConAlphaK(np.ndarray[DTYPE_t, ndim=1] p,
                     np.ndarray[DTYPE_t, ndim=1] alpha, int k):
    assert p.dtype == np.float64 and alpha.dtype == np.float64
    cdef double t1
    cdef int tmp

    t1 = np.log(p).sum()
    tmp = p.shape[0]

    cdef DTYPE_t y=((alpha[k-1]-1)*t1)-log(alpha[k-1])+(tmp*
                   (math.lgamma(alpha.sum()) - math.lgamma(alpha[k-1])))

    return y

OP 的原始解决方案、@cel 的解决方案和我的解决方案之间的一些性能比较:

In [2]: timeit solOP(a, b, 10)
1000 loops, best of 3: 273 µs per loop

In [3]: timeit solcel(a, b, 10)
10000 loops, best of 3: 30.5 µs per loop

In [4]: timeit solS(a, b, 10)
100000 loops, best of 3: 15.8 µs per loop

【讨论】:

    【解决方案2】:

    拿这个(可能完全不切实际的)样本数据:

    n = 1000000
    p = np.random.rand(n)
    alpha = np.random.rand(n)
    k = 12
    

    我得到以下时间:

    %timeit logFullConAlphaK(p, alpha, k) -> 1 loops, best of 3: 174 ms per loop

    %timeit logFullConAlphaK_opt(p, alpha, k) -> 100 loops, best of 3: 13.3 ms per loop

    此版本已经为您提供了一个数量级的速度。请注意,几乎所有的加速都来自使用 np.sum 而不是内置的 sum。所有其他更改只是为了更简洁的代码,它们不会影响速度。

    cimport numpy as np
    import numpy as np
    import math
    
    def logFullConAlphaK_opt(double[:] p, double[:] alpha, int k):
        cdef double t1=np.sum(np.log(p))
        cdef double y=((alpha[k-1]-1)*t1)-np.log(alpha[k-1])+(p.shape[0]*
                       (math.lgamma(np.sum(alpha))- math.lgamma(alpha[k-1])))
        return y 
    

    【讨论】:

    • 并不是所有的加速都来自于使用np.sum(),也来自于您使用具有指定数据类型的内存视图这一事实
    • @SaulloCastro,我在这里看到类型化内存视图与 numpy 数组版本的时间非常相似。说all 的速度来自np.sum 可能不是一个好主意,但肯定这是相关部分。我有兴趣查看比较您的答案、我的答案和原始代码的时间。
    • 我在答案中添加了时间比较,通过添加其他微调,我的速度是你的两倍
    • 谢谢你们!这对这段代码和一般来说都非常有帮助。感谢您的宝贵时间。
    • @hashimba,很高兴我们能提供帮助。如果您的问题得到解决,请考虑将其中一个答案标记为已接受的答案。我建议在这里接受 Saullo Castro 的回答。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-19
    • 2019-03-01
    • 1970-01-01
    • 2015-10-12
    相关资源
    最近更新 更多