在PyMC3中拟合两个移位的伽马分布的混合模型

时间:2017-03-11 12:57:38

标签: pymc pymc3

我是Python和MCMC技术的新手,我正在进入PyMC3。作为熟悉PyMC3的练习,我想将两个移位的伽玛分布的混合模型拟合到生成的数据中。作为下一步,我希望通过一个破坏性的过程将其扩展到“任意”数量的移位gamma,但一次只能一步。

在我对以下PyMC3 github问题的评论中可以找到指向完整代码和更多链接的指针:https://github.com/pymc-devs/pymc3/issues/864#issuecomment-285864556

以下是我尝试实施的模型:

with pm.Model() as model:
    p = pm.Beta( "p", 1., 1., testval=0.5) #this is the fraction that come from mean1 vs mean2

    alpha = pm.Uniform('alpha', 0, 10) # == k
    beta  = pm.Uniform('beta' , 0, 1) # == theta
    shifts = pm.Normal('shifts', mu=20, sd=50, shape=2)

    gamma = ShiftedGamma.dist(alpha, beta, c=shifts)
    process = pm.Mixture('obs', tt.stack([p, 1-p]), gamma, observed=data)

with model:
    step = pm.Metropolis()
    trace = pm.sample(10000, step=step)

以下是我提出的ShiftedGamma的实现:

class ShiftedLog(pm.distributions.continuous.transforms.ElemwiseTransform):
    name = "log"
    def __init__(self, c=0.0):
        self.c = c

    def backward(self, x):
        return tt.exp(x + self.c)

    def forward(self, x):
        return tt.log(x - self.c)

class ShiftedGamma(pm.distributions.continuous.Continuous):
    def __init__(self, alpha=None, beta=None, mu=None, sd=None, c=0.0, *args, **kwargs):
        super(ShiftedGamma, self).__init__(transform=ShiftedLog(c), *args, **kwargs)
        alpha, beta = self.get_alpha_beta(alpha, beta, mu, sd)
        self.alpha = alpha
        self.beta = beta
        self.mean = alpha / beta + c
        self.mode = tt.maximum((alpha - 1) / beta, 0) + c
        self.variance = alpha / beta**2
        self.c = c
        # self.testval = kwargs.pop('testval', None)
        # if not self.testval:
        #     self.testval = c + 10.0

        pm.distributions.continuous.assert_negative_support(alpha, 'alpha', 'Gamma')
        pm.distributions.continuous.assert_negative_support(beta, 'beta', 'Gamma')

    def get_alpha_beta(self, alpha=None, beta=None, mu=None, sd=None):
        if (alpha is not None) and (beta is not None):
            pass
        elif (mu is not None) and (sd is not None):
            alpha = mu**2 / sd**2
            beta = mu / sd**2
        else:
            raise ValueError('Incompatible parameterization. Either use '
                             'alpha and beta, or mu and sd to specify '
                             'distribution.')

        return alpha, beta

    def random(self, point=None, size=None, repeat=None):
        alpha, beta, c = draw_values([self.alpha, self.beta, self.c], point=point)
        return generate_samples(stats.gamma.rvs, alpha, scale=1. / beta, dist_shape=self.shape, size=size, loc=c)

    def logp(self, value):
        alpha = self.alpha
        beta = self.beta
        c = self.c
        x = value - c
        return bound(-gammaln(alpha) + logpow(beta, alpha) - beta * x + logpow(x, alpha - 1), x >= 0, alpha > 0, beta > 0)

我基本上有两个问题:

1)我的实施“好”/“正确”吗?我特别不确定变换参数,我实现了ShiftedLog变换。并且在任何地方都使用random()方法吗?如果我在这里设置断点,它似乎永远不会被调用。

2)为什么这个模型如此脆弱,基本上不会对前辈或采样方法做出任何改变?只需看看gist中的许多注释,看看我的反复实验,直到我从PyMC3获得结果。

第2点)可能是在第1点中犯了错误的结果,或者可能是Michael Betancourt在StanCon的演讲中解释的任何神奇原因:“你应该学会马尔可夫链蒙特卡罗的一切“?

围绕我的问题1)我还想了解是否有更好或更简单的方法来实现将具有移位的gammas的模型拟合到数据的相同效果,例如是否真的有必要实现ShiftedGamma,还是可以通过pm.Deterministic()或类似的方式达到相同的效果?

非常感谢您的帮助和见解!

基督教

1 个答案:

答案 0 :(得分:0)

我最初在github问题上发布了这个,但我想这是一个更好的地方。

你似乎在转换中有一个拼写错误:不应该落后tt.exp(x)+ self.c?在这种情况下,它与pm.distributions.transforms.lowerbound相同。 否则你的方法似乎很好。我不知道用deteministics做任何方法,因为混合物需要分配。在几乎所有其他情况下,ShiftedGamma不应该是必要的。

我认为您可以将其他代码简化为以下内容:(请仔细检查,我没有正确测试...)

class ShiftedGamma(pm.Gamma):
    def __init__(self, alpha, beta, shift, *args, **kwargs):
        transform = pm.distributions.transforms.lowerbound(shift)
        super().__init__(alpha=alpha, beta=beta, *args, **kwargs,
                         transform=transform)
        self.shift = shift
        self.mean += shift
        self.mode += shift

    def random(self):
        return super().random() + self.shift

    def logp(self, x):
        return super().logp(x - self.shift)


with pm.Model() as model:
    p = pm.Beta( "p", 1., 1., testval=0.5)

    alpha = pm.Uniform('alpha', 0, 10) # == k
    beta  = pm.Uniform('beta' , 0, 1) # == theta
    shifts = pm.Normal('shifts', mu=20, sd=50, shape=2, testval=floatX([0., 0.]))

    gamma = ShiftedGamma.dist(alpha=alpha, beta=beta, shift=shifts)

    pm.Mixture('y', tt.stack([p, 1-p]), gamma, observed=data))

关于初始化和采样的困难: 您正在使用MAP,这在大多数情况下是不鼓励的,使用advi进行初始化通常会更好。 我认为你有这么难的原因是很多移位值导致-inf的logp,即如果移位的最小值大于数据集的最小值。您可以尝试以某种方式重新参数化,以确保不会发生这种情况。也许是这样的:

shift1_raw = pm.HalfCauchy('shift1_raw', beta=1)
shift2_raw = pm.HalfCauchy('shift2_raw', beta=1)
shift1 = data.min() - shift1_raw
shift2 = shift1 + shift2_raw
shift = tt.stack([shift1, shift2])
shift = pm.Deterministic('shift', shift)

但这绝对需要更多思考;这个版本改变了先验,这个新的先验取决于数据集,而shift1_raw和shift2_raw可能是高度相关的(这使得采样器不满意。)

另外,请记住混合模型通常最终是多模式的,而NUTS通常不适合那些。

编辑作为旁注,如果您只是想要了解pymc3并且对Mixture模型没有特别的兴趣,也许不要从那些开始。他们往往会遇到麻烦。