Algoritmo EM implementado manualmente

20

Quiero implementar el algoritmo EM manualmente y luego compararlo con los resultados normalmixEMdel mixtoolspaquete. Por supuesto, sería feliz si ambos conducen a los mismos resultados. La referencia principal es Geoffrey McLachlan (2000), Modelos de mezclas finitas .

Tengo una densidad de mezcla de dos gaussianos, en forma general, la probabilidad logarítmica está dada por (McLachlan página 48):

logLc(Ψ)=i=1gj=1nzij{logπi+logfi(yi;θi)}.
Los son , si la observación fue de la densidad de componentes ésima , de lo contrario 0 . El f_i es la densidad de la distribución normal. La \ pi es la proporción de la mezcla, por lo que \ pi_1 es la probabilidad de que una observación sea de la primera distribución gaussiana y \ pi_2 es la probabilidad de que una observación sea de la segunda distribución gaussiana.zij1i0fiππ1π2

El paso E ahora es el cálculo de la expectativa condicional:

Q(Ψ;Ψ(0))=EΨ(0){logLc(|Ψ)|y}.
que conduce, después de algunas derivaciones al resultado (página 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))
en el caso de dos gaussianos (página 82):

τi(yj;Ψ)=πiϕ(yj;μi,Σi)h=1gπhϕ(yj;μh,Σh)
Elpaso M ahora es la maximización de Q (página 49):

Q(Ψ;Ψ(k))=i=1gj=1nτi(yj;Ψ(k)){logπi+logfi(yj;θi)}.
Esto lleva a (en el caso de dos gaussianos) (página 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)
y sabemos que (p. 50)

πi(k+1)=j=1nτi(yj;Ψ(k))n(i=1,,g).
Repetimos los pasos E, M hasta que es pequeño. L(Ψ(k+1))L(Ψ(k))

Traté de escribir un código R (los datos se pueden encontrar aquí ).

# 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

El algoritmo no funciona, ya que algunas observaciones tienen la probabilidad de cero y el registro de esto sí -Inf. ¿Dónde está mi error?

Stat Tistician
fuente
El problema no es estadístico, sino numérico. Debe agregar contingencias para probabilidades más pequeñas que la precisión de la máquina en su código.
JohnRos
¿Por qué no intentas probar la función mixtools con un ejemplo muy simple que se puede verificar a mano, digamos solo cinco o diez valores y dos series de tiempo, primero. luego, si encuentra que funciona allí, generalice su código y verifique en cada paso.

Respuestas:

17

Tiene varios problemas en el código fuente:

  1. Como señaló @Pat, no debe usar log (dnorm ()) ya que este valor puede llegar fácilmente al infinito. Deberías usar logmvdnorm

  2. Cuando use la suma , tenga en cuenta que elimina los valores infinitos o faltantes

  3. Su variable de bucle k está mal, debe actualizar loglik [k + 1] pero actualiza loglik [k]

  4. Los valores iniciales para su método y mixtools son diferentes. Está usando en su método, pero está usando para mixtools (es decir, desviación estándar, del manual de mixtools).Σσ

  5. Sus datos no se ven como una mezcla de lo normal (verifique el histograma que tracé al final). Y un componente de la mezcla tiene un SD muy pequeño, así que agregué arbitrariamente una línea para configurar y para que sean iguales para algunas muestras extremas. Los agrego solo para asegurarme de que el código pueda funcionar.τ1τ2

También le sugiero que ponga códigos completos (p. Ej., Cómo inicializa loglik []) en su código fuente y sangría el código para que sea fácil de leer.

Después de todo, gracias por presentar el paquete mixtools , y planeo usarlos en mi investigación futura.

También pongo mi código de trabajo para su referencia:

# 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 Histograma

zhanxw
fuente
@zahnxw gracias por tu respuesta, ¿eso significa que mi código está equivocado? ¿Entonces la idea básica no está funcionando?
Stat Tistician
"También le sugiero que ponga códigos completos (p. Ej., Cómo inicializa loglik []) en su código fuente y sangra el código para que sea fácil de leer". Bueno, este es mi código? el loglik [] se define como lo declare en el código que publiqué?
Stat Tistician
1
@StatTistician, la idea es correcta, pero la implementación tiene fallas. Por ejemplo, no consideró el flujo insuficiente. Además, su variable de bucle k es confusa, primero configura loglik [1] y loglik [2], después de ingresar al bucle while, configura nuevamente loglik [1]. Esta no es la forma natural de hacerlo. Mi sugerencia sobre la inicialización de loglik [] significa código:, loklik <- rep(NA, 100)que asignará previamente loglik [1], loglik [2] ... loglik [100]. Planteo esa pregunta porque en su código original, no encontré la delcaración de loglik, ¿tal vez el código se trunca durante el pegado?
zhanxw
Como publiqué a continuación: Gracias por su ayuda, pero estoy abandonando este tema, ya que es demasiado avanzado para mí.
Stat Tistician
¿Existe ahora una manera de determinar qué parte de los datos pertenece a qué mezcla?
Cardenal
2

Sigo recibiendo un error al intentar abrir su archivo .rar, pero eso puede ser solo que estoy haciendo algo tonto.

F(y;θ)Exp(-0.5 0.5(y-μ)2/ /σ2)μyτ

Si ese es el problema, hay algunas soluciones posibles:

τ

τIniciar sesión(F(yEl |θ))

evaluar

Iniciar sesión(F(yEl |θ)τ)

F(yEl |θ)τ0 0

  • 0 0Iniciar sesión(0 0)=0 0(-yonorteF)=norteunanorte

pero con tau movido obtienes

  • Iniciar sesión(0 00 0)=Iniciar sesión(1)=0 0

0 00 0=1

Otra solución es expandir las cosas dentro del logaritmo. Asumiendo que estás usando logaritmos naturales:

τIniciar sesión(F(yEl |θ))

=τIniciar sesión(Exp(-0.5 0.5(y-μ)2/ /σ2)/ /2πσ2)

=-0.5 0.5τIniciar sesión(2πσ2)-0.5 0.5τ(y-μ)2σ2

Matemáticamente es lo mismo, pero debería ser más resistente a los errores de coma flotante, ya que ha evitado calcular una gran potencia negativa. Esto significa que ya no puede usar la función de evaluación de normas incorporada, pero si eso no es un problema, esta es probablemente la mejor respuesta. Por ejemplo, digamos que tenemos la situación donde

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

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

Palmadita
fuente
mh, para ser sincero: no soy lo suficientemente bueno para hacer que esto funcione. Lo que me interesaba es: ¿Puedo obtener el mismo resultado con mi algoritmo que la versión implementada del paquete mixtools. Pero desde mi punto de vista, esto parece estar pidiendo la luna. Pero creo que pones esfuerzo en tu respuesta, ¡así que lo aceptaré! ¡Gracias!
Stat Tistician