使用 MCMC 和 PyMC 进行 2-高斯混合模型推理

机器算法验证 贝叶斯 高斯混合分布 常客 pymc 方法比较
2022-03-12 16:17:17

问题

我想拟合一个简单的 2-高斯混合总体的模型参数。鉴于围绕贝叶斯方法的所有炒作,我想了解对于这个问题,贝叶斯推理是否是比传统拟合方法更好的工具。

到目前为止,MCMC 在这个玩具示例中的表现非常糟糕,但也许我只是忽略了一些东西。所以让我们看看代码。

工具

我将使用 python (2.7) + scipy 堆栈、lmfit 0.8 和 PyMC 2.3。

可以在此处找到重现分析的笔记本

生成数据

首先让我们生成数据:

from scipy.stats import distributions

# Sample parameters
nsamples = 1000
mu1_true = 0.3
mu2_true = 0.55
sig1_true = 0.08
sig2_true = 0.12
a_true = 0.4

# Samples generation
np.random.seed(3)  # for repeatability
s1 = distributions.norm.rvs(mu1_true, sig1_true, size=round(a_true*nsamples))
s2 = distributions.norm.rvs(mu2_true, sig2_true, size=round((1-a_true)*nsamples))
samples = np.hstack([s1, s2])

的直方图samples如下所示:

数据直方图

一个“宽峰”,组件很难用肉眼发现。

经典方法:拟合直方图

让我们先尝试经典的方法。使用lmfit很容易定义一个 2-peaks 模型:

import lmfit

peak1 = lmfit.models.GaussianModel(prefix='p1_')
peak2 = lmfit.models.GaussianModel(prefix='p2_')
model = peak1 + peak2

model.set_param_hint('p1_center', value=0.2, min=-1, max=2)
model.set_param_hint('p2_center', value=0.5, min=-1, max=2)
model.set_param_hint('p1_sigma', value=0.1, min=0.01, max=0.3)
model.set_param_hint('p2_sigma', value=0.1, min=0.01, max=0.3)
model.set_param_hint('p1_amplitude', value=1, min=0.0, max=1)
model.set_param_hint('p2_amplitude', expr='1 - p1_amplitude')
name = '2-gaussians'

最后我们用单纯形算法拟合模型:

fit_res = model.fit(data, x=x_data, method='nelder')
print fit_res.fit_report()

结果如下图(红色虚线为拟合中心):

NLS 拟合结果

即使问题有点难,在适当的初始值和约束条件下,模型也能收敛到相当合理的估计。

贝叶斯方法:MCMC

我在 PyMC 中以分层方式定义模型。centers并且sigmas是代表 2 个高斯的 2 个中心和 2 个 sigma 的超参数的先验分布。alpha是第一个总体的比例,这里的先验分布是 Beta。

分类变量在两个总体之间进行选择。据我了解,此变量需要与数据 ( samples) 的大小相同。

最后mutau是确定正态分布参数的确定性变量(它们取决于category变量,因此它们在两个总体的两个值之间随机切换)。

sigmas = pm.Normal('sigmas', mu=0.1, tau=1000, size=2)
centers = pm.Normal('centers', [0.3, 0.7], [1/(0.1)**2, 1/(0.1)**2], size=2)
#centers = pm.Uniform('centers', 0, 1, size=2)

alpha  = pm.Beta('alpha', alpha=2, beta=3)
category = pm.Categorical("category", [alpha, 1 - alpha], size=nsamples)

@pm.deterministic
def mu(category=category, centers=centers):
    return centers[category]

@pm.deterministic
def tau(category=category, sigmas=sigmas):
    return 1/(sigmas[category]**2)

observations = pm.Normal('samples_model', mu=mu, tau=tau, value=samples, observed=True)
model = pm.Model([observations, mu, tau, category, alpha, sigmas, centers])

然后我以相当长的迭代次数运行 MCMC(在我的机器上为 1e5,~60s):

mcmc = pm.MCMC(model)
mcmc.sample(100000, 30000)

然而结果非常奇怪。例如α轨迹(第一个总体的分数)趋于 0 而不是收敛到 0.4,并且具有非常强的自相关性:

MCMC alpha 总结

高斯中心也不收敛。例如:

MCMC 中心_0 摘要

正如您在先前的选择中看到的,我尝试使用先前人口比例的 Beta 分布来“帮助”MCMC 算法α. 中心和西格玛的先验分布也很合理(我认为)。

那么这里发生了什么?是我做错了什么还是 MCMC 不适合这个问题?

我知道 MCMC 方法会更慢,但平凡的直方图拟合似乎在解决人口问题方面表现得更好。

3个回答

该问题是由 PyMC 为该模型抽取样本的方式引起的。正如 PyMC 文档的第 5.8.1 节所述,数组变量的所有元素都会一起更新。对于像这样的小阵列center不是问题,但是对于像这样的大阵列,category它会导致低接受率。您可以通过以下方式查看接受率

print mcmc.step_method_dict[category][0].ratio

文档中建议的解决方案是使用标量值变量数组。此外,您需要配置一些提案分布,因为默认选择不好。这是对我有用的代码:

import pymc as pm
sigmas = pm.Normal('sigmas', mu=0.1, tau=1000, size=2)
centers = pm.Normal('centers', [0.3, 0.7], [1/(0.1)**2, 1/(0.1)**2], size=2)
alpha  = pm.Beta('alpha', alpha=2, beta=3)
category = pm.Container([pm.Categorical("category%i" % i, [alpha, 1 - alpha]) 
                         for i in range(nsamples)])
observations = pm.Container([pm.Normal('samples_model%i' % i, 
                   mu=centers[category[i]], tau=1/(sigmas[category[i]]**2), 
                   value=samples[i], observed=True) for i in range(nsamples)])
model = pm.Model([observations, category, alpha, sigmas, centers])
mcmc = pm.MCMC(model)
# initialize in a good place to reduce the number of steps required
centers.value = [mu1_true, mu2_true]
# set a custom proposal for centers, since the default is bad
mcmc.use_step_method(pm.Metropolis, centers, proposal_sd=sig1_true/np.sqrt(nsamples))
# set a custom proposal for category, since the default is bad
for i in range(nsamples):
    mcmc.use_step_method(pm.DiscreteMetropolis, category[i], proposal_distribution='Prior')
mcmc.sample(100)  # beware sampling takes much longer now
# check the acceptance rates
print mcmc.step_method_dict[category[0]][0].ratio
print mcmc.step_method_dict[centers][0].ratio
print mcmc.step_method_dict[alpha][0].ratio

选项在5.7.1 节proposal_sd中解释对于中心,我将建议设置为大致匹配后验的标准差,由于数据量的原因,该标准差远小于默认值。PyMC 确实会尝试调整提案的宽度,但这仅在您的接受率足够高的情况下才有效。对于,默认值会产生较差的结果(我不知道为什么会这样,但它肯定听起来不像是二进制变量的明智提议)。proposal_distributioncategoryproposal_distribution = 'Poisson'

你不应该建模σ使用 Normal,这样您就允许标准变化的负值。改用类似的东西:

sigmas = pm.Exponential('sigmas', 0.1, size=2)

更新:

通过更改模型的这些部分,我接近了数据的初始参数:

sigmas = pm.Exponential('sigmas', 0.1, size=2)
alpha  = pm.Beta('alpha', alpha=1, beta=1)

并通过一些细化调用 mcmc:

mcmc.sample(200000, 3000, 10)

结果:

α

中心

西格玛

你的后验不是很好......在BUGS Book的第11.6节中,他们讨论了这种类型的模型并指出存在收敛问题而没有明显的解决方案。也在这里检查。

此外,不可识别性是将 MCMC 用于混合模型的一个大问题。基本上,如果您在集群均值和集群分配上切换标签,则可能性不会改变,这会混淆采样器(链之间或链内)。您可能会尝试减轻这种情况的一件事是在 PyMC3中使用电位。具有潜力的 GMM 的良好实现在这里这类问题的后验通常也是高度多模态的,这也是一个很大的问题。您可能想要使用 EM(或变分推理)。