Python 为转换点测试优化循环

Python 为转换点测试优化循环,python,numpy,statistics,Python,Numpy,Statistics,我正在尝试用Python编写一个简单的变更点查找程序。下面,函数loglike(xs)返回iid正常样本xs的最大对数似然。函数MOSTYPROBELLYP CP(XS)通过XS的中~(75%)中的每个点循环,并使用似然比找到XS中最有可能的变化点。p> 我正在使用二进制分段,我正在引导以获得似然比的临界值,因此我需要调用most_PROPERMIBLE_cp()数千次。有没有办法加快速度?Cython会帮忙吗?我从来没用过 import numpy as np def loglike(xs)

我正在尝试用Python编写一个简单的变更点查找程序。下面,函数loglike(xs)返回iid正常样本xs的最大对数似然。函数MOSTYPROBELLYP CP(XS)通过XS的中~(75%)中的每个点循环,并使用似然比找到XS中最有可能的变化点。p> 我正在使用二进制分段,我正在引导以获得似然比的临界值,因此我需要调用most_PROPERMIBLE_cp()数千次。有没有办法加快速度?Cython会帮忙吗?我从来没用过

import numpy as np

def loglike(xs):
    n = len(xs)
    mean = np.sum(xs)/n
    sigSq = np.sum((xs - mean)**2)/n
    return -0.5*n*np.log(2*np.pi*sigSq) - 0.5*n


def most_probable_cp(xs, left=None, right=None):
    """
    Finds the most probable changepoint location and corresponding likelihood for xs[left:right]
    """
    if left is None:
        left = 0

    if right is None:
        right = len(xs)

    OFFSETPCT = 0.125
    MINNOBS = 12

    ys = xs[left:right]
    offset = min(int(len(ys)*OFFSETPCT), MINNOBS)
    tLeft, tRight = left + offset, right - offset
    if tRight <= tLeft:
        raise ValueError("left and right are too close together.")

    maxLike = -1e9
    cp = None
    dataLike = loglike(ys)
    # Bottleneck is below.
    for t in xrange(tLeft, tRight):
        profLike = loglike(xs[left:t]) + loglike(xs[t:right])
        lr = 2*(profLike - dataLike)
        if lr > maxLike:
            cp = t
            maxLike = lr

    return cp, maxLike
将numpy导入为np
def loglike(xs):
n=len(xs)
平均值=np.和(xs)/n
sigSq=np.和((xs-平均值)**2)/n
返回值-0.5*n*np.log(2*np.pi*sigSq)-0.5*n
def最大可能值(xs,左=无,右=无):
"""
查找xs[左:右]最可能的变化点位置和相应的可能性
"""
如果“左”为“无”:
左=0
如果没有权利:
右=len(xs)
偏移PCT=0.125
MINNOBS=12
ys=xs[左:右]
偏移量=最小值(整数(长度)*偏移量,最小值)
t左,右=左+偏移,右-偏移
如果tRight maxLike:
cp=t
maxLike=lr
返回cp,maxLike

首先,使用Numpy的标准偏差实现。这不仅会更快,而且会更稳定

def loglike(xs):
    n = len(xs)
    return -0.5 * n * np.log(2 * np.pi * np.std(xs)) - 0.5 * n
如果您真的想压缩毫秒,可以使用瓶颈的
nanstd
函数,因为它更快。如果你想节省微秒,你可以用
math.log
替换
np.log
,因为你只对一个数字进行操作,如果
xs
是一个数组,你可以使用
xs.std()
。但在走这条路之前,我建议您使用这个版本,并分析结果,看看时间花在哪里

编辑

如果您分析loglike
python-mcprofile-o输出yourprogram.py;运行snake输出,您将看到大部分(大约80%)的时间都花在计算
np.std
。这是我们的第一个目标。正如我前面所说的,最好的调用是使用
瓶颈.nanstd

import bottleneck as bn

def loglike(xs):
    n = len(xs)
    return -0.5 * n * np.log(2 * np.pi * bn.nanstd(xs)) - 0.5 * n
在我的基准测试中,它的加速比是8倍,而且只有30%的时间
len
是5%,因此没有必要进一步研究它。将np.log和np.pi替换为它们的数学对应项,并采用公共因子,我可以将时间再次减半

return -0.5 * n * (math.log(2 * math.pi * bn.nanstd(xs)) - 1)
我仍然可以保留一个传统的10%,这有点损害了可读性:

factor = math.log(2*math.pi)

def loglike(xs):
    n = len(xs)
    return -0.5 * n * (factor + math.log(bn.nanstd(xs)) - 1)
编辑2

如果您真的想推送它,您可以替换bn.nanstd作为专用函数。在循环之前,定义
std,=bn.func.nansum_选择器(xs,axis=0)
并使用它来代替bn.nanstd,或者如果不打算更改数据类型,只使用
func.nanstd_1d_float64_axisNone


我认为这和Python中的速度一样快。尽管如此,一半的时间都花在数字运算上,也许Cython能够优化这一点,但是调用Python会增加开销,可以弥补这一点。

我将做一些基本更改:目前,在每次迭代中重新计算每个分区的平方和、平均值和计数,这使得这种“交叉验证风格”的算法是二次的

你能做的就是利用半群结构,在你改变分区时,在线计算每个元素的平方值、计数和平均值——基本上融合np.sum中的隐式循环。然后取
-0.5*n*np.log(2*np.pi*sigSq)-0.5*n
并根据更新后的n、mean和sigSq值进行计算(需要根据平方值之和计算stddev)

与np.std相比,这将渐进地加快代码的速度,并节省一些函数调用,但可能会牺牲数值稳定性。你可能需要kahan的总结

如果您不需要实际的对数似然,您可以只关注最大化
proflike
,然后在循环外计算
lr
,节省一些倍数--(如果您做一些基本代数,您可以将函数中的2*和0.5*折叠在一起)

这项技术是HLearn及其交叉验证高性能背后的技术

编辑:一些代码可能比其他代码快得多。这里最大的代价是迭代开销和np.log调用。可能存在一些fencepost错误,但要点如下:

rcnt=n
rsum=np.和(arr)
rssq=np.和(arr**2)
lcnt=0
lsum=0
lssq=0
maxlike=-1e9#或-Inf
cp=-1
对于arr中的i:#arr是长度n
lcnt+=1
lsum+=i
lssq+=i*i
lmean=lsum/lcnt
lvar=((lssq-lmean**2)/(lcnt))
loglike_l=lcnt*np.log(2*np.pi*lvar)-lcnt
rcnt-=1
rsum-=i
rssq-=i*i
rmean=rsum/rcnt
rvar=((rssq-rmean**2)/(rcnt))
loglike_r=rcnt*np.log(2*np.pi*rvar)-rcnt
loglike_总计=loglike_l+loglike_r
如果maxlike
谢谢。我试着替换这些功能,但没有多大帮助。我希望至少能加速10倍。你的时间都花在哪里了?我们可以盯着你的代码寻找可能的瓶颈,直到我们的眼睛受伤,仍然错过它们。你知道怎么做分析吗?对不起。应该更清楚:90%的时间都在profLike=loglike(xs[left:t])+loglike(xs[t:right])行中,在我的基准测试中,我现在得到了20倍的改进。希望你能在实际案例中得到类似的结果。当然,如果这仍然是一个重大的瓶颈,你可以尝试并行。非常感谢。在读了你最初的帖子后,我只是想知道,是的,这是更新左手数量的更好方法。右手也需要得到补偿。当然,在做之前先检查一下是否需要。这个