用于非参数聚类的PyMC:估计高斯混合参数的Dirichlet过程无法聚类


10

问题设定

我想将PyMC应用到的第一个玩具问题之一是非参数聚类:给定一些数据,将其建模为高斯混合,并学习聚类的数目以及每个聚类的均值和协方差。我对这种方法的大部分了解来自迈克尔·乔丹(Michael Jordan)和Yee Whye Teh(大约在2007年之前)的视频讲座(在稀疏成为流行之前),以及最近两天阅读Fonnesbeck博士和E. Chen的教程[fn1],[ fn2]。但是问题已得到充分研究,并且具有一些可靠的实现方式[fn3]。

在这个玩具问题中,我从一维高斯生成十次抽奖,并从。正如您在下面看到的那样,我没有对抽奖进行混洗,以便于分辨哪个样品来自哪个混合成分。Nμ = 4 σ = 2 N(μ=0,σ=1)N(μ=4,σ=2)

高斯混合模型数据

我对每个数据样本进行,,其中表示该第个数据点的聚类:。是使用的截短Dirichlet进程的长度:对我来说,。= 1 50 ž ž [ 1 N D P ] N D P N D P = 50yiN(μzi,σzi)i=1,...,50ziizi[1,...,NDP]NDPNDP=50

扩展Dirichlet流程基础结构,每个集群ID都是来自分类随机变量的图形,其随机变量的质量函数由结构给出:带有的a浓度参数。折断构造通过首先获得依赖于 Beta分布的 iid Beta分布绘制,构造必须为1 的长向量,请参见[fn1]。并且由于我想通过数据告知我对了解,因此我遵循[fn1]并假定 0.3,100。ž Ç ë ö ř Ç p ziziCategorical(p)α Ñ d P p Ñ d P α α α ü Ñ ˚F ö ř 0.3 100 pStick(α)αNDPpNDPαααUniform(0.3,100)

这指定了如何生成每个数据样本的集群ID。群集中的每个群集均具有关联的均值和标准差和。然后,和。 μ ž σ ž μ ž Ñμ = 0 σ = 50 σ Ž ü Ñ ˚F ö ř 0 100 NDPμziσziμziN(μ=0,σ=50)σziUniform(0,100)

(我以前一直不假思索地关注[fn1],并在上放置了一个超级,即与本身是从固定参数正态分布和统一形式的。但是根据https://stats.stackexchange.com/a/71932/31187,我的数据不支持这种分层的超优先级。) μ ž Ñμ 0σ 0μ 0 σ 0μziμziN(μ0,σ0)μ0σ0

总而言之,我的模型是:

yiN(μzi,σzi),其中从1到50(数据样本数)运行。i

ziCategorical(p)并且可以采用0到之间的值; ,一个长的向量;和,一个标量。(我现在有些遗憾地使数据样本的数量等于之前的Dirichlet的截短长度,但是我希望这很清楚。)NDP1=49pStick(α)NDPαUniform(0.3,100)

μziN(μ=0,σ=50)和。有这些平均值和标准偏差的(一个用于每个的可能簇。)σziUniform(0,100)NDPNDP

这是图形模型:名称是变量名,请参见下面的代码部分。

图形

问题陈述

尽管进行了几次调整和修复失败,但是学习到的参数与生成数据的真实值根本不相似。

目前,我正在将大多数随机变量初始化为固定值。将均值和标准偏差变量初始化为它们的期望值(即,正常变量为0,均等变量的支持量为中间值)。我将所有群集ID 初始化为0。然后初始化浓度参数。ziα=5

通过这样的初始化,100,000个MCMC迭代根本找不到第二个集群。的第一个元素接近1,并且所有数据样本的几乎都是相同的,大约为3.5。我在这里显示前20个数据样本的每100个抽奖,即表示:pμziiμzii=1,...,20

具有零初始化集群ID的均值

回顾前十个数据样本来自一种模式,其余样本来自另一种模式,以上结果显然无法捕获该数据。

如果我允许集群ID的随机初始化,那么我会获得多个集群,但是集群意味着所有集群都徘徊在相同的3.5级:

具有随机初始化集群ID的均值

这向我表明,这是MCMC的常见问题,它无法达到其后验的另一种模式:回想一下,这些不同的结果仅在更改集群ID的初始化之后发生,而不是它们的先验或还要别的吗。zi

我在建模上有任何错误吗?类似的问题:https : //stackoverflow.com/q/19114790/500207想要使用Dirichlet分布并拟合3元素高斯混合,并且遇到了类似的问题。我是否应该考虑建立完全共轭模型并使用Gi​​bbs采样进行此类聚类?(除了在白天使用固定浓度之外,我为参数Dirichlet分布情况实现了Gibbs采样器,并且效果很好,因此希望PyMC至少能够轻松解决该问题。)α

附录:代码

import pymc
import numpy as np

### Data generation

# Means and standard deviations of the Gaussian mixture model. The inference
# engine doesn't know these.
means = [0, 4.0]
stdevs = [1, 2.0]

# Rather than randomizing between the mixands, just specify how many
# to draw from each. This makes it really easy to know which draws
# came from which mixands (the first N1 from the first, the rest from
# the secon). The inference engine doesn't know about N1 and N2, only Ndata
N1 = 10
N2 = 40
Ndata = N1+N2

# Seed both the data generator RNG  as well as the global seed (for PyMC)
RNGseed = 123
np.random.seed(RNGseed)

def generate_data(draws_per_mixand):
    """Draw samples from a two-element Gaussian mixture reproducibly.

    Input sequence indicates the number of draws from each mixand. Resulting
    draws are concantenated together.

    """
    RNG = np.random.RandomState(RNGseed)
    values = np.hstack([RNG.normal(means[i], stdevs[i], ndraws)
                        for (i,ndraws) in enumerate(draws_per_mixand)])
    return values

observed_data = generate_data([N1, N2])


### PyMC model setup, step 1: the Dirichlet process and stick-breaking

# Truncation level of the Dirichlet process
Ndp = 50

# "alpha", or the concentration of the stick-breaking construction. There exists
# some interplay between choice of Ndp and concentration: a high concentration
# value implies many clusters, in turn implying low values for the leading
# elements of the probability mass function built by stick-breaking. Since we
# enforce the resulting PMF to sum to one, the probability of the last cluster
# might be then be set artificially high. This may interfere with the Dirichlet
# process' clustering ability.
#
# An example: if Ndp===4, and concentration high enough, stick-breaking might
# yield p===[.1, .1, .1, .7], which isn't desireable. You want to initialize
# concentration so that the last element of the PMF is less than or not much
# more than the a few of the previous ones. So you'd want to initialize at a
# smaller concentration to get something more like, say, p===[.35, .3, .25, .1].
#
# A thought: maybe we can avoid this interdependency by, rather than setting the
# final value of the PMF vector, scale the entire PMF vector to sum to 1? FIXME,
# TODO.
concinit = 5.0
conclo = 0.3
conchi = 100.0
concentration = pymc.Uniform('concentration', lower=conclo, upper=conchi,
                             value=concinit)

# The stick-breaking construction: requires Ndp beta draws dependent on the
# concentration, before the probability mass function is actually constructed.
betas = pymc.Beta('betas', alpha=1, beta=concentration, size=Ndp)

@pymc.deterministic
def pmf(betas=betas):
    "Construct a probability mass function for the truncated Dirichlet process"
    # prod = lambda x: np.exp(np.sum(np.log(x))) # Slow but more accurate(?)
    prod = np.prod
    value = map(lambda (i,u): u * prod(1.0 - betas[:i]), enumerate(betas))
    value[-1] = 1.0 - sum(value[:-1]) # force value to sum to 1
    return value

# The cluster assignments: each data point's estimated cluster ID.
# Remove idinit to allow clusterid to be randomly initialized:
idinit = np.zeros(Ndata, dtype=np.int64)
clusterid = pymc.Categorical('clusterid', p=pmf, size=Ndata, value=idinit)

### PyMC model setup, step 2: clusters' means and stdevs

# An individual data sample is drawn from a Gaussian, whose mean and stdev is
# what we're seeking.

# Hyperprior on clusters' means
mu0_mean = 0.0
mu0_std = 50.0
mu0_prec = 1.0/mu0_std**2
mu0_init = np.zeros(Ndp)
clustermean = pymc.Normal('clustermean', mu=mu0_mean, tau=mu0_prec,
                          size=Ndp, value=mu0_init)

# The cluster's stdev
clustersig_lo = 0.0
clustersig_hi = 100.0
clustersig_init = 50*np.ones(Ndp) # Again, don't really care?
clustersig = pymc.Uniform('clustersig', lower=clustersig_lo,
                          upper=clustersig_hi, size=Ndp, value=clustersig_init)
clusterprec = clustersig ** -2

### PyMC model setup, step 3: data

# So now we have means and stdevs for each of the Ndp clusters. We also have a
# probability mass function over all clusters, and a cluster ID indicating which
# cluster a particular data sample belongs to.

@pymc.deterministic
def data_cluster_mean(clusterid=clusterid, clustermean=clustermean):
    "Converts Ndata cluster IDs and Ndp cluster means to Ndata means."
    return clustermean[clusterid]

@pymc.deterministic
def data_cluster_prec(clusterid=clusterid, clusterprec=clusterprec):
    "Converts Ndata cluster IDs and Ndp cluster precs to Ndata precs."
    return clusterprec[clusterid]

data = pymc.Normal('data', mu=data_cluster_mean, tau=data_cluster_prec,
                   observed=True, value=observed_data)

参考文献

  1. fn1:http://nbviewer.ipython.org/urls/raw.github.com/fonnesbeck/Bios366/master/notebooks/Section5_2-Dirichlet-Processes.ipynb
  2. fn2:http//blog.echen.me/2012/03/20/infinite-mixture-models-with-nonparametric-bayes-and-the-dirichlet-process/
  3. fn3:http://scikit-learn.org/stable/auto_examples/mixture/plot_gmm.html#example-mixture-plot-gmm-py

您对组件方差的先验值为Uniform(0,100),这可能会导致您遇到重大问题。该先验质量的2%涵盖了1和2的真实方差。在此先验条件下,您的组件的期望方差为50,这是一个很宽的高斯分布,可以轻松地用一个组件来解释您的数据。
jerad

您是否阅读过《概率编程和黑客贝叶斯统计》一书的这一章?它有一个例子可以帮助您!
蒂姆(Tim)

这似乎是一个简短的答案。似乎更多是评论。您至少可以概述一下通过阅读OP将获得哪些信息吗?
Glen_b-恢复莫妮卡

@TimRich是的,我读过它,并在研究生院学习,并在行业从事应用统计工作;)这是一个PyMC特定的问题。
艾哈迈德·法西

1
我不会解雇分层优先级。众所周知,如果将先验先验放在混合物的各个成分上,则会使自己处于不利位置,尤其是在同时尝试学习簇数时。我认为这就是导致您的曲线图中出现怪异峰值的原因。在NP-贝叶斯大牌似乎都集,要么使用插件的估计或通过将一个信息在这些部件设计为前将它们放在适当的比例上。μ 0σ 0μZiN(μ0,σ0)μ0,σ0
2014年

Answers:


4

我不确定是否有人在看这个问题,但是我把您的问题放到了Rjags中,以测试Tom的Gibbs抽样建议,同时结合了Guy的关于标准差之前的平坦度的见解。

这个玩具问题可能很困难,因为如果没有先验信息,则10个甚至40个数据点不足以估计方差。当前的先验σzi〜Uniform(0,100)不能提供信息。这可以解释为什么几乎所有的μzi绘制都是两个分布的预期均值。如果它不能太多改变您的问题,我将分别使用100和400个数据点。

我也没有在代码中直接使用断棍过程。关于dirichlet过程的维基百科页面让我认为p〜Dir(a / k)可以。

最后,由于它仍然需要多个簇k,因此它只是一个半参数的实现。我不知道如何在Rjags中建立无限混合模型。

马可夫链亩集群1

马可夫链亩集群2

library("rjags")

set1 <- rnorm(100, 0, 1)
set2 <- rnorm(400, 4, 1)
data <- c(set1, set2)

plot(data, type='l', col='blue', lwd=3,
     main='gaussian mixture model data',
     xlab='data sample #', ylab='data value')
points(data, col='blue')

cpd.model.str <- 'model {
  a ~ dunif(0.3, 100)
  for (i in 1:k){
    alpha[i] <- a/k
    mu[i] ~ dnorm(0.0, 0.001)
    sigma[i] ~ dunif(0, 100)
  }
  p[1:k] ~ ddirich(alpha[1:k])
  for (i in 1:n){
    z[i] ~ dcat(p)
    y[i] ~ dnorm(mu[z[i]], pow(sigma[z[i]], -2))
  }
}' 


cpd.model <- jags.model(textConnection(cpd.model.str),
                        data=list(y=data,
                                  n=length(data),
                                  k=5))
update(cpd.model, 1000)
chain <- coda.samples(model = cpd.model, n.iter = 1000,
                      variable.names = c('p', 'mu', 'sigma'))
rchain <- as.matrix(chain)
apply(rchain, 2, mean)

1
关于使用有限数量的群集,您可以将大(并根据需要设置)并获得与Dirichlet过程有效相同的模型。在Dirichlet过程之前,此过程的收敛非常快;对于观察,像应该绰绰有余。当使用时,使用破坏结构也是很聪明的,因为不会检测到上的块更新;您在这里所做的更好。ķ α = 一个/ ķ 500 ķ = 25 pKKαi=a/K500K=25JAGSJAGSp
2014年

1

您看到的混合不良很可能是因为PyMC抽取样品的方式。如PyMC文档的5.8.1节所述,数组变量的所有元素都一起更新。在您的情况下,这意味着它将尝试clustermean一步更新整个数组,对于则类似clusterid。PyMC不执行Gibbs采样;在Metropolis中,提案是由一些简单的启发式方法选择的。这使得不可能为整个数组建议一个好的值。


正如您所说,“它将尝试一步更新整个阵列”,我了解了Metropolis(在这种情况下)与Gibbs相比的缺点。关于STAN或JAGS,有什么特别的地方可以使他们做得更好吗?无论哪种情况,我都会花一些时间在PyMC中实现Gibbs。谢谢!(自从lightspeed以来,我一直是您的工作爱好者,所以谢谢您!)
Ahmed Fasih 2014年

1
STAN不处理离散变量,但是JAGS值得一试。
汤姆·敏卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.