Это сообщение изначально было опубликовано в Техническом блоге Taboola

Если вы в последнее время следили за нашим техническим блогом, вы могли заметить, что мы используем особый тип нейронных сетей, называемый Mixture Density Network (MDN). MDN не только предсказывают ожидаемое значение цели, но и лежащее в основе распределение вероятностей.

В этом блоге основное внимание будет уделено тому, как реализовать такую ​​модель с помощью Tensorflow с нуля, включая пояснения, диаграммы и блокнот Jupyter со всем исходным кодом.

Что такое MDN и чем они полезны?

Данные из реальной жизни зашумлены. Хотя этот шум весьма раздражает, он имеет смысл, поскольку дает более широкое представление о происхождении данных. Целевое значение может иметь разные уровни шума в зависимости от ввода, и это может сильно повлиять на наше понимание данных.

Лучше пояснить это на примере. Предположим следующую квадратичную функцию:

Учитывая x в качестве входных данных, мы получаем детерминированный выход f (x). Теперь давайте превратим эту функцию в более интересную (и реалистичную) функцию: мы добавим немного нормально распределенного шума к f (x). Этот шум будет увеличиваться при увеличении x. Мы вызовем новую функцию g (x), которая формально равна g (x) = f (x) + 𝜺 (x) , где 𝜺 (x) - нормальная случайная величина.

Давайте рассмотрим пример g (x) для разных значений x:

Фиолетовая линия представляет бесшумную функцию f (x), и мы можем легко увидеть увеличение добавленного шума. Давайте рассмотрим случаи, когда x = 1 и x = 5. Для обоих этих значений f (x) = 4, поэтому 4 является разумным значением, которое может принимать g (x). Является ли 4.5 разумным предсказанием для g (x)? Ответ однозначно отрицательный. Хотя 4,5 кажется разумным значением для x = 5, мы не можем принять его как допустимое значение для g (x), когда x = 1. Если наша модель просто научится предсказывать y ’(x) = f (x), эта ценная информация будет потеряна. На самом деле нам нужна модель, способная предсказывать y ’(x) = g (x). И это именно то, что делает MDN.

Концепция MDN была изобретена Кристофером Бишопом в 1994 году. Его оригинальная статья довольно хорошо объясняет эту концепцию, но она восходит к доисторической эпохе, когда нейронные сети не были так распространены, как сегодня. Следовательно, в нем отсутствует практическая часть того, как его реализовать. Именно этим мы и займемся сейчас.

Начнем

Начнем с простой нейронной сети, которая изучает f (x) только из зашумленного набора данных. Мы будем использовать 3 скрытых плотных слоя, каждый с 12 узлами, которые будут выглядеть примерно так:

Мы будем использовать среднеквадратическую ошибку в качестве функции потерь. Давайте запрограммируем это в Tensorflow:

x = tf.placeholder(name='x',shape=(None,1),dtype=tf.float32)
layer = x
for _ in range(3):
   layer = tf.layers.dense(inputs=layer, units=12, activation=tf.nn.tanh)
output = tf.layers.dense(inputs=layer, units=1)

После обучения результат выглядит так:

Мы видим, что сеть успешно изучила f (x). Теперь не хватает только оценки шума. Давайте изменим сеть, чтобы получить эту дополнительную информацию.

Переход на MDN

Мы продолжим использовать ту же сеть, которую только что разработали, но изменим две вещи:

  1. Выходной слой будет иметь два узла, а не один, и мы назовем их mu и sigma.
  2. Мы будем использовать другую функцию потерь.

Теперь наша сеть выглядит так:

Давайте запрограммируем это:

x = tf.placeholder(name='x',shape=(None,1),dtype=tf.float32)
layer = x
for _ in range(3):
   layer = tf.layers.dense(inputs=layer, units=12, activation=tf.nn.tanh)
mu = tf.layers.dense(inputs=layer, units=1)
sigma = tf.layers.dense(inputs=layer, units=1,activation=lambda x: tf.nn.elu(x) + 1)

Давайте потратим секунду, чтобы понять функцию активации сигмы - помните, что по определению стандартное отклонение любого распределения является неотрицательным числом. Экспоненциальная линейная единица (ELU), определяемая как:

дает -1 как наименьшее значение, поэтому ELU + 1 всегда будет неотрицательным. Это обязательно должен быть ELU? Нет, подойдет любая функция, которая всегда дает неотрицательный результат - например, абсолютное значение сигмы. ELU просто, кажется, делает свою работу лучше.

Затем нам нужно настроить нашу функцию потерь. Попробуем понять, что именно мы сейчас ищем. Наш новый выходной слой дает нам параметры нормального распределения. Это распределение должно быть способно описывать данные, полученные путем выборки g (x). Как мы можем это измерить? Мы можем, например, создать нормальное распределение из выходных данных и максимизировать вероятность выборки наших целевых значений из него. С математической точки зрения, мы хотели бы максимизировать значения функции плотности вероятности (PDF) нормального распределения для всего нашего набора данных. Точно так же мы можем минимизировать отрицательный логарифм PDF:

Мы видим, что функция потерь дифференцируема как по 𝜇, так и по 𝝈. Вы будете удивлены, насколько легко кодировать:

dist = tf.distributions.Normal(loc=mu, scale=sigma)
loss = tf.reduce_mean(-dist.log_prob(y))

Вот и все. После обучения модели мы видим, что она действительно уловила g (x):

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

Следующие шаги

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

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