PyMC para cluster não paramétrico: processo de Dirichlet para estimar os parâmetros da mistura gaussiana falha ao agrupar


10

Configuração do problema

Um dos primeiros problemas de brinquedo nos quais eu queria aplicar o PyMC é o cluster não paramétrico: com alguns dados, modele-os como uma mistura gaussiana e aprenda o número de clusters e a média e covariância de cada cluster. A maior parte do que sei sobre esse método vem de videoconferências de Michael Jordan e Yee Whye Teh, por volta de 2007 (antes da escassez de raiva), e nos últimos dias lendo os tutoriais do Dr. Fonnesbeck e E. Chen [fn1], [ fn2]. Mas o problema é bem estudado e tem algumas implementações confiáveis ​​[fn3].

Neste problema de brinquedo, eu gero dez desenhos de um Gaussiano unidimensional ( μ = 0 , σ = 1 ) e quarenta desenhos de N ( μ = 4 , σ = 2 ) . Como você pode ver abaixo, eu não embaralhei os desenhos para facilitar a identificação de quais amostras vieram de qual componente da mistura.N(μ=0 0,σ=1 1)N(μ=4,σ=2)

Dados do modelo de mistura gaussiana

I modelar cada amostra de dados , para i = 1 , . . . , 50 e onde z i indica o aglomerado para este i th ponto de dados: z i[ 1 , . . . , N D P ] . N D P aqui é a duração do processo de Dirichlet truncado usado: para mim, NyEuN(μzEu,σzEu)Eu=1 1,...,50.zEuEuzEu[1 1,...,NDP]NDP.NDP=50.

Expandir a infra-estrutura processo Dirichlet, cada aglomerado ID é um desenho de uma variável aleatória categórica, cuja função de densidade de probabilidade é dada pela construção de quebra de vara: z i ~ C uma t e g o r i c a l ( p ) com p S t i c k ( α ) para um parâmetro de concentração α . Construes vara-quebrar o N D P -long vector pzEuzEuCumategorEucumaeu(p)pStEuck(α)αNDPp, que deve somar 1, primeiro obtendo iid empates distribuídos em beta que dependem de α , consulte [fn1]. E desde que eu gostaria dos dados para informar a minha ignorância de α , eu sigo [FN1] e assumir α ~ U n i f o r m ( 0.3 , 100 ) .NDPαααvocênEuform(0,3,100)

Isso especifica como o ID de cluster de cada amostra de dados é gerado. Cada um dos clusters possui uma média e desvio padrão associados, μ z i e σ z i . Em seguida, μ z i ~ N ( μ = 0 , σ = 50 ) e σ z i ~ L n i f o r m ( 0 , 100 ) .NDPμzEuσzEuμzEuN(μ=0 0,σ=50.)σziUniform(0,100)

(I foi anteriormente seguinte [FN1] automaticamente e colocando um hyperprior em , isto é, μ z i ~ N ( μ 0 , σ 0 ) com μ 0 si um empate de uma distribuição normal-parâmetro fixo, e σ 0 mas de acordo com https://stats.stackexchange.com/a/71932/31187 , meus dados não suportam esse tipo de hiperprior hierárquica.)μziμziN(μ0,σ0)μ0σ0

Em resumo, meu modelo é:

onde i é executado de 1 a 50 (o número de amostras de dados).yiN(μzi,σzi)i

e pode assumir valores entre 0 e N D P - 1 = 49 ; p S t i c k ( a ) , umvetor N D P- longo; e α ~ L n i f o r m ( 0,3 , 100 )ziCategorical(p)NDP1=49pStick(α)NDPαUniform(0.3,100), um escalar. (Agora me arrependo um pouco de tornar o número de amostras de dados igual ao comprimento truncado do Dirichlet antes, mas espero que esteja claro.)

e σ z i ~ L n i f o r m ( 0 , 100 ) . Há N D P desses meios e desvios padrão (um para cada um dospossíveis agrupamentos de N D P ).μzEuN(μ=0 0,σ=50.)σzEuvocênEuform(0 0,100)NDPNDP

Aqui está o modelo gráfico: os nomes são variáveis, veja a seção de código abaixo.

Gráfico

Declaração do problema

Apesar de vários ajustes e correções com falha, os parâmetros aprendidos não são de modo algum semelhantes aos valores reais que geraram os dados.

Atualmente, estou inicializando a maioria das variáveis ​​aleatórias para valores fixos. As variáveis ​​de média e desvio padrão são inicializadas com seus valores esperados (ou seja, 0 para os normais, o meio de seu suporte para os uniformes). I inicializa todos os IDs de fragmentação a 0. E eu inicializar o parâmetro concentração α = 5 .zEuα=5

pμzEuEuμzEuEu=1 1,...,20

Meios com ID de cluster inicializado com zero

Lembrando que as dez primeiras amostras de dados eram de um modo e as demais eram do outro, o resultado acima claramente falha em capturar isso.

Se eu permitir a inicialização aleatória dos IDs do cluster, obtenho mais de um cluster, mas o cluster significa que todos andam pelo mesmo nível 3.5:

Meios com ID de cluster inicializado aleatoriamente

zEu

α

Apêndice: código

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)

Referências

  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

Sua variação anterior dos componentes é Uniforme (0,100), o que pode estar causando grandes problemas. Apenas 2% da massa desse prioritário abrange as variações reais de 1 e 2. A variação esperada de seus componentes nos termos deste prioritário é 50, que é um Gaussiano tão amplo que pode facilmente contabilizar seus dados com um único componente.
Jerad

Você leu este capítulo do livro Probabilistic Programming and Bayesian Statistics for Hackers ? Tem um exemplo que pode ajudá-lo!
Tim

Parece um pouco breve para uma resposta. Parece ser mais um comentário. Você poderia pelo menos descrever quais informações o OP obterá lendo-o?
Glen_b -Reinstala Monica

@ TimRich sim, eu li e participei da aula de pós-graduação e trabalhei na indústria fazendo estatística aplicada;) essa é uma pergunta específica do PyMC.
Ahmed Fasih

11
μZEuN(μ0 0,σ0 0)μ0 0,σ0 0

Respostas:


4

Não tenho certeza se alguém está olhando mais para essa pergunta, mas coloquei sua pergunta em rjags para testar a sugestão de amostragem de Gibbs de Tom e incorporar informações de Guy sobre o plano anterior para o desvio padrão.

Esse problema de brinquedo pode ser difícil porque 10 e até 40 pontos de dados não são suficientes para estimar a variação sem um prévio informativo. O atual anterior σzi∼Uniform (0,100) não é informativo. Isso pode explicar por que quase todos os desvios de μzi são a média esperada das duas distribuições. Se isso não alterar muito sua pergunta, usarei 100 e 400 pontos de dados, respectivamente.

Também não usei o processo de quebra de bastão diretamente no meu código. A página da Wikipédia para o processo dirichlet me fez pensar que p ~ Dir (a / k) seria bom.

Finalmente, é apenas uma implementação semi-paramétrica, pois ainda é necessário um número de clusters k. Não sei como criar um modelo de mistura infinita em rjags.

cadeia de markov mu cluster 1

cadeia de markov mu cluster 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)

11
Com relação ao uso de um número finito de clusters , você pode simplesmente assumir K grande (e definir α iKKαEu=uma/K500K=25JAGSJAGSp

1

A mistura ruim que você está vendo é mais provável devido à maneira como o PyMC extrai amostras. Conforme explicado na seção 5.8.1 da documentação do PyMC, todos os elementos de uma variável de matriz são atualizados juntos. No seu caso, isso significa que ele tentará atualizar toda a clustermeanmatriz em uma etapa e da mesma forma para clusterid. PyMC não faz amostragem de Gibbs; faz Metropolis, onde a proposta é escolhida por algumas heurísticas simples. Isso torna improvável propor um bom valor para uma matriz inteira.


Assim que você disse, "ele tentará atualizar toda a matriz em uma etapa", entendi as desvantagens de Metropolis (neste caso) versus Gibbs. Existe algo de especial no STAN ou JAGS que possa deixá-los se sair melhor nisso? Em qualquer um dos casos, passarei algum tempo implementando o Gibbs no PyMC. Obrigado! (Eu tenho sido um fã do seu trabalho desde lightspeed, de modo duplo obrigado!)
Ahmed Fasih

11
STAN não manipula variáveis ​​discretas, mas vale a pena tentar o JAGS.
precisa
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.