22 votos

Algoritmo EM aplicado manualmente

Quiero implementar el algoritmo EM manualmente y luego compararlo con los resultados del normalmixEM de mixtools paquete. Por supuesto, me alegraría que ambos condujeran 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 log-verosimilitud viene dada por (McLachlan página 48):

$$ \log L_c(\Psi) = \sum_{i=1}^g \sum_{j=1}^n z_{ij}\{\log \pi_i + \log f_i(y_i;\theta_i)\}. $$ En $z_{ij}$ son $1$ si la observación procedía del $i$ th densidad de componentes, en caso contrario $0$ . En $f_i$ es la densidad de la distribución normal. La dirección $\pi$ es la proporción de la mezcla, por lo que $\pi_1$ es la probabilidad de que una observación proceda de la primera distribución gaussiana y $\pi_2$ es la probabilidad de que una observación proceda de la segunda distribución gaussiana.

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

$$ Q(\Psi;\Psi^{(0)}) = E_{\Psi(0)}\{\log L_c(|\Psi)|y\}. $$ que conduce, tras algunas derivaciones al resultado (página 49):

\begin{align} \tau_i(y_j;\Psi^{(k)}) &= \frac{\pi_i^{(k)}f_i(y_j;\theta_i^{(k)}}{f(y_j;\Psi^{(k)}} \\[8pt] &= \frac{\pi_i^{(k)}f_i(y_j;\theta_i^{(k)}}{\sum_{h=1}^g \pi_h^{(k)}f_h(y_j;\theta_h^{(k)})} \end{align} en el caso de dos gaussianas (página 82):

$$ \tau_i(y_j;\Psi) = \frac{\pi_i \phi(y_j;\mu_i,\Sigma_i)}{\sum_{h=1}^g \pi_h\phi(y_j; \mu_h,\Sigma_h)} $$ En M es ahora la maximización de Q (página 49):

$$ Q(\Psi;\Psi^{(k)}) = \sum_{i=1}^g\sum_{j=1}^n\tau_i(y_j;\Psi^{(k)})\{\log \pi_i + \log f_i(y_j;\theta_i)\}. $$ Esto conduce a (en el caso de dos gaussianas) (página 82):

\begin{align} \mu_i^{(k+1)} &= \frac{\sum_{j=1}^n \tau_{ij}^{(k)}y_j}{\sum_{j=1}^n \tau_{ij}^{(k)}} \\[8pt] \Sigma_i^{(k+1)} &= \frac{\sum_{j=1}^n \tau_{ij}^{(k)}(y_j - \mu_i^{(k+1)})(y_j - \mu_i^{(k+1)})^T}{\sum_{j=1}^n \tau_{ij}^{(k)}} \end{align} y sabemos que (p. 50)

$$ \pi_i^{(k+1)} = \frac{\sum_{j=1}^n \tau_i(y_j;\Psi^{(k)})}{n}\qquad (i = 1, \ldots, g). $$ Repetimos los pasos E, M hasta que $L(\Psi^{(k+1)})-L(\Psi^{(k)})$ es pequeño.

He intentado 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 probabilidad cero y el logaritmo de ésta es -Inf . ¿Dónde está mi error?

0 votos

El problema no es estadístico, sino numérico. Deberías añadir contingencias para probabilidades menores que la precisión de la máquina en tu código.

0 votos

¿por qué no pruebas a utilizar la función mixtools con un ejemplo muy simple que pueda verificarse a mano, por ejemplo, sólo cinco o diez valores y dos series temporales, primero. luego, si ves que funciona, generaliza tu código y verifícalo en cada paso.

17voto

TimDaMan Puntos 116

Tiene varios problemas en el código fuente:

  1. Como ha señalado @Pat, no debe utilizar log(dnorm()), ya que este valor puede llegar fácilmente al infinito. Debe utilizar logmvdnorm

  2. Cuando utilice suma tenga en cuenta que debe eliminar los valores infinitos o ausentes

  3. Tu bucle de la variable k está mal, deberías actualizar loglik[k+1] pero actualizas loglik[k]

  4. Los valores iniciales de tu método y mixtools son diferentes. Usted utiliza $\Sigma$ en su método, pero utilizando $\sigma$ para mixtools (es decir, la desviación estándar, según el manual de mixtools).

  5. Tus datos no parecen una mezcla de normales (comprueba el histograma que he trazado al final). Y un componente de la mezcla tiene muy pequeño s.d., por lo que arbitrariamente añadido una línea para establecer $\tau_1$ y $\tau_2$ sean iguales para algunas muestras extremas. Los añado sólo para asegurarme de que el código puede funcionar.

También te sugiero que pongas códigos completos (por ejemplo, cómo inicializas loglik[]) en tu código fuente y sangrar el código para que sea fácil de leer.

Después de todo, gracias por presentar mixtools y pienso utilizarlos en mis futuras investigaciones.

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

Historgrama Histogram

0 votos

@zahnxw gracias por su respuesta, por lo que significa, que mi código es incorrecto? Así que la idea basi no está funcionando?

0 votos

"También te sugiero que pongas códigos completos (por ejemplo, cómo inicializas loglik[]) en tu código fuente y sangrar el código para que sea fácil de leer." Bueno este es mi código? el loglik[] está definido como lo declaré en el código que publiqué?

1 votos

@StatTistician la idea es correcta, pero la implementación tiene fallos. Por ejemplo, no has tenido en cuenta el subflujo. Además, el bucle de la variable k es confuso, primero se establece loglik[1] y loglik[2], después de entrar en el bucle while, se establece loglik[1] de nuevo. Esta no es la forma natural de hacerlo. Mi sugerencia sobre inicializar loglik[] significa código: loklik <- rep(NA, 100) que preasignará loglik[1], loglik[2] ... loglik[100]. Planteo esa pregunta porque en tu código original, no encontré la delcaración de loglik, ¿quizás el código está truncado al pegarlo?

3voto

Pat Puntos 1698

Me sigue apareciendo un error al intentar abrir tu archivo .rar, pero puede que sólo sea yo haciendo alguna tontería.

No veo errores obvios en su código. Una posible razón por la que obtienes ceros es debido a la precisión en coma flotante. Recuerda, cuando calculas $f(y;\theta)$ estás evaluando $\exp(-0.5(y-\mu)^2/\sigma^2)$ . No hace falta una diferencia muy grande entre $\mu$ y $y$ para que esto se redondee a 0 cuando lo hagas en un ordenador. Esto es doblemente notable en los modelos de mezcla, ya que algunos de sus datos no serán "asignados" a cada componente de la mezcla y por lo tanto puede terminar muy lejos de ella. En teoría, estos puntos también deben terminar con un valor bajo de $\tau$ cuando se evalúa la probabilidad logarítmica, contrarrestando el problema - pero gracias al error de coma flotante, la cantidad ya ha sido evaluada como -Inf en esta etapa, por lo que todo se rompe :).

Si ese es el problema, hay algunas soluciones posibles:

Una es trasladar su $\tau$ dentro del logaritmo. Así, en lugar de evaluar

$\tau \log(f(y|\theta)) $

evaluar

$\log \left( f(y|\theta)^\tau \right)$ .

Matemáticamente es lo mismo, pero piensa en lo que ocurre cuando $f(y|\theta)$ y $\tau$ son $\approx 0$ . Actualmente tienes:

  • $0 \log (0) = 0 (-Inf) = NaN$

pero con tau movido obtienes

  • $\log \left( 0^0\right) = \log(1) = 0$

asumiendo que R evalúa $0^0 = 1$ (No se si lo hace o no ya que suelo usar matlab)

Otra solución es ampliar lo que hay dentro del logaritmo. Asumiendo que estás usando logaritmos naturales:

$\tau \log(f(y|\theta)) $

$= \tau \log(\exp(-0.5(y-\mu)^2/\sigma^2)/\sqrt{2\pi\sigma^2})$

$= -0.5\tau \log(2 \pi\sigma^2) - 0.5 \tau \frac{(y-\mu)^2}{\sigma^2}$ .

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

$-0.5\frac{(y-\mu)^2}{\sigma^2} = -0.5*40^2 = -800$ .

Evalúalo como acabo de sugerir y obtendrás -800. Sin embargo, en matlab si exp el tomar el logaritmo, obtenemos $\log(\exp(-800)) = \log(0) = -Inf$ .

0 votos

Mh, para ser honesto: No soy lo suficientemente bueno para conseguir 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 pedir la luna. Pero creo que te has esforzado en tu respuesta, ¡así que la aceptaré! Gracias.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X