Esempio numerico per comprendere l'aspettativa-massimizzazione


117

Sto cercando di capire bene l'algoritmo EM, per poterlo implementare e usarlo. Ho trascorso un'intera giornata a leggere la teoria e un documento in cui viene utilizzato EM per tracciare un aereo utilizzando le informazioni sulla posizione provenienti da un radar. Onestamente, non credo di aver compreso appieno l'idea di base. Qualcuno può indicarmi un esempio numerico che mostra alcune iterazioni (3-4) dell'EM per un problema più semplice (come stimare i parametri di una distribuzione gaussiana o una sequenza di una serie sinusoidale o adattarsi a una linea).

Anche se qualcuno può indicarmi un pezzo di codice (con dati sintetici), posso provare a scorrere il codice.


1
k-mean è molto em, ma con varianza costante ed è relativamente semplice.
EngrStudent,

2
@ arjsgh21 puoi per favore pubblicare un documento citato sull'aeromobile? Sembra molto interessante. Grazie
Wakan Tanka,

1
Esiste un tutorial online che afferma di fornire una comprensione matematica molto chiara dell'algoritmo Em "EM Demystified: An Expectation-Maximization Tutorial" Tuttavia, l'esempio è così grave che confina l'incomprensibile.
Esperto di Shamisen,

Risposte:


98

Questa è una ricetta per imparare EM con un esempio pratico e (a mio avviso) molto intuitivo di "Coin-Toss":

  1. Leggi questo breve tutorial EM di Do e Batzoglou. Questo è lo schema in cui viene spiegato l'esempio del lancio della moneta:

    inserisci qui la descrizione dell'immagine

  2. Potresti avere dei punti interrogativi in ​​testa, in particolare per quanto riguarda la provenienza delle probabilità nel passaggio Aspettativa. Dai un'occhiata alle spiegazioni su questa pagina di scambio di stack matematici .

  3. Guarda / esegui questo codice che ho scritto in Python che simula la soluzione al problema del lancio della moneta nell'articolo tutorial EM dell'articolo 1:

    import numpy as np
    import math
    import matplotlib.pyplot as plt
    
    ## E-M Coin Toss Example as given in the EM tutorial paper by Do and Batzoglou* ##
    
    def get_binomial_log_likelihood(obs,probs):
        """ Return the (log)likelihood of obs, given the probs"""
        # Binomial Distribution Log PDF
        # ln (pdf)      = Binomial Coeff * product of probabilities
        # ln[f(x|n, p)] =   comb(N,k)    * num_heads*ln(pH) + (N-num_heads) * ln(1-pH)
    
        N = sum(obs);#number of trials  
        k = obs[0] # number of heads
        binomial_coeff = math.factorial(N) / (math.factorial(N-k) * math.factorial(k))
        prod_probs = obs[0]*math.log(probs[0]) + obs[1]*math.log(1-probs[0])
        log_lik = binomial_coeff + prod_probs
    
        return log_lik
    
    # 1st:  Coin B, {HTTTHHTHTH}, 5H,5T
    # 2nd:  Coin A, {HHHHTHHHHH}, 9H,1T
    # 3rd:  Coin A, {HTHHHHHTHH}, 8H,2T
    # 4th:  Coin B, {HTHTTTHHTT}, 4H,6T
    # 5th:  Coin A, {THHHTHHHTH}, 7H,3T
    # so, from MLE: pA(heads) = 0.80 and pB(heads)=0.45
    
    # represent the experiments
    head_counts = np.array([5,9,8,4,7])
    tail_counts = 10-head_counts
    experiments = zip(head_counts,tail_counts)
    
    # initialise the pA(heads) and pB(heads)
    pA_heads = np.zeros(100); pA_heads[0] = 0.60
    pB_heads = np.zeros(100); pB_heads[0] = 0.50
    
    # E-M begins!
    delta = 0.001  
    j = 0 # iteration counter
    improvement = float('inf')
    while (improvement>delta):
        expectation_A = np.zeros((len(experiments),2), dtype=float) 
        expectation_B = np.zeros((len(experiments),2), dtype=float)
        for i in range(0,len(experiments)):
            e = experiments[i] # i'th experiment
              # loglikelihood of e given coin A:
            ll_A = get_binomial_log_likelihood(e,np.array([pA_heads[j],1-pA_heads[j]])) 
              # loglikelihood of e given coin B
            ll_B = get_binomial_log_likelihood(e,np.array([pB_heads[j],1-pB_heads[j]])) 
    
              # corresponding weight of A proportional to likelihood of A 
            weightA = math.exp(ll_A) / ( math.exp(ll_A) + math.exp(ll_B) ) 
    
              # corresponding weight of B proportional to likelihood of B
            weightB = math.exp(ll_B) / ( math.exp(ll_A) + math.exp(ll_B) ) 
    
            expectation_A[i] = np.dot(weightA, e) 
            expectation_B[i] = np.dot(weightB, e)
    
        pA_heads[j+1] = sum(expectation_A)[0] / sum(sum(expectation_A)); 
        pB_heads[j+1] = sum(expectation_B)[0] / sum(sum(expectation_B)); 
    
        improvement = ( max( abs(np.array([pA_heads[j+1],pB_heads[j+1]]) - 
                        np.array([pA_heads[j],pB_heads[j]]) )) )
        j = j+1
    
    plt.figure();
    plt.plot(range(0,j),pA_heads[0:j], 'r--')
    plt.plot(range(0,j),pB_heads[0:j])
    plt.show()

2
@Zhubarb: puoi per favore spiegare la condizione di terminazione del loop (cioè per determinare quando converge l'algoritmo)? Cosa calcola la variabile "miglioramento"?
stackoverflowuser2010,

@ stackoverflowuser2010, il miglioramento esamina due delta: 1) il cambiamento tra pA_heads[j+1]e pA_heads[j]e 2) il cambiamento tra pB_heads[j+1]e pB_heads[j]. E prende il massimo dei due cambiamenti. Ad esempio se Delta_A=0.001e Delta_B=0.02, il miglioramento dal passaggio ja j+1sarà 0.02.
Zhubarb,

1
@Zhubarb: è un approccio standard per calcolare la convergenza in EM, o è qualcosa che ti è venuto in mente? Se si tratta di un approccio standard, puoi per favore citare un riferimento?
stackoverflowuser2010,

Ecco un riferimento sulla convergenza di EM. Ho scritto il codice qualche tempo fa, quindi non ricordo troppo bene. Credo che ciò che vedi nel codice sia il mio criterio di convergenza per questo caso particolare. L'idea è di interrompere le iterazioni quando il massimo dei miglioramenti per A e B è inferiore a delta.
Zhubarb,

1
Superbo, non c'è niente come un buon codice per chiarire quali paragrafi del testo non possono
jon_simon

63

Sembra che la tua domanda abbia due parti: l'idea di base e un esempio concreto. Inizierò con l'idea di base, quindi collegherò ad un esempio in fondo.


ABBA .

Il caso più comune che le persone affrontano è probabilmente la distribuzione mista. Per il nostro esempio, diamo un'occhiata a un semplice modello di miscela gaussiana:

Hai due diverse distribuzioni gaussiane univariate con mezzi e varianza unitaria differenti.

Hai un sacco di punti dati, ma non sei sicuro di quali punti provengano da quale distribuzione e non sei nemmeno sicuro dei mezzi delle due distribuzioni.

E ora sei bloccato:

  • Se conoscessi i veri mezzi, potresti capire quali punti dati provengono da quali gaussiani. Ad esempio, se un punto dati aveva un valore molto elevato, probabilmente veniva dalla distribuzione con la media più alta. Ma non sai quali sono i mezzi, quindi non funzionerà.

  • Se sapessi da quale distribuzione proviene ogni punto, allora potresti stimare i mezzi delle due distribuzioni usando i mezzi di campionamento dei punti rilevanti. Ma in realtà non sai quali punti assegnare a quale distribuzione, quindi neanche questo funzionerà.

Quindi nessuno dei due approcci sembra funzionare: dovresti conoscere la risposta prima di poter trovare la risposta e sei bloccato.

Ciò che EM ti consente di fare è alternare questi due passaggi tracciabili invece di affrontare l'intero processo in una sola volta.

Dovrai iniziare con un'ipotesi sui due mezzi (anche se la tua ipotesi non deve necessariamente essere molto accurata, devi iniziare da qualche parte).

Se la tua ipotesi sui mezzi fosse accurata, allora avresti abbastanza informazioni per eseguire il passaggio nel mio primo punto elenco sopra e potresti (probabilisticamente) assegnare ciascun punto dati a uno dei due gaussiani. Anche se sappiamo che la nostra ipotesi è sbagliata, proviamo comunque. E quindi, date le distribuzioni assegnate a ciascun punto, è possibile ottenere nuove stime per i mezzi utilizzando il secondo punto elenco. Si scopre che, ogni volta che esegui il ciclo attraverso questi due passaggi, stai migliorando un limite inferiore della probabilità del modello.

È già abbastanza interessante: anche se i due suggerimenti nei punti elenco sopra non sembrano funzionare individualmente, puoi comunque usarli insieme per migliorare il modello. Il vero magia di EM è che, dopo un numero sufficiente di iterazioni, il limite inferiore sarà così alto che non ci sarà spazio tra esso e il massimo locale. Di conseguenza, e hai localmente ottimizzato la probabilità.

Quindi non hai appena migliorato il modello, hai trovato il miglior modello possibile che puoi trovare con aggiornamenti incrementali.


Questa pagina di Wikipedia mostra un esempio leggermente più complicato (gaussiani bidimensionali e covarianza sconosciuta), ma l'idea di base è la stessa. Include anche ben commentatoR codice per l'implementazione dell'esempio.

Nel codice, il passaggio "Aspettativa" (E-step) corrisponde al mio primo punto elenco: capire quale gaussiano si assume la responsabilità di ciascun punto dati, dati gli attuali parametri per ogni gaussiano. Il passaggio "Maximization" (M-step) aggiorna i mezzi e le covarianze, dati questi incarichi, come nel mio secondo punto elenco.

Come puoi vedere nell'animazione, questi aggiornamenti permettono rapidamente che l'algoritmo passi da una serie di stime terribili a una serie di stime molto buone: sembrano davvero esserci due nuvole di punti centrate sulle due distribuzioni gaussiane che EM trova.


13

Ecco un esempio di Expectation Maximization (EM) utilizzato per stimare la media e la deviazione standard. Il codice è in Python, ma dovrebbe essere facile da seguire anche se non si ha familiarità con la lingua.

La motivazione per EM

I punti rosso e blu mostrati di seguito sono disegnati da due diverse distribuzioni normali, ognuna con una media e una deviazione standard:

inserisci qui la descrizione dell'immagine

Per calcolare approssimazioni ragionevoli della media "vera" e dei parametri di deviazione standard per la distribuzione rossa, potremmo facilmente guardare i punti rossi e registrare la posizione di ciascuno, quindi usare le formule familiari (e similmente per il gruppo blu) .

Consideriamo ora il caso in cui sappiamo che ci sono due gruppi di punti, ma non possiamo vedere quale punto appartiene a quale gruppo. In altre parole, i colori sono nascosti:

inserisci qui la descrizione dell'immagine

Non è affatto ovvio come dividere i punti in due gruppi. Ora non siamo in grado di guardare solo le posizioni e calcolare le stime per i parametri della distribuzione rossa o blu.

È qui che EM può essere utilizzato per risolvere il problema.

Utilizzo di EM per stimare i parametri

Ecco il codice utilizzato per generare i punti mostrati sopra. Puoi vedere i mezzi effettivi e le deviazioni standard delle distribuzioni normali da cui sono stati estratti i punti. Le variabili rede bluemantengono le posizioni di ciascun punto rispettivamente nei gruppi rosso e blu:

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 potessimo vedere il colore di ogni punto, proveremmo a recuperare mezzi e deviazioni standard usando le funzioni di libreria:

>>> np.mean(red)
2.802
>>> np.std(red)
0.871
>>> np.mean(blue)
6.932
>>> np.std(blue)
2.195

Ma poiché i colori ci sono nascosti, inizieremo il processo EM ...

Innanzitutto, indoviniamo solo i valori dei parametri di ciascun gruppo ( passaggio 1 ). Queste ipotesi non devono essere buone:

# 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

inserisci qui la descrizione dell'immagine

Immaginazioni piuttosto brutte: i mezzi sembrano essere molto lontani da qualsiasi "mezzo" di un gruppo di punti.

Per continuare con EM e migliorare queste ipotesi, calcoliamo la probabilità di ogni punto di dati (indipendentemente dal suo colore segreto) che appare sotto queste ipotesi per la deviazione media e standard ( passaggio 2 ).

La variabile both_colourscontiene ogni punto dati. La funzione stats.normcalcola la probabilità del punto in una distribuzione normale con i parametri indicati:

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)

Questo ci dice, ad esempio, che con le nostre ipotesi attuali il punto dati a 1.761 ha molte più probabilità di essere rosso (0,189) rispetto al blu (0,00003).

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 nostri pesi appena calcolati, ora possiamo calcolare nuove stime, probabilmente migliori, per i parametri ( passaggio 4 ). Abbiamo bisogno di una funzione per la media e una funzione per la deviazione standard:

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)

Questi sembrano molto simili alle normali funzioni alla media e alla deviazione standard dei dati. La differenza è l'uso di un weightparametro che assegna un peso a ciascun punto dati.

Questa ponderazione è la chiave per EM. Maggiore è il peso di un colore su un punto dati, più il punto dati influenza le stime successive per i parametri di quel colore. In definitiva, questo ha l'effetto di tirare ogni parametro nella giusta direzione.

Le nuove ipotesi sono calcolate con queste funzioni:

# 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)

Il processo EM viene quindi ripetuto con queste nuove ipotesi dal passaggio 2 in poi. Possiamo ripetere i passaggi per un determinato numero di iterazioni (diciamo 20), o fino a quando i parametri non convergono.

Dopo cinque iterazioni, vediamo che le nostre ipotesi iniziali iniziano a migliorare:

inserisci qui la descrizione dell'immagine

Dopo 20 iterazioni, il processo EM è più o meno convergente:

inserisci qui la descrizione dell'immagine

Per fare un confronto, ecco i risultati del processo EM rispetto ai valori calcolati in cui le informazioni sul colore non sono nascoste:

          | 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: questa risposta è stata adattata dalla mia risposta su Stack Overflow qui .


10

Seguendo la risposta di Zhubarb, ho implementato l'esempio EM di "lancio di monete" di Do e Batzoglou in GNU R. Nota che utilizzo la mlefunzione del stats4pacchetto - questo mi ha aiutato a capire più chiaramente come sono collegati EM e MLE.

require("stats4");

## sample data from Do and Batzoglou
ds<-data.frame(heads=c(5,9,8,4,7),n=c(10,10,10,10,10),
    coin=c("B","A","A","B","A"),weight_A=1:5*0)

## "baby likelihood" for a single observation
llf <- function(heads, n, theta) {
  comb <- function(n, x) { #nCr function
    return(factorial(n) / (factorial(x) * factorial(n-x)))
  }
  if (theta<0 || theta >1) { # probabilities should be in [0,1]
    return(-Inf);
  }
  z<-comb(n,heads)* theta^heads * (1-theta)^(n-heads);
  return (log(z))
}

## the "E-M" likelihood function
em <- function(theta_A,theta_B) {
  # expectation step: given current parameters, what is the likelihood
  # an observation is the result of tossing coin A (vs coin B)?
  ds$weight_A <<- by(ds, 1:nrow(ds), function(row) {
    llf_A <- llf(row$heads,row$n, theta_A);
    llf_B <- llf(row$heads,row$n, theta_B);

    return(exp(llf_A)/(exp(llf_A)+exp(llf_B)));
  })

  # maximisation step: given params and weights, calculate likelihood of the sample
  return(- sum(by(ds, 1:nrow(ds), function(row) {
    llf_A <- llf(row$heads,row$n, theta_A);
    llf_B <- llf(row$heads,row$n, theta_B);

    return(row$weight_A*llf_A + (1-row$weight_A)*llf_B);
  })))
}

est<-mle(em,start = list(theta_A=0.6,theta_B=0.5), nobs=NROW(ds))

1
@ user3096626 Puoi spiegare perché nella fase di massimizzazione moltiplichi la probabilità di una moneta A (riga $ peso_A) per una probabilità di registro (llf_A)? C'è una regola speciale o una ragione per cui lo facciamo? Voglio dire, si moltiplicherebbero solo le probabilità o le perdite, ma non mescolandoli insieme. Ho anche aperto un nuovo argomento
Alina,


5

La risposta data da Zhubarb è ottima, ma sfortunatamente è in Python. Di seguito è un'implementazione Java dell'algoritmo EM eseguita sullo stesso problema (presentata nell'articolo di Do e Batzoglou, 2008). Ho aggiunto alcuni printf all'output standard per vedere come convergono i parametri.

thetaA = 0.71301, thetaB = 0.58134
thetaA = 0.74529, thetaB = 0.56926
thetaA = 0.76810, thetaB = 0.54954
thetaA = 0.78316, thetaB = 0.53462
thetaA = 0.79106, thetaB = 0.52628
thetaA = 0.79453, thetaB = 0.52239
thetaA = 0.79593, thetaB = 0.52073
thetaA = 0.79647, thetaB = 0.52005
thetaA = 0.79667, thetaB = 0.51977
thetaA = 0.79674, thetaB = 0.51966
thetaA = 0.79677, thetaB = 0.51961
thetaA = 0.79678, thetaB = 0.51960
thetaA = 0.79679, thetaB = 0.51959
Final result:
thetaA = 0.79678, thetaB = 0.51960

Il codice Java segue di seguito:

import java.util.*;

/*****************************************************************************
This class encapsulates the parameters of the problem. For this problem posed
in the article by (Do and Batzoglou, 2008), the parameters are thetaA and
thetaB, the probability of a coin coming up heads for the two coins A and B.
*****************************************************************************/
class Parameters
{
    double _thetaA = 0.0; // Probability of heads for coin A.
    double _thetaB = 0.0; // Probability of heads for coin B.

    double _delta = 0.00001;

    public Parameters(double thetaA, double thetaB)
    {
        _thetaA = thetaA;
        _thetaB = thetaB;
    }

    /*************************************************************************
    Returns true if this parameter is close enough to another parameter
    (typically the estimated parameter coming from the maximization step).
    *************************************************************************/
    public boolean converged(Parameters other)
    {
        if (Math.abs(_thetaA - other._thetaA) < _delta &&
            Math.abs(_thetaB - other._thetaB) < _delta)
        {
            return true;
        }

        return false;
    }

    public double getThetaA()
    {
        return _thetaA;
    }

    public double getThetaB()
    {
        return _thetaB;
    }

    public String toString()
    {
        return String.format("thetaA = %.5f, thetaB = %.5f", _thetaA, _thetaB);
    }

}


/*****************************************************************************
This class encapsulates an observation, that is the number of heads
and tails in a trial. The observation can be either (1) one of the
observed observations, or (2) an estimated observation resulting from
the expectation step.
*****************************************************************************/
class Observation
{
    double _numHeads = 0;
    double _numTails = 0;

    public Observation(String s)
    {
        for (int i = 0; i < s.length(); i++)
        {
            char c = s.charAt(i);

            if (c == 'H')
            {
                _numHeads++;
            }
            else if (c == 'T')
            {
                _numTails++;
            }
            else
            {
                throw new RuntimeException("Unknown character: " + c);
            }
        }
    }

    public Observation(double numHeads, double numTails)
    {
        _numHeads = numHeads;
        _numTails = numTails;
    }

    public double getNumHeads()
    {
        return _numHeads;
    }

    public double getNumTails()
    {
        return _numTails;
    }

    public String toString()
    {
        return String.format("heads: %.1f, tails: %.1f", _numHeads, _numTails);
    }

}

/*****************************************************************************
This class runs expectation-maximization for the problem posed by the article
from (Do and Batzoglou, 2008).
*****************************************************************************/
public class EM
{
    // Current estimated parameters.
    private Parameters _parameters;

    // Observations from the trials. These observations are set once.
    private final List<Observation> _observations;

    // Estimated observations per coin. These observations are the output
    // of the expectation step.
    private List<Observation> _expectedObservationsForCoinA;
    private List<Observation> _expectedObservationsForCoinB;

    private static java.io.PrintStream o = System.out;

    /*************************************************************************
    Principal constructor.
    @param observations The observations from the trial.
    @param parameters The initial guessed parameters.
    *************************************************************************/
    public EM(List<Observation> observations, Parameters parameters)
    {
        _observations = observations;
        _parameters = parameters;
    }

    /*************************************************************************
    Run EM until parameters converge.
    *************************************************************************/
    public Parameters run()
    {

        while (true)
        {
            expectation();

            Parameters estimatedParameters = maximization();

            o.printf("%s\n", estimatedParameters);

            if (_parameters.converged(estimatedParameters)) {
                break;
            }

            _parameters = estimatedParameters;
        }

        return _parameters;

    }

    /*************************************************************************
    Given the observations and current estimated parameters, compute new
    estimated completions (distribution over the classes) and observations.
    *************************************************************************/
    private void expectation()
    {

        _expectedObservationsForCoinA = new ArrayList<Observation>();
        _expectedObservationsForCoinB = new ArrayList<Observation>();

        for (Observation observation : _observations)
        {
            int numHeads = (int)observation.getNumHeads();
            int numTails = (int)observation.getNumTails();

            double probabilityOfObservationForCoinA=
                binomialProbability(10, numHeads, _parameters.getThetaA());

            double probabilityOfObservationForCoinB=
                binomialProbability(10, numHeads, _parameters.getThetaB());

            double normalizer = probabilityOfObservationForCoinA +
                                probabilityOfObservationForCoinB;

            // Compute the completions for coin A and B (i.e. the probability
            // distribution of the two classes, summed to 1.0).

            double completionCoinA = probabilityOfObservationForCoinA /
                                     normalizer;
            double completionCoinB = probabilityOfObservationForCoinB /
                                     normalizer;

            // Compute new expected observations for the two coins.

            Observation expectedObservationForCoinA =
                new Observation(numHeads * completionCoinA,
                                numTails * completionCoinA);

            Observation expectedObservationForCoinB =
                new Observation(numHeads * completionCoinB,
                                numTails * completionCoinB);

            _expectedObservationsForCoinA.add(expectedObservationForCoinA);
            _expectedObservationsForCoinB.add(expectedObservationForCoinB);
        }
    }

    /*************************************************************************
    Given new estimated observations, compute new estimated parameters.
    *************************************************************************/
    private Parameters maximization()
    {

        double sumCoinAHeads = 0.0;
        double sumCoinATails = 0.0;
        double sumCoinBHeads = 0.0;
        double sumCoinBTails = 0.0;

        for (Observation observation : _expectedObservationsForCoinA)
        {
            sumCoinAHeads += observation.getNumHeads();
            sumCoinATails += observation.getNumTails();
        }

        for (Observation observation : _expectedObservationsForCoinB)
        {
            sumCoinBHeads += observation.getNumHeads();
            sumCoinBTails += observation.getNumTails();
        }

        return new Parameters(sumCoinAHeads / (sumCoinAHeads + sumCoinATails),
                              sumCoinBHeads / (sumCoinBHeads + sumCoinBTails));

        //o.printf("parameters: %s\n", _parameters);

    }

    /*************************************************************************
    Since the coin-toss experiment posed in this article is a Bernoulli trial,
    use a binomial probability Pr(X=k; n,p) = (n choose k) * p^k * (1-p)^(n-k).
    *************************************************************************/
    private static double binomialProbability(int n, int k, double p)
    {
        double q = 1.0 - p;
        return nChooseK(n, k) * Math.pow(p, k) * Math.pow(q, n-k);
    }

    private static long nChooseK(int n, int k)
    {
        long numerator = 1;

        for (int i = 0; i < k; i++)
        {
            numerator = numerator * n;
            n--;
        }

        long denominator = factorial(k);

        return (long)(numerator / denominator);
    }

    private static long factorial(int n)
    {
        long result = 1;
        for (; n >0; n--)
        {
            result = result * n;
        }

        return result;
    }

    /*************************************************************************
    Entry point into the program.
    *************************************************************************/
    public static void main(String argv[])
    {
        // Create the observations and initial parameter guess
        // from the (Do and Batzoglou, 2008) article.

        List<Observation> observations = new ArrayList<Observation>();
        observations.add(new Observation("HTTTHHTHTH"));
        observations.add(new Observation("HHHHTHHHHH"));
        observations.add(new Observation("HTHHHHHTHH"));
        observations.add(new Observation("HTHTTTHHTT"));
        observations.add(new Observation("THHHTHHHTH"));

        Parameters initialParameters = new Parameters(0.6, 0.5);

        EM em = new EM(observations, initialParameters);

        Parameters finalParameters = em.run();

        o.printf("Final result:\n%s\n", finalParameters);
    }
}

5
% Implementation of the EM (Expectation-Maximization)algorithm example exposed on:
% Motion Segmentation using EM - a short tutorial, Yair Weiss, %http://www.cs.huji.ac.il/~yweiss/emTutorial.pdf
% Juan Andrade, jandrader@yahoo.com

clear all
clc

%% Setup parameters
m1 = 2;                 % slope line 1
m2 = 6;                 % slope line 2
b1 = 3;                 % vertical crossing line 1
b2 = -2;                % vertical crossing line 2
x = [-1:0.1:5];         % x axis values
sigma1 = 1;             % Standard Deviation of Noise added to line 1
sigma2 = 2;             % Standard Deviation of Noise added to line 2

%% Clean lines
l1 = m1*x+b1;           % line 1
l2 = m2*x+b2;           % line 2

%% Adding noise to lines
p1 = l1 + sigma1*randn(size(l1));
p2 = l2 + sigma2*randn(size(l2));

%% showing ideal and noise values
figure,plot(x,l1,'r'),hold,plot(x,l2,'b'), plot(x,p1,'r.'),plot(x,p2,'b.'),grid

%% initial guess
m11(1) = -1;            % slope line 1
m22(1) = 1;             % slope line 2
b11(1) = 2;             % vertical crossing line 1
b22(1) = 2;             % vertical crossing line 2

%% EM algorithm loop
iterations = 10;        % number of iterations (a stop based on a threshold may used too)

for i=1:iterations

    %% expectation step (equations 2 and 3)
    res1 = m11(i)*x + b11(i) - p1;
    res2 = m22(i)*x + b22(i) - p2;
    % line 1
    w1 = (exp((-res1.^2)./sigma1))./((exp((-res1.^2)./sigma1)) + (exp((-res2.^2)./sigma2)));

    % line 2
    w2 = (exp((-res2.^2)./sigma2))./((exp((-res1.^2)./sigma1)) + (exp((-res2.^2)./sigma2)));

    %% maximization step  (equation 4)
    % line 1
    A(1,1) = sum(w1.*(x.^2));
    A(1,2) = sum(w1.*x);
    A(2,1) = sum(w1.*x);
    A(2,2) = sum(w1);
    bb = [sum(w1.*x.*p1) ; sum(w1.*p1)];
    temp = A\bb;
    m11(i+1) = temp(1);
    b11(i+1) = temp(2);

    % line 2
    A(1,1) = sum(w2.*(x.^2));
    A(1,2) = sum(w2.*x);
    A(2,1) = sum(w2.*x);
    A(2,2) = sum(w2);
    bb = [sum(w2.*x.*p2) ; sum(w2.*p2)];
    temp = A\bb;
    m22(i+1) = temp(1);
    b22(i+1) = temp(2);

    %% plotting evolution of results
    l1temp = m11(i+1)*x+b11(i+1);
    l2temp = m22(i+1)*x+b22(i+1);
    figure,plot(x,l1temp,'r'),hold,plot(x,l2temp,'b'), plot(x,p1,'r.'),plot(x,p2,'b.'),grid
end

4
Sei in grado di aggiungere qualche discussione o spiegazione al codice non elaborato? A molti lettori sarebbe utile menzionare almeno la lingua in cui stai scrivendo.
Glen_b

1
@Glen_b - questo è MatLab. Mi chiedo quanto sia educato annotare più ampiamente il codice di qualcuno nella loro risposta.
EngrStudent,

4

Bene, ti suggerirei di leggere un libro su R di Maria L Rizzo. Uno dei capitoli contiene l'uso dell'algoritmo EM con un esempio numerico. Ricordo di aver esaminato il codice per una migliore comprensione.

Inoltre, prova a visualizzarlo dal punto di vista del raggruppamento all'inizio. Risolvi a mano, un problema di raggruppamento in cui vengono prese 10 osservazioni da due diverse densità normali. Questo dovrebbe aiutare. Prendi l'aiuto di R :)


2

θA=0.6θB=0.5

# gem install distribution
require 'distribution'

# error bound
EPS = 10**-6

# number of coin tosses
N = 10

# observations
X = [5, 9, 8, 4, 7]

# randomly initialized thetas
theta_a, theta_b = 0.6, 0.5

p [theta_a, theta_b]

loop do
  expectation = X.map do |h|
    like_a = Distribution::Binomial.pdf(h, N, theta_a)
    like_b = Distribution::Binomial.pdf(h, N, theta_b)

    norm_a = like_a / (like_a + like_b)
    norm_b = like_b / (like_a + like_b)

    [norm_a, norm_b, h]
  end

  maximization = expectation.each_with_object([0.0, 0.0, 0.0, 0.0]) do |(norm_a, norm_b, h), r|
    r[0] += norm_a * h; r[1] += norm_a * (N - h)
    r[2] += norm_b * h; r[3] += norm_b * (N - h)
  end

  theta_a_hat = maximization[0] / (maximization[0] + maximization[1])
  theta_b_hat = maximization[2] / (maximization[2] + maximization[3])

  error_a = (theta_a_hat - theta_a).abs / theta_a
  error_b = (theta_b_hat - theta_b).abs / theta_b

  theta_a, theta_b = theta_a_hat, theta_b_hat

  p [theta_a, theta_b]

  break if error_a < EPS && error_b < EPS
end
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.