什么是条件计算的最pythonic方式?

4
我正在使用Python/NumPy实现贝叶斯变点检测(如果您感兴趣,可以查看论文)。我需要计算数据范围[a, b]的似然性,其中ab可以取从1n的所有值。但是,我可以在某些点上剪枝计算,这样我就不必计算每个似然性。另一方面,有些似然性被多次使用,因此我可以将值保存在矩阵P[a, b]中以节省时间。现在,每当我使用它时,我都会检查该值是否已经计算过,但我觉得这有点麻烦。它看起来像这样:
# ...
P = np.ones((n, n)) * np.inf  # a likelihood can't get inf, so I use it 
                            # as pseudo value

for a in range(n):
    for b in range(a, n):

        # The following two lines get annoying and error prone if you 
        # use P more than once

        if P[a, b] == np.inf:  
            P[a, b] = likelihood(data, a, b)

        Q[a] += P[a, b] * g[a] * Q[a - 1]  # some computation using P[a, b]
        # ...

我想知道是否有更加直观和符合Python风格的方法来实现此目标,而不需要在每次使用P[a,b]之前都要写上if...语句。比如说,如果不满足某些条件就自动调用函数。我当然可以让likelihood函数意识到它可以保存值,但那样它就需要一些状态(比如变成对象)。我想避免这种情况。

似然函数

由于在评论中被要求提供,我会附上似然函数。它实际上计算了共轭先验和似然函数。而且全部以对数形式表示...所以它非常复杂。

from scipy.special import gammaln
def gaussian_obs_log_likelihood(data, t, s):
    n = s - t
    mean = data[t:s].sum() / n

    muT = (n * mean) / (1 + n)
    nuT = 1 + n
    alphaT = 1 + n / 2
    betaT = 1 + 0.5 * ((data[t:s] - mean) ** 2).sum() + ((n)/(1 + n)) * (mean**2 / 2)
    scale = (betaT*(nuT + 1))/(alphaT * nuT)

    # splitting the PDF of the student distribution up is /much/ faster. (~ factor 20)
    prob = 1
    for yi in data[t:s]:
        prob += np.log(1 + (yi - muT)**2/(nuT * scale)) 

    lgA = gammaln((nuT + 1) / 2) - np.log(np.sqrt(np.pi * nuT * scale)) - gammaln(nuT/2)

    return n * lgA - (nuT + 1)/2 * prob

虽然我使用的是Python 2.7,但2.7和3.x版本的答案都可以。


也许可以使用初始值为 None 吗?然后,如果 P[a, b] 不存在,你就可以使用 P[a, b] = likelihood(data, a, b) - user189
由于整个数组 P 的初始值都是 inf,那么 if 语句的条件不应该总是为真吗?或者你在代码的其他部分更改了 P 吗? - Bas Swinckels
P[a, b]被多次使用。因此,if...语句只用于计算一次。第一次需要P[a, b]时,我实际上在if语句中更改了它。为了使问题更清晰,代码有些简化。 - hildensia
@user189,由于我使用的是numpy数组而不是普通的Python数组,因此我不能在其中放置None,而只能放置数字。但是,为了进行一些其他计算,我需要它成为一个numpy数组。 - hildensia
你真的需要一个二维矩阵吗?也许每次你需要计算 P[a,b] 时,你可以分别计算 P[1,a]P[1,b],存储这些结果,然后返回它们的差异。之后,你只需要计算先前未见过的 P[1,x] - Adrian Ratnapala
显示剩余4条评论
1个回答

4
我会为此使用defaultdict的兄弟函数(无法直接使用defaultdict,因为它不会告诉你缺失的键):
class Cache(object):
    def __init__(self):
        self.cache = {}

    def get(self, a, b):
        key = (a,b)

        result = self.cache.get(key, None)
        if result is None:
            result = likelihood(data, a, b)
            self.cache[key] = result

        return result

另一种方法是在 likelihood 上使用缓存修饰符,具体方法可以参考这里

我喜欢装饰器的解决方案! - hildensia
1
我也喜欢装饰器的解决方案,但如果你需要控制缓存的生命周期,那么这个代码片段就很好了...不过,在字典上使用.get(key)而不是try:catch,它更快... - Retozi
@Retozi:已优化 :-) - Aaron Digulla

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接