Algoritmo EM implementato manualmente


20

Voglio implementare l'algoritmo EM manualmente e poi confrontarlo con i risultati normalmixEMdel mixtoolspacchetto. Certo, sarei felice se entrambi conducessero agli stessi risultati. Il riferimento principale è Geoffrey McLachlan (2000), Modelli di miscele finite .

Ho una densità mista di due gaussiani, in forma generale, la probabilità di log è data da (McLachlan pagina 48):

logLc(Ψ)=i=1gj=1nzij{logπi+logfi(yi;θi)}.
Il sono , se l'osservazione è stato dal ° densità dei componenti, altrimenti 0 . Il f_i è la densità della distribuzione normale. Il \ pi è la proporzione della miscela, quindi \ pi_1 è la probabilità, che un'osservazione provenga dalla prima distribuzione gaussiana e \ pi_2 sia la probabilità, che un'osservazione provenga dalla seconda distribuzione gaussiana.zij1i0fiππ1π2

Il passaggio E ora è, il calcolo dell'aspettativa condizionale:

Q(Ψ;Ψ(0))=EΨ(0){logLc(|Ψ)|y}.
che porta, dopo alcune derivazioni al risultato (pagina 49):

τi(yj;Ψ(k))=πi(k)fi(yj;θi(k)f(yj;Ψ(k)=πi(k)fi(yj;θi(k)h=1gπh(k)fh(yj;θh(k))
nel caso di due gaussiani (pagina 82):

τi(yj;Ψ)=πiϕ(yj;μi,Σi)h=1gπhϕ(yj;μh,Σh)
Ilpassaggio M ora è la massimizzazione di Q (pagina 49):

Q(Ψ;Ψ(k))=i=1gj=1nτi(yj;Ψ(k)){logπi+logfi(yj;θi)}.
Questo porta a (nel caso di due gaussiani) (pagina 82):

μi(k+1)=j=1nτij(k)yjj=1nτij(k)Σi(k+1)=j=1nτij(k)(yjμi(k+1))(yjμi(k+1))Tj=1nτij(k)
e lo sappiamo (p. 50)

πi(k+1)=j=1nτi(yj;Ψ(k))n(i=1,,g).
Ripetiamo i passaggi E, M fino a quando è piccolo. L(Ψ(k+1))L(Ψ(k))

Ho provato a scrivere un codice R (i dati sono disponibili qui ).

# EM algorithm manually
# dat is the data

# initial values
pi1       <-  0.5
pi2       <-  0.5
mu1       <- -0.01
mu2       <-  0.01
sigma1    <-  0.01
sigma2    <-  0.02
loglik[1] <-  0
loglik[2] <- sum(pi1*(log(pi1) + log(dnorm(dat,mu1,sigma1)))) + 
             sum(pi2*(log(pi2) + log(dnorm(dat,mu2,sigma2))))

tau1 <- 0
tau2 <- 0
k    <- 1

# loop
while(abs(loglik[k+1]-loglik[k]) >= 0.00001) {

  # E step
  tau1 <- pi1*dnorm(dat,mean=mu1,sd=sigma1)/(pi1*dnorm(x,mean=mu1,sd=sigma1) + 
          pi2*dnorm(dat,mean=mu2,sd=sigma2))
  tau2 <- pi2*dnorm(dat,mean=mu2,sd=sigma2)/(pi1*dnorm(x,mean=mu1,sd=sigma1) + 
          pi2*dnorm(dat,mean=mu2,sd=sigma2))

  # M step
  pi1 <- sum(tau1)/length(dat)
  pi2 <- sum(tau2)/length(dat)

  mu1 <- sum(tau1*x)/sum(tau1)
  mu2 <- sum(tau2*x)/sum(tau2)

  sigma1 <- sum(tau1*(x-mu1)^2)/sum(tau1)
  sigma2 <- sum(tau2*(x-mu2)^2)/sum(tau2)

  loglik[k] <- sum(tau1*(log(pi1) + log(dnorm(x,mu1,sigma1)))) + 
               sum(tau2*(log(pi2) + log(dnorm(x,mu2,sigma2))))
  k         <- k+1
}


# compare
library(mixtools)
gm <- normalmixEM(x, k=2, lambda=c(0.5,0.5), mu=c(-0.01,0.01), sigma=c(0.01,0.02))
gm$lambda
gm$mu
gm$sigma

gm$loglik

L'algoritmo non funziona, poiché alcune osservazioni hanno la probabilità di zero e il registro di ciò è -Inf. Dov'è il mio errore?


Il problema non è statistico, ma piuttosto numerico. È necessario aggiungere contingenze per probabilità inferiori alla precisione della macchina nel codice.
JohnRos,

perché non provi a perfezionare la funzione mixtools con un esempio molto semplice che può essere verificato a mano, diciamo prima solo cinque o dieci valori e due timeseries. quindi, se trovi che funziona lì, generalizza il tuo codice e verifica ad ogni passaggio.

Risposte:


17

Hai diversi problemi nel codice sorgente:

  1. Come ha sottolineato @Pat, non dovresti usare log (dnorm ()) poiché questo valore può facilmente andare all'infinito. È necessario utilizzare logmvdnorm

  2. Quando usi la somma , fai attenzione a rimuovere valori infiniti o mancanti

  3. La variabile looping k è errata, dovresti aggiornare loglik [k + 1] ma aggiorni loglik [k]

  4. I valori iniziali per il metodo e i mixtools sono diversi. Stai usando nel tuo metodo, ma stai usando per mixtools (cioè deviazione standard, dal manuale di mixtools).Σσ

  5. I tuoi dati non sembrano un misto di normale (controlla l'istogramma che ho tracciato alla fine). E un componente della miscela ha sd molto piccolo, quindi ho arbitrariamente aggiunto una riga per impostare e in modo che siano uguali per alcuni campioni estremi. Li aggiungo solo per assicurarmi che il codice funzioni.τ1τ2

Suggerisco anche di inserire codici completi (ad es. Come si inizializza loglik []) nel codice sorgente e di indentare il codice per facilitarne la lettura.

Dopotutto, grazie per aver introdotto il pacchetto mixtools e ho intenzione di usarli nella mia ricerca futura.

Ho anche messo il mio codice di lavoro come riferimento:

# EM algorithm manually
# dat is the data
setwd("~/Downloads/")
load("datem.Rdata")
x <- dat

# initial values
pi1<-0.5
pi2<-0.5
mu1<--0.01
mu2<-0.01
sigma1<-sqrt(0.01)
sigma2<-sqrt(0.02)
loglik<- rep(NA, 1000)
loglik[1]<-0
loglik[2]<-mysum(pi1*(log(pi1)+log(dnorm(dat,mu1,sigma1))))+mysum(pi2*(log(pi2)+log(dnorm(dat,mu2,sigma2))))

mysum <- function(x) {
  sum(x[is.finite(x)])
}
logdnorm <- function(x, mu, sigma) {
  mysum(sapply(x, function(x) {logdmvnorm(x, mu, sigma)}))  
}
tau1<-0
tau2<-0
#k<-1
k<-2

# loop
while(abs(loglik[k]-loglik[k-1]) >= 0.00001) {
  # E step
  tau1<-pi1*dnorm(dat,mean=mu1,sd=sigma1)/(pi1*dnorm(x,mean=mu1,sd=sigma1)+pi2*dnorm(dat,mean=mu2,sd=sigma2))
  tau2<-pi2*dnorm(dat,mean=mu2,sd=sigma2)/(pi1*dnorm(x,mean=mu1,sd=sigma1)+pi2*dnorm(dat,mean=mu2,sd=sigma2))
  tau1[is.na(tau1)] <- 0.5
  tau2[is.na(tau2)] <- 0.5

  # M step
  pi1<-mysum(tau1)/length(dat)
  pi2<-mysum(tau2)/length(dat)

  mu1<-mysum(tau1*x)/mysum(tau1)
  mu2<-mysum(tau2*x)/mysum(tau2)

  sigma1<-mysum(tau1*(x-mu1)^2)/mysum(tau1)
  sigma2<-mysum(tau2*(x-mu2)^2)/mysum(tau2)

  #  loglik[k]<-sum(tau1*(log(pi1)+log(dnorm(x,mu1,sigma1))))+sum(tau2*(log(pi2)+log(dnorm(x,mu2,sigma2))))
  loglik[k+1]<-mysum(tau1*(log(pi1)+logdnorm(x,mu1,sigma1)))+mysum(tau2*(log(pi2)+logdnorm(x,mu2,sigma2)))
  k<-k+1
}

# compare
library(mixtools)
gm<-normalmixEM(x,k=2,lambda=c(0.5,0.5),mu=c(-0.01,0.01),sigma=c(0.01,0.02))
gm$lambda
	gm$mu
gm$sigma

gm$loglik

Historgram Istogramma


@zahnxw grazie per la tua risposta, quindi significa che il mio codice è sbagliato? Quindi l'idea di base non funziona?
Stat Tistician,

"Suggerisco anche di inserire codici completi (ad es. Come inizializzare loglik []) nel codice sorgente e di indentare il codice per facilitarne la lettura." Bene, questo è il mio codice? il loglik [] è definito come l'ho dichiarato nel codice che ho pubblicato?
Stat Tistician,

1
@StatTistician l'idea è corretta, ma l'implementazione ha dei difetti. Ad esempio, non hai considerato il flusso insufficiente. Inoltre, la variabile looping k è confusa, devi prima impostare loglik [1] e loglik [2], dopo aver inserito il ciclo while, imposti di nuovo loglik [1]. Questo non è il modo naturale di fare. Il mio suggerimento sull'inizializzazione di loglik [] significa codice:, loklik <- rep(NA, 100)che pre-allocerà loglik [1], loglik [2] ... loglik [100]. Sollevo questa domanda perché nel tuo codice originale non ho trovato la delcaration di loglik, forse il codice viene troncato durante l'incollaggio?
zhanxw,

Come ho pubblicato di seguito: Grazie per il tuo aiuto, ma sto abbandonando questo argomento, poiché è troppo avanzato per me.
Stat Tistician,

Esiste ora un modo per determinare quale parte dei dati appartiene a quale miscela?
Cardinale

2

Continuo a ricevere un errore quando provo ad aprire il tuo file .rar, ma potrebbe essere solo io a fare qualcosa di stupido.

f(y;θ)exp(-0.5(y-μ)2/σ2)μyτ

Se questo è il problema, ci sono alcune possibili soluzioni:

τ

τlog(f(y|θ))

valutare

log(f(y|θ)τ)

f(y|θ)τ0

  • 0log(0)=0(-ionf)=Nun'N

ma con tau commosso si ottiene

  • log(00)=log(1)=0

00=1

Un'altra soluzione è quella di espandere le cose all'interno del logaritmo. Supponendo che tu stia utilizzando logaritmi naturali:

τlog(f(y|θ))

=τlog(exp(-0.5(y-μ)2/σ2)/2πσ2)

=-0.5τlog(2πσ2)-0.5τ(y-μ)2σ2

Matematicamente lo stesso, ma dovrebbe essere più resistente agli errori in virgola mobile poiché hai evitato di calcolare una grande potenza negativa. Ciò significa che non è più possibile utilizzare la funzione di valutazione delle norme incorporata, ma se questo non è un problema questa è probabilmente la risposta migliore. Ad esempio, supponiamo di avere la situazione in cui

0.5(yμ)2σ2=0.5402=800

log(exp(800))=log(0)=Inf


ad essere sincero: non sono abbastanza bravo per far funzionare questa cosa. Quello che mi interessava è: posso ottenere lo stesso risultato con il mio algoritmo della versione implementata del pacchetto mixtools. Ma dal mio punto di vista sembra chiedere la luna. Ma penso che tu abbia messo uno sforzo nella tua risposta, quindi lo accetterò! Grazie!
Stat Tistician,
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.