Voici un exemple d'optimisation des attentes (EM) utilisé pour estimer la moyenne et l'écart type. Le code est en Python, mais il devrait être facile à suivre même si vous n'êtes pas familier avec le langage.
La motivation pour l'EM
Les points rouges et bleus ci-dessous sont tirés de deux distributions normales différentes, chacune avec une moyenne et un écart type particuliers:
Pour calculer des approximations raisonnables des paramètres "vrais" de moyenne et d'écart type pour la distribution rouge, nous pourrions très facilement examiner les points rouges et enregistrer la position de chacun d'eux, puis utiliser les formules familières (et de la même manière pour le groupe bleu). .
Considérons maintenant le cas où nous savons qu’il existe deux groupes de points, mais nous ne pouvons pas voir quel point appartient à quel groupe. En d'autres termes, les couleurs sont cachées:
Il n’est pas du tout évident de diviser les points en deux groupes. Nous ne sommes plus en mesure de regarder les positions et de calculer des estimations pour les paramètres de la distribution rouge ou de la distribution bleue.
C’est là que l’EM peut être utilisé pour résoudre le problème.
Utiliser EM pour estimer les paramètres
Voici le code utilisé pour générer les points indiqués ci-dessus. Vous pouvez voir les moyennes et les écarts-types réels des distributions normales à partir desquelles les points ont été tirés. Les variables red
et blue
tiennent les positions de chaque point dans les groupes rouge et bleu respectivement:
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)))
Si nous pouvions voir la couleur de chaque point, nous essaierions de récupérer les moyennes et les écarts-types à l'aide des fonctions de la bibliothèque:
>>> np.mean(red)
2.802
>>> np.std(red)
0.871
>>> np.mean(blue)
6.932
>>> np.std(blue)
2.195
Mais puisque les couleurs nous sont cachées, nous allons commencer le processus EM ...
Premièrement, nous devinons simplement les valeurs des paramètres de chaque groupe ( étape 1 ). Ces suppositions ne doivent pas forcément être bonnes:
# 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
Assez mauvaises conjectures - les moyens semblent être loin du "milieu" d'un groupe de points.
Pour continuer avec EM et améliorer ces suppositions, nous calculons la probabilité que chaque point de données (quelle que soit sa couleur secrète) apparaisse sous ces suppositions pour la moyenne et l'écart type ( étape 2 ).
La variable both_colours
contient chaque point de données. La fonction stats.norm
calcule la probabilité du point sous une distribution normale avec les paramètres donnés:
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)
Cela nous indique, par exemple, qu’avec nos suppositions actuelles, le point de données situé à 1,761 est beaucoup plus susceptible d’être rouge (0,189) que bleu (0,00003).
Nous pouvons transformer ces deux valeurs de vraisemblance en pondérations ( étape 3 ) afin qu'elles totalisent 1 comme suit:
likelihood_total = likelihood_of_red + likelihood_of_blue
red_weight = likelihood_of_red / likelihood_total
blue_weight = likelihood_of_blue / likelihood_total
Avec nos estimations actuelles et nos poids nouvellement calculés, nous pouvons maintenant calculer de nouvelles estimations, probablement meilleures, pour les paramètres ( étape 4 ). Nous avons besoin d’une fonction pour la moyenne et d’une fonction pour l’écart type:
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)
Celles-ci ressemblent beaucoup aux fonctions habituelles à la moyenne et à l'écart type des données. La différence est l'utilisation d'unweight
paramètre qui attribue une pondération à chaque point de données.
Cette pondération est la clé de la SE. Plus le poids d'une couleur sur un point de données est important, plus le point de données influence les estimations suivantes pour les paramètres de cette couleur. En fin de compte, cela a pour effet de tirer chaque paramètre dans la bonne direction.
Les nouvelles suppositions sont calculées avec ces fonctions:
# 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)
Le processus EM est ensuite répété avec ces nouvelles suppositions à partir de l'étape 2. Nous pouvons répéter les étapes pour un nombre donné d'itérations (par exemple 20) ou jusqu'à ce que les paramètres convergent.
Après cinq itérations, nous voyons que nos mauvaises hypothèses initiales commencent à s’améliorer:
Après 20 itérations, le processus EM a plus ou moins convergé:
À des fins de comparaison, voici les résultats du processus EM comparés aux valeurs calculées pour lesquelles les informations de couleur ne sont pas masquées:
| 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
Remarque: cette réponse a été adaptée de ma réponse sur Stack Overflow ici .