Uwaga: kod odpowiadający za tę odpowiedź można znaleźć tutaj .
Załóżmy, że mamy pewne dane pobrane z dwóch różnych grup, czerwonej i niebieskiej:
Tutaj możemy zobaczyć, który punkt danych należy do grupy czerwonej lub niebieskiej. Ułatwia to znalezienie parametrów charakteryzujących każdą grupę. Na przykład, średnia grupy czerwonej wynosi około 3, średnia grupy niebieskiej wynosi około 7 (i moglibyśmy znaleźć dokładną średnią, gdybyśmy chcieli).
Jest to ogólnie znane jako oszacowanie maksymalnego prawdopodobieństwa . Biorąc pod uwagę pewne dane, obliczamy wartość parametru (lub parametrów), który najlepiej wyjaśnia te dane.
Teraz wyobraź sobie, że nie możemy zobaczyć, która wartość była próbkowana z której grupy. Dla nas wszystko wygląda na fioletowe:
Tutaj wiemy, że istnieją dwie grupy wartości, ale nie wiemy, do której grupy należy dana wartość.
Czy nadal możemy oszacować średnie dla grupy czerwonej i niebieskiej, które najlepiej pasują do tych danych?
Tak, często możemy! Maksymalizacja oczekiwań daje nam na to sposób. Bardzo ogólna idea algorytmu jest taka:
- Zacznij od wstępnego oszacowania, jaki może być każdy parametr.
- Oblicz prawdopodobieństwo że każdy parametr tworzy punkt danych.
- Oblicz wagi dla każdego punktu danych, wskazując, czy jest bardziej czerwony, czy bardziej niebieski, w oparciu o prawdopodobieństwo, że zostanie wytworzony przez parametr. Połącz wagi z danymi ( oczekiwanie ).
- Oblicz lepsze oszacowanie parametrów przy użyciu danych skorygowanych o wagę ( maksymalizacja ).
- Powtarzaj kroki od 2 do 4, aż oszacowanie parametru osiągnie zbieżność (proces przestanie dawać inną ocenę).
Te kroki wymagają dalszych wyjaśnień, więc omówię problem opisany powyżej.
Przykład: szacowanie średniej i odchylenia standardowego
W tym przykładzie użyję Pythona, ale kod powinien być dość łatwy do zrozumienia, jeśli nie znasz tego języka.
Załóżmy, że mamy dwie grupy, czerwoną i niebieską, z wartościami rozłożonymi jak na powyższym obrazku. W szczególności każda grupa zawiera wartość pobraną z rozkładu normalnego z następującymi parametrami:
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...
Oto ponownie obraz tych czerwonych i niebieskich grup (aby uniknąć konieczności przewijania w górę):
Kiedy widzimy kolor każdego punktu (tj. Do której grupy należy), bardzo łatwo jest oszacować średnią i odchylenie standardowe dla każdej grupy. Po prostu przekazujemy wartości czerwony i niebieski do funkcji wbudowanych w NumPy. Na przykład:
>>> np.mean(red)
2.802
>>> np.std(red)
0.871
>>> np.mean(blue)
6.932
>>> np.std(blue)
2.195
Ale co, jeśli nie widzimy kolorów punktów? Oznacza to, że zamiast czerwonego lub niebieskiego każdy punkt został pokolorowany na fioletowo.
Aby spróbować odzyskać średnią i parametry odchylenia standardowego dla grup czerwonych i niebieskich, możemy użyć maksymalizacji oczekiwań.
Naszym pierwszym krokiem ( krok 1 powyżej) jest odgadnięcie wartości parametrów dla średniej i odchylenia standardowego każdej grupy. Nie musimy inteligentnie zgadywać; możemy wybrać dowolne liczby:
# 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
Te oszacowania parametrów dają krzywe dzwonowe, które wyglądają następująco:
To są złe szacunki. Oba środki (pionowe przerywane linie) wyglądają na daleko od wszelkiego rodzaju „środka”, na przykład w przypadku rozsądnych grup punktów. Chcemy poprawić te szacunki.
Następnym krokiem ( krok 2 ) jest obliczenie prawdopodobieństwa pojawienia się każdego punktu danych pod bieżącymi domysłami parametrów:
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)
Tutaj po prostu umieściliśmy każdy punkt danych w funkcji gęstości prawdopodobieństwa dla rozkładu normalnego, używając naszych aktualnych przypuszczeń na temat średniej i odchylenia standardowego dla czerwieni i niebieskiego. To mówi nam na przykład, że przy naszych aktualnych domysłach punkt danych przy 1,761 jest znacznie bardziej prawdopodobny, że będzie czerwony (0,189) niż niebieski (0,00003).
Dla każdego punktu danych możemy zamienić te dwie wartości prawdopodobieństwa na wagi ( krok 3 ), aby sumowały się do 1 w następujący sposób:
likelihood_total = likelihood_of_red + likelihood_of_blue
red_weight = likelihood_of_red / likelihood_total
blue_weight = likelihood_of_blue / likelihood_total
Dzięki naszym bieżącym szacunkom i nowo obliczonym wagom możemy teraz obliczyć nowe oszacowania średniej i odchylenia standardowego grup czerwonych i niebieskich ( krok 4 ).
Dwukrotnie obliczamy średnią i odchylenie standardowe przy użyciu wszystkich punktów danych, ale z różnymi wagami: raz dla wag czerwonych i raz dla wag niebieskich.
Kluczową intuicją jest to, że im większa waga koloru w punkcie danych, tym bardziej punkt danych wpływa na następne oszacowania parametrów tego koloru. Powoduje to „ciągnięcie” parametrów we właściwym kierunku.
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)
Mamy nowe szacunki parametrów. Aby je ponownie ulepszyć, możemy wrócić do kroku 2 i powtórzyć proces. Robimy to do osiągnięcia zbieżności szacunków lub po wykonaniu pewnej liczby iteracji ( krok 5 ).
W przypadku naszych danych pierwsze pięć iteracji tego procesu wygląda następująco (ostatnie iteracje mają silniejszy wygląd):
Widzimy, że średnie już zbiegają się na niektórych wartościach, a kształty krzywych (regulowane odchyleniem standardowym) również stają się bardziej stabilne.
Jeśli będziemy kontynuować przez 20 iteracji, otrzymamy co następuje:
Proces EM zbiegał się do następujących wartości, które okazują się bardzo zbliżone do rzeczywistych wartości (gdzie widzimy kolory - brak ukrytych zmiennych):
| 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
W powyższym kodzie mogłeś zauważyć, że nowe oszacowanie odchylenia standardowego zostało obliczone przy użyciu oszacowania średniej z poprzedniej iteracji. Ostatecznie nie ma znaczenia, czy najpierw obliczymy nową wartość dla średniej, ponieważ właśnie znajdujemy (ważoną) wariancję wartości wokół jakiegoś centralnego punktu. Nadal będziemy widzieć zbieżność szacunków parametrów.