为了账号安全,请及时绑定邮箱和手机立即绑定

“reduceat”中最快的 Python log-sum-exp

“reduceat”中最快的 Python log-sum-exp

杨魅力 2022-07-05 14:53:42
作为统计编程包的一部分,我需要将对数转换后的值与LogSumExp 函数一起添加。这比将未记录的值加在一起效率要低得多。此外,我需要使用numpy.ufunc.reduecat功能将值相加。我考虑过多种选择,代码如下:(用于在非对数空间中进行比较)使用numpy.add.reduceatNumpy 的 ufunc 用于将记录的值相加:np.logaddexp.reduceat具有以下 logsumexp 函数的手写 reduceat 函数:scipy对logsumexp的实现Python 中的 logsumexp 函数(带有numba)Python 中的流式 logsumexp 函数(使用numba)def logsumexp_reduceat(arr, indices, logsum_exp_func):    res = list()    i_start = indices[0]    for cur_index, i in enumerate(indices[1:]):        res.append(logsum_exp_func(arr[i_start:i]))        i_start = i    res.append(logsum_exp_func(arr[i:]))    return res@numba.jit(nopython=True)def logsumexp(X):    r = 0.0    for x in X:        r += np.exp(x)      return np.log(r)@numba.jit(nopython=True)def logsumexp_stream(X):    alpha = -np.Inf    r = 0.0    for x in X:        if x != -np.Inf:            if x <= alpha:                r += np.exp(x - alpha)            else:                r *= np.exp(alpha - x)                r += 1.0                alpha = x    return np.log(r) + alphaarr = np.random.uniform(0,0.1, 10000)log_arr = np.log(arr)indices = sorted(np.random.randint(0, 10000, 100))# approach 1%timeit np.add.reduceat(arr, indices)12.7 µs ± 503 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)# approach 2%timeit np.logaddexp.reduceat(log_arr, indices)462 µs ± 17.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)# approach 3, scipy function%timeit logsum_exp_reduceat(arr, indices, scipy.special.logsumexp)3.69 ms ± 273 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)# approach 3 handwritten logsumexp%timeit logsumexp_reduceat(log_arr, indices, logsumexp)139 µs ± 7.1 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)timeit 结果表明,带有 numba 的手写 logsumexp 函数是最快的选项,但仍然比 numpy.add.reduceat 慢 10 倍。几个问题:还有其他更快的方法(或对我提出的选项进行调整)吗?例如,有没有办法使用查找表来计算 logsumexp 函数?为什么 Sebastian Nowozin 的“流式 logsumexp”函数不比天真的方法快?
查看完整描述

1 回答

?
largeQ

TA贡献2039条经验 获得超8个赞

有一些改进的空间


但永远不要期望 logsumexp 和标准求和一样快,因为这exp是一项相当昂贵的操作。


例子


import numpy as np


#from version 0.43 until 0.47 this has to be set before importing numba

#Bug: https://github.com/numba/numba/issues/4689

from llvmlite import binding

binding.set_option('SVML', '-vector-library=SVML')

import numba as nb


@nb.njit(fastmath=True,parallel=False)

def logsum_exp_reduceat(arr, indices):

    res = np.empty(indices.shape[0],dtype=arr.dtype)


    for i in nb.prange(indices.shape[0]-1):

        r = 0.

        for j in range(indices[i],indices[i+1]):

            r += np.exp(arr[j])  

        res[i]=np.log(r)


    r = 0.

    for j in range(indices[-1],arr.shape[0]):

        r += np.exp(arr[j])  

    res[-1]=np.log(r)

    return res

计时


#small example where parallelization doesn't make sense

arr = np.random.uniform(0,0.1, 10_000)

log_arr = np.log(arr)

#use arrays if possible

indices = np.sort(np.random.randint(0, 10_000, 100))


%timeit logsum_exp_reduceat(arr, indices)

#without parallelzation 22 µs ± 173 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

#with parallelization   84.7 µs ± 32.2 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)


%timeit np.add.reduceat(arr, indices)

#4.46 µs ± 61.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)


#large example where parallelization makes sense

arr = np.random.uniform(0,0.1, 1000_000)

log_arr = np.log(arr)

indices = np.sort(np.random.randint(0, 1000_000, 100))


%timeit logsum_exp_reduceat(arr, indices)

#without parallelzation 1.57 ms ± 14.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

#with parallelization   409 µs ± 14.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)


%timeit np.add.reduceat(arr, indices)

#340 µs ± 11.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)


查看完整回答
反对 回复 2022-07-05
  • 1 回答
  • 0 关注
  • 233 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号