Примечание: код этого ответа можно найти здесь .
Предположим, у нас есть данные, взятые из двух разных групп, красной и синей:
Здесь мы можем увидеть, какая точка данных принадлежит красной или синей группе. Это позволяет легко найти параметры, характеризующие каждую группу. Например, среднее значение для красной группы составляет около 3, а для синей группы - около 7 (и мы могли бы найти точные средние значения, если бы захотели).
Это, вообще говоря, называется оценкой максимального правдоподобия . Учитывая некоторые данные, мы вычисляем значение параметра (или параметров), которое лучше всего объясняет эти данные.
Теперь представьте, что мы не можем видеть, какое значение было выбрано из какой группы. Нам все кажется фиолетовым:
Здесь мы знаем, что существует две группы ценностей, но мы не знаем, к какой группе принадлежит конкретная ценность.
Можем ли мы по-прежнему оценить средние значения для красной группы и синей группы, которые лучше всего соответствуют этим данным?
Да, часто можно! Максимизация ожиданий дает нам возможность это сделать. Самая общая идея алгоритма такова:
- Начните с первоначальной оценки того, каким может быть каждый параметр.
- Вычислите вероятность того, что каждый параметр создает точку данных.
- Вычислите веса для каждой точки данных, указав, является ли она более красной или более синей, на основе вероятности того, что она создается параметром. Объедините веса с данными ( ожидание ).
- Вычислите лучшую оценку параметров, используя данные с поправкой на вес ( максимизация ).
- Повторяйте шаги 2–4 до тех пор, пока оценка параметра не сойдется (процесс перестанет производить другую оценку).
Эти шаги требуют дальнейшего объяснения, поэтому я рассмотрю проблему, описанную выше.
Пример: оценка среднего и стандартного отклонения
В этом примере я буду использовать Python, но код должен быть довольно простым для понимания, если вы не знакомы с этим языком.
Предположим, у нас есть две группы, красная и синяя, со значениями, распределенными, как на изображении выше. В частности, каждая группа содержит значение, полученное из нормального распределения со следующими параметрами:
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...
Вот снова изображение этих красных и синих групп (чтобы избавить вас от необходимости прокручивать вверх):
Когда мы видим цвет каждой точки (то есть к какой группе она принадлежит), очень легко оценить среднее значение и стандартное отклонение для каждой группы. Мы просто передаем красные и синие значения встроенным функциям в NumPy. Например:
>>> np.mean(red)
2.802
>>> np.std(red)
0.871
>>> np.mean(blue)
6.932
>>> np.std(blue)
2.195
Но что, если мы не можем видеть цвета точек? То есть вместо красного или синего каждая точка была окрашена в фиолетовый цвет.
Чтобы попытаться восстановить параметры среднего и стандартного отклонения для красной и синей групп, мы можем использовать максимизацию ожидания.
Наш первый шаг ( шаг 1 выше) - угадать значения параметров для среднего значения и стандартного отклонения каждой группы. Нам не нужно гадать разумно; мы можем выбрать любые числа, которые нам нравятся:
# 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
Эти оценки параметров дают колоколообразные кривые, которые выглядят следующим образом:
Это плохие оценки. Оба средства (вертикальные пунктирные линии) выглядят далеко от любой «середины», например, для разумных групп точек. Мы хотим улучшить эти оценки.
Следующим шагом ( шаг 2 ) является вычисление вероятности появления каждой точки данных в предположениях текущего параметра:
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)
Здесь мы просто поместили каждую точку данных в функцию плотности вероятности для нормального распределения, используя наши текущие предположения о среднем значении и стандартном отклонении для красного и синего. Это говорит нам, что , например, с нашими текущими предположениями данные указывают на 1.761 это гораздо более вероятно, будет красный (0,189) , чем синий (0,00003).
Для каждой точки данных мы можем превратить эти два значения правдоподобия в веса ( шаг 3 ), чтобы они суммировались до 1 следующим образом:
likelihood_total = likelihood_of_red + likelihood_of_blue
red_weight = likelihood_of_red / likelihood_total
blue_weight = likelihood_of_blue / likelihood_total
С нашими текущими оценками и нашими недавно вычисленными весами теперь мы можем вычислить новые оценки для среднего и стандартного отклонения красной и синей групп ( шаг 4 ).
Мы дважды вычисляем среднее значение и стандартное отклонение, используя все точки данных, но с разными весами: один раз для красных весов и один раз для синих весов.
Ключевым моментом интуиции является то, что чем больше вес цвета в точке данных, тем больше точка данных влияет на следующие оценки параметров этого цвета. Это дает эффект «подтягивания» параметров в правильном направлении.
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)
У нас есть новые оценки параметров. Чтобы снова улучшить их, мы можем вернуться к шагу 2 и повторить процесс. Мы делаем это до тех пор, пока оценки не сойдутся, или после того, как будет выполнено некоторое количество итераций ( шаг 5 ).
Для наших данных первые пять итераций этого процесса выглядят следующим образом (последние итерации выглядят сильнее):
Мы видим, что средние значения уже сходятся на некоторых значениях, а формы кривых (определяемые стандартным отклонением) также становятся более стабильными.
Если мы продолжим 20 итераций, мы получим следующее:
Процесс EM сошелся на следующие значения, которые оказались очень близкими к фактическим значениям (где мы можем видеть цвета - никаких скрытых переменных):
| 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
В приведенном выше коде вы могли заметить, что новая оценка стандартного отклонения была вычислена с использованием оценки среднего значения предыдущей итерацией. В конечном итоге не имеет значения, вычисляем ли мы сначала новое значение для среднего, поскольку мы просто находим (взвешенную) дисперсию значений вокруг некоторой центральной точки. Мы по-прежнему увидим, что оценки параметров сходятся.