Ejemplo numérico para entender la Expectativa-Maximización

117

Estoy tratando de comprender bien el algoritmo EM, para poder implementarlo y usarlo. Pasé un día completo leyendo la teoría y un documento donde se usa EM para rastrear un avión utilizando la información de posición proveniente de un radar. Honestamente, no creo entender completamente la idea subyacente. ¿Puede alguien señalarme un ejemplo numérico que muestre algunas iteraciones (3-4) del EM para un problema más simple (como estimar los parámetros de una distribución gaussiana o una secuencia de una serie sinusoidal o ajustar una línea).

Incluso si alguien puede señalarme un fragmento de código (con datos sintéticos), puedo intentar pasar por el código.

arjsgh21
fuente
1
k-means es muy em, pero con varianza constante, y es relativamente simple.
EngrStudent
2
@ arjsgh21 ¿puede publicar el documento mencionado sobre el avión? Suena muy interesante. Gracias
Wakan Tanka
1
Hay un tutorial en línea que pretende proporcionar una comprensión matemática muy clara del algoritmo Em "EM desmitificado: un tutorial de maximización de expectativas" Sin embargo, el ejemplo es tan malo que limita con lo incomprensible.
Shamisen Expert

Respuestas:

98

Esta es una receta para aprender EM con un ejemplo práctico y (en mi opinión) muy intuitivo de 'Coin-Toss':

  1. Lea este breve documento tutorial de EM de Do y Batzoglou. Este es el esquema donde se explica el ejemplo de lanzamiento de moneda:

    ingrese la descripción de la imagen aquí

  2. Es posible que tenga signos de interrogación en su cabeza, especialmente con respecto a de dónde provienen las probabilidades en el paso Expectativa. Por favor, eche un vistazo a las explicaciones en esta página de intercambio de pila de matemáticas .

  3. Mire / ejecute este código que escribí en Python que simula la solución al problema del lanzamiento de monedas en el documento tutorial EM del artículo 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()
    
Zhubarb
fuente
2
@ Zhubarb: ¿puede explicar la condición de terminación del bucle (es decir, para determinar cuándo converge el algoritmo)? ¿Qué calcula la variable "mejora"?
stackoverflowuser2010
@ stackoverflowuser2010, la mejora se ve en dos deltas: 1) el cambio entre pA_heads[j+1]y pA_heads[j]y 2) el cambio entre pB_heads[j+1]y pB_heads[j]. Y toma el máximo de los dos cambios. Por ejemplo, si Delta_A=0.001y Delta_B=0.02, la mejora de paso ja paso j+1será 0.02.
Zhubarb
1
@ Zhubarb: ¿Es ese un enfoque estándar para la convergencia informática en EM, o es algo que se te ocurrió? Si se trata de un enfoque estándar, ¿puede citar una referencia?
stackoverflowuser2010
Aquí hay una referencia sobre la convergencia de EM. Escribí el código hace algún tiempo, así que no recuerdo muy bien. Creo que lo que ves en el código es mi criterio de convergencia para este caso en particular. La idea es detener las iteraciones cuando el máximo de mejoras para A y B es menor que delta.
Zhubarb
1
Excelente, no hay nada como un buen código para aclarar lo que los párrafos de texto no pueden
jon_simon
63

Parece que su pregunta tiene dos partes: la idea subyacente y un ejemplo concreto. Comenzaré con la idea subyacente, luego vincularé a un ejemplo en la parte inferior.


EM es útil en situaciones de Catch-22 donde parece que lo que necesita saber antes de poder calcular y lo que necesita saber antes de poder calcular .B B AABBA

El caso más común con el que las personas lidian es probablemente la distribución de mezclas. Para nuestro ejemplo, veamos un modelo simple de mezcla gaussiana:

Tiene dos distribuciones gaussianas univariadas diferentes con medias diferentes y varianza unitaria.

Tiene un montón de puntos de datos, pero no está seguro de qué puntos provienen de qué distribución, y tampoco está seguro de los medios de las dos distribuciones.

Y ahora estás atrapado:

  • Si supiera los medios verdaderos, podría averiguar qué puntos de datos provienen de qué Gaussian. Por ejemplo, si un punto de datos tenía un valor muy alto, probablemente provenía de la distribución con la media más alta. Pero no sabes cuáles son los medios, así que esto no funcionará.

  • Si supiera de qué distribución proviene cada punto, entonces podría estimar las medias de las dos distribuciones utilizando las medias de muestra de los puntos relevantes. Pero en realidad no sabe qué puntos asignar a qué distribución, por lo que tampoco funcionará.

Entonces, ninguno de los enfoques parece funcionar: necesitaría saber la respuesta antes de poder encontrar la respuesta, y está atascado.

Lo que EM le permite hacer es alternar entre estos dos pasos manejables en lugar de abordar todo el proceso a la vez.

Deberá comenzar con una conjetura sobre los dos medios (aunque su conjetura no necesariamente tiene que ser muy precisa, debe comenzar en algún lugar).

Si su suposición sobre los medios era precisa, entonces tendría suficiente información para llevar a cabo el paso en mi primer punto anterior, y podría (probabilísticamente) asignar cada punto de datos a uno de los dos gaussianos. Aunque sabemos que nuestra suposición es incorrecta, intentemos esto de todos modos. Y luego, dadas las distribuciones asignadas a cada punto, puede obtener nuevas estimaciones para las medias usando el segundo punto. Resulta que, cada vez que recorre estos dos pasos, está mejorando un límite inferior en la probabilidad del modelo.

Eso ya es bastante bueno: a pesar de que las dos sugerencias en los puntos anteriores no parecían funcionar de manera individual, todavía puede usarlas juntas para mejorar el modelo. La verdadera magia de EM es que, después de suficientes iteraciones, el límite inferior será tan alto que no habrá espacio entre él y el máximo local. Como resultado, y ha optimizado localmente la probabilidad.

Entonces, no solo ha mejorado el modelo, ha encontrado el mejor modelo posible que se puede encontrar con actualizaciones incrementales.


Esta página de Wikipedia muestra un ejemplo un poco más complicado (gaussianos bidimensionales y covarianza desconocida), pero la idea básica es la misma. También incluye Rcódigo bien comentado para implementar el ejemplo.

En el código, el paso "Expectativa" (E-step) corresponde a mi primer punto: averiguar qué gaussiano tiene la responsabilidad de cada punto de datos, dados los parámetros actuales para cada gaussiano. El paso "Maximización" (paso M) actualiza los medios y las covarianzas, dadas estas asignaciones, como en mi segundo punto.

Como puede ver en la animación, estas actualizaciones permiten rápidamente que el algoritmo pase de un conjunto de estimaciones terribles a un conjunto de muy buenas: realmente parece haber dos nubes de puntos centradas en las dos distribuciones gaussianas que EM encuentra.

David J. Harris
fuente
13

Aquí hay un ejemplo de Maximización de Expectativas (EM) utilizado para estimar la media y la desviación estándar. El código está en Python, pero debería ser fácil de seguir, incluso si no está familiarizado con el idioma.

La motivación para EM

Los puntos rojo y azul que se muestran a continuación se extraen de dos distribuciones normales diferentes, cada una con una media particular y una desviación estándar:

ingrese la descripción de la imagen aquí

Para calcular aproximaciones razonables de la media "verdadera" y los parámetros de desviación estándar para la distribución roja, podríamos mirar fácilmente los puntos rojos y registrar la posición de cada uno, y luego usar las fórmulas familiares (y de manera similar para el grupo azul) .

Ahora considere el caso en el que sabemos que hay dos grupos de puntos, pero no podemos ver qué punto pertenece a qué grupo. En otras palabras, los colores están ocultos:

ingrese la descripción de la imagen aquí

No es del todo obvio cómo dividir los puntos en dos grupos. Ahora no podemos mirar las posiciones y calcular estimaciones para los parámetros de la distribución roja o la distribución azul.

Aquí es donde se puede usar EM para resolver el problema.

Usando EM para estimar parámetros

Aquí está el código utilizado para generar los puntos que se muestran arriba. Puede ver las medias reales y las desviaciones estándar de las distribuciones normales de las que se extrajeron los puntos. Las variables redy bluemantienen las posiciones de cada punto en los grupos rojo y azul respectivamente:

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 pudiéramos ver el color de cada punto, intentaríamos recuperar las medias y las desviaciones estándar utilizando las funciones de la biblioteca:

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

Pero como los colores están ocultos para nosotros, comenzaremos el proceso EM ...

Primero, simplemente adivinamos los valores para los parámetros de cada grupo ( paso 1 ). Estas conjeturas no tienen que ser buenas:

# 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

ingrese la descripción de la imagen aquí

Suposiciones bastante malas: parece que los medios están muy lejos de cualquier "centro" de un grupo de puntos.

Para continuar con EM y mejorar estas conjeturas, calculamos la probabilidad de que cada punto de datos (independientemente de su color secreto) aparezca bajo estas conjeturas para la desviación media y estándar ( paso 2 ).

La variable both_colourscontiene cada punto de datos. La función stats.normcalcula la probabilidad del punto bajo una distribución normal con los parámetros dados:

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)

Esto nos dice, por ejemplo, que con nuestras conjeturas actuales, el punto de datos en 1.761 es mucho más probable que sea rojo (0.189) que azul (0.00003).

Podemos convertir estos dos valores de probabilidad en pesos ( paso 3 ) para que sumen 1 como sigue:

likelihood_total = likelihood_of_red + likelihood_of_blue

red_weight = likelihood_of_red / likelihood_total
blue_weight = likelihood_of_blue / likelihood_total

Con nuestras estimaciones actuales y nuestros pesos recién calculados, ahora podemos calcular nuevas estimaciones, probablemente mejores, para los parámetros ( paso 4 ). Necesitamos una función para la media y una función para la desviación estándar:

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)

Estos se parecen mucho a las funciones habituales de la media y la desviación estándar de los datos. La diferencia es el uso de un weightparámetro que asigna un peso a cada punto de datos.

Esta ponderación es la clave de EM. Cuanto mayor sea el peso de un color en un punto de datos, más influirá el punto de datos en las próximas estimaciones para los parámetros de ese color. En última instancia, esto tiene el efecto de tirar de cada parámetro en la dirección correcta.

Las nuevas conjeturas se calculan con estas funciones:

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

El proceso EM se repite con estas nuevas conjeturas desde el paso 2 en adelante. Podemos repetir los pasos para un número dado de iteraciones (digamos 20), o hasta que veamos que los parámetros convergen.

Después de cinco iteraciones, vemos que nuestras malas conjeturas iniciales comienzan a mejorar:

ingrese la descripción de la imagen aquí

Después de 20 iteraciones, el proceso EM ha convergido más o menos:

ingrese la descripción de la imagen aquí

A modo de comparación, aquí están los resultados del proceso EM en comparación con los valores calculados donde la información de color no está oculta:

          | 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: esta respuesta fue adaptada de mi respuesta en Stack Overflow aquí .

Alex Riley
fuente
10

Siguiendo la respuesta de Zhubarb, implementé el ejemplo EM de "lanzamiento de monedas" de Do y Batzoglou en GNU R. Tenga en cuenta que uso la mlefunción del stats4paquete, esto me ayudó a comprender más claramente cómo se relacionan EM y 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))
usuario3096626
fuente
1
@ user3096626 ¿Puede explicar por qué en el paso de maximización multiplica la probabilidad de una moneda A (fila $ peso_A) por una probabilidad de registro (llf_A)? ¿Hay una regla especial o razón por la que lo hacemos? Quiero decir, uno simplemente multiplicaría las probabilidades o loglikes pero no mezclaría el dobladillo. También abrí un nuevo tema
Alina
5

La respuesta dada por Zhubarb es genial, pero desafortunadamente está en Python. A continuación se muestra una implementación Java del algoritmo EM ejecutado en el mismo problema (planteado en el artículo de Do y Batzoglou, 2008). He agregado algunos printf a la salida estándar para ver cómo convergen los parámetros.

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

El código Java sigue a continuación:

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);
    }
}
stackoverflowuser2010
fuente
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, [email protected]

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
Juan andrade
fuente
44
¿Puede agregar alguna discusión o explicación al código sin procesar? Sería útil para muchos lectores al menos mencionar el idioma en el que está escribiendo.
Glen_b
1
@Glen_b: este es MatLab. Me pregunto cuán cortés se considera anotar más ampliamente el código de alguien en su respuesta.
EngrStudent
4

Bueno, te sugiero que leas un libro sobre R de Maria L Rizzo. Uno de los capítulos contiene el uso del algoritmo EM con un ejemplo numérico. Recuerdo haber leído el código para comprenderlo mejor.

Además, intente verlo desde un punto de vista de agrupamiento al principio. Trabaje a mano, un problema de agrupamiento en el que se toman 10 observaciones de dos densidades normales diferentes. Esto debería ayudar. Toma la ayuda de R :)

Vani
fuente
2

Por si acaso, escribí una implementación de Ruby del ejemplo de lanzamiento de moneda mencionado anteriormente por Do & Batzoglou y produce exactamente los mismos números que los mismos parámetros de entrada y . θ B = 0.5θ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
gung
fuente