Два отличных способа оценить ненаблюдаемые переменные

Недавно я начал работать над Вероятностным программированием и байесовскими методами для хакеров, который долгое время был в моем списке дел. Говоря как человек, который прошел свою долю занятий по статистике и машинному обучению (включая байесовскую статистику), я обнаружил, что благодаря этому подходу, ориентированному на кодирование, я понимаю вещи, которые раньше никогда не были ясны. Я настоятельно рекомендую это прочитать!

Некоторые щедрые души обновили код, чтобы использовать библиотеку вероятностей TensorFlow, которую я часто использую в своей работе. Таким образом, мой мозг наконец нашел время для установления связи между байесовским анализом скрытых переменных и машинным обучением посредством обратного распространения. Я подключаю мир цепей Маркова Монте-Карло (MCMC) к нейронным сетям. Оглядываясь назад, кажется, что я должен был понять это раньше, ну да ладно. Если вы похожи на меня и у вас в голове нет этой связи, давайте исправим это прямо сейчас.

Байесовский анализ скрытых переменных

«Скрытая переменная» = скрытая; не наблюдается и не измеряется непосредственно.

Анализ в данном случае — это процесс построения вероятностной модели для представления процесса генерации данных. Типичным примером является анализ точки переключения. Предположим, у нас есть данные подсчета за временной интервал (например, количество несчастных случаев на шахтах в год в течение столетия). Мы хотим оценить год, когда были введены новые правила техники безопасности. Мы ожидаем, что уровень аварийности снизится после этого момента, давая один уровень аварийности до и другой (надеюсь, более низкий) уровень после.

Вот первая связь с нейронными сетями: модель, которую мы используем для подбора данных, несколько произвольна. «Произвольно» — пожалуй, сильно сказано — можно сказать, что структура модели выбрана рационально, с некоторой художественной вольностью.

Идея состоит в том, что мы наблюдаем данные подсчета, поэтому распределение Пуассона имеет смысл генерировать данные, которые мы наблюдаем. Но на самом деле есть два распределения Пуассона или, по крайней мере, два параметра скорости — один до точки переключения и один после. Затем есть сама точка переключения, которая представляет собой значение времени в пределах наших данных.

Но, как и любое моделирование, это произвольная модельная структура. Мы хотим сделать его достаточно простым, чтобы быть полезным выразительным, но не более того. Это очень похоже на кластеризацию (вспомните К-средних или EM-алгоритм), поэтому вы можете представить себе добавление большего количества точек переключения (т. е. больше кластеров). Вы можете рассматривать частоту аварий до и после как линейную функцию, а не фиксированную частоту, превратив ее в задачу регрессии с изменением наклона в точке переключения. Варианты усложнения или обогащения этой модели безграничны. Нейронные сети устроены так же. Выбор правильной архитектуры и размера модели — это вопрос проб и ошибок, художественной лицензии, избегания переобучения, но позволяющего получить достаточно богатое выражение модели.

Следующее сходство, у нас есть скрытые переменные, которые мы хотим оценить. В некотором смысле мы хотим «подогнать модель» к данным. Мы хотим выбрать параметры, которые максимизируют вероятность наблюдаемых данных. У нас есть модель, которая позволяет нам вычислить p(X|Z) (вероятность данных X при некоторых параметрах Z), и нам нужно p(Z|X) (он же «апостериорный»). Это можно сделать с помощью классического статистического метода, всеми любимого MCMC. Точно так же параметры можно узнать с помощью градиентного спуска в процессе, называемом вариационным выводом. Мы поговорим об обоих на высоком уровне.

MCMC: Выборка (не "подгонка") параметров модели

MCMC — метод выборки. Это чрезвычайно умный алгоритм выборки из распределения скрытых (ненаблюдаемых) параметров модели. Важно отметить, что это не метод оценки параметризованных распределений. MCMC просто генерирует образцы параметров, которые затем можно построить в виде гистограммы и просмотреть в виде распределения. Но вы не «подгоняете» данные. Вы выбираете параметры модели, которые могли бы сгенерировать ваши данные.

То, как это работает, довольно изящно. Для получения более подробной информации посмотрите отличное видео от RitVikMath или прочитайте Алгоритм Метрополиса. Вы начинаете с создания нескольких случайных кандидатов. Как правило, это не совсем случайно, хотя могло бы быть. Обычно вы генерируете эти значения, используя предыдущие распределения (теперь они становятся байесовскими). Например, параметры скорости должны быть неотрицательными, чтобы быть действительными параметрами Пуассона, и они должны генерировать подсчеты, похожие на наши входные данные, поэтому хорошей отправной точкой является экспоненциальное распределение с центром в среднем наблюдаемом подсчете. данные. Но, в конце концов, это всего лишь способ сгенерировать начальное предположение для скорости. То же самое касается точки переключения. Его можно получить путем равномерного распределения между начальным и конечным годами в наших данных.

Мы установили некоторые разумные априорные значения и случайным образом выбрали кандидата для каждого параметра в нашей модели (3 в нашем случае). Это позволяет нам оценить вероятность наших данных с учетом этих случайно сгенерированных значений p(X|Z). На приведенном ниже рисунке красная линия на графике указывает точку переключения аварийности, и если вы визуализируете распределение Пуассона в каждой точке данных вдоль этой линии, вы можете представить, как можно получить вероятность данных в рамках этой модели. В этом случае вторая скорость (справа от точки переключения) выглядит слишком высокой, и данные будут оценены очень маловероятно.

Вот где все становится интересно. Начиная с последней выборки (которая была полностью случайной), мы центрируем функцию ядра (часто нормальное распределение) в этих точках и снова делаем выборку, но теперь мы делаем выборку из распределения ядра. Если вероятность данных выше, чем была при последнем предположении, мы сразу же принимаем новые выборки параметров. Если вероятность данных такая же или ниже, чем наш текущий шаг, мы принимаем пропорционально вероятности. Другими словами, мы абсолютно, всегда и навсегда принимаем переменные выборки, которые лучше объясняют наши данные. Мы только иногда будем принимать худшие.

С течением времени вы можете видеть, как цепь Маркова начинает выбирать из наиболее разумного распределения параметров, даже если мы не знаем, что это за распределение. Поскольку мы можем оценить вероятность данных, мы можем приближаться (это технический термин) все ближе и ближе к хорошим предположениям для наших параметров. Поскольку мы собираемся принимать лучшие предположения в 100% случаев, а менее удачные — только иногда, общая тенденция будет направлена ​​на лучшие предположения. Поскольку мы делаем выборку, основываясь только на последнем предположении, выборки имеют тенденцию дрейфовать в сторону более вероятных параметров по сравнению с данными.

Поскольку требуется несколько сэмплов, чтобы попасть в область «разумных предположений», обычно MCMC сэмплируется несколько раз перед использованием. Это известно как «прожиг», и правильная продолжительность прожига также является догадкой (как и многое в моделировании… *вздох*). Вы также можете увидеть, как выбор функции ядра повлияет на окончательные результаты. Например, если ваша функция ядра суперрассеянная, то выборочные параметры также будут более рассредоточенными.

Конечным результатом является то, что вы можете выбирать разумные параметры, которые хорошо «соответствуют» вашим данным. Вы можете нанести эти выбранные параметры на гистограмму, оценить их среднее значение или медиану и все такое прочее. Вы отправляетесь на гонки!

Вариационный вывод: новый алгоритм, та же цель

Если вы проводите время в нейронных сетях, в мире обратного распространения, то, как и я, вы, вероятно, видите всевозможные параллели. Маленькие шаги к оптимуму функции потерь? Звучит как градиентный спуск для меня! И действительно, та же самая модель может быть подогнана с помощью градиентного спуска в процессе, называемом Вариационный вывод (VI).

Опять же, как отмечалось ранее, у нас есть модель, которую можно использовать для оценки p(X|Z), и нам нужна обратная модель: p(Z|X). Апостериор описывает параметры, которые максимизируют вероятность наблюдаемых данных. Идея VI состоит в том, чтобы подогнать репрезентативное распределение к p(Z|X), максимизируя функцию потерь нижняя граница доказательств (ELBO). Мы поговорим о каждой из этих частей по очереди.

Конечно, мы не знаем апостериорное значение p(Z|X) заранее, поэтому мы начинаем с распределения, достаточно гибкого для его представления. В этом туториале они демонстрируют несколько вариантов, включая независимые нормальные распределения (по одному на каждую скрытую переменную в модели), многомерное нормальное распределение (распределения и их ковариации поддаются обучению) и самый навороченный вариант — нейронную сеть в качестве подставки. -ин, также известный как Авторегрессивный поток. Дело в том, что для аппроксимации апостериорного можно использовать множество распределений.

ELBO — отдельная тема, и она заслуживает внимания. Статья в Википедии неплохая, как и серия видеороликов на YouTube от Machine Learning & Simulation. С точки зрения развития интуиции, ELBO представляет собой баланс между априорными и оптимальными точечными оценками Z, который максимизирует вероятность наблюдаемых данных X. Это достигается за счет функции потерь, одновременно стимулирующей высокую вероятность данных и наказывающей большую. отклонения от них предшествующих.

Максимизируя ELBO, мы можем производить выборку с апостериорной точки, аналогично подходу MCMC.

Примеры

Теперь, когда у вас есть представление о том, что такое MCMC и VI, вот пример использования TensorFlow Probability. Здесь я буду выделять фрагменты. Полный пример находится на GitHub.

Я придерживаюсь модели подсчета катастроф, описанной выше. В примере с MCMC модель настроена так:

disaster_count = tfd.JointDistributionNamed(
    dict(
        early_rate=tfd.Exponential(rate=1.),
        late_rate=tfd.Exponential(rate=1.),
        switchpoint=tfd.Uniform(low=tf_min_year, high=tf_max_year),
        d_t=lambda switchpoint, late_rate, early_rate: tfd.Independent(
            tfd.Poisson(
                rate=tf.where(years < switchpoint, early_rate, late_rate),
                force_probs_to_zero_outside_support=True
            ),
            reinterpreted_batch_ndims=1
        )
    )
)

Результатом является совместное распределение, которое может оценить вероятность наших данных с учетом некоторых параметров. Мы оцениваем логарифмическую вероятность наших данных по отношению к определенному набору параметров следующим образом:

model.log_prob(
  switchpoint=switchpoint,
  early_rate=early_rate,
  late_rate=late_rate,
  d_t=disaster_data
)

Затем мы настраиваем объект MCMC следующим образом:

tfp.mcmc.HamiltonianMonteCarlo(
    target_log_prob_fn=target_log_prob_fn,
    step_size=0.05,
    num_leapfrog_steps=3
)

Мы заключаем объект MCMC в TransformedTransitionKernel, чтобы мы могли производить выборку в непрерывном пространстве, ограничивая обучение поддержкой нашей модели (например, без отрицательных значений года). Мы определяем количество прожигов по шагам и количество образцов, которые мы хотим нарисовать. Затем, менее чем через минуту, у нас есть образцы.

В примере с VI мы снова настраиваем Joint Distribution:

early_rate = yield tfd.Exponential(rate=1., name='early_rate')
late_rate = yield tfd.Exponential(rate=1., name='late_rate')
switchpoint = yield tfd.Uniform(low=tf_min_year, high=tf_max_year, name='switchpoint')
yield tfd.Poisson(
    rate=tf.where(years < switchpoint, early_rate, late_rate),
    force_probs_to_zero_outside_support=True,
    name='d_t'
)

На этот раз он настроен как генератор (отсюда и операторы yield). Мы гарантируем, что модель оценивает логарифмическую вероятность наших данных, закрепляя их:

target_model = vi_model.experimental_pin(d_t=disaster_data)

Мы настраиваем гибкое распределение, которое можно оптимизировать для представления апостериорного анализа. Я выбрал авторегрессивный поток (нейронная сеть), но, как уже говорилось, есть много вариантов.

tfb.MaskedAutoregressiveFlow(
    shift_and_log_scale_fn=tfb.AutoregressiveNetwork(
        params=2,
        hidden_units=[hidden_size]*num_hidden_layers,
        activation='relu'
    )
)

Опять же, мы используем биекторы для перехода от непрерывной численной оптимизации к поддержке нашей модели. Наконец, мы подогнали нашу модель следующим образом:

optimizer = tf.optimizers.Adam(learning_rate=0.001)
iaf_loss = tfp.vi.fit_surrogate_posterior(
    target_model.unnormalized_log_prob,
    iaf_surrogate_posterior,
    optimizer=optimizer,
    num_steps=10**4,
    sample_size=4,
    jit_compile=True
)

В этом случае VI не соответствует точке переключения так точно, как MCMC, и я не совсем понимаю, почему. Если у вас есть идеи, прокомментируйте эту статью или поднимите вопрос на GitHub! Но, в любом случае, вы поняли.

Заключение

Я надеюсь, что это короткое упражнение было информативным. MCMC предназначен для выборки из скрытых переменных. VI предназначен для подбора скрытых переменных с использованием градиентного спуска. И то, и другое можно выполнить с помощью TensorFlow Probability. Теперь мы знаем.