Nota: il codice dietro questa risposta può essere trovato qui .
Supponiamo di avere alcuni dati campionati da due diversi gruppi, rosso e blu:
Qui possiamo vedere quale punto dati appartiene al gruppo rosso o blu. Questo rende facile trovare i parametri che caratterizzano ogni gruppo. Ad esempio, la media del gruppo rosso è di circa 3, la media del gruppo blu è di circa 7 (e potremmo trovare la media esatta se volessimo).
Questa è, in generale, nota come stima di massima verosimiglianza . Dati alcuni dati, calcoliamo il valore di un parametro (o parametri) che meglio spiega quei dati.
Ora immagina di non poter vedere quale valore è stato campionato da quale gruppo. Tutto sembra viola per noi:
Qui abbiamo la consapevolezza che ci sono due gruppi di valori, ma non sappiamo a quale gruppo appartiene un valore particolare.
Possiamo ancora stimare le medie per il gruppo rosso e il gruppo blu che meglio si adattano a questi dati?
Sì, spesso possiamo! La massimizzazione delle aspettative ci offre un modo per farlo. L'idea molto generale dietro l'algoritmo è questa:
- Inizia con una stima iniziale di ciò che potrebbe essere ogni parametro.
- Calcola la probabilità che ogni parametro produca il punto dati.
- Calcola i pesi per ogni punto dati che indica se è più rosso o più blu in base alla probabilità che sia prodotto da un parametro. Combina i pesi con i dati ( aspettativa ).
- Calcolare una stima migliore per i parametri utilizzando i dati aggiustati in base al peso ( massimizzazione ).
- Ripetere i passaggi da 2 a 4 finché la stima del parametro non converge (il processo interrompe la produzione di una stima diversa).
Questi passaggi richiedono ulteriori spiegazioni, quindi esaminerò il problema sopra descritto.
Esempio: stima della media e della deviazione standard
Userò Python in questo esempio, ma il codice dovrebbe essere abbastanza facile da capire se non hai familiarità con questo linguaggio.
Supponiamo di avere due gruppi, rosso e blu, con i valori distribuiti come nell'immagine sopra. Nello specifico, ogni gruppo contiene un valore tratto da una distribuzione normale con i seguenti parametri:
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...
Ecco di nuovo un'immagine di questi gruppi rossi e blu (per evitare di dover scorrere verso l'alto):
Quando possiamo vedere il colore di ogni punto (cioè a quale gruppo appartiene), è molto facile stimare la media e la deviazione standard per ogni gruppo. Passiamo semplicemente i valori rosso e blu alle funzioni incorporate in NumPy. Per esempio:
>>> np.mean(red)
2.802
>>> np.std(red)
0.871
>>> np.mean(blue)
6.932
>>> np.std(blue)
2.195
Ma cosa succede se non possiamo vedere i colori dei punti? Cioè, invece di rosso o blu, ogni punto è stato colorato di viola.
Per provare a recuperare i parametri della media e della deviazione standard per i gruppi rosso e blu, possiamo utilizzare la massimizzazione delle aspettative.
Il nostro primo passaggio ( passaggio 1 sopra) consiste nell'indovinare i valori dei parametri per la media e la deviazione standard di ciascun gruppo. Non dobbiamo indovinare in modo intelligente; possiamo scegliere qualsiasi numero che ci piace:
# 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
Queste stime dei parametri producono curve a campana simili a queste:
Queste sono stime sbagliate. Entrambi i mezzi (le linee tratteggiate verticali) sembrano lontani da qualsiasi tipo di "mezzo" per gruppi di punti sensibili, per esempio. Vogliamo migliorare queste stime.
Il passaggio successivo ( passaggio 2 ) consiste nel calcolare la probabilità che ogni punto dati appaia sotto le ipotesi dei parametri correnti:
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)
Qui, abbiamo semplicemente inserito ogni punto di dati nella funzione di densità di probabilità per una distribuzione normale utilizzando le nostre ipotesi attuali alla media e alla deviazione standard per il rosso e il blu. Questo ci dice, ad esempio, che con le nostre attuali ipotesi il punto dati a 1,761 è molto più probabile che sia rosso (0,189) che blu (0,00003).
Per ogni punto dati, possiamo trasformare questi due valori di probabilità in pesi ( passaggio 3 ) in modo che si sommino a 1 come segue:
likelihood_total = likelihood_of_red + likelihood_of_blue
red_weight = likelihood_of_red / likelihood_total
blue_weight = likelihood_of_blue / likelihood_total
Con le nostre stime attuali e i pesi appena calcolati, possiamo ora calcolare nuove stime per la media e la deviazione standard dei gruppi rosso e blu ( passaggio 4 ).
Calcoliamo due volte la media e la deviazione standard utilizzando tutti i punti dati, ma con le diverse ponderazioni: una volta per i pesi rossi e una volta per i pesi blu.
Il punto chiave dell'intuizione è che maggiore è il peso di un colore su un punto dati, più il punto dati influenza le stime successive per i parametri di quel colore. Questo ha l'effetto di "tirare" i parametri nella giusta direzione.
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)
Abbiamo nuove stime per i parametri. Per migliorarli di nuovo, possiamo tornare al passaggio 2 e ripetere il processo. Lo facciamo finché le stime non convergono o dopo che è stato eseguito un certo numero di iterazioni ( passaggio 5 ).
Per i nostri dati, le prime cinque iterazioni di questo processo hanno questo aspetto (le iterazioni recenti hanno un aspetto più forte):
Vediamo che le medie stanno già convergendo su alcuni valori, e anche le forme delle curve (governate dalla deviazione standard) stanno diventando più stabili.
Se continuiamo per 20 iterazioni, finiamo con quanto segue:
Il processo EM è convergente ai seguenti valori, che risultano molto vicini ai valori effettivi (dove possiamo vedere i colori - nessuna variabile nascosta):
| 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
Nel codice sopra potresti aver notato che la nuova stima per la deviazione standard è stata calcolata utilizzando la stima dell'iterazione precedente per la media. In definitiva, non importa se calcoliamo prima un nuovo valore per la media poiché stiamo solo trovando la varianza (ponderata) dei valori attorno a un punto centrale. Vedremo ancora convergere le stime per i parametri.