Nota: el código detrás de esta respuesta se puede encontrar aquí .
Supongamos que tenemos algunos datos muestreados de dos grupos diferentes, rojo y azul:
Aquí, podemos ver qué punto de datos pertenece al grupo rojo o azul. Esto facilita la búsqueda de los parámetros que caracterizan a cada grupo. Por ejemplo, la media del grupo rojo es alrededor de 3, la media del grupo azul es alrededor de 7 (y podríamos encontrar la media exacta si quisiéramos).
Esto se conoce, en general, como estimación de máxima verosimilitud. . Dados algunos datos, calculamos el valor de un parámetro (o parámetros) que mejor explica esos datos.
Ahora imagine que no podemos ver qué valor se muestreó de qué grupo. Todo nos parece morado:
Aquí tenemos el conocimiento de que hay dos grupos de valores, pero no sabemos a qué grupo pertenece un valor en particular.
¿Todavía podemos estimar las medias para el grupo rojo y el grupo azul que mejor se ajustan a estos datos?
¡Sí, a menudo podemos! La maximización de expectativas nos da una forma de hacerlo. La idea muy general detrás del algoritmo es esta:
- Comience con una estimación inicial de lo que podría ser cada parámetro.
- Calcule la probabilidad de que cada parámetro produzca el punto de datos.
- Calcule los pesos para cada punto de datos indicando si es más rojo o más azul en función de la probabilidad de que lo produzca un parámetro. Combine los pesos con los datos ( expectativa ).
- Calcule una mejor estimación de los parámetros utilizando los datos ajustados por peso ( maximización ).
- Repita los pasos 2 a 4 hasta que la estimación del parámetro converja (el proceso deja de producir una estimación diferente).
Estos pasos necesitan una explicación más detallada, por lo que analizaré el problema descrito anteriormente.
Ejemplo: estimación de la desviación estándar y media
Usaré Python en este ejemplo, pero el código debería ser bastante fácil de entender si no está familiarizado con este lenguaje.
Supongamos que tenemos dos grupos, rojo y azul, con los valores distribuidos como en la imagen de arriba. Específicamente, cada grupo contiene un valor extraído de una distribución normal con los siguientes parámetros:
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...
Aquí hay una imagen de estos grupos rojo y azul nuevamente (para evitar que tenga que desplazarse hacia arriba):
Cuando podemos ver el color de cada punto (es decir, a qué grupo pertenece), es muy fácil estimar la media y la desviación estándar de cada grupo. Simplemente pasamos los valores rojo y azul a las funciones integradas en NumPy. Por ejemplo:
>>> np.mean(red)
2.802
>>> np.std(red)
0.871
>>> np.mean(blue)
6.932
>>> np.std(blue)
2.195
Pero, ¿y si no podemos ver los colores de los puntos? Es decir, en lugar de rojo o azul, todos los puntos se han coloreado de púrpura.
Para intentar recuperar los parámetros de desviación estándar y media de los grupos rojo y azul, podemos utilizar la maximización de expectativas.
Nuestro primer paso ( paso 1 anterior) es adivinar los valores de los parámetros para la media y la desviación estándar de cada grupo. No tenemos que adivinar inteligentemente; podemos elegir los números que queramos:
# 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
Estas estimaciones de parámetros producen curvas de campana que se ven así:
Estas son malas estimaciones. Ambos medios (las líneas verticales punteadas) se ven lejos de cualquier tipo de "medio" para grupos sensibles de puntos, por ejemplo. Queremos mejorar estas estimaciones.
El siguiente paso ( paso 2 ) es calcular la probabilidad de que cada punto de datos aparezca bajo las suposiciones de parámetros actuales:
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)
Aquí, simplemente hemos puesto cada punto de datos en la función de densidad de probabilidad para una distribución normal usando nuestras suposiciones actuales en la desviación estándar y media para el rojo y el azul. Esto nos dice, por ejemplo, que con nuestras suposiciones actuales, es mucho más probable que el punto de datos en 1,761 sea rojo (0,189) que azul (0,00003).
Para cada punto de datos, podemos convertir estos dos valores de probabilidad en pesos ( paso 3 ) para que sumen 1 de la siguiente manera:
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 nuestras ponderaciones recién calculadas, ahora podemos calcular nuevas estimaciones para la media y la desviación estándar de los grupos rojo y azul ( paso 4 ).
Calculamos dos veces la media y la desviación estándar usando todos los puntos de datos, pero con diferentes ponderaciones: una vez para las ponderaciones rojas y una vez para las azules.
La clave de la intuición es que cuanto mayor es el peso de un color en un punto de datos, más influye el punto de datos en las próximas estimaciones de los parámetros de ese color. Esto tiene el efecto de "tirar" de los parámetros en la dirección correcta.
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)
Tenemos nuevas estimaciones para los parámetros. Para mejorarlos de nuevo, podemos volver al paso 2 y repetir el proceso. Hacemos esto hasta que las estimaciones converjan, o después de que se hayan realizado algunas iteraciones ( paso 5 ).
Para nuestros datos, las primeras cinco iteraciones de este proceso se ven así (las iteraciones recientes tienen una apariencia más fuerte):
Vemos que las medias ya están convergiendo en algunos valores, y las formas de las curvas (gobernadas por la desviación estándar) también se están volviendo más estables.
Si continuamos durante 20 iteraciones, terminamos con lo siguiente:
El proceso EM ha convergido a los siguientes valores, que resultan muy cercanos a los valores reales (donde podemos ver los colores, sin variables ocultas):
| 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
En el código anterior, es posible que haya notado que la nueva estimación de la desviación estándar se calculó utilizando la estimación de la iteración anterior para la media. En última instancia, no importa si primero calculamos un nuevo valor para la media, ya que solo estamos encontrando la varianza (ponderada) de los valores alrededor de algún punto central. Seguiremos viendo converger las estimaciones de los parámetros.