Aqui está um exemplo de Maximização de Expectativa (EM) usada para estimar a média e o desvio padrão. O código está em Python, mas deve ser fácil de seguir, mesmo que você não esteja familiarizado com o idioma.
A motivação para EM
Os pontos vermelho e azul mostrados abaixo são extraídos de duas distribuições normais diferentes, cada uma com uma média e desvio padrão específicos:
Para calcular aproximações razoáveis dos parâmetros "verdadeiro" de média e desvio padrão para a distribuição vermelha, poderíamos facilmente olhar para os pontos vermelhos e registrar a posição de cada um e, em seguida, usar as fórmulas familiares (e da mesma forma para o grupo azul) .
Agora considere o caso em que sabemos que existem dois grupos de pontos, mas não podemos ver qual ponto pertence a qual grupo. Em outras palavras, as cores estão ocultas:
Não é de todo óbvio como dividir os pontos em dois grupos. Agora somos incapazes de olhar apenas as posições e calcular estimativas para os parâmetros da distribuição vermelha ou azul.
É aqui que o EM pode ser usado para resolver o problema.
Usando EM para estimar parâmetros
Aqui está o código usado para gerar os pontos mostrados acima. Você pode ver as médias reais e os desvios padrão das distribuições normais das quais os pontos foram extraídos. As variáveis red
e blue
mantêm as posições de cada ponto nos grupos vermelho e azul respectivamente:
import numpy as np
from scipy import stats
np.random.seed(110) # for reproducible random results
# set parameters
red_mean = 3
red_std = 0.8
blue_mean = 7
blue_std = 2
# draw 20 samples from normal distributions with red/blue parameters
red = np.random.normal(red_mean, red_std, size=20)
blue = np.random.normal(blue_mean, blue_std, size=20)
both_colours = np.sort(np.concatenate((red, blue)))
Se pudéssemos ver a cor de cada ponto, tentaríamos recuperar médias e desvios padrão usando as funções da biblioteca:
>>> np.mean(red)
2.802
>>> np.std(red)
0.871
>>> np.mean(blue)
6.932
>>> np.std(blue)
2.195
Mas como as cores estão ocultas, iniciaremos o processo EM ...
Primeiro, adivinhamos os valores para os parâmetros de cada grupo ( etapa 1 ). Essas suposições não precisam ser boas:
# estimates for the mean
red_mean_guess = 1.1
blue_mean_guess = 9
# estimates for the standard deviation
red_std_guess = 2
blue_std_guess = 1.7
Suposições muito ruins - os meios parecem estar muito longe de qualquer "meio" de um grupo de pontos.
Para continuar com o EM e melhorar essas suposições, calculamos a probabilidade de cada ponto de dados (independentemente de sua cor secreta) aparecer sob essas suposições para a média e o desvio padrão ( etapa 2 ).
A variável both_colours
contém cada ponto de dados. A função stats.norm
calcula a probabilidade do ponto em uma distribuição normal com os parâmetros fornecidos:
likelihood_of_red = stats.norm(red_mean_guess, red_std_guess).pdf(both_colours)
likelihood_of_blue = stats.norm(blue_mean_guess, blue_std_guess).pdf(both_colours)
Isso nos diz, por exemplo, que, com nossas suposições atuais, o ponto de dados em 1.761 tem muito mais probabilidade de ser vermelho (0,189) do que azul (0,00003).
Podemos transformar esses dois valores de probabilidade em pesos ( etapa 3 ) para que eles somarem 1 como a seguir:
likelihood_total = likelihood_of_red + likelihood_of_blue
red_weight = likelihood_of_red / likelihood_total
blue_weight = likelihood_of_blue / likelihood_total
Com nossas estimativas atuais e nossos pesos recém-calculados, agora podemos calcular novas estimativas, provavelmente melhores, para os parâmetros ( etapa 4 ). Precisamos de uma função para a média e uma função para o desvio padrão:
def estimate_mean(data, weight):
return np.sum(data * weight) / np.sum(weight)
def estimate_std(data, weight, mean):
variance = np.sum(weight * (data - mean)**2) / np.sum(weight)
return np.sqrt(variance)
Eles parecem muito semelhantes às funções usuais ao desvio médio e padrão dos dados. A diferença é o uso de um weight
parâmetro que atribui um peso a cada ponto de dados.
Essa ponderação é a chave para o EM. Quanto maior o peso de uma cor em um ponto de dados, mais ele influencia as próximas estimativas para os parâmetros dessa cor. Por fim, isso tem o efeito de puxar cada parâmetro na direção certa.
As novas suposições são computadas com estas funções:
# new estimates for standard deviation
blue_std_guess = estimate_std(both_colours, blue_weight, blue_mean_guess)
red_std_guess = estimate_std(both_colours, red_weight, red_mean_guess)
# new estimates for mean
red_mean_guess = estimate_mean(both_colours, red_weight)
blue_mean_guess = estimate_mean(both_colours, blue_weight)
O processo EM é então repetido com essas novas suposições da etapa 2 em diante. Podemos repetir as etapas para um determinado número de iterações (digamos 20), ou até vermos os parâmetros convergirem.
Após cinco iterações, vemos nossas primeiras suposições ruins começarem a melhorar:
Após 20 iterações, o processo EM convergiu mais ou menos:
Para comparação, eis os resultados do processo EM comparados com os valores calculados onde as informações de cores não estão ocultas:
| EM guess | Actual
----------+----------+--------
Red mean | 2.910 | 2.802
Red std | 0.854 | 0.871
Blue mean | 6.838 | 6.932
Blue std | 2.227 | 2.195
Nota: esta resposta foi adaptada da minha resposta no Stack Overflow aqui .