terça-feira, 15 de março de 2022

Gaussian Mixture Models - Parte I

Normalmente utilizamos uma determinada distribuição para modelar dados. Escolhida uma distribuição para a modelagem, precisamos estimar seus parâmetros por algum método de estimação. Por exemplo, se estamos trabalhando com a distribuição normal, é de interesse estimar os parâmetros $\mu$ e $\sigma^2$, que são a média e a variância da distribuição. Note que, nesse caso, supomos que os dados são originados de uma distribuição apenas.

Mas e se os dados vierem de duas ou mais distribuições? Confuso? Imagine clusters (ou agrupamentos), como na figura abaixo. Percebemos que uma parte dos dados parecem ser originados de uma distribuição e a outra parte, de outra distribuição. Os dados observados (representados pelo histograma) contemplam os dados das duas distribuições mas, na prática, não sabemos quais são (ou quantas são). Dessa forma, a modelagem de mistura de distribuições se torna bastante útil.



Nesse post, ensino como trabalhar com mistura de distribuições Gaussianas, ou seja, quando os dados pertencem a mais de uma distribuição normal (médias e/ou variâncias distintas). Para a estimação dos parâmetros, utilizaremos o algoritmo EM (já explicado aqui no blog).

Gaussian Mixture Models

Para começarmos, vamos supor que nossos dados sejam provenientes de duas distribuições Gaussianas com parâmetros desconhecidos. Seja $X$ nossa variável de interesse. A função de densidade do modelo é dada por

$$\begin{eqnarray}\label{eq:model} f(x|\theta) = p_1f(x|\theta_1) + p_2f(x|\theta_2) \end{eqnarray}$$

em que $\theta_1 = (\mu_1,\sigma^2_1)$ e $\theta_2 = (\mu_2,\sigma^2_2)$ são os vetores de parâmetros das distribuições normais 1 e 2, respectivamente, e $p_1$ e $p_2$ são os pesos relacionados a ambas distribuições. Podemos interpretar os pesos como as probabilidades de uma observação ser proveniente de uma determinada distribuição, ou seja, $p_1$ é a probabilidade dessa observação pertencer à distribuição 1 e $p_2$, a probabilidade dela pertencer à distribuição 2. O vetor de parâmetros do modelo é $\theta = (\theta_1,\theta_2,p_1)$. Por que apenas $p_1$? Porque $p_2$ é completamente determinada por $p_1$, isto é, $p_2 = 1 - p_1$.

Seja $X_1, X_2, ..., X_n$ uma amostra aleatória da distribuição em ($\ref{eq:model}$). A função de log-verossimilhança do modelo é dada por

$$\begin{eqnarray} l(\theta) &=& \log \left[ \prod_{i=1}^n f(x_i|\theta) \right] = \sum_{i=1}^n \log f(x_i|\theta) = \sum_{i=1}^n \log \big[ p_1 f(x_i|\theta_1) + p_2 f(x_2|\theta_2) \big]\end{eqnarray}$$

Se prosseguirmos para encontrar os estimadores de máxima verossimilhança (EMV) a partir da função acima, veremos que não há forma fechada para os estimadores. Logo, outra abordagem é necessária.

Modelo aumentado

Para isso, imagine que para cada observação $x_i$ conhecemos a qual distribuição ela pertence. Isso é equivalente a dizer que conhecemos uma variável $Y_i$ que indica se a observação $x_i$ pertence a distribuição $C$, $C = 1,2$. A função de densidade de $Y_i$ é dada por

$$f(y_i) = \prod_{y_i \in C} p_{y_i}^{1 \{y_i = c\}}, \quad C = \{1,2\}$$

em que $1\{\}$ é a função indicadora. Dado o valor de $y_i$, a função de densidade de $X_i$ será

$$f(x_i|y_i) = \prod_{y_i \in C} f(x_i|\theta_{y_i})^{1\{y_i = c\}}$$

Na prática não observamos os valores de $Y_i$. Dessa forma, $Y_i$ é uma variável latente e trabalhamos com o modelo aumentado $(X,Y)$. A função de densidade conjunta de $(X_i,Y_i)$ é dada por

\begin{eqnarray}
f(x_i,y_i) &=& f(x_i|y_i) f(y_i)  = \prod_{y_i \in C} \left( p_{y_i} f(x_i|\theta_{y_i}) \right)^{1\{y_i = c\}} \nonumber
\end{eqnarray}

A função de verossimilhança do modelo aumentado, dada uma amostra aleatória $(X,Y) = ((X_1,Y_1),...,(X_n,Y_n))$, será 

\begin{eqnarray}
L(\theta) &=& \prod_{i=1}^n f(x_i|y_i) f(y_i)  = \prod_{i=1}^n \left[ \prod_{y_i \in C} \left( p_{y_i} f(x_i|\theta_{y_i}) \right)^{1\{y_i = c\}} \right] \nonumber
\end{eqnarray}

e a função de log-verossimilhança é dada por 

\begin{eqnarray}
l(\theta) &=& \sum_{i=1}^n  \sum_{y_i \in C} {1\{y_i = c\}} \left[ \log (p_{y_i}) + \log (f(x_i|\theta_{y_i}))  \right]  \nonumber
\end{eqnarray}

Vamos refletir um pouco sobre o que nós fizemos. 

  • Primeiro modelamos o problema e chegamos na função de verossimilhança em (\ref{eq:model}).
  • Percebemos que não há como estimar os parâmetros do modelo dessa forma e outra abordagem se tornou necessária.
  • Consideramos uma variável $Y$ para indicar em que grupo (cluster) a variável $X$ pertence.
  • Escrevemos o modelo completo para $(X,Y)$.

Observe que $X$ é variável observada, enquanto que $Y$ é não observada (latente). Dessa forma, precisamos de um método para estimar os parâmetros do modelo nesse contexto. Como falamos no começo do post, utilizaremos o algoritmo EM. Mas por quê? Se você acompanha o blog, já deve saber a resposta.

Temos um modelo com variáveis observadas e variáveis não observadas. O algoritmo EM é excelente no contexto de dados faltantes, em que a função de verossimilhança completa é mais simples de se trabalhar do que a função de verossimilhança dos dados observados. Dessa forma, vamos prosseguir para a estimação dos parâmetros.

Estimação dos parâmetros - Algoritmo EM

Passo E

A função $Q$ será dada por

\begin{eqnarray}
Q(\theta|\theta^{(k)}) &=& \sum_{i=1}^n  \sum_{y_i \in C} E[{1\{y_i = c\}}|x,\theta_{(k)}] \left[ \log (p_{y_i}) + \log (f(x_i|\theta_{y_i}))  \right]  \nonumber
\end{eqnarray}

Para facilitar a notação, escrevemos $\gamma_{ic} =  E[{1\{y_i = c\}}|x,\theta_{(k)}] $. Note que $\gamma_{ic}$ é a probabilidade de $Y_i$ ser igual a $c$ (ou seja, indicar que $x_i$ pertence a distribuição $c$) dado o valor de $x_i$. Ou seja, 

\begin{eqnarray} \gamma_{ic} = P(Y_i = c|x_i) = f(y_i = c|x_i) \nonumber \end{eqnarray}

Para calcularmos o valor de $\gamma_{ic}$, precisamos encontrar a distribuição de $Y_i|X_i$. Temos então

\begin{eqnarray}
f(y_i|x_i) &=& \dfrac{f(x_i|y_i)f(y_i)}{f(x_i)} = \dfrac{\prod_{y_i \in C} \left[p_{y_i}f(x_i|\theta_{y_i}) \right] ^{1\{y_i = c\}}}{p_{1}f(x_i|\theta_{1})+p_{2}f(x_i|\theta_{2})}  \nonumber
\end{eqnarray}

Dessa forma,

\begin{eqnarray} \gamma_{ic} &=& P(Y_i = c|x_i)  = \dfrac{p_{c}f(x_i|\theta_{c}) }{p_{1}f(x_i|\theta_{1})+p_{2}f(x_i|\theta_{2})} \nonumber \end{eqnarray}

Passo M

Para encontrar os estimadores, precisamos derivar a função $Q()$ em relação aos parâmetros de interesse. Vamos realizar primeiro a estimação de $\mu_1$:

\begin{eqnarray}
\dfrac{d}{d\mu_1}Q()  &=&\sum_{i=1}^{n} \gamma_{i1}  \dfrac{d}{d\mu_1} \log f(x_i|\theta_1) = \sum_{i=1}{n} \gamma_{i1}  \dfrac{(x_i-\mu_1)}{\sigma^2_1} \nonumber
\end{eqnarray}

Igualando $(d/d\mu_1)Q()$ a zero, obtemos $\hat{\mu}_1 = \dfrac{\sum_{i=1}^n (\gamma_{i1} x_i)}{\sum_{i=1}^n \gamma_{i1}}$.

De forma análoga, $\hat{\mu}_2 = \dfrac{\sum_{i=1}^n (\gamma_{i2} x_i)}{\sum_{i=1}^n \gamma_{i2}}$

Em relação à variância da distribuição 1, $\sigma^2_1$, temos

\begin{eqnarray}
\dfrac{d}{d\sigma^2_1}Q()  &=&\sum_{i=1}{n} \gamma_{i1}  \left(\dfrac{-1}{2\sigma^2_1} + \dfrac{1}{2\sigma^4_1}(x_i-\mu_1)^2 \right)\nonumber \\
\end{eqnarray}

Igualando $(d/d\sigma^2_1)Q()$ a zero, obtemos $\hat{\sigma}^2_1 = \dfrac{\sum_{i=1}^n \gamma_{i1}(x_i-\mu_1)^2 }{\sum_{i=1}^n \gamma_{i1}}$.

De forma análoga, $\hat{\sigma}^2_2 = \dfrac{\sum_{i=1}^n \gamma_{i2}(x_i-\mu_2)^2 }{\sum_{i=1}^n \gamma_{i2}}$.

Em relação aos pesos $p_1$ e $p_2$, temos que lembrar que

$$p_1 + p_2 = 1 \rightarrow p_2 = 1 - p_1$$

Dessa forma, vamos encontrar o estimador de $p_1$.

\begin{eqnarray}
\dfrac{d}{d p_1}Q()  &=&\sum_{i=1}^{n} \left( \gamma_{i1}  \dfrac{1}{p_1} - \gamma_{i2} \dfrac{1}{1-p_1} \right) \nonumber 
\end{eqnarray}

Igualando $(d/dp_1)Q()$ a zero, obtemos $\hat{p}_1 = \dfrac{\sum_{i=1}^n \gamma_{i1} }{\sum_{i=1}^n (\gamma_{i1}+\gamma_{i2})}$.

Logo, o estimador para $p_2$ é $\hat{p}_2 = 1 - \hat{p}_1$.

Dessa forma, encontramos uma metodologia para estimar os parâmetros da distribuição de mistura de normais. A implementação deste exemplo será feita no próximo post.

Espero que tenha gostado da aula.

Até a próxima aula!

sexta-feira, 4 de março de 2022

Algoritmo EM - Parte III

No post anterior, falamos da necessidade de utilizar vários valores iniciais no algoritmo EM para verificar se a convergência se dá na mesma região. Hoje vamos programar este teste.

Para fazer isso, vamos fazer algumas alterações no código anterior:

  1. Nossa função vai receber o argumento adicional dif_parada, que é o critério de parada do algoritmo (ou a diferença máxima permitida entre duas estimações).
  2. Vamos criar uma variável para identificar se o algoritmo parou porque convergiu (ou seja, a diferença entre duas estimações consecutivas foi menor que dif_parada) ou se atingiu o limite máximo de iterações.
  3. Em cada um dos dois casos, uma mensagem deve ser exibida para o usuário.
O código alterado você pode conferir abaixo.
algoritmoEM_tStudent = function(par_ini,max_ite,y,nu,plot.trace=0,dif_parada=10^-4){
  theta.curr = c(par_ini[1],par_ini[2]);
  n = length(y);
  
  theta.trace = matrix(ncol=2,nrow=1);
  theta.trace[1,] = theta.curr;
  atingiu_maxit = TRUE;
  for(i in 1:max_ite){
    #Passo 1 do EM - calcular E(U|Y)
    
    alfa = 0.5*(n + nu)
    beta = 0.5*(nu + sum((y-theta.curr[1])^2)/theta.curr[2]);
    u.y = alfa/beta;
    
    #Passo 2 do EM - Maximizar a função Q
    # em relação aos parâmetros
    
    mu.hat = sum(y)/n;
    sigma2.hat = u.y*(sum((y-mu.hat)^2)/n);
    
    theta.curr = c(mu.hat,sigma2.hat);
    theta.trace = rbind(theta.trace,theta.curr);
    
    dif = abs(theta.trace[i+1,]-theta.trace[i,])

    if(dif[1]&lt dif_parada & dif[2]&lt dif_parada) {
      cat("Algoritmo convergiu com",i,"iterações.\n")
      atingiu_maxit=FALSE;
      break;
    }
    
  }
  
  if(atingiu_maxit==T) cat("Algoritmo atingiu número máximo de iterações.\n")
  
  if(plot.trace==1){
    par(mfrow=c(1,2))
    plot(theta.trace[,1],main=expression(mu),ylab='',
         xlab='Iteração',type='l',lwd=2)
    
    plot(theta.trace[,2],main=expression(sigma^2),ylab='',
         xlab='Iteração',type='l',lwd=2)
  }
  
  return(theta.trace)
}

Antes o programa parava a execução depois que todas as iterações fossem executadas. Agora, se o algoritmo convergir antes, paramos sua execução e ganhamos tempo computacional e memória. Ou seja, estamos evitando realizar trabalho desnecessário.

Para testar se o algoritmo está convergindo para uma única determinada região, precisamos executá-lo a partir de vários valores iniciais. Porém, fazer cada um na mão pode ser trabalhoso, então vamos automatizar esse processo.

Primeiro, vamos criar um vetor de valores iniciais para os parâmetros $\mu$ e $\sigma^2$.

vi_mu = c(-2,-1,0,1,2)
vi_sigma2 = c(0.1,0.5,1,2,5)

Para cada par de valor inicial $(\mu^{(0)},\sigma^{2(0)})$, vamos executar nosso algoritmo e retornar a estimação dos parâmetros. Para armazenar os resultados da estimação, utilizaremos uma lista.

erro_max = 10^-4;
max_ite = 100;

result = list();
for(i in 1:length(vi_mu)){
  result[[i]] = algoritmoEM_tStudent(c(vi_mu[i],vi_sigma2[i]),max_ite,y,nu,0,erro_max)
}

No código acima:

  • Definimos o critério de parada como $10^4$.
  • Definimos o número máximo de iterações igual a 100.
  • Definimos a estrutura de dados que vai receber os resultados: a lista result.
  • Para cada par de valores iniciais, executamos o algoritmo EM e armazenamos seu respectivo resultado em result.

Se executarmos o código, result será uma lista de tamanho 5 contendo os resultados da estimação de cada par de valores iniciais dos parâmetros.

> result = list();
> for(i in 1:length(vi_mu)){
+   result[[i]] = algoritmoEM_tStudent(c(vi_mu[i],vi_sigma2[i]),max_ite,y,nu,0,erro_max)
+ }
Algoritmo atingiu número máximo de iterações.
Algoritmo atingiu número máximo de iterações.
Algoritmo convergiu com 99 iterações.
Algoritmo convergiu com 91 iterações.
Algoritmo convergiu com 77 iterações.

> length(result)
[1] 5

Observe que o algoritmo atingiu o número máximo de iterações em 2 pares e convergiu antes para os restantes. Isso exemplifica que, dependendo do valor inicial, a convergência pode ser mais lenta, principalmente se os valores iniciais estiverem distantes da região de convergência.

Agora, precisamos analisar se a convergência se dá na mesma região. Para isso, vamos plotar as linhas de trajetórias das cinco estimações em um único gráfico. Para obtermos um bom gráfico, necessitamos de duas coisas:

  1. calcular os valores mínimo e máximo de estimações (caso contrário, algumas curvas podem aparecer parcialmente se o eixo y não for bem definido),
  2. identificar o número máximo de iterações dos resultados para definir até onde plotar a curva no eixo x.

Vejamos:

#encontrar os limites do eixo y para ambos parametros
#e o número máximo de iterações executado
min_mu = max_mu = min_sigma2 = max_sigma2 = NULL;
ite_res = NULL;
for(i in 1:length(result)){
  min_mu = min(min_mu,result[[i]][,1]);
  max_mu = max(max_mu,result[[i]][,1]);
  min_sigma2 = min(min_sigma2,result[[i]][,2]);
  max_sigma2 = max(max_sigma2,result[[i]][,2]);
  ite_res = max(ite_res,dim(result[[i]])[1]);
}

O que estamos fazendo no código acima é varrer cada matriz de result, identificando os valores máximo e mínimo das estimações dos parâmetros. Se executarmos o código acima, obtemos

> cbind(min_mu,max_mu);
     min_mu max_mu
[1,]     -2      2
> cbind(min_sigma2,max_sigma2);
     min_sigma2 max_sigma2
[1,] 0.01044391          5
> ite_res
[1] 101

Obtemos ite_res igual a 101 porque são 100 iterações do algoritmo mais a linha do valor inicial. Agora estamos prontos para construir o gráfico de trajetórias.

par(mfrow=c(2,1))

#Grafico para mu
plot(result[[1]][,1],type='l',main=expression(mu),
     ylab='',xlab='Iteração',lwd=1,
     xlim=c(0,ite_res),ylim=c(min_mu,max_mu))
abline(h=mu,col='blue',lwd=2)
abline(h=emv_mu,col='red',lwd=2)
for(i in 2:length(result))  lines(result[[i]][,1],lwd=1)


#Grafico para sigma2
plot(result[[1]][,2],type='l',main=expression(sigma^2),
     ylab='',xlab='Iteração',lwd=1,
     xlim=c(0,ite_res),ylim=c(min_sigma2,max_sigma2))
abline(h=sigma2,col='blue',lwd=2)
abline(h=emv_sigma2,col='red',lwd=2)
for(i in 2:length(result))  lines(result[[i]][,2],lwd=1)

Vejamos o resultado:


A linha azul representa o valor verdadeiro do parâmetro e a linha vermelha, o valor de seu EMV. Como a estimação de $\mu$ converge depois de uma iteração (discutimos isso no post anterior), nesse gráfico fica ruim de observar a convergência. Vamos alterar a janela do eixo x para xlim=c(0,5), apenas para observarmos o comportamento da saída do valor inicial para a estimação. No gráfico de $\sigma^2$ é mais fácil visualizar que o algoritmo está convergindo para o mesmo lugar (o EMV).

E se quisermos rodar o algoritmo para 20 valores iniciais? Ou 100? Seria bom gerar valores iniciais ao invés de escrevê-los a mão, concorda comigo? É o que fazemos abaixo:

set.seed(7)

vi_mu = rnorm(20,mean=0,sd=2)
vi_sigma2 = rgamma(20,shape=2,rate=2)

Dessa forma, geramos 20 valores iniciais para os parâmetros. Veja que para $\sigma^2$ estamos gerando de uma distribuição Gama, uma vez que seu valor deve ser positivo. Poderíamos utilizar outras distribuições? Claro que sim.

Se executarmos o algoritmo EM com os valores iniciais acima, obtemos:


Alterei o limite máximo do eixo y do gráfico de $\sigma^2$ apenas para termos uma melhor visualização das curvas. Analisemos o gráfico acima:

  • No gráfico de $\mu$, observamos a convergência para o mesmo ponto (EMV) após 1 iteração do algoritmo.
  • No gráfico de $\sigma^2$, notamos que a convergência para a mesma região (EMV) partindo dos diferentes valores iniciais. Observe que há curvas que não alcançaram o EMV. Isso ocorre porque o algoritmo atingiu o limite de iterações que definimos.
Portanto, vamos executar novamente o algoritmo com 2000 iterações. Observe o gráfico abaixo. Agora conseguimos visualizar todas as curvas convergindo para o EMV, pois permitimos iterações suficientes para o algoritmo convergir. Além disse, o número máximo de iterações executados foi 183, bem menor do que o máximo que definimos. Isso ilustra a vantagem de verificar convergência.


Já analisamos o efeito do número de iterações máxima no resultado do algoritmo. Agora, qual o efeito de alterar o critério de parada do algoritmo? Ou seja, o que acontece se alteramos o critério que definimos para considerar convergência? Vamos fazer o teste com o valor de erro_max igual a $10^5$. O resultado é o "mesmo" que o anterior, pois as estimativas são as mesmas. A diferença é que o algoritmo demorou mais para convergir, ou seja, precisou de mais iterações para conferir o critério de convergência. No caso, obtemos um número máximo de iterações executadas igual a 230.


Por fim, faremos o mesmo exemplo, mas com um tamanho de amostra igual a 5 mil (utilize a mesma semente do exemplo anterior). Nesse caso, os valores do EMV dos parâmetros são mais próximos do valor verdadeiro (pois temos mais informação).

> emv_mu
[1] 0.003103879
> emv_sigma2
[1] 0.9086786

Executando nossa função para os 20 valores iniciais, 10 mil iterações e erro igual a $10^5$, obtemos o seguinte gráfico de trajetória para $\sigma^2$.


A partir do critério de parada estabelecido, o algoritmo convergiu antes de 10 mil iterações para todas as 20 execuções. Note que precisamos de muito mais iterações com essa amostra. Isso ilustra a necessidade de adequar o erro e o número máximo de iterações para cada problema que se tem em mãos. A análise gráfica ajuda muito nessa etapa.

Espero que tenha gostado da aula.

Até a próxima aula!

quarta-feira, 2 de março de 2022

Algoritmo EM - Parte II

No post anterior apresentei o algoritmo EM e expliquei como o algoritmo funciona. Também apresentei um exemplo em que calculamos as quantidades necessárias para sua execução. Vamos relembrar esse exemplo.

Suponha que temos uma amostra aleatória de tamanho $n$ de uma população com distribuição t-Student com $\nu$ graus de liberdade. Nosso interesse é estimar os parâmetros de média $\mu$ e de escala $\sigma^2$. Seja $Y$ nossa variável de interesse. Escrevemos a distribuição de $Y$ na forma hierárquica

$$\begin{eqnarray}Y|U &\sim& N(\mu,u^{-1}\sigma^2), \nonumber \\ U &\sim& Gamma(\nu/2,\nu/2). \nonumber \end{eqnarray}$$

Vimos no post anterior que precisamos da quantidade $E[U|Y,\theta]$ e a calculamos:

$$E_{U|Y} = \dfrac{(n-\nu)\sigma^2}{\nu\sigma^2 + \sum_{i=1}^{n} (y_i- \mu)^2}.$$

Também encontramos os estimadores dos parâmetros ao maximizar a função $Q$, obtendo

$$\begin{eqnarray} \hat{\mu} &=& \dfrac{\sum_{i=1}^{n} y_i}{n}. \nonumber \\ \hat{\sigma}^2 &=& E_{U|Y} \dfrac{\sum_{i=1}^{n} (y_i- \mu)^2}{n}. \nonumber \end{eqnarray}$$

Lembrando que o primeiro passo - E - é calcular o valor esperado da variável latente dados os valores observados $y$ e os valores dos parâmetros na $k$-ésima iteração, e o segundo passo - M - é maximizar a função $Q$ dados os valores observados e o valor esperado da variável latente para obter os valores dos parâmetros da iteração $k+1$. Os passos são repetidos até que se observe convergência ou um número máximo de iterações seja atingido.

IMPORTANTE RELEMBRAR: os estimadores EM convergem para os estimadores de máxima verossimilhança (EMV).

Agora que relembramos as quantidades que serão necessárias, vamos implementar o algoritmo EM para nosso exemplo. Vamos começar gerando nossos dados:

n  = 100;
nu = 5;
mu = 0;
sigma2 = 1;

set.seed(7);
u = rgamma(1,shape=n/2,rate=n/2);
y = rnorm(1000,mean = 0, sd = sqrt(sigma2)*(u^-1));

Estamos gerando $n=100$ observações de uma distribuição t-Student com 5 graus de liberdade, com média igual a 0 e escala igual a 1. Para visualizar os dados, computamos seu histograma, por exemplo.

hist(y,main='')

Qual a estimativa dos parâmetros pelos EMVs?

emv_mu = sum(y)/n 
emv_sigma2 = sum((y-emv_mu)^2)/n

> emv_mu
[1] 0.09044438
> emv_sigma2
[1] 0.4832229

Observe que a estimativa da média é um pouco maior que zero e que, apesar de assumirmos que $\sigma^2 =1$, temos que $\hat{\sigma}^2 = 0,48$, menos que a metade do valor original. Isso é possível pois estamos trabalhando com uma amostra e essa foi a informação que a amostra nos forneceu.

Vamos partir para o algoritmo EM. O começo do código:

theta.curr = c(1,2);
max_ite = 100;

theta.trace = matrix(ncol=2,nrow=1);
theta.trace[1,] = theta.curr;

O que estamos fazendo?

  1. theta.curr armazenará os valores correntes do vetor de parâmetros. Iniciamos com $\mu=3$ e $\sigma^2=5$.
  2. Definimos o número máximo de iterações como 100.
  3. theta.trace é onde armazenaremos os valores estimados dos parâmetros em cada iteração do algoritmo.
  4. O primeiro valor armazenado em theta.trace são os valores iniciais. 

Ao final do algoritmo, faremos um plot para visualizar as estimativas de cada parâmetro, observando visualmente se o algoritmo convergiu ou não. Essa é uma maneira de observar convergência, mas não a melhor. Discutiremos isso mais adiante.

A seguir, construímos o algoritmo propriamente dito. Os passos E e M são realizados até o número de iterações atingir o número máximo. Portanto

for(i in 1:max_ite){
  #Passo 1 do EM - calcular E(U|Y)
  
  alfa = 0.5*(n + nu)
  beta = 0.5*(nu + sum((y-theta.curr[1])^2)/sigma2);
  u.y = alfa/beta;
  
  #Passo 2 do EM - Maximizar a função Q
  # em relação aos parâmetros
  
  mu.hat = sum(y)/n;
  sigma2.hat = u.y*(sum((y-mu.hat)^2)/n);
  
  theta.curr = c(mu.hat,sigma2.hat);
  theta.trace = rbind(theta.trace,theta.curr);
}

O que estamos fazendo?

  1. Calculamos o valor de $E_{U|Y}$ - passo E.
  2. Calculamos os valores de $\hat{\mu}^{(k+1)}$ e $(\hat{\sigma}^2)^{(k+1)}$ - passo M.
  3. Atualizamos o vetor theta.curr com as novas estimativas dos parâmetros.
  4. Armazenamos essas estimativas em theta.trace.
  5. Esse bloco é repetido até que i seja igual a max_ite.

Se executarmos o código acima, obtemos:

> head(theta.trace)
                 [,1]      [,2]
           1.00000000 2.0000000
theta.curr 0.09044438 0.7194313
theta.curr 0.09044438 0.7030660
theta.curr 0.09044438 0.6881575
theta.curr 0.09044438 0.6745351
theta.curr 0.09044438 0.6620535

Observe que a estimativa da média não mudou após a primeira iteração. Isso ocorre porque seu estimador não depende do valor de $u$ nem da estimativa de $\sigma^2$. Em relação ao parâmetro de escala, perceba que temos uma diferença grande nas estimativas nas iterações iniciais e essa diferença diminui. É melhor visualizar isso em um gráfico de trajetória, construído com estimativa versus iteração.

par(mar=c(4,2,2,2),mfrow=c(1,2))
plot(theta.trace[,1],main=expression(mu),ylab='',
     xlab='Iteração',type='l',ylim=c(-0.5,1),lwd=2)
abline(h=mu,col='blue',lwd=2)

plot(theta.trace[,2],main=expression(sigma^2),ylab='',
     xlab='Iteração',type='l',lwd=2)
abline(h=sigma2,col='blue',lwd=2)


Nos gráficos acima, a linha azul representa o valor verdadeiro do parâmetro e a linha vermelha, o valor do EMV. Então perceba que as estimativas pelo algoritmo EM estão convergindo para os valores do EMV.

Simples, não? É claro que a programação do método não é tão simples para modelos mais complexos.

Você pode estar se perguntando: por que os estimadores do EM convergem para os valores do EMV e não para os valores verdadeiros? A resposta é bem teórica, mas vou resumir a ideia. O EM é uma ferramenta utilizada para estimar os parâmetros do modelo em um modelo aumentado onde há dados faltantes. A verossimilhança incompleta (ou observada) é obtida integrando a verossimilhança completa em relação às variáveis latentes. Dessa forma, os estimadores do EM convergem para os EMVs. Como os EMVs são não viciados para os valores dos parâmetros, então os estimadores do EM convergem para os parâmetros da população. Essa é só a ideia, se quiser aprofundar sobre o assunto aconselho estudar as provas matemáticas do método.

É importante executar o algoritmo EM com diferentes valores iniciais para os parâmetros para verificar se a convergência se dá na mesma região. Caso haja diferença da região de convergência a partir de valores iniciais distintos, pode significar que o algoritmo está convergindo para máximos locais e não para o máximo global. Ou seja, sempre faça essa verificação.

Para terminar, vamos organizar nosso código e colocá-lo em uma função. Dessa forma, não precisaremos executar o bloco de código do for toda vez que quisermos rodar o algoritmo. Além disso, vamos

  1. implementar um critério de parada, para que não seja necessário chegar ao máximo de iterações se o algoritmo convergir antes,
  2. plotar as trajetórias das estimativas se o usuário quiser.

O código da função fica como:

algoritmoEM_tStudent = function(par_ini,max_ite,y,nu,plot.trace=0){
  theta.curr = c(par_ini[1],par_ini[2]);
  n = length(y);
  
  theta.trace = matrix(ncol=2,nrow=1);
  theta.trace[1,] = theta.curr;
  dif_parada = 10^-4;
  for(i in 1:max_ite){
    #Passo 1 do EM - calcular E(U|Y)
    
    alfa = 0.5*(n + nu)
    beta = 0.5*(nu + sum((y-theta.curr[1])^2)/theta.curr[2]);
    u.y = alfa/beta;
    
    #Passo 2 do EM - Maximizar a função Q
    # em relação aos parâmetros
    
    mu.hat = sum(y)/n;
    sigma2.hat = u.y*(sum((y-mu.hat)^2)/n);
    
    theta.curr = c(mu.hat,sigma2.hat);
    theta.trace = rbind(theta.trace,theta.curr);

    dif = abs(theta.trace[i+1,]-theta.trace[i,])

    if(dif[1]<dif_parada & dif[2]<dif_parada) {
      cat("Algoritmo convergiu com",i,"iterações.\n")
      break;
    }
    
  }
  
  if(plot.trace==1){
    par(mfrow=c(1,2))
    plot(theta.trace[,1],main=expression(mu),ylab='',
         xlab='Iteração',type='l',lwd=2)
    
    plot(theta.trace[,2],main=expression(sigma^2),ylab='',
         xlab='Iteração',type='l',lwd=2)
  }
  
  return(theta.trace)
}

1: Para dizer que o algoritmo convergiu, verificamos se a diferença das estimativas de cada parâmetro da iteração atual para anterior é menor que 0,0001. Caso isso ocorra, paramos o algoritmo e evitamos iterações desnecessárias. Obviamente, se alterarmos o valor do critério de parada, alteramos os números de iterações para o mesmo problema.

2: Caso o usuário passe 1 no parâmetro plot.trace, após a execução do EM um gráfico de trajetória para cada parâmetro é construído.

3: Ao final do algoritmo, retornamos theta.trace, que contém todas as estimativas dos parâmetros.

Vamos testar nossa função.

> res = algoritmoEM_tStudent(c(1,2),100,y,nu,1)
Algoritmo convergiu com 91 iterações.

Armazenamos theta.trace (objeto retornado pela nossa função) em res. Os atributos passados são os mesmos do começo do post, então devemos ter os mesmos gráficos de trajetórias. Observe que o algoritmo parou com 91 iterações, antes do número máximo definido. Pense que, se tivéssemos atribuído um número máximo de 1000 iterações, esse critério de parada nos pouparia muito trabalho (e tempo e memória). Por fim, os gráficos plotados são


Perceba que são os mesmos gráficos que construímos anteriormente (sem as personalizações). Agora temos nossa função executando perfeitamente para o nosso problema.

Espero que tenha gostado da aula!

Até a próxima!