Nota: o código por trás desta resposta pode ser encontrado aqui .
Suponha que temos alguns dados amostrados de dois grupos diferentes, vermelho e azul:
Aqui, podemos ver qual ponto de dados pertence ao grupo vermelho ou azul. Isso facilita encontrar os parâmetros que caracterizam cada grupo. Por exemplo, a média do grupo vermelho é cerca de 3, a média do grupo azul é cerca de 7 (e poderíamos encontrar a média exata se quiséssemos).
Geralmente, isso é conhecido como estimativa de máxima verossimilhança . Com alguns dados, calculamos o valor de um parâmetro (ou parâmetros) que melhor explica esses dados.
Agora imagine que não podemos ver qual valor foi amostrado de qual grupo. Tudo parece roxo para nós:
Aqui, sabemos que existem dois grupos de valores, mas não sabemos a qual grupo pertence um determinado valor.
Ainda podemos estimar as médias para o grupo vermelho e o grupo azul que melhor se ajustam a esses dados?
Sim, muitas vezes podemos! A maximização da expectativa nos dá uma maneira de fazer isso. A ideia geral por trás do algoritmo é esta:
- Comece com uma estimativa inicial do que cada parâmetro pode ser.
- Calcule a probabilidade de que cada parâmetro produza o ponto de dados.
- Calcule pesos para cada ponto de dados indicando se é mais vermelho ou mais azul com base na probabilidade de ser produzido por um parâmetro. Combine os pesos com os dados ( expectativa ).
- Calcule uma estimativa melhor para os parâmetros usando os dados ajustados por peso ( maximização ).
- Repita as etapas 2 a 4 até que a estimativa do parâmetro convirja (o processo para de produzir uma estimativa diferente).
Essas etapas precisam de mais explicações, portanto, examinarei o problema descrito acima.
Exemplo: estimativa de média e desvio padrão
Usarei Python neste exemplo, mas o código deve ser bem fácil de entender se você não estiver familiarizado com essa linguagem.
Suponha que temos dois grupos, vermelho e azul, com os valores distribuídos como na imagem acima. Especificamente, cada grupo contém um valor retirado de uma distribuição normal com os seguintes parâmetros:
import numpy as np
from scipy import stats
np.random.seed(110) # for reproducible 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))) # for later use...
Aqui está uma imagem desses grupos vermelho e azul novamente (para evitar que você tenha que rolar para cima):
Quando podemos ver a cor de cada ponto (ou seja, a qual grupo ele pertence), é muito fácil estimar a média e o desvio padrão para cada grupo. Apenas passamos os valores de vermelho e azul para as funções embutidas no NumPy. Por exemplo:
>>> np.mean(red)
2.802
>>> np.std(red)
0.871
>>> np.mean(blue)
6.932
>>> np.std(blue)
2.195
Mas e se não pudermos ver as cores dos pontos? Ou seja, em vez de vermelho ou azul, cada ponto foi colorido de roxo.
Para tentar recuperar os parâmetros de média e desvio padrão para os grupos vermelho e azul, podemos usar a maximização da expectativa.
Nossa primeira etapa ( etapa 1 acima) é adivinhar os valores dos parâmetros para a média e o desvio padrão de cada grupo. Não precisamos adivinhar de forma inteligente; podemos escolher qualquer número que quisermos:
# 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
Essas estimativas de parâmetro produzem curvas de sino que se parecem com isto:
Essas são estimativas ruins. Ambos os meios (as linhas pontilhadas verticais) parecem distantes de qualquer tipo de "meio" para grupos de pontos sensíveis, por exemplo. Queremos melhorar essas estimativas.
A próxima etapa ( etapa 2 ) é calcular a probabilidade de cada ponto de dados aparecer sob as estimativas dos parâmetros atuais:
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)
Aqui, simplesmente colocamos cada ponto de dados na função de densidade de probabilidade para uma distribuição normal usando nossas estimativas atuais na média e no desvio padrão para vermelho e azul. Isso nos diz, por exemplo, que com nossas estimativas atuais, o ponto de dados em 1,761 tem muito mais probabilidade de ser vermelho (0,189) do que azul (0,00003).
Para cada ponto de dados, podemos transformar esses dois valores de verossimilhança em pesos ( etapa 3 ) para que somam 1 da seguinte forma:
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 para a média e o desvio padrão dos grupos vermelho e azul ( etapa 4 ).
Calculamos duas vezes a média e o desvio padrão usando todos os pontos de dados, mas com os pesos diferentes: uma vez para os pesos vermelhos e uma vez para os pesos azuis.
O ponto chave da intuição é que quanto maior o peso de uma cor em um ponto de dados, mais o ponto de dados influencia as próximas estimativas para os parâmetros dessa cor. Isso tem o efeito de "puxar" os parâmetros na direção certa.
def estimate_mean(data, weight):
"""
For each data point, multiply the point by the probability it
was drawn from the colour's distribution (its "weight").
Divide by the total weight: essentially, we're finding where
the weight is centred among our data points.
"""
return np.sum(data * weight) / np.sum(weight)
def estimate_std(data, weight, mean):
"""
For each data point, multiply the point's squared difference
from a mean value by the probability it was drawn from
that distribution (its "weight").
Divide by the total weight: essentially, we're finding where
the weight is centred among the values for the difference of
each data point from the mean.
This is the estimate of the variance, take the positive square
root to find the standard deviation.
"""
variance = np.sum(weight * (data - mean)**2) / np.sum(weight)
return np.sqrt(variance)
# 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)
Temos novas estimativas para os parâmetros. Para melhorá-los novamente, podemos voltar para a etapa 2 e repetir o processo. Fazemos isso até que as estimativas convirjam ou depois que algum número de iterações tiver sido executado ( etapa 5 ).
Para nossos dados, as primeiras cinco iterações desse processo são assim (as iterações recentes têm uma aparência mais forte):
Vemos que as médias já estão convergindo em alguns valores, e as formas das curvas (governadas pelo desvio padrão) também estão se tornando mais estáveis.
Se continuarmos por 20 iterações, terminaremos com o seguinte:
O processo EM convergiu para os seguintes valores, que se revelaram muito próximos dos valores reais (onde podemos ver as cores - sem variáveis ocultas):
| EM guess | Actual | Delta
----------+----------+--------+-------
Red mean | 2.910 | 2.802 | 0.108
Red std | 0.854 | 0.871 | -0.017
Blue mean | 6.838 | 6.932 | -0.094
Blue std | 2.227 | 2.195 | 0.032
No código acima, você deve ter notado que a nova estimativa para o desvio padrão foi calculada usando a estimativa da iteração anterior para a média. Em última análise, não importa se calcularmos primeiro um novo valor para a média, pois estamos apenas encontrando a variância (ponderada) dos valores em torno de algum ponto central. Ainda veremos as estimativas para os parâmetros convergirem.