PyMC per clustering non parametrico: il processo di Dirichlet per stimare i parametri della miscela gaussiana non riesce a raggrupparsi


10

Impostazione del problema

Uno dei primi problemi con i giocattoli a cui volevo applicare PyMC è il clustering non parametrico: dati alcuni dati, modello come una miscela gaussiana e apprendimento del numero di cluster, media e covarianza di ciascun cluster. La maggior parte di ciò che so di questo metodo proviene dalle lezioni video di Michael Jordan e Yee Whye Teh, circa 2007 (prima che la scarsità diventasse la rabbia), e gli ultimi due giorni di lettura dei tutorial del dott. Fonnesbeck e E. Chen [fn1], [ Fn2]. Ma il problema è ben studiato e ha alcune implementazioni affidabili [fn3].

In questo problema con il giocattolo, ho generato dieci disegni da un gaussiano monodimensionale e quaranta disegni da . Come puoi vedere di seguito, non ho mischiato i disegni, per rendere facile dire quali campioni provengono da quale componente della miscela.N(μ=0,σ=1)N(μ=4,σ=2)

Dati del modello di miscela gaussiana

Io modello ciascun campione di dati , per e dove indica il cluster per questo esimo punto di dati: . qui è la lunghezza del processo Dirichlet troncato utilizzato: per me, .yiN(μzi,σzi)i=1,...,50ziizi[1,...,NDP]NDPNDP=50

Espandendo l'infrastruttura del processo Dirichlet, ogni ID cluster è un sorteggio da una variabile casuale categoriale, la cui funzione di massa di probabilità è data dal costrutto di rottura: con per un parametro di concentrazione . Stick-breaking costruisce il vettore lungo , che deve essere pari a 1, ottenendo prima disegni distribuiti in beta che dipendono da , vedi [fn1]. E poiché vorrei che i dati informassero la mia ignoranza di , seguo [fn1] e presumo .ziziCategorical(p)pStick(α)αNDPpNDPαα U n i f o r m ( 0.3 , 100 )ααUniform(0.3,100)

Questo specifica come viene generato l'ID cluster di ciascun campione di dati. Ciascuno dei cluster ha una media e deviazione standard associate, e . Quindi, e . μ z i σ z i μ z iN ( μ = 0 , σ = 50 ) σ z iU n i f o r m ( 0 , 100 )NDPμziσziμziN(μ=0,σ=50)σziUniform(0,100)

(In precedenza stavo seguendo [fn1] senza pensare e ponendo un hyperprior su , cioè con stesso un pareggio da un distribuzione normale a parametri fissi e da un'uniforme. Ma per https://stats.stackexchange.com/a/71932/31187 , i miei dati non supportano questo tipo di hyperprior gerarchico.) μ z iN ( μ 0 , σ 0 ) μ 0 σ 0μziμziN(μ0,σ0)μ0σ0

In sintesi, il mio modello è:

iyiN(μzi,σzi) dove corre da 1 a 50 (il numero di campioni di dati).i

ziCategorical(p) e può assumere valori compresi tra 0 e ; , un vettore lungo ; e , uno scalare. (Ora mi pento leggermente di aver reso il numero di campioni di dati uguale alla lunghezza troncata del Dirichlet precedente, ma spero sia chiaro.)NDP1=49pStick(α)NDPαUniform(0.3,100)

μziN(μ=0,σ=50) e . Esistono di questi mezzi e deviazioni standard (uno per ciascuno dei possibili cluster .)σziUniform(0,100)NDPNDP

Ecco il modello grafico: i nomi sono nomi di variabili, vedere la sezione codici di seguito.

Grafico

Dichiarazione problema

Nonostante diverse modifiche e correzioni non riuscite, i parametri appresi non sono affatto simili ai valori reali che hanno generato i dati.

Attualmente sto inizializzando la maggior parte delle variabili casuali su valori fissi. Le variabili di deviazione media e standard sono inizializzate ai loro valori attesi (cioè 0 per quelli normali, al centro del loro supporto per quelli uniformi). Inizializzo tutti gli ID cluster su 0. E inizializzo il parametro di concentrazione .ziα=5

Con tali inizializzazioni, 100'000 iterazioni MCMC semplicemente non riescono a trovare un secondo cluster. Il primo elemento di è vicino a 1 e quasi tutti i disegni di per tutti i campioni di dati sono gli stessi, circa 3,5. Mostro qui ogni centesimo disegno per i primi venti esempi di dati, ovvero per :pμziiμzii=1,...,20

Significa con ID cluster zero inizializzato

Ricordando che i primi dieci campioni di dati provenivano da una modalità e il resto dall'altra, il risultato di cui sopra chiaramente non riesce a catturarlo.

Se consento l'inizializzazione casuale degli ID del cluster, ottengo più di un cluster ma il cluster significa che tutti vagano intorno allo stesso livello 3.5:

Mezzi con ID cluster inizializzato in modo casuale

Questo mi suggerisce che è il solito problema con MCMC, che non può raggiungere un'altra modalità del posteriore rispetto a quella in cui si trova: ricorda che questi diversi risultati si verificano dopo aver cambiato l' inizializzazione degli ID cluster , non i loro precedenti o qualunque altra cosa.zi

Sto commettendo errori di modellazione? Domanda simile: https://stackoverflow.com/q/19114790/500207 vuole usare una distribuzione di Dirichlet e adattarsi a una miscela gaussiana a 3 elementi e sta riscontrando problemi in qualche modo simili. Dovrei considerare la creazione di un modello completamente coniugato e l'utilizzo del campionamento Gibbs per questo tipo di clustering? (Ho implementato un campionatore Gibbs per il caso di distribuzione parametrica di Dirichlet, ad eccezione di una concentrazione fissa , nel corso della giornata e ha funzionato bene, quindi aspettatevi che PyMC sia in grado di risolvere almeno quel problema facilmente.)α

Appendice: codice

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)

Riferimenti

  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

Il tuo precedente rispetto alle variazioni dei componenti è Uniforme (0,100) che potrebbe causare gravi problemi. Solo il 2% della massa di questo precedente copre le varianze reali di 1 e 2. La varianza prevista dei componenti in base a questo precedente è 50, che è un gaussiano così ampio che può facilmente spiegare i tuoi dati con un singolo componente.
Jerad

Hai letto questo capitolo del libro Probabilistic Programming and Bayesian Statistics for Hackers ? Ha un esempio che può aiutarti!
Tim

Questo sembra un po 'breve per una risposta. Sembra essere più di un commento. Potresti almeno delineare quali informazioni l'OP otterrà leggendole?
Glen_b -Restate Monica

@TimRich sì, l'ho letto, ho preso la classe in una scuola di specializzazione e ho lavorato nell'industria facendo statistiche applicate;) questa è una domanda specifica per PyMC.
Ahmed Fasih, il

1
Non respingerei il precedente gerarchico. È noto che ti metti in una brutta posizione se metti i priori piatti sui componenti di una miscela, specialmente quando stai contemporaneamente cercando di imparare il numero di cluster. Questo è ciò che sta causando i bizzarri picchi nei tuoi tracciati, credo. I grandi nomi di NP-Bayes sembrano tutti impostare e utilizzare stime di plug-in di o inserendo un precedente informativo su questi componenti progettati per metterli sulle scale appropriate. μ 0 , σ 0μZiN(μ0,σ0)μ0,σ0
ragazzo

Risposte:


4

Non sono più sicuro che qualcuno stia ancora esaminando questa domanda, ma ho messo la tua domanda a rjags per testare il suggerimento di campionamento di Tom Gibbs mentre incorporavo informazioni da Guy sull'appartamento prima della deviazione standard.

Questo problema del giocattolo potrebbe essere difficile perché 10 e persino 40 punti dati non sono sufficienti per stimare la varianza senza un precedente informativo. L'attuale precedente σzi∼Uniform (0,100) non è informativo. Questo potrebbe spiegare perché quasi tutti i disegni di μzi sono la media attesa delle due distribuzioni. Se non altera troppo la tua domanda, userò rispettivamente 100 e 400 punti dati.

Inoltre non ho usato il processo di rottura del bastoncino direttamente nel mio codice. La pagina di Wikipedia per il processo dirichlet mi ha fatto pensare che p ~ Dir (a / k) sarebbe ok.

Infine è solo un'implementazione semi-parametrica poiché richiede ancora un numero di cluster k. Non so come creare un modello di miscela infinita in rjags.

markov chain mu cluster 1

markov chain 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)

1
Rispetto all'utilizzo di un numero finito di cluster , puoi semplicemente prendere grande (e impostare , come hai fatto) e ottenere un modello che è effettivamente identico al processo di Dirichlet. La convergenza di questo prima del processo Dirichlet precedente è molto rapida; per osservazioni, qualcosa come dovrebbe essere più che sufficiente. È anche intelligente, quando si usa , non usare la costruzione di rottura, perché non rileverà l'aggiornamento del blocco su ; quello che hai fatto qui è meglio. K α i = a / K 500 K = 25 pKKαi=a/K500K=25JAGSJAGSp
ragazzo

1

La scarsa miscelazione che stai vedendo è molto probabilmente dovuta al modo in cui PyMC estrae i campioni. Come spiegato nella sezione 5.8.1 della documentazione di PyMC, tutti gli elementi di una variabile di array vengono aggiornati insieme. Nel tuo caso, ciò significa che proverà ad aggiornare l'intero clustermeanarray in un solo passaggio e allo stesso modo per clusterid. PyMC non esegue il campionamento di Gibbs; fa Metropolis dove la proposta è scelta da una semplice euristica. Ciò rende improbabile che proponga un buon valore per un intero array.


Non appena hai detto "tenterà di aggiornare l'intero array in un solo passaggio", ho capito gli svantaggi di Metropolis (in questo caso) rispetto a Gibbs. C'è qualcosa di speciale in STAN o JAGS che potrebbe consentire loro di fare meglio su questo? In entrambi i casi, impiegherò un po 'di tempo a implementare Gibbs in PyMC. Grazie! (Sono stato un fan del tuo lavoro dalla velocità della luce, quindi doppio grazie!)
Ahmed Fasih,

1
STAN non gestisce variabili discrete, ma vale la pena provare JAGS.
Tom Minka,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.