Основные понятия в машинном обучении

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

Параметризм

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

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

2 Неконтролируемое параметрическое обучение использует ручки для группировки данных, добавляя новые ручки, когда находит что-то новое для подсчета. Но в этом случае у него обычно есть несколько ручек для каждой группы, каждая из которых отображает принадлежность входных данных к этой конкретной группе (с исключениями и нюансами — это высокоуровневое описание).

Статистическая классификация

В статистической классификации два основных подхода называются генеративный и дискриминационный. Эти классификаторы вычисляют по разным подходам, отличающимся степенью статистического моделирования. Терминология противоречива, но можно выделить два основных типа:

  • Учитывая наблюдаемую переменную X и целевую переменную Y, генеративная модель представляет собой статистическую модель совместного распределения вероятностей.
  • Дисциплинарная модель – это модель условной вероятности целевого значения Y при заданном наблюдении x, символически

Аналогично, классификатор, основанный на генеративной модели, называется генеративным классификатором, а классификатор, основанный на дискриминационной модели, является дискриминационным классификатором, хотя этот термин также относится к классификаторам, которые не на основе модели.

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

Более общее (но более неформальное) определение, дополняющее его, может быть следующим:

  • Дискриминативные модели изучают границу между классами
  • Генеративные модели изучают распределение данных

Независимо от точного определения, терминология является конституционной, поскольку генеративная модель может использоваться для «генерации» случайных экземпляров, в то время как дискриминантная модель или дискриминативный классификатор могут использоваться для «различения» значения целевой переменной Y с учетом наблюдения x.

Несколько моментов, которые стоит упомянуть о взаимосвязи между контролируемой, неконтролируемой, генеративной и дискриминационной моделями:

  1. Не все генеративные модели изучаются с помощью обучения без учителя. Многие генеративные модели — от базовых, таких как Наивный Байес, до сложных, таких как GAN или эта генеративная сверточная сеть с повышением — изучаются в контролируемом или полуконтролируемом режиме.
  2. Не каждый алгоритм обучения без учителя является генеративной моделью. Алгоритмы обучения без учителя охватывают весь генеративно-дискриминационный спектр, т.е. k-ближайших соседей (kNN) находится где-то между генеративным и дискриминативным (формально генеративным, неформально дискриминативным).
  3. Многие алгоритмы кластеризации являются генеративными моделями, например. латентное распределение Дирихле (LDA).
  4. Не все дискриминационные модели изучаются с помощью обучения с учителем. Существуют дискриминационные модели, обученные на неконтролируемых наборах данных, например. монокулярные модели глубины и оптического потока.
  5. Каждый алгоритм контролируемого обучения представляет собой дискриминационную модель. Есть несколько примеров алгоритмов обучения с учителем с генеративными моделями (например, наивный байесовский алгоритм).

Немного теории вероятностей

Давайте более точно определим контролируемое обучение. Предположим, что во время обучения нам дали набор данных:

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

Тем не менее, есть несколько вопросов, на которые мы должны ответить, чтобы фактически реализовать метод обучения с учителем. Одной из важных отправных точек является то, что предсказание вероятностей часто имеет больше смысла, чем предсказание отдельных меток.

  1. Если нам позволено иметь вероятности, мы можем гораздо лучше выразить то, что, по нашему мнению, происходит на самом деле; мы могли бы уловить тот факт, что 4, скорее всего, будет 4, но также имеет довольно высокие шансы быть 9. Эта неопределенность может быть полезна для принятия решений.
  2. Если мы позволим иметь вероятности, проблема станет более гладкой: интуитивно мы не можем изменить дискретную метку чуть-чуть, это как бы все или ничего. Однако проблема нахождения оптимального θ упрощается тем фактом, что мы можем изменить эти вероятности на небольшую величину и посмотреть, изменяются ли они в направлении, которое ближе к тому, что нам нужно.

Итак, имея обучающий набор, вместо того, чтобы изучать функцию, которая отображает x в y, теперь мы собираемся изучить функцию, которая отображает x в распределение по y:

Теперь мы возьмем наш ввод, например изображение собаки, и выведем распределение по соответствующей ей метке y.

Напоминание об условных вероятностях

Случайная величина определяется как функция, которая присваивает значения каждому из результатов эксперимента. У нас есть:

  • Вход х
  • Выход у

Мы можем записать совместную вероятность x и y как p (x, y). Совместная вероятность – это статистическая мера, которая вычисляет вероятность того, что два события произойдут вместе и в один и тот же момент времени. Другими словами, совместная вероятность — это вероятность того, что событие Y произойдет одновременно с событием X.

Совместную вероятность не следует путать с условной вероятностью, которая представляет собой вероятность того, что одно событие произойдет при условии другого действия или события.

Фактически совместная вероятность p(x, y) может быть разложена с использованием цепного правила вероятности:

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

Фактически, вы также можете переписать приведенное выше уравнение, разделив обе его части на p(x), чтобы получить определение условной вероятности:

Итак, если вы знаете p(x) и p(x, y), вы можете восстановить свою условную вероятность.

Контролируемое обучение

Мы сосредоточимся на изучении p(y|x). Для этого мы будем использовать «метод машинного обучения», который представляет собой трехэтапную программу. Этот способ декомпозиции учебных задач тесно связан с уровнями анализа Марра:

Теперь я перечислю три шага метода машинного обучения с соответствующими уровнями Марра.

1. Определите свою функцию потерь: как вы будете измерять, насколько хороша конкретная модель? Этот вопрос относится к вычислительному уровню пирамиды.

2 Определите класс модели: как вы будете представлять свою программу? Этот вопрос относится к середине пирамиды: алгоритмическому уровню. Другими словами, какая структура будет использоваться для нашей оптимизации?

3. Выберите оптимизатор:как вы будете искать в классе модели модель, минимизирующую функцию потерь? Этот вопрос относится к уровню реализации пирамиды.

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

Мы начнем с середины пирамиды, думая о нашем представлении проблемы.

1 Как мы представляем p(y|x)?

Например, скажем, что у нас есть этот ввод:

И наша цель — сопоставить его с вероятностью того, что это целое число от 0 до 9.

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

Давайте рассмотрим простой пример:

На приведенном выше изображении мы говорим, что вероятность того, что y — собака (обусловленная x), строится как «x» транспонирует «θ собака»; вероятность того, что y — кошка (обусловленная x), строится как «x» транспонирует «θ cat»; параметры определяются конкатенацией «θ dog» и «θ cat».

Однако эта программа не выводит действительного распределения вероятностей: в общем случае p(y = собака) + p(y = кошка) не даст в сумме 1, если θs не выбраны очень тщательно. Таким образом, мы нарушаем наше ограничение.

Давайте решим эту проблему: вместо того, чтобы «x» транспонировал «θ dog» равным p(dog), мы собираемся определить его как равный некоторой функции f dog(x); таким образом, у нас есть некоторая промежуточная функция внутри нашей программы:

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

Функция f из домена в диапазон является биективной, если (1) ни один элемент диапазона не является образом более чем одного элемента в домене (2) используются все элементы в диапазоне.

Но помимо этого условия, почему мы можем использовать множество различных функций, которые делают эти числа положительными и дают в сумме 1?

Выбор вашей функции в машинном обучении не означает, что вы получите правильный ответ; вы производите правильный ответ, выбирая правильные параметры. Функция просто должна быть достаточно общей, чтобы существовал выбор θ, который представляет правильный ответ.

В общем, у вас может быть n меток (не только собака), а p (y|x) может быть вектором с n элементами, по одному элементу для каждой возможной метки:

У вас есть некоторая функция f θ(x), которая является векторной функцией с n выходными значениями, которые могут быть не положительными и в сумме не равными единице:

Итак, softmax позаботится об этом:

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

2 Как мы выбираем наши параметры θ?

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

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

Когда дело доходит до попытки смоделировать это, мы можем придумать очень грубую модель: мы собираемся представить это как случайный процесс; существует некоторое распределение p(x) по фотографиям, и люди случайным образом выбирают из p(x), когда фотографируют.

А метка «собака» выбирается из некоторого распределения по меткам, которое не совсем случайно и зависит от фотографии:

По цепному правилу вероятности мы можем скомпоновать эти два и сгенерировать выборки (x, y) из их совместного распределения p(x, y):

Наш набор данных выбирается из этого распределения и состоит из множества кортежей (x, y), которые генерируются в соответствии с распределением p (x, y).

Чтобы сделать это немного более формальным, какова вероятность увидеть конкретный тренировочный набор? Нам нужно сделать предположение о том, как разные точки данных соотносятся друг с другом. Предположение, которое мы собираемся сделать, заключается в том, что мы имеем дело с независимыми и одинаково распределенными данными.

  • Независимый означает, что каждый кортеж (x, y) независим от любого другого кортежа (x, y), а это означает, что если вы наблюдаете кошку для x1, это не меняет вероятность того, что вы увидите кошку или собаку для x2
  • Одинаковое распределение означает, что все (xi, yi) происходят из одного и того же распределения.

Если у нас есть это предположение, мы знаем, что можем записать вероятность p (D) увидеть конкретный обучающий набор как произведение всех i из p (xi, yi).

Почему? Потому что «независимый» означает, что сустав p(D) образуется как произведение маргиналов p(xi) и p(yi).

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

У нас есть вероятность, связанная с нашим набором данных. Поскольку мы изучаем p(y|x), мы можем разложить приведенную выше формулу, используя цепное правило вероятности:

Теперь у нас есть вероятность, связанная с нашим набором данных, выраженная как произведение всех наших точек данных p(xi) на p(yi|xi). То, что мы изучаем, это pθ от (y|x). Важно отметить, что это обучаемая функция, которая зависит от наших параметров: это модель истинного p(y|x). Конечно, это не реальный процесс, посредством которого ярлыки ассоциируются с изображениями, это модель этого процесса. И мы постараемся сделать эту модель максимально точной.

В хорошей модели данные должны выглядеть вероятными: одно θ лучше другого θ, если соответствующее p θ(y|x) делает весь набор данных более вероятным и увеличивает p(D):

Однако проблема здесь в том, что мы выражаем эту вероятность p(D) как произведение множества других вероятностей, находящихся в диапазоне от 0 до 1: если вы перемножите миллионы чисел, находящихся в диапазоне от 0 до 1, вы получите что-то очень близкое к нулю. По сути, эта цель является численно сложной.

Вместо этого мы можем взять логарифм p(D). Когда вы логарифмируете произведение, вы получаете сумму логарифмов:

Но поскольку p(xi) не зависит от θ, мы можем просто рассматривать его как константу:

То же самое можно сказать, что наша цель – это сумма по всем точкам данных логарифма p θ(yi|xi) плюс некоторые другие данные, не зависящие от θ.

Мы можем сформулировать задачу изучения θ как нашу попытку найти θ, которое максимизирует log p(D):

Часто мы увидим, что это написано как минимизация (это просто соглашение):

Мы по-прежнему хотим максимизировать логарифм p(D), что равносильно тому, что мы хотим минимизировать отрицательную логарифмическую вероятность. Итак, это наша функция потерь, которая количественно определяет, насколько плох θ.

Есть и другие способы количественно определить, насколько плохой θ. Например, у вас может быть ноль-один проигрыш: это просто говорит о том, что вы получаете проигрыш в один, если вы дали неправильный ответ, и вы получаете проигрыш в ноль, если вы получили правильный ответ:

Если вы решаете задачу непрерывной регрессии, у вас может быть потеря среднего квадрата ошибки:

Если вы прогнозируете число с непрерывным значением, возможно, вы захотите быть как можно ближе к этому числу.

3 Как мы выбираем наш оптимизатор?

Теперь нам нужно выяснить, как найти настройку θ, которая минимизирует нашу функцию потерь. Одна из вещей, о которых полезно думать при воображении алгоритмов оптимизации, — это то, что называется ландшафтом потерь. Вы можете думать о ландшафте потерь как о графике функции потерь. L(θ) обозначает функцию потерь.

Предположим, что θ является двумерным: вы можете представить себе график, где горизонтальные оси — это θ1 и θ2. Вертикальная ось представляет функцию L(θ). Просто взглянув на него, вы поймете, где находится лучший θ: он прямо там, на дне чаши.

Мы хотим найти направление, в котором функция потерь уменьшается, и сдвинуть наш θ в этом направлении. Очень общий набросок алгоритма будет таким:

Найдите направление v так, чтобы L(θ) уменьшалось, а затем выберите новое θ как старое θ плюс некоторую скорость α, умноженную на v. α — это небольшая константа, которая также называется скоростью обучения. Затем мы повторяем этот процесс достаточное количество раз, и мы надеемся, что остановимся на минимуме.

Интересно, что направление наискорейшего спуска на самом деле не всегда является лучшим выбором, но что объединяет все эти методы, так это то, что все они будут двигаться в направлении, где L(θ) уменьшается по крайней мере локально.

С математической точки зрения мы представляем направление как вектор с n элементами, указывающими направление, в котором L(θ) будет уменьшаться. Легче визуализировать в 1D:

Если я нахожусь в определенной точке, наклон функции в этой точке говорит мне, в каком направлении он убывает. Если наклон отрицательный, функция убывает вправо; если наклон положительный, функция убывает влево:

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

Что такое склон? Наклон - это просто производная. Все, что мы делаем, это просто устанавливаем направление для каждого измерения как отрицательное значение частной производной нашей функции L по отношению к этому измерению θ.

Тогда в 2D V1:

V2 is:

Это не единственные направления, в которых θ уменьшается: на самом деле существует целое полупространство векторов, которое кодирует направления, в которых θ уменьшается. Итак, эти частные производные дают вам направление наибольшего убывания, но если вы пойдете немного влево или вправо, θ все равно будет уменьшаться. Конечно, если вы пойдете в обратном направлении, θ будет увеличиваться. Пока у вас есть положительное скалярное произведение с v, вы будете двигаться в направлении, где L (θ) уменьшается. Это означает, что это не уникальная структура степеней: это самая крутая, что не обязательно означает, что она лучшая.

Для этого очень полезна концепция, называемая градиентом, символом которой является ∇. ∇ — это вектор; каждое измерение этого вектора является частной производной нашей функции по соответствующему измерению θ.

Вот набросок алгоритма градиентного спуска:

  1. Вычислить шаг градиента
  2. Установите θ равным:

Поэтому мы выбрали наш оптимизатор.

Минимизация эмпирического риска

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

Давайте вернемся к проигрышу ноль-один, который присваивает 0 проигрышу за правильную классификацию и 1 за неправильную классификацию.

Вы можете думать о риске как о вероятности того, что вы ошибетесь. Вспомните наш генеративный процесс: кто-то делает снимок. Это случайно выбранная картинка из раздачи картинок. У него есть метка, выбранная из истинного распределения меток. Учитывая эту картину, какова вероятность того, что мы поймем ее правильно или неправильно?

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

Если мы используем отрицательное логарифмическое правдоподобие, мы могли бы сказать, что риск — это ожидаемое значение при истинном распределении по x и истинном распределении по y потерь, которые мы получим для этих x и y и нашего изученного θ:

Риск — это то, что мы хотим минимизировать. Однако мы не можем рассчитать истинный риск, потому что мы не можем получить бесконечные выборки. Вместо этого нам дан набор данных D. Таким образом, мы можем рассчитать эмпирический риск, который представляет собой выборочную оценку истинного риска. Вы получаете эту оценку на основе выборки, усредняя свои потери по всем вашим выборкам.

Является ли эмпирический риск хорошим приближением к истинному риску? Есть две ситуации, когда у нас могут возникнуть проблемы.

  1. Переоснащение происходит, когда эмпирический риск низок, но истинный риск высок. Это означает, что ваши средние потери в тренировочном наборе невелики, но истинное ожидаемое значение ваших потерь не является низким.

Допустим, я пытаюсь подогнать эту линию, а точки соответствуют моим тренировочным данным:

Полином высокой степени идеально соответствует точкам и приводит к очень низкому эмпирическому риску, поскольку он проходит прямо через точки. Тем не менее, его истинный риск будет довольно высоким. Это может произойти (1) если набор данных слишком мал или (2) если модель слишком мощная и имеет слишком много параметров.

2. Недостаточное соответствие происходит, когда эмпирический риск соответствует истинному риску, но оба они высоки.

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

Это может произойти (1) если в модели слишком мало параметров или (2) если ваш оптимизатор настроен неправильно.

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

Регрессия в основном представляет собой подбор кривой:

У вас есть непрерывные или дискретные входные данные, у вас есть непрерывный вывод, и вы хотите предсказать этот вывод. Например, вы предсказываете, насколько велика собака, сколько она весит, насколько дорог дом и так далее.

Если мы хотим применить вероятностный подход, для проведения регрессии нам потребуется распределение вероятностей по непрерывному значению для вывода y. Есть много вероятностных распределений, которые мы могли бы использовать для представления действительных чисел, но очень простой выбор — это нормальное распределение или распределение Гаусса.

Нормальное распределение определяется двумя параметрами: средним значением и дисперсией. Но, конечно же, мы хотим решить проблему прогнозирования — мы хотим предсказать распределение по y при заданном x. В нашем случае у нас будет условное гауссово распределение; другими словами, среднее значение и, возможно, дисперсия этого гауссовского распределения сами по себе являются функциями x. Логарифмическая вероятность нормального распределения может быть определена как:

Это похоже на большое уравнение, но на самом деле оно состоит из трех простых вещей:

  • Первый член — это просто разница между f θ(x) и y, но при определенной норме, определяемой сигмой. Насколько близко среднее значение f θ(x) к истинному значению y.
  • Второй член (отрицательная 1/2 логарифма определителя сигмы) пытается уменьшить дисперсию. Он говорит следующее: если вы можете точно подобрать истинные значения y, попробуйте использовать меньшую дисперсию.
  • Третий член — это константа, которая не зависит от x или y.

Если мы не будем изучать сигму и зафиксируем ее как единичную матрицу, то нормальное распределение сведется к квадрату разницы между f θ(x) и y плюс некоторая константа:

Часто, когда мы говорим о регрессии, конкретное распределение, которое мы выбираем, является нормальным распределением, но мы выбираем сигму как тождество, и тогда логарифм этого нормального распределения просто соответствует квадрату разницы между f θ(x) и у.

После этого упрощения это то же самое, что и потеря среднеквадратичной ошибки (MSE):

Потеря MSE также является логарифмической вероятностью для нормального распределения с сигмой ковариации, установленной на идентичность. Если у вас есть линейная модель, то минимизация потерь MSE просто соответствует обычной линейной регрессии. Таким образом, анализ в настоящей статье будет посвящен MSE, но имейте в виду, что его можно распространить и на другие потери.

Теперь давайте попробуем более формально понять переоснащение и недообучение. Здесь возникает вопрос: как изменяется ошибка для разных обучающих наборов?

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

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

Когда я подгоняю свою модель к этому тренировочному набору, я получаю оранжевую кривую:

Здесь эмпирический риск низкий потому, что оранжевая кривая проходит прямо через синие круги. Однако истинный риск высок, потому что оранжевая кривая сильно отличается от черной кривой. Это переоснащение.

Теперь представьте, что у меня есть другой набор синих точек. У меня такая же истинная функция, задача обучения не изменилась, распределение по x и y тоже не изменилось. Единственная разница в том, что у меня другой набор данных, взятый из того же дистрибутива.

Если вы посмотрите на второй и третий наборы данных, вы можете получить следующие полиномиальные регрессии:

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

С другой стороны, если вы недостаточно приспособлены, ваш эмпирический риск высок, и ваш истинный риск высок. Вы получаете форму, которая не будет сильно различаться между различными тренировочными наборами:

Фактически, если вы объедините все эти три набора данных вместе:

Линейная подгонка к этому агрегированному набору данных, вероятно, будет очень похожа на подгонку к отдельным наборам данных:

Если мы недообучаемся, это означает, что наша модель недостаточно мощная; у него недостаточно свободных параметров, чтобы точно соответствовать нашей функции, и он не использует всю информацию, присутствующую в обучающем наборе. Таким образом, по мере изменения тренировочной выборки она не будет сильно меняться, потому что не извлекает всю информацию.

Напомним, в переоснащении:

(1) Обучающие данные подобраны очень хорошо (2) Истинная функция подобрана плохо (3) Выученные функции каждый раз будут выглядеть по-разному

В недооснащении:

(1) Обучающие данные плохо подобраны (2) Истинная функция плохо подобрана (3) Выученная функция выглядит похожей, даже если мы соберем вместе все наборы данных

Теперь давайте сделаем это более математически точным. Мы собираемся записать ожидаемое значение ошибки с учетом распределения по наборам данных. Итак, мы не берем ожидаемое значение относительно x или y, мы собираемся принять ожидаемое значение относительно набора данных D. Если мы можем записать вероятность D:

Тогда мы можем взять ожидаемое значение функции D:

Каково ожидаемое значение ошибки между fθ(x) и y?

Здесь fθ(x) обучается на D, но D — случайная величина. Это не выражение, которое измеряет среднюю ошибку для конкретного fθ(x), оно фактически измеряет ожидаемую ошибку вашего обучающего алгоритма. Для каждого значения D вы получаете разное значение fθ(x): на самом деле это функция от D. Теперь D входит в ваш алгоритм обучения, выдает θ, а затем мы рассмотрим его ошибку.

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

Приведенное выше уравнение также можно записать в виде:

F(x) — истинная функция; fD(x) — это функция, которую я получаю при обучении D.

Как вы, возможно, знаете, ожидаемое значение некоторой функции — это просто сумма всех значений вашей случайной величины, вероятности этого значения, умноженной на значение этой функции. В этом случае ожидаемое значение представляет собой сумму по всем возможным наборам данных вероятности этого набора данных, умноженной на квадрат разницы между fD(x) и f(x).

Затем мы можем взять это ожидаемое значение ошибки и разбить его на два члена, возложив ответственность на эти члены. Мы можем обвинить одного из них в переоснащении, а другого — в недостаточном.

Для небольшого удобства обозначений я буду использовать f bar (x) для обозначения ожидаемого значения fD(x). Важно отметить, что f bar (x) — это среднее значение по всем возможным функциям, которые вы могли бы получить для всех возможных наборов данных, взвешенных по вероятности этих наборов данных.

Это не означает, что fD(x) — это то, что вы получили бы, если бы у вас были бесконечные данные, потому что каждое отдельное fD(x) по-прежнему соответствует определенному обучающему набору конечного размера, но вы усредняете их прогнозы вместе.

Теперь мы возьмем это уравнение для ожидаемой ошибки и внутри квадрата вычтем f bar и добавим f bar.

Затем я заключаю в скобки первые два термина и скобки вокруг третьего и четвертого терминов.

Теперь я собираюсь оценить квадрат этой величины:

Третий член в конечном итоге сокращается, потому что этот член:

Не зависит от D. Следовательно, в силу линейности математического ожидания мы можем его вынести. Кроме того, этот термин:

Также заканчивается равным 0, потому что f bar (x) обозначает ожидаемое значение fD(x); следовательно, ожидаемое значение fD(x) минус f bar (x) равно 0.

Теперь у нас остались первые два члена:

Первый член - это дисперсия. Обратите внимание, что в этом первом члене на самом деле нет f(x), так что это не зависит от истинной функции; это количественная оценка того, насколько наш прогноз меняется с набором данных. Другими словами, насколько fD(x) различается для разных обучающих наборов?

Во втором члене мы можем полностью исключить математическое ожидание, поскольку f bar (x) минус f (x) не зависит от D. Это просто квадрат разницы между средним значением по всем вашим прогнозам (f bar (x)) и истинная функция f (f(x)). Мы называем это смещением, технически — квадратом смещения. Смещение, по сути, количественно определяет, насколько далеко средний прогноз, который вы получаете от истинной функции.

В этом случае уклон довольно высок. Эта ошибка не исчезнет независимо от того, сколько у нас данных, поскольку она количественно определяет, насколько мы недостаточно параметризованы.

Полная ошибка — это просто дисперсия плюс квадрат смещения. Таким образом, эти два термина объясняют переоснащение и недообучение.

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

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

Регуляризация

Давайте поговорим о том, как мы можем найти компромисс между смещением и дисперсией: ключевая идея нейронных сетей заключается в том, что они используют «слишком сложные» модели — достаточно сложные, чтобы вместить весь шум в данные. Затем нужно «упорядочить» их, чтобы сделать модели достаточно сложными, но не слишком сложными. Чем сложнее модель, тем лучше она соответствует обучающим данным, но если она слишком сложна, то хуже обобщает; он запоминает данные обучения, но менее точен в отношении данных будущих испытаний.

  1. Получение большего количества данных устраняет дисперсию. Чем больше у вас данных, тем меньше вы будете переобучать и тем меньше будет дисперсия. Получение большего количества данных обычно не улучшает предвзятость:

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

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

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

Поэтому нам нужно каким-то образом предоставить алгоритму немного больше знаний; поскольку в данных недостаточно информации, мы можем предоставить ее через функцию потерь. Нам нужно ответить на вопрос: для набора данных D, каков наиболее вероятный вектор параметров θ?

Другими словами, этот вопрос касается ваших знаний о домене. В этом случае у вас могут быть основания полагать, что для вашей задачи лучше подойдет третья функция. Затем байесовская точка зрения пытается сформулировать всю проблему обучения, заключающуюся в том, как вероятностно получить θ из D.

D — это просто набор (xi,yi) кортежей:

Если вам нужно распределение по θ с учетом D, вы можете просто применить определение условной вероятности:

По цепному правилу вероятности мы можем разложить это на множители как:

Это должно прозвенеть. Вероятность D при некотором θ, используя предположение IID, может быть разложена на множители как произведение по всем вашим точкам данных:

А как насчет p(θ)? Это наш приор. Это показывает, насколько вероятно θ априори, даже до того, как вы увидите свой тренировочный набор.

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

С другой стороны, если вы считаете, что остроконечные функции более вероятны, первая функция является лучшим кандидатом.

Допустим, мы идем со второй функцией. Основываясь на этом априорном знании, как нам построить априорное значение таким образом, чтобы сглаженная функция стала более вероятной? Прежде чем ответить на этот вопрос, обратите внимание, что теперь у нас есть другая функция потерь:

Первая часть — это именно та отрицательная логарифмическая вероятность, к которой мы привыкли. Теперь мы просто вычитаем априорную вероятность из отрицательной логарифмической вероятности.

Опять же, мы должны выбрать априор — и это выбор дизайна, который не исходит из данных. Это наше прежнее убеждение.

Другими словами, поскольку полиномы с малыми коэффициентами не приводят к большим всплескам, какое распределение приписывает более высокие вероятности малым числам?

Существует широкий диапазон распределений, из которых мы можем выбирать, но один из очень и очень простых вариантов — использовать нормальное распределение, которое присваивает большую часть вероятностной массы небольшим числам, близким к нулю. Как близко? Это определяется дисперсией. Чем ниже мы выбираем дисперсию, тем менее вероятными становятся функции с пиками.

Вот выражение для логарифмической вероятности нормального распределения со средним значением, равным нулю:

Второе и третье слагаемые не влияют на θ, поэтому мы можем их игнорировать и выразить как постоянное значение. Мы можем переписать этот априор так:

Лямбда — это 1/2 сигма в квадрате, но нас не волнует ее точное значение, так как мы просто оставим его как гиперпараметр — нам нужно будет выбрать его, чтобы алгоритм работал хорошо. Таким образом, наша новая функция потерь:

Это та же самая цель, которую мы преследовали раньше; все, что мы сделали, это добавили квадратичный штрафной член к функции перекрестных потерь энтропии L.

Одним из распространенных альтернативных вариантов является регуляризация L1 (или «LASSO»), в которой используется штраф, представляющий собой сумму абсолютного значения всех весов в DLN, что приводит к следующей функции потерь:

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

Другой регуляризатор, который используется с глубокими нейронными сетями, называется отсевом. При отсеве мы буквально выбрасываем (обнуляем) некоторые нейроны во время обучения. Во время обучения на каждой итерации стандартное отсев обнуляет некоторую часть (обычно 1/2) узлов в каждом слое перед вычислением последующего слоя. Случайный выбор различных подмножеств для исключения вносит шум в процесс и уменьшает переоснащение. Отсев снижает пропускную способность модели во время обучения, и, следовательно, при использовании отсева обычно используются более широкие сети. Если вы используете отсев со случайной вероятностью 0,5, вам может понадобиться удвоить количество скрытых нейронов в этом слое.

Зачем много слоев?

Проблема XOR — это проблема использования нейронной сети для прогнозирования выходных данных логических элементов XOR при наличии двух двоичных входных данных. Функция XOR должна возвращать истинное значение, если два входных параметра не равны, и ложное значение, если они равны. XOR — это задача классификации, для которой ожидаемые результаты известны заранее. Персептрон, представляющий собой сеть с прямой связью, способен разделять точки данных только одной линией. Это печально, потому что входы XOR не являются линейно разделимыми. Решение этой проблемы состоит в том, чтобы выйти за рамки одноуровневой архитектуры, добавив дополнительный уровень модулей без прямого доступа к внешнему миру, известный как скрытый уровень. Этот тип архитектуры представляет собой еще одну сеть с прямой связью, известную как многослойный персептрон (MLP). В целом, глубокие нейронные сети часто удивительно эффективны в изучении сложных функций без ущерба для обобщения. И основная интуиция, лежащая в основе глубокого обучения, заключается в том, что глубинные сети черпают свою силу из изучения внутренних представлений.

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

Как мы выбираем гиперпараметры?

Идея состоит в том, что мы берем набор данных и разделяем его на набор для обучения и набор для проверки. Как правило, обучающий набор может составлять 80/90% набора данных и используется для выбора (1) параметров θ (путем оптимизации) и (2) тех гиперпараметров, которые влияют на оптимизацию, таких как скорость обучения.

Это потери при обучении, и они зависят от поезда D:

С другой стороны, проверочный набор не используется для обучения; мы будем использовать его, чтобы сделать любой другой выбор, который необходим для разработки вашего метода. В частности, он используется для выбора (1) класса модели (2) гиперпараметров регуляризатора (3) используемых функций. Потеря валидации:

Эта потеря позволит нам диагностировать переоснащение и недообучение. Почему? Потому что потеря при обучении — это ваш эмпирический риск, а потеря при проверке использует данные, которые не использовались для обучения, и, таким образом, является объективной оценкой истинного риска. Сравнивая эти две потери, вы можете определить, переоснащаетесь вы или нет.

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

1 Тренируйте параметры θ на тренировочном наборе.

Если тренировочные потери недостаточно низки, вы недооцениваете. Чтобы уменьшить недообучение, вы можете (1) уменьшить регуляризацию (2) улучшить свой оптимизатор.

Если потери при обучении низкие:

2 Посмотрите на потери проверки. Если потери при проверке выше, чем в тренировочном наборе, вы, вероятно, переобучаетесь. Чтобы уменьшить переоснащение, вы можете (1) увеличить объем регуляризации (2) пойти и получить больше данных

Как только вы это сделаете, вы можете повторить процесс.

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

Например, это стереотипное переоснащение:

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

Это стереотипное недообучение:

Потери при валидации и потери при обучении уменьшаются с одинаковой скоростью, но не так сильно, как хотелось бы. Этот пробел в конце - предвзятость.

При рассмотрении случая переобучения возникает одна мысль: имеет ли смысл попасть в золотую середину до того, как ошибка валидации увеличится и остановит наш процесс оптимизации? Фактически, это называется ранней остановкой. Когда валидация последовательно повышалась в течение последних n итераций, вы просто перематываете n шагов назад и берете то, что у вас было тогда.

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

Теперь предположим, что вы следовали этому рецепту: у вас нет недообучения, у вас нет переоснащения, все ваши потери в порядке. Как вы оцениваете окончательную производительность вашей модели?

Мы больше не можем полагаться на беспристрастность потерь проверки, потому что мы использовали их для выбора (1) класса модели (2) гиперпараметров регуляризатора (3) какие функции использовать. Мы также не можем полагаться на потери при обучении, потому что мы использовали их для выбора (1) параметров θ (через оптимизацию) и (2) тех гиперпараметров, которые влияют на оптимизацию, таких как скорость обучения.

Мы могли бы зарезервировать еще один фрагмент набора данных для финального экзамена. Мы назовем его тестовым набором и будем использовать только для отчета об окончательной производительности. Поэтому вместо того, чтобы разбивать наш набор данных на две части, мы разделим его на три части.

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

  • Поиск по сетке: попробуйте все возможные комбинации гиперпараметров
  • Случайный поиск: попробуйте разные комбинации гиперпараметров случайным образом.
  • Координатно-градиентный спуск: начните с одного набора гиперпараметров и пытайтесь менять их по одному, принимайте любые изменения, которые уменьшат вашу ошибку проверки.
  • Байесовская оптимизация/автоматическое машинное обучение: начните с набора гиперпараметров, которые хорошо зарекомендовали себя в аналогичной задаче, а затем проведите какое-то локальное исследование (например, градиентный спуск) оттуда.

Есть много вариантов, например, какой диапазон исследовать, какой параметр оптимизировать в первую очередь и т. д. Некоторые гиперпараметры не имеют большого значения (люди используют отсев 0,5 или 0, но не более того). Другие могут иметь гораздо большее значение (например, размер и глубина нейронной сети). Ключ в том, чтобы увидеть, что работало над похожими проблемами.

Оптимизация в глубоком обучении

Мы поговорим об алгоритмах оптимизации. Давайте начнем с резюме градиентного спуска.

Мы можем сформулировать задачу изучения θ как нашу попытку найти θ, которое максимизирует log p(D):

Часто мы увидим, что это написано как минимизация (это просто соглашение):

Мы по-прежнему хотим максимизировать логарифм p(D), что равносильно тому, что мы хотим минимизировать отрицательную логарифмическую вероятность. Итак, это наша функция потерь, которая количественно определяет, насколько плох θ. Чтобы найти настройку θ, которая минимизирует нашу функцию потерь, мы должны найти направление v такое, что L(θ) уменьшается, а затем выбрать новое θ как старое θ плюс некоторую скорость α, умноженную на v. Затем мы повторяем этот процесс. достаточно раз. В общем, мы устанавливаем направление для каждого измерения как отрицательное значение частной производной нашей функции L по отношению к этому измерению θ.

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

Мы визуализируем путь оптимизации для градиентного спуска с помощью линий. Конечная точка каждого оранжевого сегмента представляет собой значение θ на одной из итераций градиентного спуска; сегмент между этими конечными точками представляет собой шаг (который представляет собой направление градиента, умноженное на скорость обучения).

Выпуклые функции, как правило, довольно хорошо оптимизируются. Вот функция в одном измерении:

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

Если я нарисую линию между двумя точками, она пересечет график. Эта функция также имеет два оптимума. В общем случае функция может быть невыпуклой, даже если она имеет только один оптимум. Например, эта функция имеет только один оптимум, но, поскольку она также имеет плато, если я проведу линию между этими двумя точками, она пересечет график:

Таким образом, выпуклая функция означает, что она имеет только один оптимум, а невыпуклая функция не означает, что она имеет более одного оптимума.

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

Мы видим, что она уходит от оптимума.

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

В этот момент, если мы вычислим направление наискорейшего спуска, мы пойдем другим путем:

А потом в другую сторону:

В конце концов это достигнет оптимума, но это может занять довольно много времени.

Как насчет поверхности потерь нейронной сети?

Нейронная сеть — это более сложная функция со многими параметрами и множеством уровней представления:

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

Однако в целом потери выглядят так:

В целом, ландшафты потерь нейронных сетей определенно не являются выпуклыми.

О каких географических особенностях ландшафта убытков нам следует беспокоиться?

Есть три особенности, которые важно знать.

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

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

Вы можете визуализировать это на следующем графике:

По горизонтальной оси отложено численное значение потерь для решения, обнаруженного градиентным спуском. Это гистограмма, поэтому вертикальная ось показывает, сколько раундов градиентного спуска, каждый из которых начинается с другого случайного места, привели к этой потере. Это своего рода график частоты прогонов, которые привели к этой потере. Разные цвета представляют сети разного размера; большие числа здесь соответствуют большим сетям с большим количеством параметров.

Грубо говоря, для самых маленьких сетей, показанных желтым цветом, существует широкое распределение различных значений потерь. Он еще не такой широкий, все цифры между 0,08 и 0,10, но там есть некоторая вариативность, а значит оптимизация попадает в локальные оптимумы с диапазоном разных значений потерь. Но по мере того, как сети становятся все больше и больше, частота сетей с разными потерями становится все меньше; для красных, самых больших сетей, вы можете видеть, что потери все еще имеют некоторую изменчивость, но решения сгруппированы гораздо более плотно в диапазоне от 0,072 до 0,078, примерно.

Это означает, что более крупные сети имеют локальные оптимумы, но все эти локальные оптимумы довольно похожи друг на друга, и все они довольно хороши.

Переходим ко второму вопросу:

Мы могли бы представить ландшафт потерь, в котором у нас нет локального оптимума, но есть большая область, где градиент потерь очень мал. Если бы не эти плато, мы могли бы просто использовать небольшие скорости обучения и могли бы медленно и неуклонно продвигаться к оптимуму. Но когда эти плато действительно плоские, небольшая скорость обучения может означать, что вам, по сути, понадобится вечность, чтобы преодолеть плато. Таким образом, вам нужны скорости обучения, которые достаточно велики, чтобы не застрять на плато, подобном этому.

Третий вопрос:

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

Вот где наша интуиция о функциях, основанная на размышлениях о низких измерениях, очень обманчива.

Что такое критическая точка?

Критическая точка — это любая точка, где градиент равен нулю, что означает, что все частные производные потерь по каждому измерению θ равны 0:

Существуют различные типы критических точек; для данной критической точки это максимум, минимум или седловая точка? Способ, который вы знаете, - это посмотреть на вторые производные.

В 1D, если вы находитесь в критической точке, а вторая производная положительна, это означает, что график имеет наклон вверх. Это означает, что у вас есть локальный минимум. С другой стороны, если вторая производная отрицательна, то это означает, что вы находитесь на локальном максимуме. Но в более высоких измерениях некоторые вторые производные могут быть положительными, а некоторые — отрицательными.

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

Градиент — это вектор, в котором каждая запись является частной производной по соответствующему размеру тета; гессиан - это матрица, в которой каждая запись является второй производной по отношению к двум записям тета.

Матрица Гессе в седловой точке будет иметь диагональные элементы, равные 1 для одних измерений и -1 для других.

Точка является максимальной или минимальной, только если все диагональные элементы имеют одинаковый знак. В противном случае это седловая точка. Представьте, что происходит в более высоких измерениях: как часто все сотни диагональных элементов имеют один и тот же знак? В более высоких измерениях маловероятно, что каждая отдельная диагональная запись будет положительной или каждая диагональная запись будет отрицательной. Итак, большинство критических точек в более высоких измерениях являются седловыми точками.

Направления улучшения

Существуют ли методы, которые ускоряют градиентный спуск, не требуя вторых производных (что потребовало бы очень длительного времени выполнения)?

1 Momentum — это именно то, на что это похоже. Если мы вспомним эту картинку раньше:

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

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

Правило обновления, которое мы собираемся использовать с импульсом, очень похоже на градиентный спуск. Разница в том, что мы также берем предыдущее направление и умножаем его на коэффициент затухания от 0 до 1:

Если предыдущее направление и новое указывают в противоположных направлениях, они отменяются. Если они указывают в одинаковых направлениях, они на самом деле складываются вместе и движутся в этом направлении еще быстрее. Momentum приносит множество преимуществ практически бесплатно.

Следующая проблема, о которой я собираюсь поговорить, — это масштаб градиента. Знак градиента важен, потому что он говорит нам, указывает ли градиент влево или вправо; но величина, которая говорит нам, как далеко она простирается по каждому измерению, гораздо менее информативна.

Хуже того, общая величина градиента может резко измениться в ходе оптимизации, что может затруднить настройку скорости обучения. Объяснение состоит в том, что чем больше f θ(x) отличается от y, тем больше будет градиент. Итак, если изначально f θ(x) сильно отличается от y, то ваши градиенты будут огромными, и когда ваши градиенты станут такими огромными, вы почти наверняка превысите оптимум и выйдете с другой стороны. Но по мере того, как ваша функция f θ(x) становится все ближе и ближе к y, градиент будет становиться крошечным, и в этот момент вы можете делать очень маленькие шаги, и вам может потребоваться более высокая скорость обучения.

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

RMSProp — один из алгоритмов, который делает это. Идея состоит в том, чтобы оценить величину градиента по измерению в среднем:

На каждом этапе мы будем брать старое значение Sk и умножать его на некоторый коэффициент β (коэффициент от 0 до 1). Затем мы возьмем 1-β и умножим его на квадрат каждой записи в градиенте. Это примерно квадрат длины каждого измерения.

Когда мы обновляем θ, мы делаем шаг в направлении градиента, но делим его на квадратный корень из Sk, который является его средней квадратичной длиной. Таким образом, каждое измерение делится примерно на его величину:

3 AdaGrad — это более теоретически обоснованная версия RMSProp. С помощью AdaGrad мы также оцениваем величины по измерениям, но оцениваем их кумулятивно.

Как видите, он точно такой же, как RMSProp, но без β и 1-β. Это означает, что Sk будет становиться все больше и больше.

Итак, как сравнить AdaGrad и RMSProp?

В AdaGrad есть несколько очень привлекательных гарантий для выпуклых задач. Однако для невыпуклых задач тот факт, что мы продолжаем делить на все большие и большие Sk, означает, что скорость обучения со временем уменьшается, и это может быть проблемой. AdaGrad работает только в том случае, если вы быстро найдете оптимум, прежде чем Sk станет настолько большим, что ваши обновления станут крошечными.

С другой стороны, RMSProp имеет тенденцию работать немного лучше, чем AdaGrad, для невыпуклых задач, потому что, в отличие от AdaGrad, он забывает. Со временем тот факт, что вы умножаете Sk-1 на β, означает, что алгоритм забудет градиенты, которые он видел давным-давно.

Адам сочетает импульс и RMSProp. Мы используем импульс для ускорения градиентного спуска и делим на скользящее среднее квадратов длин, чтобы нормализовать длины.

Первый шаг, аналогичный импульсу:

Подобно импульсу, мы накапливаем наши градиенты по мере продвижения. Вы можете думать о mk (где «m» означает среднее значение) как о скользящем среднем градиентах.

Второй шаг:

vk — вторая оценка момента: также скользящее среднее, но квадратов длин градиента.

Третий шаг, когда мы инициализируем m0 и v0 равными нулю, мы хотим, чтобы шаги, которые мы предпринимаем, были маленькими в начале и большими с течением времени:

Четвертый шаг, наше обновление:

Стохастическая оптимизация

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

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

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

Если вы рассматриваете ожидаемое значение при распределении данных логарифмической вероятности y при заданном x, сумма, которую вы фактически вычисляете для своего убытка, является просто оценкой, основанной на выборке. И оценка ожидаемого значения на основе выборки является несмещенной для любого количества выборок. Если вы используете меньшее количество выборок, вы получите худшую оценку, но это все равно непредвзятая оценка.

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

Конечно, если мы просто выберем эти образцы один раз и будем использовать их для всего процесса оптимизации, это довольно расточительно, потому что мы не используем большую часть наших данных. Что мы можем сделать, так это выбрать другое подмножество размера B для каждого отдельного шага градиента, и таким образом наш предполагаемый градиент с небольшим количеством выборок будет выглядеть как истинный градиент — с некоторым шумом, потому что просто случайно каждый шаг градиента получит немного другую группу образцов B.

Стохастический градиентный спуск с мини-пакетами

1 Образец подмножества B всего вашего тренировочного набора D

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

3 Используйте градиентный спуск с приблизительным градиентом. Конечно, вы также можете использовать моментум, ADAM и т. д.

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

Случайная выборка каждого шага довольно медленная из-за случайного доступа к памяти. Если у вас очень сложные данные, они будут располагаться в памяти последовательно; поэтому, если вы каждый раз индексируете случайное изображение, то вы выполняете много случайного доступа к памяти, что нарушит когерентность вашего кеша. Вместо этого вы можете заранее перетасовать наш набор данных, точно так же, как вы перетасовываете колоду карт, а затем последовательно индексировать этот набор данных. Технически это не совсем то же самое, что и обычный градиентный спуск, потому что в начале вы перемешиваете только один раз, но порядок, в котором вы перебираете пакеты, является случайным, поэтому на практике это оказывается почти таким же хорошим, как и настоящий стохастический оценочный спуск и намного дешевле в вычислительном отношении.

Оценка обучения

Как мы можем настроить скорость обучения для этих алгоритмов?

На этом графике вертикальная ось — это потери при обучении; горизонтальная ось — это эпоха — эпоха — это то, сколько раз вы прошли через весь набор данных. При обычном градиентном спуске количество эпох равно количеству шагов оптимизации; со стохастическим градиентным спуском количество эпох равно общему размеру вашего набора данных, деленному на размер ваших мини-пакетов.

Красная кривая показывает, что вы ожидаете увидеть в потерях при обучении, если бы у вас была хорошо выбранная скорость обучения.

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

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

Снижение скорости обучения

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

Вот график обучения нейронной сети AlexNet на ImageNet:

Он обучается по графику снижения скорости обучения, где скорость обучения время от времени делится на 10. Вы можете видеть, что сначала при скорости обучения 0,01 точность быстро улучшается, а затем она как бы стабилизируется. Скорость обучения снижается еще больше, и теперь ваша точность еще больше улучшается, а затем она стабилизируется. Опять же, скорость обучения снижается, а затем точность улучшается и выходит на плато. В конце концов вы достаточно приблизитесь к локальному оптимуму и перестанете улучшаться. Стоит отметить, что обычно вам не нужно снижение скорости обучения с ADAM.

Настройка стохастического градиентного спуска

Гиперпараметры для настройки:

  • Размер партии B. Обычно большие партии приводят к менее шумным градиентам, что безопаснее, но дороже.
  • Скорость обученияα — довольно сложный выбор. Как правило, лучше использовать самую большую скорость обучения, которая работает. Вы могли бы хотеть распасться это в течение долгого времени.
  • Для импульса μ хорошими настройками являются 0,90 или 0,99, но вы должны настроить его в соответствии с вашей задачей.
  • параметры ADAM β1 и β2; обычно оставьте значения по умолчанию.

Как настроить эти гиперпараметры? Технически вы хотите настроить эти параметры на потери при обучении, потому что, в отличие от регуляризации, они не пытаются дать вам лучшее обобщение для новых точек.

Однако на практике гиперпараметры стохастического градиентного спуска часто также настраиваются на проверочном наборе, и это довольно интересный выбор. Между стохастическими градиентами и регуляризацией существует сложная взаимосвязь; условно мы бы отделили оптимизацию от обобщения. Но в последнее время было проведено довольно много исследований, изучающих, как отличный стохастический градиентный спуск сам по себе может действовать как своего рода регуляризатор. Таким образом, интуиция подсказывает, что если вы начнете переобучение, вы ожидаете, что градиенты будут выглядеть очень по-разному для разных партий, поэтому вы не хотите усреднять их — вы не хотите добираться до самой нижней части вашего ландшафта потерь. Итак, это грубая интуиция, почему некоторые люди думают, что стохастический градиент имеет желаемый эффект регуляризации, и поэтому, когда вы настраиваете гиперпараметры стохастического градиентного спуска, вы на самом деле должны смотреть на потерю проверки.

Расчетные графики для глубокого обучения

Все алгоритмы машинного обучения представляют собой программу с некоторыми параметрами. Один из способов визуализировать эту программу — нарисовать график, представляющий поток вычислений.

Вот как выглядит график вычислений:

Этот график вычислений представляет собой линейную регрессию. Мы вычисляем x1, умноженное на θ1, плюс x2, умноженное на θ2; затем мы вычитаем y и возводим в квадрат разницу, и это дает нам потерю среднего квадрата ошибки (MSE). Как правило, когда мы рисуем графики вычислений для нейронных сетей, мы рисуем график в сочетании с функцией потерь.

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

Давайте нарисуем тот же график линейной регрессии, что и раньше, но гораздо проще, используя векторы:

Теперь у нас есть узел x, представляющий вектор из двух элементов x1 и x2; у нас также есть узел θ, который представляет θ1 и θ2; после умножения двух векторов мы вычитаем из них метку y и возводим в квадрат разницу. Это точно такой же график вычислений, который я показывал ранее, только записанный гораздо более лаконично с использованием векторной записи.

Аналогичным образом, мы можем посмотреть на этот сложный график для логистической регрессии:

И сделать это проще, используя матрицы. Мы можем записать логистическую регрессию в матричной записи:

Как некоторая матрица θ, умноженная на вектор x. Таким образом, наш график вычислений становится следующим:

Мы могли бы изобразить логистическую регрессию еще более лаконично. Обратите внимание, что у нас есть два типа переменных в этих вычислениях. У нас есть переменные данных, такие как x и y, которые являются входными и целевыми выходными данными; и у нас есть параметры (θ), которые обычно влияют на одну конкретную операцию (хотя иногда есть некоторое совместное использование параметров). Если мы заметим эти свойства, мы сможем еще немного обобщить этот комбинированный график. Поскольку мы знаем, что θ будет влиять только на то, что происходит в одном месте, мы можем думать о нем как о параметре этого умножения и сложить его в узел умножения. Мы могли бы нарисовать ту же картину, но более кратко, сказав, что x переходит в линейный слой (линейный, потому что это произведение матрицы на вектор), затем он переходит в softmax, а затем softmax переходит в функцию потерь:

Теперь у нас есть эта сильно упрощенная диаграмма графа вычислений, и мы можем сравнить ее с другими распространенными способами визуализации нейронных сетей.

Обычный способ нарисовать диаграмму нейронной сети — визуализировать переменные в сети в виде прямоугольников:

Первая переменная, с которой все начинается, — это вход x; затем у нас есть линейный слой, который по умолчанию имеет параметры; этот линейный слой преобразует x в некоторое промежуточное представление, которое мы будем называть z(1). z(1) — это результат применения первого линейного слоя — верхний индекс означает, что это первый слой. Затем мы берем это значение z(1) и передаем его в softmax.

Даже упрощенный рисунок будет таким:

Здесь мы отсекаем потери, потому что знаем, что потери перекрестной энтропии всегда следуют за softmax. Мы также удалим зеленые трапеции и вместо этого просто пометим стрелку. Мы также удаляем параметры, потому что линейные слои всегда имеют параметры. Такой график вы очень часто будете видеть в научных статьях.

Обратное распространение шаг за шагом

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

Что нам действительно нужно, так это алгоритм, который может автоматически вычислять производные с помощью нейронных сетей, без необходимости выполнять все вычисления вручную. Затем мы могли бы реализовать любую нейронную сеть, какую захотим, запустить этот алгоритм и получить градиент. Этот алгоритм называется обратным распространением.

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

Если у вас есть выражение:

Это означает, что у вас есть вход x, вы применяете функцию g к x, получаете y и применяете другую функцию f к y. В конце концов, вы получаете z.

Допустим, вы хотите продифференцировать все это выражение по x. Цепное правило говорит вам, что производная определяется произведением производной от g на производную от f:

Точно такое же выражение применимо и к многомерным функциям — нам просто нужно быть немного осторожными с порядком, потому что порядок скалярного умножения является заменяемым, в то время как то же самое не верно для матричного умножения.

Справедливо то же цепное правило: dz/dy — вектор, а dy/dx — матрица.

Давайте посмотрим на эту нейронную сеть:

Мы могли бы использовать цепное правило, чтобы записать, например, производную потерь по W(2). Это равно производной z(2) по W(2), умноженной на производную потерь по z(2):

Точно так же мы можем выписать производную по W(1):

Вот как мы могли бы попытаться сделать что-то более эффективно. Все эти якобианы в середине будут примерно nxn, но самый последний якобиан всегда будет примерно nx1, потому что потеря всегда является скалярной величиной:

Итак, эти продукты в середине довольно дороги, потому что они будут стоить O(n³), в то время как последний продукт довольно дешев, потому что он всегда будет примерно O(n²).

Вот как мы можем использовать эту интуицию для создания гораздо более эффективного алгоритма. Идея состоит в том, что мы начнем справа, чтобы наши вычисления всегда оставались равными O(n²). Сначала вычисляем:

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

И снова начинаем справа. Мы вычисляем:

Это матрица, умноженная на вектор, и ее легко вычислить. Мы называем это гамма (γ). И наконец:

Опять же, этот последний продукт дешев.

Основываясь на этой интуиции, мы можем вывести классический алгоритм обратного распространения ошибки. Допустим, у нас есть эта сеть:

Во-первых, мы собираемся сделать прямой проход и вычислить все a(i) и z(i). Затем мы выполним обратный проход, и это то, к чему относится обратное распространение. Сначала мы инициализируем наш дельта-вектор как производную потерь по последнему z. Затем мы перейдем от конца сети обратно к началу и для каждой функции (как линейного, так и нелинейного слоев) с входными данными xf и параметрами θf мы собираемся выполнить эту операцию:

И обновите дельту следующим образом:

Внутренние представления

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

  1. Обучение через градиентный спуск кажется очень похожим на эволюцию динамической системы. Оба они описываются набором дифференциальных уравнений. Динамические системы часто имеют «постоянную времени», которая описывает скорость изменения, аналогичную скорости обучения, только вместо времени градиентный спуск развивается через эпохи. Сакс и др. (2013) показали, что для анализа и понимания нелинейной динамики обучения глубокой LNN мы можем использовать разложение по сингулярным значениям (SVD) для разложения матрицы весов на ортогональные векторы, где ортогональность векторов обеспечит их индивидуальность (независимость). . Это означает, что мы можем разбить глубокую широкую LNN на несколько глубоких узких LNN, поэтому их активность не зависит друг от друга. Краткое введение в SVD: любую матрицу A с действительным знаком можно разложить на 3 матрицы:

A=UΣV⊤

где U — ортогональная матрица, Σ — диагональная матрица и V — снова ортогональная матрица. Диагональные элементы Σ называются сингулярными значениями.

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

Практическая реализация

Прежде чем мы сможем обучить его обратному распространению и градиентному спуску, нам нужно ответить на несколько вопросов о нашей нейронной сети:

  • Сколько слоев должно быть в сети?
  • Насколько велики эти слои?
  • Какой тип функции активации мы должны использовать?

Что касается функций активации, особенно популярным выбором является то, что называется выпрямленной линейной единицей или ReLU. ReLU берет предыдущую активацию, скажем, z(1), и устанавливает a(1) как максимальное значение между нулем и соответствующей записью z(1):

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

  • Это чрезвычайно дешево в вычислительном отношении для реализации, а это означает, что для очень больших нейронных сетей это может значительно ускорить их работу.
  • Его производные действительно просты, и это также делает их вычислительно эффективными и с ними легче работать.

Еще одна деталь, о которой мы должны поговорить при реализации нейронных сетей, — это термин смещения. Это классический линейный слой:

Но этот слой имеет серьезное ограничение. Представим, что a(i) — это вектор нулей. В этом случае линейный слой никогда не сможет выдать результат, отличный от нуля, а это значит, что вы всегда будете иметь нули везде. Такая нейронная сеть никогда не может представлять определенные функции — это не универсальный аппроксиматор функций.

Решением этой проблемы является добавление вектора смещения, который превращает линейные слои в это:

Его также называют аффинным слоем. Оказывается, если у вас есть нейронная сеть с такого рода слоями и нелинейностью, такой как сигмоид, пока эти слои достаточно велики, вы можете аппроксимировать любую функцию с произвольным уровнем точности. Вы не можете этого сделать, если у вас нет предубеждений. Итак, теперь параметры вашей нейронной сети будут состоять из весовых матриц W1 и W2 и векторов смещения b1 и b2.

Что еще нам нужно, чтобы реализовать обратное распространение? Нам нужно уметь вычислять:

И:

Автоградация PyTorch

Фреймворки глубокого обучения, такие как PyTorch, JAX и TensorFlow, поставляются с очень эффективным и сложным набором алгоритмов, широко известных как автоматическое дифференцирование. AutoGrad — это механизм автоматической дифференциации PyTorch.

Все начинается с прямого распространения (pass). PyTorch отслеживает все инструкции по мере того, как мы объявляем переменные и операции, и строит график, когда мы вызываем проход .backward(). PyTorch перестраивает график каждый раз, когда мы итерируем или изменяем его (или, проще говоря, PyTorch использует динамический график).

Для градиентного спуска требуется только наличие градиентов функции стоимости по отношению к переменным, которые мы хотим изучить. Эти переменные часто называют «обучаемыми/обучаемыми параметрами» или просто «параметрами» в PyTorch. В нейронных сетях веса и смещения часто являются обучаемыми параметрами.

В PyTorch, чтобы указать, что определенный тензор содержит обучаемые параметры, мы можем установить необязательный аргумент requires_grad в True. Затем PyTorch будет отслеживать каждую операцию с использованием этого тензора при настройке вычислительного графа. Важно понимать тот факт, что PyTorch может следить за нашими операциями, когда мы произвольно проходим через классы и функции.

Вот в чем заключается все волшебство: в PyTorch Tensor и Function связаны между собой и создают ациклический граф, кодирующий полную историю вычислений. Каждая переменная имеет атрибут grad_fn, который ссылается на функцию, создавшую тензор (за исключением тензоров, созданных пользователем — они имеют None как grad_fn). В этот момент мы запускаем обратный проход для вычисления градиентов, вызывая .backward() для тензора, с которого мы хотим инициировать обратное распространение. Часто .backward() вызывается при убытке, который является последним узлом на графике.

Важные примечания

  • Обучаемые параметры (т. е. тензоры reguires_grad) «заразны». Давайте рассмотрим простой пример: Y = W @ X, где X — тензоры признаков, а W — тензор весов (обучаемые параметры, reguires_grad), вновь сгенерированный выходной тензор Y также будет reguires_grad. Таким образом, любая операция, примененная к Y, будет частью вычислительного графа. Следовательно, если нам нужно отобразить или сохранить тензор, равный reguires_grad, мы должны сначала .detach() извлечь его из графа, вызвав метод .detach() для этого тензора.
  • .backward() накапливает градиенты в листовых узлах (т. е. входных узлах к интересующему узлу). Мы можем вызвать .zero_grad() для потери или оптимизатора, чтобы обнулить все атрибуты .grad (дополнительную информацию см. в autograd.backward).
  • Напомним, что в Python мы можем получить доступ к переменным и связанным с ними методам с помощью .method_name. Вы можете использовать команду dir(my_object) для наблюдения за всеми переменными и связанными методами вашего объекта, например, dir(simple_graph.w).

Сверточные сети, рекуррентные нейронные сети и преобразователи

Сверточные сети

Допустим, у вас есть фотография милого щенка. Вы хотите классифицировать, является ли это изображением щенка, кота, бегемота или жирафа.

Вы можете обучить полносвязную сеть для решения этой задачи:

Однако есть как минимум две проблемы:

1 Не поддается вычислительной обработке

Если мы посмотрим на первый линейный слой в такой сети, мы увидим, что он считывает изображение и создает первый вектор активации. Количество весов в этом линейном слое будет равно количеству активаций в первом скрытом слое z(1), умноженному на количество числовых значений во всем изображении. Для каждого пикселя изображения и для каждого цветового канала нам нужно количество весов, равное количеству выходов.

Скажем:

  • Это изображение 128x128x3 (49,152 числа)
  • Наш первый скрытый слой имеет 64 измерения, что на самом деле является довольно маленьким скрытым слоем.

Даже тогда общее количество параметров в этом первом векторе весов будет 64 умножить на 49,152, что составляет более 3 миллионов:

Это только самый первый слой для этого крошечного 64-мерного скрытого слоя.

2 Неустойчив к сменам

Если фотография щенка сдвинута влево или вправо хотя бы на один пиксель, в сети она будет выглядеть совершенно иначе.

Идея

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

А затем мы извлекли бы некоторые локальные области, такие как уши и носы:

Эти свойства интересны тем, что все они являются локальными объектами: чтобы определить, есть ли край в определенном месте, достаточно посмотреть на близлежащие пиксели.

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

Свертка

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

  • Наш маленький детектор будет называться фильтром (или ядром).
  • Оно будет иметь ту же глубину, что и входное изображение (в данном случае 3 цветовых канала).
  • Поскольку имеется 64 функции, мы будем объединять 64 фильтра для каждой области 3x3x3. Здесь я нарисовал только четыре:

  • У нас есть 64 фильтра для каждой позиции в этом входном изображении, что создает небольшой вектор длиной 64 для каждой области 3x3x3.
  • Мы применим этот фильтр 3x3x3 (27 чисел) к каждому фрагменту изображения и вычислим 64 функции. Теперь 64 умножить на 27 — это всего лишь 1728, а не 3 миллиона. Когда фильтр свертки скользит по входной матрице для слоя, операция свертки создает карту объектов, которая, в свою очередь, способствует вводу следующего слоя.
  • После этого применяем нелинейность к каждому из них, как мы это делали в обычной нейронной сети
  • Теперь у нас есть еще одна коробка, которая имеет глубину 64, а также ширину и высоту 128.

Теперь мы превратили наше изображение 128x128x3 в карту активаций 128x128x64. Как будто каждый пиксель стал вектором из 64 признаков. Как выглядят фильтры?

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

Кроме того, одним из преимуществ сверточных нейронных сетей является то, что сеть учится оптимизировать фильтры посредством автоматического обучения, и поэтому они не зависят от предшествующих знаний и вмешательства человека.

Свертка использует три важные идеи, которые могут помочь улучшить систему машинного обучения: разреженные взаимодействия, совместное использование параметров и эквивариантные представления.

  1. Традиционные слои нейронной сети используют матричное умножение на матрицу параметров с отдельным параметром, описывающим взаимодействие между каждым входным блоком и каждым выходным блоком. Это означает, что каждая единица вывода взаимодействует с каждой единицей ввода. Однако сверточные сети обычно имеют разреженные взаимодействия (также называемые разреженной связностью или разреженными весами). Это достигается за счет уменьшения размера ядра по сравнению с входными данными. Например, при обработке изображения входное изображение может иметь тысячи или миллионы пикселей, но мы можем обнаружить небольшие значимые особенности, такие как края с ядрами, которые занимают только десятки или сотни пикселей.
  2. Совместное использование параметров означает использование одного и того же параметра для более чем одной функции в модели. В традиционной нейронной сети каждый элемент матрицы весов используется ровно один раз при вычислении выходных данных слоя. Он умножается на один элемент ввода, а затем никогда не пересматривается. В качестве синонима совместного использования параметров можно сказать, что сеть имеет связанные веса, потому что значение веса, примененного к одному входу, привязано к значению веса, примененному в другом месте. В сверточной нейронной сети каждый элемент ядра используется в каждой позиции ввода (за исключением, возможно, некоторых граничных пикселей, в зависимости от проектных решений, касающихся границы). Совместное использование параметров, используемое операцией свертки, означает, что вместо изучения отдельного набора параметров для каждого местоположения мы изучаем только один набор.
  3. В случае свертки конкретная форма совместного использования параметров приводит к тому, что слой имеет свойство, называемое эквивариантностью к переводу. Сказать, что функция эквивариантна, означает, что если входные данные изменяются, выходные данные изменяются таким же образом.

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

Объединение

Чтобы уменьшить разрешение слоя, мы возьмем каждый патч 2x2x64 и превратим его в 1x1x64, взяв максимальную активацию для каждого канала в каждом регионе. Почему макс? Интуитивно, если активации на этой карте представляют степень присутствия этой функции, имеет смысл, что мы будем оценивать присутствие этой функции в этом регионе, взяв максимальную активацию. Это также делает его устойчивым к небольшим изменениям перевода: если изображение перемещается на небольшую величину, максимум в каждой области, вероятно, все равно останется прежним.

После объединения вы можете выполнить еще одну свертку, применить еще одну нелинейность, а затем выполнить еще одно объединение. В общем, типичный слой сверточной сети состоит из трех этапов.

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

Свертка и объединение

Вспомните концепцию априорного распределения вероятностей. Это распределение вероятностей по параметрам модели, которое кодирует наши убеждения о том, какие модели являются разумными, до того, как мы увидели какие-либо данные. Априоры можно считать слабыми или сильными в зависимости от того, насколько сконцентрирована плотность вероятности в априоре. Слабое априорное распределение — это априорное распределение с высокой энтропией, такое как распределение Гаусса с высокой дисперсией. Такой априор позволяет данным перемещать параметры более или менее свободно. Сильный априор имеет очень низкую энтропию, например, распределение Гаусса с низкой дисперсией. Такой априор играет более активную роль в определении того, где заканчиваются параметры. Бесконечно сильная априорная вероятность приписывает некоторым параметрам нулевую вероятность и говорит, что эти значения параметров полностью запрещены, независимо от того, насколько данные подтверждают эти значения. Мы можем представить себе сверточную сеть как полностью связную сеть, но с бесконечно сильным априорным отношением к ее весам. Этот бесконечно сильный априор говорит, что веса одной скрытой единицы должны быть идентичны весам ее соседа, но смещены в пространстве. Априор также говорит, что веса должны быть равны нулю, за исключением небольшого пространственно непрерывного рецептивного поля, назначенного этой скрытой единице. В целом, мы можем думать об использовании свертки как о введении бесконечно сильного априорного распределения вероятностей по параметрам слоя. Этот априор говорит, что функция, которую должен изучить слой, содержит только локальные взаимодействия и эквивалентна переводу. Точно так же использование пулинга является бесконечно сильным априорным условием того, что каждая единица должна быть инвариантна к небольшим переводам.

Как выглядит CNN?

Например, это LeNet, сеть, которая использовалась для распознавания рукописных цифр.

  • Он принимает 32x32 рукописных символа в качестве входных данных.
  • Сверточный слой C1: 28x28x6 (28 вместо 32 из-за ребер (о которых мы поговорим ниже) и 6 из-за 6 признаков)
  • Нелинейность
  • Объединение S2 (подвыборка): объединение 2x2, которое превращает карты 28x28 в карты 14x14.
  • Сверточный слой C3: 10x10x16 (10 вместо 14 из-за ребер и 16 из-за 16 функций)
  • Нелинейность
  • Объединение S4 (подвыборка): объединение 2x2, которое превращает карты 10x10 в карты 5x5.
  • Размер 5x5x16 достаточно мал, чтобы теперь вы могли объединить эти активации в большой вектор и поместить их в стандартный полносвязный линейный слой.

Реализация сверточных слоев

Нам понадобятся N-мерные массивы, часто используемые как синоним «тензоров». Тензоры - это обобщения матриц более высокой размерности.

Например, входное изображение может быть трехмерным массивом; фильтр может быть 4D; активации могут быть 3D. Это аналогично весам и активациям, которые мы видим в стандартных нейронных сетях, где у нас есть 2D-матрицы весов и 1D-активация.

Допустим, у меня есть активация a(1), и я собираюсь превратить ее в z(2):

Наш фильтр задается тензором W:

Операция свертки возьмет этот фильтр, поместит его в каждую позицию на входной карте и создаст соответствующую карту. Запишем это как уравнение для положения i по вертикальной оси и j по горизонтальной оси в z(2):

Мы собираемся умножить W(2) на точку на входной карте активации; мы собираемся получить эту точку, центрируя ее в (i, j), а затем двигаясь влево или вправо в зависимости от l и m. Это способ сказать, что в свертке у вас есть небольшой линейный слой в каждой отдельной позиции, и вы перемещаете его.

После того, как вы это сделаете, не забудьте применить свою нелинейность:

Отступы и края

Что, если с вашим ядром вы сделаете два шага влево и уйдете за конец образа?

У вас есть два варианта:

1. Обрежьте края

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

Если вход 32x32x32, а ваш фильтр 5x5x6, выход будет 28x28x6.

Вот как мы можем думать об этом. Мы могли бы представить, что у фильтра есть радиус, который мы можем вычислить, разделив его высоту на два минус один:

Таким образом, в нашем примере радиус равен 2. Если этот радиус выходит за пределы изображения, то он недействителен — поэтому мы отрезаем с каждой стороны (снизу, вверху, слева, справа) количество мест, равное до радиуса. Таким образом, 32x32 станет 28x28.

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

2 Нулевой отступ

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

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

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

Шаговые извилины

Как мы уже говорили, стандартная структура на каждом уровне состоит из трех шагов:

1. Свертка
2. Функция активации
3. Объединение

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

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

Важно отметить, что пошаговые свертки — это не то же самое, что объединение, потому что при объединении вы вычисляете активации в каждом месте, а затем берете максимум, тогда как при пошаговой свертке вы пропускаете некоторые местоположения все вместе.

Примеры сверточных нейронных сетей

AlexNet — это классическая сверточная нейронная сеть средней глубины:

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

  • На входе изображение размером 224x224x3.
  • Первый сверточный слой 11x11x96.
  • Эти фильтры применяются с шагом 4. Вы уменьшите размер изображения примерно в четыре раза за вычетом краевых эффектов из-за отступов по бокам; это дает нам карту активации размером 55x55x96.
  • Активация ReLU

  • Максимальный слой пула 3x3 с шагом 2. Вы получаете 27x27x96.
  • Слой нормализации. Это больше не используется широко.

Популярная викторина: сколько параметров в первом сверточном слое? Количество параметров зависит только от фильтра и размер фильтра 11х11х96.

Помните, что размер матриц W в каждом из этих сверточных слоев:

Таким образом, вы получите 11x11x3x96, что составляет 34 848 параметров. Нам не хватает количества параметров в векторе смещения, которое зависит только от количества выходов (96).

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

Наконец, у нас есть полностью связанные слои:

Вы можете увидеть несколько шаблонов:

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

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

VGG построен с целью стандартизации и модульности, что действительно помогает, когда нам нужно создавать очень глубокие сети.

  • Ввод представляет собой изображение размером 224x224x3, и мы попали в него с шагом 1 и свертками, дополненными нулями: уменьшение разрешения отсутствует. Это гигантские карты активации, но фильтры очень маленькие (3х3х64).
  • Нелинейность
  • Максимальный слой пула 2x2 для уменьшения разрешения.
  • Два слоя свертки
  • Нелинейность
  • Максимальный слой пула 2x2
  • Три слоя свертки
  • Максимальный слой пула 2x2
  • Три слоя свертки

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

Несколько наблюдений:

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

ResNet изначально был представлен со 152 уровнями, но с тех пор люди использовали аналогичные идеи для масштабирования сетей.

Я покажу схему сильно упрощенного 18-слойного прототипа: полная 152-слойная версия аналогична этой, за исключением того, что все довольно сильно растянуто.

  • Мы начинаем с одной свертки 7x7, которая приводит нас к более низкому разрешению. Затем у нас есть эти повторяющиеся блоки сверток 3x3 с уменьшением разрешения между ними; количество фильтров увеличивается в два раза по мере того, как мы идем, пока вы не достигнете 512, а затем остаетесь на этом уровне.
  • У нас есть общие блоки со многими слоями, перемежающимися несколькими операциями объединения.
  • Другая примечательная особенность этой модели заключается в том, что вместо этого гигантского полностью связанного слоя в конце мы просто берем нашу последнюю карту сверточных откликов и усредняем векторы признаков в каждом месте.
  • Затем у нас есть один линейный слой, идущий прямо в softmax.

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

Слева вы можете увидеть их эксперименты со стандартной сверточной сетью по мере увеличения количества слоев с 20 до 32, затем с 44 до 56. Здесь вы можете видеть, что частота ошибок увеличивается с увеличением количества слоев. Люди из ResNet поняли, что с модификацией архитектуры нейронной сети вы можете обратить эту тенденцию вспять и увидеть повышение точности по мере увеличения количества слоев.

Какова основная идея?

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

Итак, если в обычной сети вы рассматриваете каждую группу из двух сверток, вычисляющую некоторую функцию h(x), в остаточной сети эта группа из двух сверток будет вычислять h(x), где h(x) равно тому, что происходит в обычной сети плюс х. X обходит эти извилины и добавляется в конце. Интуиция такова, что вы вычисляете изменение x вместо вычисления совершенно нового x.

Чтобы понять эту интуицию, нам нужно переосмыслить, почему так сложно обучать очень глубокие сети. Цепное правило просто выглядит как умножение группы якобианцев; для некоторой сколь угодно глубокой сети производная потерь по матрице весов первого слоя будет произведением огромного числа матриц якобиана. Затем в конце вы умножаете это число dL / dz(n):

Эти якобианские матрицы могут быть самыми разными. Производные нелинейностей, производные линейных слоев, производные сверток. Если мы на секунду упростим и подумаем о скалярах, проблема с умножением многих чисел состоит в том, что есть два возможных результата: если большинство чисел меньше единицы, мы получим ноль, а если большинство чисел больше единицы, то получаем бесконечность. Есть очень небольшой набор случаев, когда ответ более интересен, и этот небольшой набор случаев — это когда все числа близки к единице: единственный раз, когда вы получаете разумный ответ от перемножения многих скаляров, — это если они все близки к одному.

Кстати, именно поэтому мы предпочитаем ReLU вместо сигмоидов; производная выпрямленной линейной единицы является индикатором того, является ли вход положительным, что означает, что большая часть этих производных равна 1.

Для матриц близость к единице не означает, что каждый элемент равен единице, это означает, что собственные значения матрицы должны быть близки к единице.

Если мы вернемся к нашему изображению, с остаточным слоем dH/dx задается как dF/dx плюс тождество, потому что вы просто добавляете x. Пока веса в сверточных слоях не слишком велики, вы можете надеяться, что это dF/dx также не будет слишком большим. В этот момент эта сумма была бы довольно близка к единице:

Заставляем нейронные сети обучаться

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

  • Нормализация входов и выходов
  • Нормализация активаций (пакетная нормализация)
  • Инициализация весовых матриц и векторов смещения
  • Отсечение градиента
  • Лучшие практики оптимизации гиперпараметров
  • Сборка, отсев

Нормализация входных и выходных данных

Допустим, у вас есть вот такая нейронная сеть:

Он принимает 2D-ввод, и некоторые из наших точек данных выглядят так:

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

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

Стандартизация — это то, что мы делаем, чтобы решить эту проблему: мы преобразуем наши входные данные так, чтобы они имели среднее значение, равное нулю, и стандартное отклонение, равное единице. Все, что вам нужно сделать, если вы хотите, чтобы среднее значение было равно нулю, это вычесть среднее значение из каждого измерения x:

Ожидаемое значение x является средним, и вы оцениваете его, усредняя все x в вашем наборе данных.

Если вы хотите также сделать стандартное отклонение единицей, вы делаете то же самое и делите на стандартное отклонение x в наборе данных:

Это также хорошая идея сделать это для выходных данных, если вы решаете проблему регрессии.

Нормализация активаций (пакетная нормализация)

Чтобы также стандартизировать активации, мы будем использовать мини-партии для оценки среднего значения и стандартного отклонения. Вместо оценки среднего значения путем усреднения активаций по каждой точке данных в наборе данных, что было бы очень дорого, мы будем оценивать среднее значение путем усреднения активаций всех точек данных в пакете.

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

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

Несколько практических деталей:

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

Инициализация весовых матриц и векторов смещения

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

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

На самом деле это соответствует очень плохому плато.

Чтобы избежать этой проблемы, мы используем инициализацию Xavier. Цель Xavier Initialization — инициализировать веса таким образом, чтобы дисперсия активаций была одинаковой для всех слоев. Эта постоянная дисперсия помогает предотвратить взрыв или исчезновение градиента. Я не буду объяснять все детали математического обоснования, потому что это не является целью данной статьи. Далее следует грубое описание.

Допустим, мы инициализируем наши веса на каком-то линейном слое, так что наши веса выбираются в соответствии с гауссовым распределением со средним значением 0 и некоторой дисперсией сигма w в квадрате.

Какой должна быть дисперсия?

  • Смещения будут инициализированы до нуля, и это формула для каждой записи в z

  • Мы стандартизируем x, поэтому активации являются гауссовыми.

Какова будет величина этих z? Если все средние равны нулю, то величина z является их стандартным отклонением. Если мы выпишем дисперсию z, поскольку z является нулевым средним, это просто ожидаемое значение z в квадрате:

Вы берете математическое ожидание квадрата суммы и берете каждый член суммы в квадрате. Это упрощает:

Размерность, умноженная на сигму w в квадрате на сигму a в квадрате.

Мы встали на этот путь, потому что были обеспокоены тем, что наш выбор инициализации для весов либо увеличит, либо уменьшит величину активаций. Если предыдущий слой имеет активации с дисперсией:

И следующий слой имеет активацию величины:

Тогда мы знаем, что:

Если мы хотим, чтобы величина активаций оставалась примерно одной и той же слой за слоем, нам нужно каким-то образом убедиться, что квадрат Da sigma w равен единице. Мы могли бы выбрать сигму w в квадрате на единицу больше, чем Da:

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

Есть одна деталь, которую мы упустили: мы забыли о нелинейностях. После того, как мы применим этот линейный слой, мы собираемся применить некоторую нелинейную функцию к z, и эта нелинейная функция изменит их величины. Одной очень часто используемой нелинейной функцией является выпрямленная линейная единица. Более глубокие сети почти всегда будут использовать ReLU, потому что они ведут себя намного лучше, чем сигмоиды. Однако проблема в том, что ReLU обнулит многие наши активации; на самом деле, если наши активации нормально распределены и имеют нулевое среднее значение, ReLU обнулит примерно половину из них.

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

Еще меньшая деталь: предубеждения. Ранее мы говорили, что инициализируем все эти векторы смещения примерно равными нулю; но опять же, ReLU убьет половину из них. Часто, особенно когда люди не используют этот половинный коэффициент, довольно часто инициализируют смещения некоторой небольшой положительной константой, такой как 0,1, потому что это мертвые единицы.

Расширенная инициализация

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

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

Чтобы матрица была близка к единице, ее собственные значения должны быть близки к единице.

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

  • U и V — преобразования, сохраняющие масштаб; они являются ортонормированными базисами. Это означает, что когда вы умножаете вектор на матрицу U или V, это не изменит длину этого вектора, а просто повернет его по-разному.
  • Лямбда является диагональной, а диагональные элементы лямбда являются собственными значениями якобиана. Грубо говоря, лямбда фиксирует все изменения масштаба, которые делает эта матрица, а U и V фиксируют все повороты.

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

Мы могли бы взять некоторую произвольную инициализацию, запустить на ней разложение по сингулярным числам, заставить диагональ быть идентичной, а затем собрать ее обратно. Принуждение диагонали к идентичности означает просто ее отбрасывание и построение конечной матрицы как U, умноженное на V.

Градиентная обрезка

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

Отсечение градиента часто может быть разумным средством для исправления этой проблемы. Есть два способа обрезать градиенты.

  • Один из способов — вы можете обрезать каждую запись в градиенте так, чтобы она была не больше некоторой константы c и не меньше отрицательного c.

  • Еще одна вещь, которую вы можете сделать, это обрезать норму градиента: вы сохраняете направление и просто обрезаете длину.

Чтобы выбрать c вручную, вы можете запустить несколько эпох обучения и просто посмотреть, как выглядят здоровые величины градиента. Это должно быть крайней мерой.

Ансамбли и отсев

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

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

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

Предположим, что мы решаем задачу регрессии, и предположим, что эта зеленая кривая представляет собой истинную функцию, а синие точки — мой обучающий набор:

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

Я могу обучить другую модель с совершенно другой инициализацией и получить другое решение. Обе эти модели хороши на тренировочном наборе, и обе они довольно плохи на тестовом наборе.

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

Конечно, эта картина немного идеализирована, но это основная интуиция.

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

Если дисперсия высока, вы ожидаете, что ваша ошибка будет высокой. Но если мы сможем оценить f bar, то сможем устранить большую часть нашей дисперсии. Можем ли мы каким-то образом получить m разных тренировок, наборы для обучения разных моделей на каждой из них, а затем усреднить эти модели вместе? Можно было бы ожидать, что если все эти разные модели расходятся во мнениях и допускают разные ошибки в контрольных точках, то их средние значения будут вынуждены совпадать в чем-то близком к правильному ответу.

Но где мы можем получить эти наборы данных? Можем ли мы приготовить несколько независимых наборов данных из одного набора данных, не имея при этом необходимости иметь дело с меньшим объемом данных?

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

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

Мы создаем наш первый набор данных с повторной выборкой; бросаем трехгранный кубик и получаем вот это:

Затем снова:

Если мы выберем новые точки данных i.i.d. из исходной обучающей выборки они также будут и.о.р. образцы из оригинального обучающего дистрибутива. И поскольку они выбираются независимо друг от друга, они в конечном итоге оказываются i.i.d. образцы из того же исходного дистрибутива. Эти наборы данных в конечном итоге станут независимыми.

Что отличает эти модели друг от друга, так это то, что они имеют разные векторы параметров, потому что они обучались независимо друг от друга на разных, но тесно связанных наборах данных. Когда у нас есть эти разные модели, нам нужно каким-то образом объединить их прогнозы при классификации новой контрольной точки.

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

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

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

Еще более быстрые ансамбли

Эти сети могут быть очень большими, и их обучение может быть очень дорогостоящим. Но помните, что глубокое обучение — это представления, а последние уровни — это те, которые обычно зависят от задачи и пытаются выяснить представление. Это означает, что мы можем попытаться объединить только эти последние несколько слоев. Мы можем обучить несколько разных сетей, но сделать так, чтобы они совместно использовали магистраль, которая выполняет извлечение признаков, а затем иметь разные группы полностью связанных слоев для каждой головы.

Это не так хорошо, как настоящий ансамбль, но его намного быстрее обучить и зачастую проще настроить.

Обычно ансамбли работают лучше, когда они больше, но создавать огромные ансамбли очень дорого, особенно если вы тренируете m независимых моделей. Это вычисление легко распараллелить, но оно все равно стоит вам денег. Итак, можем ли мы сделать огромный ансамбль из одной сети.

Это то, что пытается сделать техника, называемая отсевом. Идея отсева заключается в том, что вы берете свою нейросеть и случайным образом убиваете некоторые из активаций — вы заходите в каждый из скрытых слоев и, с некоторой вероятностью, просто удаляете какие-то единицы. установив их на ноль.

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

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

Обратный проход работает точно так же. Градиент является внешним произведением на a, поэтому, если активация была установлена ​​​​на ноль, ее производная также будет равна нулю. Фактически, когда вы делаете это, вы удаляете эту активацию из своей сети.

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

Что происходит во время тестирования?

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

Гиперпараметры

Со всеми этими трюками, о которых мы говорили, мы получим множество гиперпараметров. Некоторые из них влияют на оптимизацию:

  • Скорость обучения
  • Импульс
  • Инициализация
  • Пакетная нормализация

Некоторые из них влияют на генерализацию:

  • Ансамбль
  • Выбывать
  • Архитектура

Как выбрать все эти параметры? Одна важная вещь, которую нужно сделать, это определить, какой тип параметров должен на что влиять.

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

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

Рекуррентные нейронные сети

Что, если у нас есть входные данные переменного размера?

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

Это может включать:

  • Последовательность слов → Проблема классификации настроений: у вас есть отзыв в сети, и вы хотите угадать, положительный это отзыв или отрицательный.
  • Последовательность звуков → Задача распознавания фонемы по звуку
  • Последовательность изображений → Проблема классификации активности в видео

Вам нужна сеть, которая может:

  • Вмещать несколько входов и несколько разных количеств входов
  • Поделиться функциями

Одна идея

Количество слоев становится таким же, как и количество входов. Другими словами, вы получаете отдельный ввод для каждого слоя:

Если у вас есть последовательность длины четыре, то у вас будет четыре слоя; если у вас есть последовательность длины три, то у вас будет три слоя; если у вас есть последовательность длиной пять, то у вас будет пять слоев.

Теперь на каждом слое у нас есть два входа:

  • Активации предыдущего слоя
  • Новый вход х

Затем применяем к этому обычную линейную операцию:

Как обычно, применяем нелинейность:

Это очень похоже на стандартные линейные слои; единственное отличие состоит в том, что вместо применения линейного слоя к активациям предыдущего слоя теперь вы объединяете новые входные данные с этими активациями предыдущего слоя, а затем пропускаете их через линейные и нелинейные операции.

Что происходит с отсутствующими слоями? Если у вас есть последовательность длины пять, то у вас будет пять слоев, а если у вас есть последовательность длины три, то у вас будет три слоя.

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

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

На самом первом уровне мы просто соединим входные данные с большим вектором нулей.

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

Одна дополнительная модификация, которая нам нужна, чтобы превратить этот дизайн в полноценную рекуррентную нейронную сеть, — это совместное использование весовых матриц.

Это означает, что W(l) будет одинаковым для всех этих слоев.

  • Все в том, как вы оцениваете эту сеть, не меняется
  • Во время тестирования все точно так же. Мы просто инициализируем этот виртуальный первый слой нулем, а затем выполняем следующие операции:

  • Это влияет только на обучение. Во время обучения мы должны заставить матрицы W(l) быть одинаковыми для всех слоев:

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

Если мы заставим весовые матрицы быть одинаковыми на каждом слое, мы можем иметь столько слоев, сколько захотим.

Базовая конструкция RNN:

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

Как мы обучаем RNN?

Короткий ответ: мы собираемся немного изменить обратное распространение.

Когда мы запускаем обратное распространение, базовый дизайн тот же. Во-первых, мы запускаем прямой проход, чтобы вычислить все a и z на каждом шаге. Затем для обратного прохода поступаем так же. Мы инициализируем дельту как производную от окончательных потерь по отношению к выходу последнего слоя, а затем для каждого линейного слоя и для каждого ReLU мы вычисляем производную по его параметрам и производную по его входам (что является предыдущим слой).

Проблема в том, что параметры теперь одинаковы на всех этих слоях. Это означает, что θf на уровне 4 является той же переменной, что и θf на уровне 3. Если бы мы запустили этот алгоритм на следующем уровне:

Начиная справа, мы вычисляем дельту как производную от потерь по отношению к выходу на четвертом слое, а затем на четвертом уровне мы вычисляем производную от потерь по весам и по отношению к смещению. Затем мы вычислим новую дельту, вернемся к слою 3, а на слое 3 переопределим градиент градиентом w и b на уровне 3. Таким образом, буквально, градиент на слое l-1 будет перезаписать градиент слоя l — а мы этого не хотим. Мы хотим, чтобы производная потерь по w и производная потерь по b учитывала влияние w и b на каждый слой, а не только на первый слой.

Оказывается, решить эту проблему очень просто. Вместо того, чтобы устанавливать производную «df / dθf» как «df / dθf» x «δ», вам просто нужно добавить ее к значению производной:

Вы сделаете это:

Вместо этого:

В начале обратного распространения вы инициализируете все свои градиенты, все свои производные нулем, а затем на каждом шаге обратного распространения для каждого слоя вы добавляете «df / dθf» x «δ» к текущему значению производной.

Что, если у нас есть выходные данные переменного размера?

Например, мы можем захотеть сгенерировать текстовую подпись к изображению, предсказать последовательность будущих видеокадров или сгенерировать аудиопоследовательность. Раньше у нас были входные данные на каждом уровне; теперь у нас будет вывод на каждом слое.

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

Теперь на этом уровне будет некоторая функция, примененная к (l), и это своего рода функция считывания, которую иногда называют декодером. Параметры f будут общими для всех временных шагов, как и параметры линейного слоя. У нас будет убыток на каждой шляпе y(l), и наш общий убыток будет просто суммой убытков на каждом шаге. Это очень простой способ объединить потери.

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

Мы собираемся нарисовать граф вычислений для этой процедуры.

У вас есть вход x, который переходит в линейный слой, затем нелинейный, а затем у нас есть наша первая функция считывания f1, которая может быть линейным слоем, за которым следует softmax, и входит в потерю первого временного шага:

Сигма 1 также переходит во второй линейный слой. Итак, он выдает значение, скажем, a1, и это же a1 используется двумя нижестоящими функциями: f1 и lin2.

Затем lin2 переходит в сигму 2 и снова тот же механизм. Потери на всех временных шагах складываются, и это то, что в конечном итоге дает окончательное значение потерь.

Мы можем думать о каждом слое как о функции f, которая принимает на вход xf и производит на выходе yf. Мы всегда начинаем обратное распространение с последней функции, где начальная дельта равна единице. Это графический способ представления обратного распространения для обычных нейронных сетей:

Алгоритм для обычных сетей — это просто цикл, который начинается с последнего слоя и продолжается в обратном направлении, и в каждом из этих слоев он будет вычислять эти дельты «yf» и использовать их для вычисления дельты «xf» и «dL/dθ».

Теперь мы собираемся немного обобщить это. Проблема, с которой нам приходится иметь дело с RNN, заключается в том, что у нас есть некоторые узлы, выходные данные которых поступают в несколько узлов. Если вы посмотрите на график вычислений, который я нарисовал выше, вы заметите, что выход sigma1 входит в f1, а также входит в lin2. Во время обратного распространения будет одна дельта, поступающая от одного из этих выходов, и другая дельта, поступающая от другого из этих выходов. Все, что мы делаем, это просто складываем их вместе, а затем просто подключаем к одному и тому же алгоритму.

Это также иногда называют «автоматической дифференциацией в обратном режиме».

Что делать, если у вас есть несколько входных и несколько выходных данных на каждом этапе?

Мы просто объединяем две концепции, которые мы видели раньше. На каждом этапе мы:

  • Объединить активации предыдущего слоя с новыми входными данными.
  • Применить линейный слой
  • Применить нелинейный слой
  • Имейте некоторую функцию считывания (которая может быть просто линейным слоем и softmax) для получения выходных данных на этом этапе.

Что затрудняет обучение RNN?

RNN на самом базовом уровне представляют собой очень глубокие сети.

Мы должны переосмыслить, почему так сложно обучать очень глубокие сети. Цепное правило просто выглядит как умножение группы якобианцев; для некоторой сколь угодно глубокой сети производная потерь по матрице весов первого слоя будет произведением огромного числа матриц якобиана. Затем в конце вы умножаете это число dL / dz(n):

Эти якобианские матрицы могут быть самыми разными. Производные нелинейностей, производные линейных слоев, производные сверток. Если мы на секунду упростим и подумаем о скалярах, проблема с умножением многих чисел состоит в том, что есть два возможных результата: если большинство чисел меньше единицы, мы получим ноль (исчезающие градиенты), тогда как если если большинство чисел больше единицы, мы получаем бесконечность (взрывающиеся градиенты). Есть очень небольшой набор случаев, когда ответ более интересен, и этот небольшой набор случаев — это когда все числа близки к единице: единственный раз, когда вы получаете разумный ответ от перемножения многих скаляров, — это если они все близки к одному. Кстати, мы предпочитаем ReLU вместо сигмоидов; производная выпрямленной линейной единицы является индикатором того, является ли вход положительным, что означает, что большая часть этих производных равна 1.

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

Содействие лучшему градиентному потоку через RNN

На каждом уровне мы будем объединять новые входные данные с активациями предыдущего слоя, а затем передавать их через линейные и нелинейные операции. Мы называем это «динамикой RNN».

Конкретная производная, которая нас интересует, когда мы говорим об исчезающих градиентах, — это якобиан динамики RNN:

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

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

Интуиция такова, что для каждой единицы у нас есть небольшая нейронная цепь, которая решает, помнить или забыть. Если вы помните, вы просто копируете предыдущую активацию как есть и не меняете ее; если вы забываете, то вы заменяете это чем-то другим

У нас будет состояние ячейки, и мы умножим его на некоторое число f(t), которое находится между 0 и 1. Это называется воротами забывания, потому что, если он установлен в 0, вы забываете, что у вас было, иначе вы помните:

Затем мы добавляем к нему некоторое число g(t).

  • Если f(t) близко к нулю, то состояние ячейки заменяется на g(t)
  • Если f(t) близко к единице, а g(t) близко к нулю, то состояние ячейки сохраняется.
  • если f(t) близко к единице, а g(t) не равно нулю, то состояние ячейки модифицируется аддитивным образом, и это становится нашим новым состоянием ячейки a(t)

Откуда мы берем эти f(t) и g(t)?

Ячейки LSTM

Способ, которым мы собираемся решить, помнить или забыть, основан на другом временном сигнале h (t-1). H (t-1) является результатом предыдущего временного шага вместе с (t-1).

Затем у нас есть вход на этом временном шаге, x (t). Мы сделаем то же самое, что и раньше: мы объединим x(t) с h (t-1) и применим к ним линейный и нелинейный слои. Этот линейный слой будет давать результат в четыре раза больше, чем у нас был раньше:

Каждый из этих 4 рядов будет выполнять разные функции. Одно из них — это f(t), которое мы видели ранее как ворота забывания, и одно из них — это g(t), которое мы ранее добавили к произведению.

1. Мы берем f(t) и пропускаем его через сигмоиду, которая помещает ее в диапазон 0, и это становится воротами забвения.

2. Берем i(t) и тоже пропускаем через сигмоиду. Это называется входными воротами, и они контролируют изменение состояния ячейки.

3. Берем g(t) и пропускаем через некоторую нелинейность. Это может быть тан или ReLU

g (t) поточечно умножается на i (t), а затем вы добавляете произведение к состоянию ячейки после «забывания». Интуитивно i (t) определяет, хотите ли вы иметь модификацию состояния ячейки, а g (t) определяет, какой будет эта модификация. Это очень приятно, потому что позволяет сети самостоятельно выбирать, модифицировать или нет, и отдельно выбирать, как модифицировать

4. o(t) называется выходным вентилем, он также проходит через сигмоиду и управляет следующим h(t). После ворот забывания и после добавления g(t) мы берем состояние нашей ячейки a(t) и пропускаем его через нелинейность. Затем мы поточечно умножаем на o (t), и это становится новым h (t)

Этот новый h (t) также используется, если нам нужна функция считывания/декодер на этом временном шаге.

Здесь происходит следующее: мы пытаемся сохранить роль (t) как линейного сигнала. Все нелинейности применяются к h, и в результате градиенты на (t) будут довольно простыми.

  • Отсутствие нелинейных функций, влияющих на a (t), делает его производные очень хорошими.
  • В то же время мы по-прежнему сохраняем преимущество нелинейности, имея нелинейную модификацию через g (t) и нелинейное считывание через h (t).

Почему эти ячейки LSTM работают лучше? Простейшая причина в том, что a (t) очень просто модифицируется на каждом шаге.

Вы можете думать о a(t) как о долговременной памяти, а h(t) — как о кратковременной памяти — она постоянно изменяется и выполняет сложную нелинейную обработку.

Практические примечания

  • RNN почти всегда имеют как вход, так и выход на каждом этапе.
  • Наивные RNN, подобные приведенным в первой части, почти никогда не работают; на практике, если вам нужна RNN, вы, вероятно, будете использовать что-то вроде ячейки LSTM, хотя это требует большей настройки гиперпараметров, чем стандартные полносвязные или сверточные сети.
  • Есть также несколько альтернатив RNN для обработки последовательностей, которые на практике могут работать намного лучше (временные свертки и преобразователи).
  • Есть несколько вариантов LSTM, которые немного проще и работают так же хорошо (закрытые рекуррентные единицы).

Модели авторегрессии и структурированный прогноз

Большинство RNN, которые мы фактически используем на практике, имеют несколько входов и несколько выходов, потому что большинство задач, требующих нескольких выходов, имеют сильную зависимость между этими выходами.

Такого рода проблемы иногда называют структурированным предсказанием, потому что то, что вы предсказываете, имеет структуру, в отличие от чего-то вроде ярлыка. Классический пример — генерация текста. Независимо от того, является ли вывод текста правильным ответом или нет, существуют отношения между словами и текстом, которые определяют, является ли это допустимым текстом.

Допустим, у вас есть тренировочный набор, состоящий из трех предложений:

  • «Я мыслю, следовательно, я существую»
  • «Мне нравится машинное обучение»
  • «Я не просто нейронная сеть»

Скажем пока, что вас не волнует обобщение: вы просто хотите, чтобы ваша модель запомнила эти три предложения. Мы будем рассматривать слова как категориальные переменные; каждый временной шаг будет другим словом, и это будет просто одно из n возможных слов.

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

Мы выберем случайную выборку из этого распределения softmax и выберем второе слово. Теперь сеть завершает предложение и говорит «Я думаю». Затем мы переходим к следующему временному шагу; у нас есть линейный слой, который использует предыдущие активации для вычисления новых активаций, а затем у нас есть выход на втором временном шаге, который снова проходит через распределение softmax.

Проблема в том, что сеть не знает, какое слово было случайно выбрано из этого softmax:

Сеть думала, что произвела вывод, который на 30% «думаю», на 30% «нравится» и на 40% «я». Проблема в том, что вы пытаетесь семплировать эти слова независимо друг от друга. На втором временном шаге вы можете случайным образом получить «машину», а затем на третьем временном шаге вы можете случайным образом получить «просто». Это поколение не имеет смысла, хотя сеть проделала большую работу по изучению этого дистрибутива. Таким образом, мы получаем бессмысленный результат, потому что в этой задаче ковариации между словами имеют такое же значение, как и вероятности получения правильных слов.

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

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

Этот базовый дизайн используется для всех RNN, которые должны выводить структурированную последовательность, например модели подписей к изображениям или модели прогнозирования видео.

Модели авторегрессии

Самый простой способ обучить его — установить входными данными всю обучающую последовательность, а исходными данными — ту же последовательность, но со смещением на один шаг. Вместо того, чтобы просить нейронную сеть во время обучения просто придумать предложение, вы просите завершить предложение. Вы говорите: на временном шаге t возьмите все слова с временного шага 1 до t и выведите слово на временном шаге t+1.

Ваши иксы будут всеми токенами в предложении, а ваши выходные данные истинности будут всеми токенами, сдвинутыми назад на единицу. Последний будет заменен стоп-токеном.

По сути, эту сеть учат, что если она видит «я», она должна выводить «думаю»; если он увидит «думать», он должен вывести «поэтому» и так далее.

Сдвиг распределения

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

Допустим, у вас тот же пример, что и раньше, но сеть делает небольшие ошибки:

Почему это происходит? Может быть, он не очень хорошо тренировался. Допустим, вам не повезло, и вы выбрали 10% слово. Проблема в том, что теперь вы собираетесь использовать его в качестве входных данных для следующего временного шага. Поскольку это всего лишь какой-то случайный токен, сеть никогда его раньше не видела и будет в полном замешательстве. Таким образом, на следующем временном шаге он выведет какую-то совершенно сумасшедшую вещь, а затем не выдаст ничего разумного:

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

Запланированная выборка

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

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

Как мы хотим выбрать эту вероятность для замены истинного входа собственным предыдущим выходом модели? Интуитивно понятно, что вначале мы хотим в основном вводить истинные входные данные, а затем, когда сеть станет достаточно хорошей, мы хотим в основном вводить собственные прогнозы модели, чтобы смягчить этот сдвиг в распределении. Поэтому мы устанавливаем график для этой вероятности: вероятность использования истинного ввода вначале очень высока, а затем постепенно падает. А поскольку вероятность использования выходных данных модели равна 1 минус это, со временем вы в основном обучаете сеть на ее собственных предыдущих выходных данных.

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

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

Детали реализации

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

Вы также можете иметь несколько слоев RNN, взять выходные данные вашей RNN на определенном временном шаге и передать их на другой уровень RNN, а затем, в конечном итоге, в softmax.

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

Последовательность за последовательностью

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

Рекуррентные нейронные сети очень гибкие и могут использоваться для решения широкого спектра различных типов задач обработки последовательностей:

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

Вот пример модели нейронной сети, которая генерирует фразы. Учебными данными для этого будет большая коллекция предложений на естественном языке. Здесь я показываю только три:

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

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

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

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

Несколько деталей

  • Нам нужно понять, когда модель заканчивает выводить предложение. Например, если вы даете модели «я» и хотите, чтобы она завершала предложение фразой «я мыслю, следовательно, я существую», откуда вы знаете, что должны остановиться на «м»? «Я думаю, следовательно, я гиппопотам» — тоже правильное предложение. Таким образом, мы включим специальный токен в обучающие данные в конце всех наших предложений. Это токен конца последовательности, иногда называемый токеном конца предложения. Он не представляет никакого фактического слова, он просто представляет тот факт, что модель сделана.
  • Нам нужно как-то запустить модель. Для второго временного шага он будет использовать выходные данные первого временного шага в качестве входных данных. Но что мы используем на самом первом временном шаге? Мы могли бы просто вычислить частоту, с которой каждое предложение начинается со слова, произвести случайную выборку из нее, а затем передать ее в качестве первого временного шага в модель. Чуть более элегантное решение, не требующее наличия специального компонента, состоит в том, чтобы ввести токен начала предложения. Таким образом, модель узнает, что она должна выводить случайное слово с вероятностью, пропорциональной вероятности фактического начала предложения.

С помощью этих токенов начала и токенов конца предложения мы знаем, как заставить RNN генерировать совершенно случайные предложения. Но если мы хотим, чтобы RNN завершала предложение, например, «я думаю», для этого вообще не требуется изменять процедуру обучения. Мы по-прежнему подаем токены по одному. Вы вводите «старт», вы позволяете ему сделать прогноз, но затем не делаете выборку из этого прогноза; вместо этого вы напрямую вводите следующее слово, на которое вы обуславливаетесь, то есть «я». Затем вы позволяете ему сделать какой-то прогноз, игнорируете его, а затем вводите «думать», делаете какой-то прогноз, а затем фактически делаете выборку из этого и передаете это в качестве входных данных для четвертого шага. Все, что вам нужно сделать, чтобы настроить генерацию на основе определенного начального фрагмента, — это просто заставить эти первые несколько входных данных согласовываться с этим фрагментом независимо от того, что выводит сеть.

Условная языковая модель

До сих пор мы обсуждали безусловные языковые модели; Теперь я собираюсь поговорить о том, как мы можем создавать условные языковые модели. Для этих условных языковых моделей текст является выходом модели, а не вводом. Вы обусловите эту модель некоторыми входными данными, которые сообщат ей, какой текст вы хотите, чтобы она сгенерировала. Например, вы можете представить условную языковую модель для подписей к изображениям:

У нас может быть какая-то модель кодировщика, считывающая кондиционирующую информацию (изображение щенка) с помощью сверточной сети, которая создаст вектор, представляющий начальное состояние RNN. Этот вектор, a0, является начальным состоянием RNN. Затем все это будет обучено от начала до конца — CNN и RNN вместе будут обучаться вместе для создания правильного текста.

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

Наши обучающие данные будут содержать изображения и метки.

Если, например, вы хотите перевести французский язык на английский, у вас может быть RNN, которая читает французский текст и создает начальные активации скрытого состояния для другой RNN, которая создает английский текст. Теперь вместо кодировщика CNN у вас есть кодировщик RNN, и все, что происходит на последнем шаге французского предложения, переходит непосредственно к первому шагу английского предложения.

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

Последовательность моделей

У нас есть два разных RNN для ввода и вывода; кодер и декодер имеют разные веса, и кодер производит начальную активацию скрытого состояния для декодера. Вот более реалистичный пример:

1 Мы читаем входное предложение в обратном порядке и
2 Мы добавляем несколько слоев; несколько LSTM (от двух до пяти) накладываются друг на друга
3 Сеть обучается от начала до конца на парах последовательностей
4 Последовательности могут быть разной длины

Выполнение декодирования

До сих пор очевидный способ получить предложение от одной из этих RNN — делать это шаг за шагом, а затем на первом шаге мы должны что-то вывести, мы получаем распределение softmax. Это означает, что мы получаем некоторый вектор длины, равной количеству возможных слов, вы применяете к нему softmax, и это превращает его в вектор вероятностей. Это распределение вероятностей для первого слова в декодированном предложении. Очевидной вещью, которую нужно сделать на этом этапе, может быть выбор слова с наибольшей вероятностью, а затем вы подаете это выбранное слово в качестве входных данных на следующем временном шаге. Сюда:

Но обратите внимание, что сеть допустила небольшую ошибку. На первом временном шаге у него была пара возможных слов на выбор (конечно, в упрощенном сценарии), и правильное слово было бы «а», но слово «один» имело аналогичную вероятность, т. е. немного больше, потому что французское слово «un» может означать оба слова. Это кажется разумным выбором, но в конечном итоге он отбрасывает остальную часть декодирования. Он выбирает слово «щенок» для второго, но «один милый щенок» не является правильным английским предложением. Итак, сеть выбирает «is», что дает правильное английское предложение… Но это не правильный перевод.

Итак, что пошло не так? Проблема в том, что мы решили выбрать «один», прежде чем увидеть остальную часть расшифровки. Та первая ошибка, не то чтобы неразумная, привела к совершенно неразумному результату. Если бы вместо выбора «единицы» на первом временном шаге мы выбрали «а», то позже мы получили бы довольно разумное поколение. При выборе «а» вероятность «щенка» на третьем временном шаге была бы намного выше.

Здесь вероятности зависят от всей входной последовательности и от всех предыдущих слов, которые мы сгенерировали для выходной последовательности:

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

Так как работа с произведениями множества вероятностей утомительна, мы предпочитаем выражать их в логарифмическом пространстве, где произведения становятся суммами:

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

Сколько существует возможных расшифровок? Для M слов существует M ^ T последовательностей длины T. Если у нас есть только четыре токена на каждом шаге, в принципе мы могли бы выбрать любой из этих четырех маркеров на первом временном шаге и любой из этих четырех маркеров на втором шаге и любой из этих четырех жетонов на третьем шаге и так далее. Конечно, вероятность большинства из этих последовательностей очень мала, но в принципе любая из них может быть оптимальной. По сути, декодирование — это поиск по дереву, где стоимость перехода равна отрицательной величине его логарифмической вероятности (поскольку при поиске по дереву вы суммируете стоимость по мере продвижения), а количество путей поиска увеличивается экспоненциально. Мы могли бы использовать любой алгоритм поиска по дереву, чтобы решить эту проблему, но точный поиск в этом случае очень дорог, потому что у этой проблемы нет хорошей структуры, которая делает такие вещи, как поиск в ширину и поиск в глубину, работать действительно хорошо. К счастью, структура этой задачи позволяет очень хорошо работать некоторым простым приближенным методам поиска.

Основная интуиция, основанная на том, что мы знаем о декодировании последовательности, заключается в том, что, хотя выбор слова с наивысшей вероятностью на первом этапе может быть неоптимальным, выбор слова с очень низкой вероятностью вряд ли приведет к хорошему результату — это не всегда так. правда в поиске вообще. Итак, мы не можем быть жадными, но мы можем быть несколько жадными. Этот алгоритм называется поиском луча, и вместо того, чтобы жадно декодировать лучшее слово на каждом шаге, он сохраняет k лучших последовательностей на данный момент, а затем обновляет каждую из них. Частный случай, когда вы устанавливаете k равным 1, является обычным декодированием, потому что вы всегда берете наиболее вероятное слово.

Вот пример поиска луча с k = 2. Есть предложение на французском языке, которое мы собираемся перевести на английский, и оно намеренно выбрано как несколько нетривиальный перевод, в том смысле, что нет идеально эквивалентного английского слова для « Антарте».

На каждом этапе пути мы отслеживаем две наиболее вероятные гипотезы. Мы начинаем с «стартового» токена и создаем распределение softmax — мы запускаем нашу RNN на один шаг вперед, создаем распределение softmax по первому слову и отслеживаем две наиболее вероятные гипотезы. Получаем «он» и «я». Теперь мы продвинем RNN вперед в зависимости от слова «он», а затем отдельно продвинем RNN вперед в зависимости от слова «я». Для этих двух шагов вперед у нас будет всего четыре разных распределения softmax по второму слову.

Затем мы собираемся вычислить суммы логарифмических вероятностей для двух верхних слов, когда мы сделали шаг с «он», и для двух верхних слов, когда мы сделали шаг с «я». Теперь у нас четыре оценки, и мы берем две лучшие: «Я был» и «Он попал». Мы продолжим расширять эти два:

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

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

Подводя итог, можно сказать, что это лучевой поиск, и в большинстве случаев он действительно хорошо расшифровывает наиболее вероятное предложение. На самом деле часто очень маленькие значения k могут работать хорошо, поэтому от 5 до 10 довольно типичны, но даже такие низкие числа, как 2, иногда могут работать хорошо.

Внимание

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

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

Основная идея заключается в том, что:

  1. Во время кодирования у нас будет каждый шаг кодировщика RNN, который будет генерировать небольшой фрагмент информации, описывающий то, что присутствует на этом временном шаге. Эта часть информации не будет для нас семантически значимой, это будет просто небольшой вектор активаций, который сам по себе ничего не значит. Он будет называться «ключевым» и будет изучаться как часть учебного процесса.
  2. При декодировании каждый шаг декодера RNN будет выводить небольшой вектор той же длины, который мы называем «запросом». Интуитивно он представляет тип информации, которая нам нужна на данном временном шаге.
  3. Мы сравниваем вектор запроса с каждым из ключей, чтобы найти тот, который наиболее похож, и который скажет нам, какой временной шаг во входных данных является наиболее подходящим для этого временного шага в процессе декодирования. Затем мы отправим эту информацию в декодер.

Некоторая грубая интуиция для того, что это может сделать, заключается в том, что один ключ может кодировать некоторое представление о том, что происходит там в предложении, например, «это слово является подлежащим предложения»; если в запросе закодировано какое-то понятие типа «Мне нужно знать тему предложения», то будет выбран вышеупомянутый ключ, поскольку он наиболее похож на запрос. Затем мы берем соответствующий временной шаг, берем его скрытое состояние и передаем его в декодер.

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

На практике я буду использовать букву h для обозначения скрытых состояний энкодера. Я буду использовать букву s для обозначения скрытых состояний декодера. Для ключа я буду использовать букву k; на каждом временном шаге есть отдельный ключ, и ключ — это некоторая функция, которую мы применяем к скрытому состоянию RNN. Например, это может быть просто линейный слой, за которым следует нелинейность, и очень часто на практике функция k представляет собой просто линейное преобразование. Для запроса я буду использовать букву q для обозначения его с нижним индексом l вместо t. Для запроса я буду использовать букву q для обозначения его с нижним индексом l вместо t.

Точечный продукт внимания «e» — это показатель, который показывает, насколько ключ похож на запрос. Оно будет дано скалярным произведением между kt и ql, и мы хотим извлечь скрытое состояние ht для временного шага t, для которого скалярное произведение наибольшее.

Для этого мы создадим вектор alpha(l), полученный с помощью softmax точечных произведений внимания:

Чем больше точечный продукт внимания, тем ближе этот softmax становится к arg max. Затем, если мы хотим отправить ключ, который примерно соответствует наибольшему скалярному произведению, мы собираемся использовать альфу во взвешенной сумме:

Интуитивно эти альфы будут небольшими числами для всех временных шагов t, за исключением того, который имеет наибольшее скалярное произведение; поэтому в этой взвешенной сумме будет доминировать активация, соответствующая arg max скалярного произведения.

Интуиция такова, что мы пытаемся извлечь состояние RNN кодировщика для временного шага t, соответствующего наибольшему скалярному произведению между ключом и запросом. Причина, по которой мы называем это вниманием, заключается в том, что сеть пытается обратить внимание на наиболее важную часть ввода. Подводя итог, для конкретного запроса qt:

  1. Ключи изучаются из скрытых состояний энкодера, «h», например с линейным слоем
  2. Показатели внимания «e» получаются путем расставления точек над ключами с этим запросом.
  3. Мы применяем softmax ко всем точечным произведениям «e» и называем результаты «альфа».
  4. Самый большой альфа-канал отправляется в декодер

Что значит отправить альфу на декодер? Альфа-каналы могут использоваться декодером по-разному.

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

Варианты внимания

  • Один из простых вариантов — использовать k и q в качестве функций тождества. В этом случае ключ на временном шаге t — это просто ht, а запрос на временном шаге t — это просто sl. Когда вы декодируете, ваша оценка внимания — это точечный продукт между скрытым состоянием ввода и скрытым состоянием декодера. Его очень легко реализовать, но он может иметь несколько ограниченную выразительность.
  • Другой очень простой выбор — использовать линейное мультипликативное внимание, где ключ и запрос — просто линейные функции скрытого состояния. Это очень разумно, потому что скрытое состояние RNN уже сформировано применением нелинейной функции. Таким образом, это не сильно снижает нашу репрезентативность.
  • Последний вариант немного сложнее. У него все еще есть ключи и запросы, но в конце, когда вы строите свой вектор внимания, вы не строите взвешенную комбинацию скрытых состояний кодировщика, а вместо этого вы строите взвешенную комбинацию этих скрытых состояний, преобразованных некоторой изученной функцией v. , V здесь означает «значение», и интерпретация заключается в том, что во время кодирования вы создаете пары ключ-значение, тогда как во время декодирования вы находите временной шаг наибольшего произведения между k и q и берете его значение. Это может дать некоторую дополнительную гибкость.

Сводка

  • Каждый шаг энкодера t создает ключ kt
  • Каждый шаг декодера l производит запрос ql
  • Декодер получает активацию кодировщика ht, соответствующую наибольшему значению произведения kt и ql.
  • Внимание очень мощно, потому что все шаги декодера в конечном итоге связаны со всеми шагами кодировщика.
  • Градиенты ведут себя лучше, потому что количество якобианов, которые перемножаются вместе на маршруте внимания, равно O (1). Это становится очень важным для очень длинных последовательностей.

Трансформеры

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

Здесь мы собираемся поговорить о другом классе моделей для обработки последовательностей, которые не используют рекуррентные соединения, а вместо этого полностью полагаются на внимание. Мы будем строить класс моделей, называемых трансформерами.

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

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

Чтобы решить эту проблему, мы используем внимание к себе. Мы возьмем все временные шаги нашей последовательности и будем кодировать каждый временной шаг. Это похоже на модель с прямой связью. H1 — некоторая функция от x1; h2 — некоторая функция от x2; h3 — некоторая функция от x3. Веса являются общими для всех временных шагов, потому что функция одинакова в каждом случае.

Затем мы собираемся получить значение для каждого временного шага, где vt — некоторая линейная функция ht:

Мы также собираемся вывести ключ, где ключ представляет собой некоторую линейную функцию от ht:

И затем каждый временной шаг также будет выводить запрос; это как если бы у нас были кодировщик и декодер вместе. На данный момент у нас есть ключ, значение и запрос, и все они являются линейными функциями ht.

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

  1. Мы вычислим скалярные произведения между каждым qt и каждым kt, и это даст нам наши оценки внимания.
  2. Мы пропустим каждый из них через softmax и получим альфа-значения.
  3. Затем мы берем значения на каждом шагу, взвешиваем их по альфа-каналу, и это привлекает к нам внимание.

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

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

Вот о чем нам нужно позаботиться:

  • Позиционное кодирование решает проблему с вниманием к себе, которая заключается в том, что внимание к себе не имеет представления о близости и времени. Все эти x обрабатывались полностью параллельно, без учета их порядка. Если бы мы поменяли их порядок, уровень само-внимания дал бы точно такой же ответ. Это может быть большой проблемой при работе с такими данными, как естественный язык, где порядок слов действительно имеет значение.
  • Многоголовое внимание позволяет запрашивать несколько позиций на каждом уровне; на каждом уровне у нас не просто одно значение в запросе и ключе, а может быть несколько — так что мы можем выполнять более сложные операции. Грубо говоря, вы можете думать о кортеже (ключ, значение, запрос) как о фильтре в CNN; на практике вы никогда не создадите CNN только с одним фильтром на слой, и точно так же вы не захотите строить чистую модель внутреннего внимания только с одной головкой внимания на слой.
  • Добавление нелинейности, потому что модель, которую мы обсуждали до сих пор, полностью линейна.
  • Маскированное декодирование предотвращает поиск внимания в будущем. Прямо сейчас механизм внимания не различает прошлое и будущее. Это может быть очень плохо, когда вы пытаетесь использовать внутреннее внимание для декодирования языковой модели.

От внимания к себе до трансформеров

Позиционное кодирование

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

Чтобы решить эту проблему, мы добавим некоторую информацию к представлению в начале, которая указывает, где оно находится в последовательности. Позиционное кодирование просто означает, что этот первый ht будет некоторой функцией как xt, так и t:

Это сохранит порядок токенов в последовательности и позволит внутреннему вниманию использовать это. Простое добавление t к входному x не было бы идеальным, потому что абсолютное положение менее важно, чем относительное положение. Если я дам вам два предложения («Я выгуливаю свою собаку каждый день» и «Каждый божий день я выгуливаю свою собаку»), важной частью информации будет индекс слова «собака» относительно слова «гулять», а не индекс слова «собака». слово собака само собой.

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

Более ранние записи имеют очень высокие частоты, потому что они делятся на какое-то маленькое число; в конце вектора у них очень низкие частоты, потому что он делится на большое число, например 10.000. Если бы вы нарисовали эти позиционные кодировки, они выглядели бы так:

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

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

В каждой позиции у нас есть x, и он разный для каждой входной последовательности; в каждой позиции у нас также есть p, и это то же самое для каждой последовательности, но оно изучено — так что это разные p на каждом временном шаге, но p1 для одной последовательности точно такое же, как p1 для другой последовательности, так что это просто изученное постоянный. Это имеет то преимущество, что является более гибким и, возможно, в некотором смысле более оптимальным, чем синусно-косинусное кодирование, потому что в конечном итоге мы изучаем позиционные кодирования, которые лучше всего подходят для нашей модели последовательности. Недостатком является то, что это немного сложнее, и вам нужно вручную выбрать максимальную длину последовательности.

Как мы включаем эти позиционные кодировки в нашу модель внутреннего внимания?

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

Внимание нескольких голов

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

Способ, которым вы можете это сделать, заключается в том, что вы можете иметь несколько ключей, запросов и значений на каждом этапе:

Разные цветовые контуры представляют разные головы. Каждая из этих головок производит оценку внимания, и веса вычисляются независимо для каждой головы. В конце формируется полный вектор внимания путем сложения трех головок. И, может быть, одна из этих головок вытаскивает подлежащее, одна — прилагательное, третья — глагол и так далее.

Это действительно хорошая идея — построить само-внимание с несколькими головками на слой само-внимания — часто кажется, что что-то около восьми головок работает очень хорошо для больших моделей.

Добавление нелинейностей

До сих пор само-внимание полностью линейно по значениям, потому что наши ключи, наши запросы и наши значения являются линейными функциями ht. Каждый слой само-внимания имеет разный набор весов. Затем наши альфа-значения рассчитываются путем применения softmax, что является нелинейной операцией, но фактическое внимание привлекается линейной комбинацией значений, взвешенных по этим оценкам softmax. Итак, внимание линейно по v, а v линейно по h. Это означает, что каждый слой само-внимания представляет собой линейное преобразование предыдущего слоя с нелинейными весами, и это не очень выразительно.

Один очень простой способ добавить нелинейность — просто чередовать само-внимание с каким-то нелинейным слоем:

Эта позиционно-нелинейная функция обрабатывает информацию, в то время как уровень самоконтроля — это выборка из памяти.

Маскированное декодирование

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

Проблема в том, что у нас циклическая зависимость.

Во время кодирования внутреннее внимание на шаге 1 может смотреть на значение на шагах 2 и 3, которое основано на входных данных на шагах 2 и 3. Во время тестирования входные данные на шагах 2 и 3 будут основаны на выходных данных на шагах 2 и 3. шаг 1, который требует знания входных данных на шагах 2 и 3.

Есть очень простое решение, которое мы можем использовать для устранения этой проблемы, которое называется скрытым вниманием. Вы должны позволить себе обращать внимание на прошлое, но не на будущее:

Математически, чтобы вычислить оценки внимания, вы делаете то же самое, что и раньше, только если ключ стоит перед запросом, в противном случае вы устанавливаете оценку внимания на отрицательную бесконечность. Таким образом, когда вы помещаете его в softmax, e до отрицательной бесконечности равно нулю, а будущие временные шаги всегда будут иметь нулевой вес и никогда не использоваться. В коде работа с бесконечностями раздражает, поэтому вы напрямую устанавливаете экспоненту в 0.

Сводка

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

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

Трансформеры

Теперь мы собираемся объединить части, о которых мы узнали до сих пор, чтобы пройти через классическую модель трансформера.

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

Последовательность для последовательности модели RNN имеет кодировщик RNN и декодер RNN. Обычно они могут иметь несколько слоев. Чтобы превратить это в преобразователь, мы заменим кодировщик и декодер последовательными слоями внутреннего внимания, чередующимися с позиционными нелинейными преобразованиями.

Кодировщик получит последовательность x с соответствующими позиционными кодами p1, p2 и p3. В каждой позиции мы закодируем их, а затем передадим их в слой самоконтроля. Затем мы возьмем выход этого многоголового потенциала и передадим его в позиционно-нелинейную сеть. Это повторяется n раз.

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

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

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

В самом конце, после n таких блоков, мы применяем softmax в каждой позиции и считываем вывод:

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

Мы собираемся использовать ht с надстрочным индексом l для обозначения скрытых состояний уровня l в кодировщике и надстрочным индексом sl для обозначения скрытых состояний l в декодере. Эти состояния создаются позиционно-нелинейной сетью. В перекрестном внимании у нас есть:

  • Запрос, полученный путем применения матрицы Wql к выходу позиционно-нелинейной сети на уровне декодера l, шаг t
  • Ключ, полученный путем применения матрицы Wkl к выходу позиционно-нелинейной сети на слое кодера l, шаг t
  • Значение, полученное путем применения матрицы Wvl к выходу позиционно-нелинейной сети на уровне кодера l, шаг t

Затем мы вычисляем показатель внимания между каждым t и l, применяем softmax, а затем вычисляем перекрестное внимание.

На самом деле, перекрестное внимание также многогранно. Прежде чем представить реальную модель трансформера, есть еще одна маленькая деталь: нормализация слоев.

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

  • Последовательности различаются по длине, что затрудняет нормализацию по пакету.
  • Последовательности могут быть очень длинными, поэтому иногда у нас очень маленькие партии.

Простое решение, которое мы можем использовать, состоит в том, чтобы заменить нормализацию партии так называемой нормализацией слоя, которая похожа на нормализацию партии, но для разных активаций в слое, а не для разных образцов в партии. В стандартной пакетной норме на каждом уровне у вас есть набор векторов активации, по одному от каждого входа; норма партии вычисляет среднее значение и стандартное отклонение по всем точкам в этой партии. Затем, когда вы преобразуете их с пакетной нормализацией, все по элементу, и для каждого элемента вы вычитаете соответствующий элемент среднего значения, делите на соответствующий элемент стандартного отклонения, умножаете на соответствующий элемент гаммы (который является изученной шкалой ) и добавляем соответствующий элемент beta:

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

Собираем все вместе

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

Кодировщик

  • Мы начинаем с того, что берем нашу входную последовательность, отдельно внедряем каждый токен в эту входную последовательность и добавляем соответствующее позиционное кодирование к этому внедрению.
  • Затем у нас есть наш первый многоголовый слой само-внимания.
  • Вы объединяете внимание со всех голов, чтобы получить альфу на каждой позиции.
  • Затем у вас есть эта вещь, называемая «Добавить и норма», которая представляет собой остаточное соединение, за которым следует норма слоя. Причина в том, что они хотят, чтобы каждый слой был модификацией с выгодой от корректных градиентов, которые вы получаете от остаточных соединений.
  • Тогда у вас есть позиционная нелинейная функция. В частности, он состоит из линейного слоя, затем ReLU и еще одного линейного слоя.
  • Затем есть еще одно «Добавить и нормализовать».

У них есть n таких блоков; каждый блок представляет собой многоголовое самовнимание, добавление и нормирование, нелинейность по положению, добавление и нормирование, передача его следующему блоку, многоголовое самовнимание, добавление и нормирование, нелинейность по положению, добавление и нормирование , передать его следующему блоку и так далее.

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

Декодер

  • Декодер имеет такой же бизнес позиционного кодирования, и затем для каждого блока кодировщика есть соответствующий блок декодера, но блоки декодера немного сложнее. Теперь внимание с несколькими головками замаскировано и смотрит только на прошлые временные шаги, потому что оно будет использоваться для создания последовательности.
  • Добавить и норма
  • Существует перекрестное внимание, которое производит запросы, основанные на результатах само-внимания. Эти запросы используются для поиска ключей и значений кодировщика.
  • Опять же, есть надстройка и норма. Это добавление и норма суммируют результаты перекрестного внимания и внимания к себе.
  • Затем у вас есть позиционная нелинейная функция, которая использует ту же архитектуру, но другие веса, что и кодировщик.
  • Тогда у вас есть еще одно дополнение и норма
  • Затем вы передаете их в следующий блок
  • В конце последнего блока в каждой позиции есть линейный слой, за которым следует softmax, который выводит распределение вероятностей по токену в этой позиции.

Декодер декодирует одну позицию за раз с маскированным вниманием.

Почему трансформеры?

Несколько плюсов и минусов трансформеров:

  • Вычисление внимания технически равно O из n в квадрате, хотя это не такая большая проблема, как кажется, потому что большая часть вычислительного времени тратится не на выполнение этих скалярных произведений, а на все остальные части сети. На практике, с точки зрения вычислительных затрат, трансформаторы часто намного дешевле, чем RNN.
  • Они несколько более сложны в реализации и включают множество, казалось бы, странных решений, таких как позиционное кодирование, что может сделать их немного сложными для работы. Может потребоваться небольшая настройка гиперпараметров, чтобы трансформеры хорошо обучались.
  • У них намного лучше дальние связи: каждая позиция на выходе связана с каждой другой позицией на выходе и каждой другой позицией на входе с переходом длины один. В вычислительном отношении реализация преобразователя на параллельном оборудовании, таком как графические процессоры, позволяет нам работать намного быстрее.
  • На практике преобразователи можно делать намного глубже, чем сложенные RNN.

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

Генеративное моделирование

Основная идея генеративного моделирования связана с обучением генеративной модели, образцы которой x ∼ pθ(x) поступают из того же распределения, что и распределение обучающих данных, x ∼ pd(x).

Рассмотрим задачу медицинского диагноза, в которой мы сделали рентгеновский снимок пациента и хотим определить, есть ли у пациента рак или нет. Генеративное моделирование сначала решает проблему логического вывода, заключающуюся в определении условных классов плотностей p(x|Ck) для каждого класса Ck в отдельности. Кроме того, он отдельно выводит вероятности предшествующего класса p (Ck). Затем он использует теорему Байеса в форме:

Эквивалентно, мы можем моделировать совместное распределение p(x, Ck) напрямую, а затем нормализовать, чтобы получить апостериорные вероятности. Найдя апостериорные вероятности, мы используем теорию принятия решений, чтобы определить принадлежность к классу для каждого нового входа x. Подходы, которые явно или неявно моделируют распределение входных и выходных данных, известны как генеративные модели, потому что путем выборки из них можно генерировать синтетические точки данных во входном пространстве.

Хотя диапазон приложений, в которых использовались генеративные модели, продолжает расти, мы можем выделить три основных запроса на вывод для оценки генеративной модели:

  1. Оценка плотности: при заданной точке данных x какова вероятность, назначенная моделью, т. е. pθ(x)?
  2. Выборка: как мы можем генерировать новые данные из модельного распределения, т. е. xnew∼pθ(x)?
  3. Неконтролируемое обучение представлению: как мы можем изучить значимые представления функций для точки данных x?

Это таксономия генеративных моделей:

В частности, мы обсудим четыре генеративные модели:

  1. Авторегрессионные модели
  2. Вариационные автоэнкодеры
  3. Нормализация моделей потока
  4. Генеративно-состязательные сети

Авторегрессионные генеративные модели

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

  1. Разделим x на его размерности x1,...,xn
  2. Мы дискретизируем каждое из этих измерений на k значений (например, когда мы генерируем изображения, пиксели изображения естественным образом дискретизируются на 256 значений, поскольку пиксели могут принимать только 256 различных цветов).
  3. Мы представляем полное соединение по всем пикселям или по всем измерениям x по цепному правилу
  4. Используйте свою любимую модель последовательности, чтобы фактически смоделировать это p (x)

Использование авторегрессионных генеративных моделей:

  • Выборка
  • Завершение
  • Представительства

Давайте рассмотрим один популярный класс моделей в этой категории, относящийся к авторегрессивному генеративному моделированию изображений. Это называется «PixelRNN»; идея в том, что мы собираемся генерировать пиксели по одному, слева направо, сверху вниз в порядке строки сканирования. Таким образом, вероятность всего изображения определяется произведением на все пиксели, и каждый пиксель зависит от своих предшественников, которые определяются как все предшествующие строки развертки и все предшествующие пиксели слева от текущего пикселя в текущей строке развертки.

Каждый пиксель состоит из трех цветовых каналов, поэтому мы также должны генерировать по одному цветовому каналу за раз — на самом деле для каждого пикселя существует небольшая сеть, которая генерирует красный цвет, учитывая все предыдущие пиксели, затем зеленый цвет, учитывая все предыдущие пиксели, и это собственный красный цвет пикселя, а затем генерирует синий цвет, учитывая все предыдущие пиксели и красный и зеленый цвет этого пикселя. Кроме того, каждый цветовой канал представляет собой softmax с 256 путями, поэтому яркость каждого цветового канала может принимать 256 возможных значений.

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

Некоторые практические соображения:

  1. Это довольно медленно, потому что, хотя основной рецепт тот же, что и у языковой модели, если изображение 32 на 32 пикселя, это означает, что в этом изображении около тысячи пикселей — так что это будет похоже на предложение из тысячи слова. Кроме того, каждый пиксель сам по себе имеет три цветовых канала, что увеличивает его до 3000.
  2. Генерация изображения построчно, как это, может не уловить часть пространственного контекста, присутствующего в изображениях, потому что пиксели, расположенные прямо над строкой сканирования, считаются далекими в порядке RNN.
  3. Есть много практических улучшений и лучших архитектур, которые можно улучшить PixelRNN (PixelCNN, PixelTransformer, …).

Условные авторегрессионные модели

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

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

Рецепт очень похож на условную языковую модель; у вас есть что-то, на что вы обуславливаетесь, что мы назовем «контекстом». Контекст обрабатывается с использованием какой-то сети кодировщика, которая может быть просто моделью с прямой связью, CNN или другой RNN и используется для запуска первого временного шага авторегрессивной генеративной модели.

Кодировщик может быть очень простым, если, например, вы создаете изображения класса, или очень сложным, если, например, вы представляете политику в имитационном обучении.

Подводить итоги:

  • Хотя авторегрессионные модели являются чрезвычайно мощными средствами оценки плотности, выборка по своей сути представляет собой последовательный процесс и может быть чрезвычайно медленным для данных высокой размерности. Кроме того, данные должны быть разложены в фиксированном порядке; в то время как выбор порядка может быть очевиден для некоторых модальностей (например, текста и аудио), он не очевиден для других, таких как изображения, и может повлиять на производительность в зависимости от используемой сетевой архитектуры.
  • Авторегрессивные генеративные модели в основном похожи на языковые модели, но для других типов данных, хотя правильнее сказать, что языковые модели — это всего лишь особый тип авторегрессионной генеративной модели. Другими словами, понятие авторегрессивной генеративной модели на самом деле более общее, чем языковая модель.
  • Вы можете представлять авторегрессионные модели разными способами (RNN, LSTM, PixelCNN, Transformers,…)
  • У этих моделей есть несколько интересных компромиссов по сравнению с другими моделями, о которых мы поговорим позже. Одна действительно хорошая вещь в таких авторегрессионных генеративных моделях заключается в том, что они предоставляют полные распределения с вероятностями, поэтому они присваивают вероятность каждому возможному x, и эти вероятности легко получить. Недостатком является то, что они медленны для больших точек данных (например, изображений) и, как правило, ограничены в разрешении изображения.

Подводя итог, некоторые плюсы pixelRNN и pixelCNN:

  • Они позволяют явно вычислить вероятность P(X). Это явная плотность, которую мы можем оптимизировать.
  • Возможность сделать это также имеет еще одно преимущество, заключающееся в предоставлении хорошей метрики оценки. Вы можете измерить, насколько хороши ваши выборки, по этой вероятности данных, которые вы можете вычислить.
  • Он может производить довольно хорошие образцы

Тем не менее, это все еще активная область исследований, и основным недостатком этих методов является то, что генерация является последовательной и поэтому может быть довольно медленной.

Автоэнкодеры

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

В более широком смысле автоэнкодер — это тип искусственной нейронной сети, используемый для обучения эффективному кодированию неразмеченных данных (обучение без учителя). Кодировка проверяется и уточняется путем повторного создания входных данных из кодировки. Автоэнкодер изучает представление (кодирование) набора данных, как правило, для уменьшения размерности, обучая сеть игнорировать несущественные данные («шум»). Существуют варианты, направленные на то, чтобы выученные представления приобрели полезные свойства.

Давайте начнем с представления на очень-очень высоком уровне вероятностных моделей обучения без учителя, например таких вещей, как языковые модели. На очень высоком уровне вы можете думать о них как о входных данных, где входные данные соответствуют пикселям изображения, и цель состоит в том, чтобы создать то же самое изображение, но в середине у вас есть некоторая структура, которая делает эту задачу восстановления изображения несколько нетривиальный. Таким образом, PixelRNN, например, не может просматривать пиксели, которые он еще не сгенерировал; он должен сгенерировать пиксель, прежде чем он действительно увидит, что это за пиксель.

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

  • Модели последовательности RNN/LSTM, которые должны предсказывать значение пикселя только на основе предыдущих пикселей.
  • Модели PixelCNN, которые должны предсказывать значение пикселя на основе замаскированного соседства
  • Пиксельный преобразователь, который делает прогнозы на основе замаскированного собственного внимания.

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

Что-то в дизайне модели или что-то в обработке или регуляризации данных должно заставить автоэнкодер изучить структурированное представление.

  • Размерность: сделайте скрытое состояние меньше (с точки зрения количества измерений), чем вход/выход, чтобы сети пришлось сжимать его.
  • Разреженность: сделать скрытое состояние разреженным, что означает, что большинство записей в скрытом состоянии равны нулю, поэтому сети приходится сжимать входные данные.
  • Шумоподавление: искажает ввод шумом, заставляя автоэнкодер научиться отличать шум от сигнала.
  • Вероятностное моделирование: принудительное согласование скрытого состояния с предыдущим распределением. Это подход, который мы можем использовать, чтобы автоэнкодеры также были очень хорошими генеративными моделями.
  1. Автоэнкодер с узким местом основан на том факте, что какой бы ни была размерность вашего ввода, вы выбираете размерность скрытого состояния намного меньше. Если у вас есть изображение размером 100 на 100 пикселей, а скрытое состояние состоит только из 128 измерений, он не сможет изучить функцию идентификации, потому что не может хранить значения каждого отдельного пикселя в этих 128 измерениях. Это очень просто, и у него есть некоторые интересные свойства. Это нелинейное обобщение анализа главных компонентов, потому что, если и кодировщик, и декодер линейны, то обучение такого типа автокодировщика узких мест со среднеквадратичной ошибкой для реконструкции точно восстанавливает анализ главных компонентов, а размерность скрытого состояния равна количество основных компонентов, которые вы собираетесь получить. Подводя итог, можно сказать, что его очень просто реализовать; недостатком является то, что простое уменьшение размерности часто не дает желаемой структуры.
  2. Разреженный автоэнкодер опирается на этот базовый принцип: мы можем описать ввод с помощью небольшого набора атрибутов — вместо того, чтобы описывать изображение собаки, указывая цвет каждого пикселя, который не структурирован и не поддается интерпретации. , вы можете поставить 1 за каждое свойство, которым обладает собака (уши, крылья, колеса и т. д.). Интересным свойством этих видов представлений является то, что большинство атрибутов для большинства изображений будут нулевыми, потому что в мире существует огромное количество возможных объектов, которые не обладают большинством атрибутов. Это идет рука об руку со структурой; принцип разреженности говорит, что вам нужен такой словарь атрибутов, который очень структурирован и имеет тенденцию быть разреженным. Это аккуратно, потому что тогда присутствующие атрибуты очень специфичны для этого объекта и очень его описывают. Кстати, эта идея возникла в неврологии, где исследователи считают, что мозг использует такие разрозненные представления для представления вещей в мире («разреженное кодирование»). Практически говоря, единственное, что нам действительно нужно сделать, чтобы получить разреженный автоэнкодер, — это выбрать потерю разреженности. Мы собираемся обучить разреженный автоэнкодер с обратным распространением, чтобы минимизировать некоторую функцию потерь при восстановлении ввода, но у него будет дополнительная функция потерь, применяемая к скрытому состоянию, которая будет способствовать тому, чтобы это скрытое состояние было разреженным. Подводя итог, можно сказать, что преимущество заключается в том, что это принципиальный подход, который может обеспечить распутанное представление; Недостатком является то, что на практике это сложнее, так как требует выбора регуляризатора и настройки гиперпараметров.
  3. Автокодировщик шумоподавления основывается на идее, что хорошая модель, которая изучила осмысленную структуру, должна быть в состоянии заполнить пробелы. Если вы возьмете изображение и каким-то образом испортите его, а затем попросите свою модель удалить искажение, то вашей модели придется кое-что узнать о том, как выглядят реалистичные изображения. Подводя итог, можно сказать, что его очень просто реализовать; Недостатком является то, что неясно, какой слой выбрать для узкого места, и есть много вариантов выбора.
  4. Вариационный автоэнкодер выполняет вероятностное моделирование: он соединяет автоэнкодеры с вероятностными генеративными моделями. Это также решает большую проблему, с которой сталкиваются другие автоэнкодеры: выборка или генерация из них очень сложны, что ограничивает их использование. В общем, вариационные автокодировщики предназначены для сжатия входной информации в ограниченное многомерное скрытое распределение (кодирование) для ее максимально точного восстановления (декодирование).

Авторегрессионные модели определяют управляемую функцию плотности, и на ее основе мы можем напрямую оптимизировать вероятность обучающих данных. С другой стороны, вариационные автоэнкодеры определяют неразрешимую функцию плотности со скрытым z. Вероятность наших данных p(x) теперь является интегралом по сравнению с математическим ожиданием по всем возможным значениям z, однако мы не можем оптимизировать это напрямую.

Модели скрытых переменных

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

В общем, предположим, что наше распределение p(x) очень сложное, и мы не можем легко его представить. Давайте тогда введем другое распределение по другой переменной z; p (z) - это просто нормальное распределение единичной дисперсии с нулевым средним. Затем мы построим нашу генеративную модель как вероятностное отображение от z до x. Другими словами, мы пытаемся представить p(x) как композицию двух очень простых распределений. Одно из них — p(z), просто гауссовское; другой — p(x|z), который также является простым распределением, но его параметры — очень сложные и детерминированные функции от z.

p(x) теперь является интегралом по p(x|z) p(z) dz. Все сложные модели, которые мы будем обсуждать, основаны на этом принципе: у вас есть простое распределение по очень абстрактной скрытой переменной z, и у вас есть простое условное распределение по вашей сложной переменной x, но отображение от z к x представлено нейронной сетью. сеть.

Как мы обучаем модели скрытых переменных?

У нас будет некоторая модель pθ(x) и набор данных x. Тренируем с максимальной вероятностью:

Поскольку каждый из ваших p (x):

Мы заменяем:

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

Вместо интегрирования z мы, по сути, угадываем, что такое z, оценивая p(z|x) для каждого изображения, а затем максимизируем вероятность этого x с этим z. Но как вычислить p(z|x)? Это называется вероятностным выводом.

Модели скрытых переменных в глубоком обучении

Некоторая переменная z имеет некоторое априорное распределение p(z). Это предварительное распределение обычно устанавливается очень простым, потому что значение z не является фиксированным; значение z полностью определяется вашей моделью, поэтому вам не нужно изучать или проектировать p(z), и вы можете выбрать что-то простое, например многомерное нормальное распределение ковариации идентичности с нулевым средним значением. Затем у вас есть декодер, который отображает вектор z в распределения по x: это нейронная сеть.

Использование модели для генерации работает следующим образом:

  1. Выборка z из p(z): генерация случайных чисел.
  2. Образец z из p(x|z): превратите этот вектор случайных чисел в изображение

Глубокая генеративная модель со скрытой переменной обычно представляет собой просто модель, которая превращает случайные числа в действительные выборки.

Как мы представляем модели скрытых переменных?

Легкая часть представляет p(z): генерация случайных гауссовых чисел. Представление pθ(x|z) немного сложнее.

  1. Первый вариант — сказать, что ваши пиксели являются непрерывными значениями; вы бы представили их как многомерное нормальное распределение с диагональной ковариацией, что означает, что каждый пиксель будет иметь среднее значение и каждый пиксель будет иметь дисперсию. И среднее значение, и дисперсия каждого пикселя в общем случае могут быть функциями от z, поэтому они могут быть выходными данными нейронной сети. Очень часто мы упрощаем это и делаем сигму константой — в этом случае вы просто получаете потерю среднего квадрата ошибки.
  2. Другой вариант - сказать, что ваши пиксели имеют дискретное значение, и в этом случае все пиксели зависят от z, но в зависимости от z их можно рассматривать как независимые. Это на самом деле очень хорошо, но немного медленно.
  3. Есть и другие варианты (дискретизированная логистика, бинарная кросс-энтропия и т. д.).

Какую архитектуру следует использовать для pθ(x|z)?

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

Обучение моделей скрытых переменных

  1. Первый вариант — выполнить логический вывод, чтобы вычислить p(z|x) для каждого тренировочного изображения x, а затем минимизировать ожидаемую отрицательную логарифмическую вероятность (это то, что делают вариационные автоэнкодеры).
  2. Используйте обратимое отображение z в x
  3. Не утруждайте себя получением z для каждого отдельного x, а сопоставьте сами распределения. В основном выборка z случайным образом, выборка x с учетом этого z и попытка сопоставить эти выборки на уровне населения с обучающими данными (это то, что делают генеративно-состязательные сети).

Способ, которым мы собираемся угадать z, состоит в том, чтобы взять ожидаемое значение при p(z|xi) для каждой точки данных xi в нашем наборе данных, а затем мы максимизируем совместную логарифмическую вероятность xi и z, которые мы догадался. На самом деле, p(z|xi) — это распределение, означающее, что мы не знаем точно, какой индивидуум z соответствует какому xi, но мы можем угадать вероятность различий: нам нужно усреднить все эти возможности и это то, что делает ожидаемое значение. Большой проблемой с этой схемой является способ вычисления p(z|xi): мы существенно преобразовали эту проблему вычисления сложного интеграла в проблему того, как каким-то образом оценить, а затем выбрать из p(z|xi). Это называется вероятностным выводом, потому что мы делаем вывод, какой z соответствует конкретному x.

Вариационное приближение

Основная идея того, что мы собираемся сделать, называется вариационной аппроксимацией. Вместо фактического вычисления реального распределения p(z|xi) мы собираемся аппроксимировать его каким-то гораздо более простым распределением, с которым гораздо легче иметь дело. Например, мы можем просто сказать, что p(z|xi) для конкретного изображения xi является некоторым гауссовским qi(z) с некоторым средним значением и дисперсией — и это будет другое среднее значение и различная дисперсия для каждого изображения.

Конечно, реальное p(z|xi) — это не просто гауссиана, это может быть какая-то очень сложная вещь — так что это приближение и, возможно, довольно грубое. Замечательным фактом является то, что для любого выбора qi(z) мы можем построить нижнюю границу для log p(xi), так что величина, которую мы вычислим, не равна log p(xi), а только оно меньше или равно ему. Это означает, что если мы пытаемся максимизировать log p(xi), вместо этого мы можем максимизировать эту нижнюю границу величины.

Вот как мы можем это сделать: давайте начнем с выражения для log p(xi), которое использует этот сложный интеграл из предыдущего.

Затем мы умножаем на 1 и переписываем логарифм как ожидаемое значение при qi(z):

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

Другими словами, если у вас есть логарифм ожидаемого значения, эта величина всегда больше или равна ожидаемому значению логарифма. Если это выпуклая функция, то неравенство выполняется в другую сторону.

Вот что мы получаем:

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

Последний член — это отрицательная энтропия ци.

Вывод из всего этого обсуждения состоит в том, что максимизация этой величины, которая у нас есть в конце, подтолкнет нижнюю границу к log p(xi), что означает, что в конечном итоге она также максимизирует log p(xi). Краткая скобка об энтропии:

Энтропия позволяет нам задавать вопросы:

  1. Насколько случайна случайная величина? Энтропия показывает, насколько случайным является распределение; если все исходы равновероятны, то у вас самая высокая энтропия, а если гарантирован один исход, то у вас самая низкая энтропия.
  2. Насколько велика логарифмическая вероятность в ожидании сама по себе? Например, если у вас есть два таких дистрибутива:

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

Тогда чего мы ожидаем от этого выражения?

Мы максимизируем энтропию по qi, а также максимизируем ожидаемое значение этих логарифмических вероятностей.

  • Первый член будет поощрять придание большой массы вероятности значению z, для которого log p (xi | z) + log p (z) велик.
  • Второй член будет способствовать расширению этой вероятностной массы.

Это означает, что мы захватим точку с наибольшей вероятностью при p, но мы также сделаем результирующее распределение q как можно более широким, чтобы оно охватывало как можно большую вероятностную массу и имело самая высокая энтропия.

KL-расхождение

Еще одно понятие в теории информации, которое мы должны обсудить, — это KL-дивергенция. Это можно рассматривать как:

1 Мера того, насколько различны два распределения, потому что, если q и p точно такие же, то расхождение равно нулю:

2 Мера того, насколько мала ожидаемая логарифмическая вероятность одного распределения при другом, минус энтропия.

Вариационное приближение

Выражение, которое мы получили ранее, используя неравенство Дженсена, на самом деле является KL-дивергенцией. Мы называем это нижней границей доказательства, и это функция двух распределений: p, которое является распределением, которое мы изучаем, и qi, которое является нашим приближением к апостериорному. Так что же делает qi(z) хорошим? Способность точно аппроксимировать p(z|xi) с низким KL-расхождением между ними.

Можно доказать, что журнал p(xi) равен:

Где второй член:

Следовательно, если мы хотим, чтобы qi(z) хорошо аппроксимировало p(z|xi), KL-дивергенция должна быть низкой.

Если мы хотим знать, как обучать нашу модель, мы можем обучить нашу модель, максимизируя нижнюю границу доказательства по отношению к модели p, и, если мы хотим получить наилучшее qi, мы также максимизируем ту же нижнюю границу доказательства по отношению к модели p. ци. Это говорит нам о том, что если мы просто максимизируем нижнюю границу свидетельства, мы на самом деле минимизируем KL-дивергенцию.

Затем цель состоит в том, чтобы максимизировать нижнюю границу доказательства: для каждого xi в нашем наборе данных мы собираемся вычислить градиент нижней границы доказательства по отношению к параметрам нашей модели. Как мы это делаем, мы сначала выбираем z из qi(z); затем мы аппроксимируем наш градиент как градиент для нашей модели для этого одного образца; затем мы делаем один шаг градиентного спуска и обновляем нашу qi, чтобы также максимизировать нижнюю ожидаемую границу (существуют разные способы сделать это).

Давайте начнем с чего-то простого: просто скажем, что qi — это гауссово распределение по z со средним значением и дисперсией. Для каждого изображения в нашем наборе данных у нас есть другое среднее значение и другая дисперсия, которые мы будем отслеживать.

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

2. Амортизированный вариационный вывод
Амортизированный VI — это идея о том, что вместо оптимизации набора свободных параметров мы можем ввести параметризованную функцию, которая отображает из пространства наблюдения параметры приближенного апостериорного распределения. На практике мы могли бы (например) ввести нейронную сеть, которая принимает наблюдение в качестве входных данных и выводит параметр среднего значения и дисперсии для скрытой переменной, связанной с этим наблюдением 4 5. Затем мы можем оптимизировать параметры этой нейронной сети вместо отдельных параметров каждого наблюдения.

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

Выполнение шага градиентного восхождения на φ: путем вычисления градиента по φ для L. φ появляется в двух местах в нижней границе свидетельства: (1) оно появляется как параметры распределения, при котором мы вычисляем математическое ожидание (2) он появляется внутри второго члена энтропии

Это наша нижняя граница доказательства:

С градиентом энтропийного члена легко справиться, но первая часть немного сложнее. Один из способов, которым мы можем думать о вычислении градиента первой части, состоит в том, чтобы отметить, что все, что находится внутри ожидания, не зависит от φ, поэтому мы можем собрать эти члены в одно выражение, которое я назову r(xi, г):

Это похоже на цель обучения с подкреплением, которая состоит в том, чтобы максимизировать ожидаемую ценность вознаграждения, когда ожидания принимаются в отношении распределения, определяемого политикой. Здесь у нас есть ожидаемое значение некоторой функции r, где распределение определяется qφ: мы могли бы просто использовать градиент политики.

Простая версия будет:

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

Есть гораздо лучший способ обучения этих моделей, который на практике будет намного быстрее и эффективнее; он будет использовать тот факт, что, в отличие от обучения с подкреплением, в случае моделей со скрытыми переменными мы действительно можем вычислить производную от r. Это называется приемом репараметризации.

Вот идея. У нас есть выражение:

Для которого мы хотим вычислить производную по φ, но φ — это не просто любое распределение: это нормальное распределение. Это означает, что мы могли бы также записать z как детерминированную функцию среднего значения, дисперсии и эпсилон, что является стандартной гауссовой выборкой:

Это связано с тем, что гауссиана с ненулевым средним значением и дисперсией, отличной от единицы, может быть просто выражена как аффинное преобразование гауссианы с нулевой единицей. Здесь все детерминировано, кроме эпсилон, но эпсилон не зависит от φ. Это не зависит от нейронной сети, поэтому мы можем эквивалентно записать нашу цель как ожидаемое значение по отношению к эпсилону, где мы подставляем это уравнение для z:

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

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

Тета входит только в первый термин; второй и третий члены не зависят от тета, и если мы сгруппируем их вместе, мы заметим, что это просто отрицательное KL-расхождение. Очень удобно выражать нижнюю границу доказательства таким образом, потому что все сложные вещи в основном находятся в первом члене.

Если мы применим трюк с репараметризацией, то и тета, и фи теперь появляются внутри ожидания, и распределение, при котором мы берем ожидание, не зависит ни от одного из параметров. Мы можем оценить это, используя только одну выборку эпсилон, просто подключив этот эпсилон.

На этом этапе может быть полезно нарисовать получившиеся нейронные сети:

Вот как вы реализуете вариационный автоэнкодер. Причина, по которой это называется вариационным автоэнкодером, заключается в том, что если вы посмотрите на структуру этой нейронной сети, она выглядит как автоэнкодер. У него есть энкодер с параметрами φ, который имеет этот необычный шум, среднее значение и дисперсию в конце; тогда у него есть декодер с параметрами θ. Итак, это просто автоэнкодер, в котором, в отличие от шумоподавляющего автоэнкодера, шум помещается в скрытое состояние z, а не в его начало. Но у вариационного автоматического кодировщика есть очень привлекательная интерпретация модели со скрытыми переменными.

Трюк с репараметризацией и градиент политики:каковы компромиссы?

Градиент политики:

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

Трюк с репараметризацией:

  • Он обрабатывает только непрерывные скрытые переменные
  • Его очень просто реализовать, и он работает немного лучше, если вы можете его использовать.

Теперь давайте соберем все воедино и пройдемся по полному вариационному автоэнкодеру.

Вариационный автоэнкодер — это модель со скрытой переменной, в которой есть скрытая переменная z и наблюдаемые переменные x.

  • Он имеет кодировщик, который выполняет вывод:

Это нейронная сеть, которая принимает x и выдает среднее значение и дисперсию по z.

  • Есть декодер:

Это нейронная сеть, которая принимает z и выдает среднее значение и дисперсию по x.

Это цель:

Первая часть задачи выглядит как задача автоэнкодера: вы кодируете xi в среднее значение и дисперсию, получаете z, а затем декодируете, чтобы получить результирующее распределение по x, и пытаетесь максимизировать вероятность реального изображения.

Вторую часть можно рассматривать как своего рода штраф, который наказывает, насколько qφ(z|xi) отклоняется от предыдущего p(z). Из этого мы можем сделать небольшое интуитивное представление о том, почему работает вариационный автокодер: поскольку этот второй член KL-дивергенции побуждает закодированные z выглядеть похожими на выборки из p(z), во время тестирования декодер будет знать, что делать. делать с этими z.

Мы также можем обучать условные модели вариационных автоэнкодеров; если у вас есть условная модель, то вы сопоставляете x с некоторой выходной переменной y и хотите, чтобы p(y|x) было некоторым довольно сложным распределением. Мы будем использовать ту же интуицию, что и раньше: p(y|x) может быть сложной, но p(y|x, z) будет простой. Все как раньше, только теперь мы генерируем y и все зависит от x.

VAE со свертками

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

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

Затем мы возьмем наши нормально распределенные эпсилоны, умножим один из них на сигму, и это даст нам z.

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

Но для обучения VAE нам также нужен декодер. Для этого мы собираемся использовать свертки транспонирования, которые увеличивают выборку вектора вплоть до изображения 64 на 64. Затем мы тренируем все это от начала до конца с обратным распространением, используя два термина потерь:

  • Отрицательная логарифмическая вероятность изображения
  • KL-расхождение между распределением, определяемым μz и σz, и нашим предыдущим значением p(z), которое обычно представляет собой гауссовскую дисперсию с нулевой средней единичной дисперсией.

Наш декодер выводит два числа для каждого пикселя: среднее значение и дисперсию.

VAE на практике

  • Одна из распространенных проблем заключается в том, что VAE очень заманчиво игнорировать скрытые коды или генерировать плохие образцы. Причина в том, что если вы можете аппроксимировать свой x обычным гауссовским распределением, это будет сделать намного проще, особенно для условных VAE, потому что изначально z просто выглядит как шум, и выяснить, как его использовать, может быть довольно сложно. Вы можете сказать, что это происходит, когда вы получаете размытое «среднее» изображение, которое не похоже на ввод.
  • Вторая проблема, с которой вы можете столкнуться, заключается в том, что скрытый код не сжимается. Вы можете сказать, что это происходит, когда ваши реконструкции великолепны, но когда вы выбираете z из p(z) и затем декодируете, вы получите нереалистичное изображение.

Для задачи 1 KL-дивергенция слишком мала: обычно qφ(z|x) игнорирует x и просто выводит нулевое среднее единичной дисперсии. Это означает, что z не несут никакой информации о x, что означает, что ваш декодер pθ не использует z. Вот почему он создает эти размытые средние изображения: в z нет информации, которая была бы полезна для фактического восстановления x.

Проблема 2 характеризуется противоположным, KL-дивергенция слишком высока: слишком много информации упаковано в z, а это означает, что вы можете точно реконструировать x, но поскольку он так далек от априорного, когда вы выбираете z из него, ваш декодер не знает, что с ним делать.

Чтобы контролировать KL-дивергенцию, очень распространенным приемом является добавление множителя перед этой KL-дивергенцией. Вы настраиваете этот гиперпараметр в зависимости от того, какую проблему вы видите: если у вас проблема номер один, вам нужно уменьшить бета, тогда как если вы видите проблему два, вам нужно увеличить бета.

Иногда трудно найти бета-версию, которая дает хорошие образцы и хорошие реконструкции на ранних этапах обучения. Интуитивно понятно, что в начале обучения VAE нужно научиться обращать внимание на z, а позже в обучении ему нужно лучше выполнять сжатие. Тогда может быть очень полезно фактически запланировать бета-версию, чтобы изменить бета-версию. Например, вы можете начать с очень низких значений бета, чтобы VAE научился использовать z, а затем повышать бета, чтобы образцы были качественными. Другими словами, как только ваши реконструкции будут выглядеть хорошо, вы можете увеличить бету, и тогда ваши образцы тоже станут лучше.

Обратимые модели и нормализующие потоки

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

Теперь, если мы хотим вычислить p(x) для обучения, мы можем использовать формулу замены переменных, которая говорит, что если x является детерминированной функцией z, то p(x) равно:

Это означает, что если у вас есть некоторое распределение по z и z преобразуется детерминированным образом для получения x, то для получения плотности по x все, что вам нужно сделать, это изменить плотность по z на то, насколько изменяется объем в соответствии с к ф.

Если мы сможем вычислить определитель якобиана df/dz и вычислить обратную, то мы сможем реализовать модель такого рода с детерминированной функцией, отображающей эти стеки. Таким образом, этот определитель является просто поправкой на изменение локальной плотности из-за f. Таким образом, основная идея состоит в том, что если мы сможем изучить обратимые отображения от z до x, это может упростить вычисление этого определителя, и мы сможем строить такие модели, не беспокоясь обо всех этих вариационных выводах. Нам также не нужны нижние границы, и мы можем получить точные вероятности.

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

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

Несколько наблюдений:

  • Если каждый слой обратим, то обратимо и все.
  • Часто обратимые слои также имеют очень удобные определители.
  • Логарифмический определитель всей модели — это просто сумма логарифмических определителей слоев.
  • Наша цель — спроектировать обратимый слой, а затем скомпоновать многие из них для создания полностью обратимой нейронной сети.

Существует множество различных способов создания обратимых слоев; Я начну с одного из самых простых типов, который называется нелинейной оценкой независимых компонентов (NICE).

Обычно слой в нейронной сети представляет собой некоторое отображение от z до x. Например, x задается ReLU, примененным к (Wz+b). Но мы не можем использовать это, потому что это необратимо. Вот идея: что, если мы заставим часть слоя хранить всю информацию, чтобы потом можно было восстановить все, что было изменено нелинейной частью?

Мы можем взять наш z и разделить его на две части. Первая часть идет от номера 1 до d, вторая часть идет от номера d+1 до n. X тоже будет состоять из двух частей: первая часть полностью копируется в первую часть z, но также пропускается через какую-то нейросеть и добавляется ко второй части z.

Возникает вопрос: если у нас есть x, можем ли мы восстановить z? Если да, то этот слой обратим. Затем мы можем составить целую сеть таких обратимых слоев.

  1. Восстановить z (1: d) = x (1: d)
  2. Восстановить gθ(z(1:d))
  3. Восстановить z(d+1:n) = x(d+1:n) — gθ(z(1:d))

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

А как насчет якобиана?

Нам нужен определитель для вычисления нашей цели нормализации потока; и x, и z имеют эти две части, так что это будет df/dz как матрица:

Производные этой матрицы:

Однако вы можете знать, что определитель не зависит от нижнего левого блока, потому что верхний правый блок равен нулю. Таким образом, определитель такой матрицы — нижняя треугольная матрица; он просто определяется диагональными элементами, а все диагональные элементы равны единице, поэтому определитель равен 1.

Это очень удобно: из-за этой конкретной формы для этих обратимых слоев детерминанты всегда одни, а это означает, что этот термин в цели никак не влияет на обучение модели. К сожалению, это репрезентативно немного ограничивает, потому что определитель всегда один, и поэтому мы не можем всегда изменять масштаб определителей. Сеть такого типа только преобразует z в x, но никогда не масштабирует их, поэтому таким образом может быть очень сложно обучать очень сложные распределения.

Итак, на практике мы видим, что если мы возьмем такой обратимый слой, сложим несколько из этих слоев и воспользуемся ими для обучения генеративной модели, мы сможем довольно хорошо смоделировать простые наборы данных (MNIST), но когда мы если вы начнете смотреть на более сложные изображения, то это приведет к поломке (CIFAR10).

Мы можем внести в эту модель небольшое изменение, которое сделает ее существенно более выразительной, и это то, что называется Real-NVP: трансформация без сохранения объема. Мы собираемся разделить z и x на две части, как и раньше, но теперь у нас будет две нейронные сети: масштабирующая сеть и смещающая сеть. Мы возьмем вторую половину и умножим ее на первую нейронную сеть, а затем добавим член из второй нейронной сети, так что вторая половина x будет равна второй половине z, умноженной на экспоненциальную выхода первой нейронной сети плюс второй нейронной сети:

Это также обратимая операция, и способ получения обратной операции очень похож на предыдущий. Вы восстанавливаете первую половину z, просто используя тот факт, что она равна первой половине x; как только вы восстановите это, вы можете восстановить gθ и hθ, а затем вы можете найти вторую половину z, просто взяв вторую половину x, вычитая g, а затем разделив на экспоненту h.

Теперь якобиан имеет гораздо более интересную форму:

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

Некоторые заключительные замечания о нормализации потоков

  • Одним из больших преимуществ нормализации потоков является то, что вы можете получить от них точные вероятности или правдоподобия; вы можете фактически оценить p (x), тогда как вариационный автоэнкодер дает вам только ограничение на p (x)
  • Вам не нужны нижние границы
  • Концептуально несколько проще

Он также имеет несколько существенных недостатков:

  • Это требует особой архитектуры, поэтому вы не можете просто использовать обычные слои.
  • Поскольку каждый слой обратим, размерность на каждом слое должна оставаться неизменной, и это может иметь большое значение для изображений с высоким разрешением. Если в вашем изображении миллион пикселей, то вам нужен миллион измерений по оси z, тогда как вариационный автоматический кодировщик может иметь скрытый код, размерность которого намного меньше, чем у изображения, и это может быть серьезным ограничением.

Генеративно-состязательные сети

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

Другими словами, вместо того, чтобы пытаться угадать для каждого реального изображения, каково его соответствующее z, а затем использовать это для наблюдения за нашей сетью, что, если мы просто создадим набор случайных изображений, а затем попытаемся изменить нашу сеть так, чтобы эти случайные изображения в уровень популяции напоминает популяцию реалистичных изображений, данных нам в нашем обучающем наборе? Это означает, что мы хотим получить выборку из сложного многомерного обучающего распределения, и нет прямого способа сделать это; поэтому решение состоит в том, чтобы взять образец из простого распределения (например, случайного шума), а затем изучить преобразование в обучающее распределение (с помощью нейронной сети).

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

Вот как мы это устроим: мы создадим игру, в которой единственным выигрышным ходом будет создание реалистичных изображений. Другими словами, идея состоит в том, чтобы обучить сеть, которая будет угадывать, какие изображения настоящие, а какие поддельные; этот классификатор будет выводить вероятность того, что это изображение является реальным изображением. Чтобы обучить такую ​​сеть, все, что вам нужно, — это помеченный обучающий набор; поддельные изображения могут быть получены из нашей генеративной модели, тогда как настоящие изображения могут быть получены из нашего тренировочного набора.

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

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

Основная идея GAN основана на «косвенном» обучении через дискриминатор, который также динамически обновляется. В основном это означает, что генератор обучен не минимизировать расстояние до конкретного изображения, а скорее обманывать дискриминатор. Это позволяет модели обучаться без присмотра.

Вот возможный алгоритм:

  1. Получите настоящий набор данных реальных изображений
  2. Получите генератор Gθ(z), который можно даже инициализировать случайным образом
  3. Создайте набор данных ложных изображений
  4. Обучите свой дискриминатор, который является просто классификатором, который пытается предсказать, было ли изображение получено из истинного набора данных или ложного.
  5. Используйте этот дискриминатор, чтобы построить некоторую функцию потерь для обучения G (z).

Этот рецепт почти работает, но у него есть несколько серьезных проблем, которые нам нужно решить, прежде чем мы сможем создать жизнеспособный метод.

Если мы выполним пятый шаг только один раз, большая проблема будет заключаться в том, что на пятом шаге G(z) очень легко обмануть D(x). Если D(x) был обучен для поддельных изображений, исходящих от действительно плохого генератора, то новый генератор на пятом шаге просто должен быть лучше старого, но он все еще может быть не очень реалистичным. Чтобы заставить его работать, мы выбираем итеративный подход. Получив генератор, мы собираемся сэмплировать наши ложные изображения, а затем обновлять наш дискриминатор только с одним шагом градиента: на самом деле мы не будем тренировать сходимость дискриминатора, мы просто сделаем один шаг градиента. Это даст нам немного лучший дискриминатор, затем мы обновим наш генератор только для одного шага градиента, а затем сгенерируем новые ложные изображения; поэтому вместо того, чтобы просто заранее генерировать все ложные изображения и тренировать сходимость вашего дискриминатора, вы фактически будете чередовать обновление дискриминатора, чтобы лучше классифицировать настоящее от подделки, и затем обновление генератора, чтобы генерировать более реалистичные изображения. Это будет своего рода игра, в которой дискриминатор будет продолжать пытаться не отставать от генератора, а генератор будет продолжать пытаться его обмануть. Поскольку у них такой соревновательный процесс, единственный способ генератор для победы в игре должен создавать изображения, которые действительно неотличимы от реальных изображений.

Сводка

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

Немного больше интуиции, почему эта основная идея действительно работает

Что хочет сделать G(z)? В идеале его цель — каким-то образом убедить дискриминатор выдавать 0,5 для всех сгенерированных изображений. Для этого он должен генерировать изображения, которые выглядят реалистично; но несколько более тонкий момент заключается в том, что для того, чтобы действительно получить 0,5 на всех ваших сгенерированных изображениях, вам нужно не просто сгенерировать изображения, которые выглядят реалистично, на самом деле вам нужно сгенерировать все возможные реалистичные изображения, и это почему GAN соответствуют дистрибутивам.

Допустим, у нас есть набор данных с фотографиями кошек и собак; половина картинок - кошки, а половина - собаки. Наш генератор действительно хорош в создании очень реалистичных изображений, но он генерирует только собак, поэтому он не покрывает весь дистрибутив. Представьте, что в этом случае будет делать дискриминатор:

  • Если мы сфотографируем кошку и передадим ее дискриминатору, дискриминатор скажет, что вероятность того, что это истинное изображение, равна 1. Это связано с тем, что в обучающем наборе есть примерно половина истинных изображений, содержащих кошек, но ни одно из них. ложные изображения содержат кошек.
  • Если мы возьмем фотографию собаки и передадим ее дискриминатору, дискриминатор скажет, что вероятность того, что это истинное изображение, равна 0,25. Это связано с тем, что в обучающем наборе примерно половина истинных изображений содержит собак, но все ложные изображения были собаками.

Вот почему для того, чтобы генератор выиграл эту игру против дискриминатора, он действительно должен генерировать не только реалистичные изображения, но и все реалистичные изображения. Для полного раскрытия, конкретная проблема с GAN, которая Я не буду слишком много говорить в этой лекции о том, что в действительности в практических реализациях эти модели часто страдают от того, что называется проблемой коллапса мод. Проблема коллапса режима — это тип переоснащения, и когда это происходит, GAN фактически не могут захватить все распределение.

Технические детали фактической настройки GAN

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

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

D пытается максимизировать логарифмическую вероятность, так что D(x) близко к 1 (настоящее), а D(G(z)) близко к 0 (фальшивое); G пытается сделать наоборот. Это уже не обычная оптимизация градиентного спуска, а проблема нахождения равновесия Нэша в игре с двумя игроками. Вот игра GAN, переписанная в терминах θ (параметры G) и φ (параметры D):

Алгоритм в основном такой же, как чередование шага градиентного подъема по φ, потому что φ максимизирует, и шага градиентного спуска по θ, потому что θ минимизируется. Эти два шага просто чередуются.

Есть две важные детали, которые мы должны сделать правильно, чтобы это сработало:

  1. Как заставить это работать со стохастическим градиентным спуском или восхождением, потому что мы хотим использовать мини-пакет

Для использования стохастического градиентного подъема или спуска мы аппроксимируем градиент, используя мини-партию обучающих точек для первого ожидания, а затем для второго ожидания мы отбираем такое же количество z ​​и пропускаем их через генератор. Это в основном дает нам версию мини-пакетного градиентного восхождения или мини-градиентного спуска. Потеря - это кросс-энтропийная потеря. Потери генератора просто оцениваются с той же мини-партией z; вы просто пропускаете их через генератор, спрашиваете дискриминатор, какую вероятность им дает, а затем используете это как потерю для вашего генератора.

2. Как вычислить градиенты

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

Какую конкретную цель на самом деле оптимизирует GAN?

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

В целях теоретического анализа выразим оптимальное D(x) в замкнутом виде как функцию G(z); у нас есть внешняя минимизация и внутренняя максимизация, поэтому, возможно, решение этой внутренней максимизации может быть выражено в замкнутой форме как функция G. Очевидно, что мы не можем реализовать это в коде, нам нужно на самом деле обучить нейронную сеть для этого , но, может быть, хотя бы теоретически мы можем охарактеризовать решение для D как функцию от G

Очень полезным свойством для этого является тождество, которое можно использовать для описания оптимального байесовского классификатора. Допустим, у нас есть набор изображений с положительной меткой (исходящих из p(x)) и набор изображений с отрицательной меткой (исходящих из q(x)). P(x) — это p(данные), а q(x) — это распределение наших изображений, полученных путем выборки z из p(z) и последующего пропускания через G. Оптимальный дискриминатор, который я назову D*, — это просто arg max логарифмической вероятности, которую мы можем записать как ожидание при p для log(D) плюс ожидание при q для log(1-D).

Теперь мы возьмем производную от этого по D; мы не берем производные каких-либо параметров, мы просто берем производную по функции D, и мы собираемся установить эту производную равной нулю и найти D. Если мы сможем найти D, который устанавливает нас к нулю, мы восстановим оптимальный дискриминатор. Важно отметить, что это не означает, что ваша нейронная сеть может представлять этот оптимальный дискриминатор — это больше в случае, когда у нас есть очень выразительная функция, и мы можем просто не беспокоиться о параметрах и просто охарактеризовать функцию, которая будет оптимальным решением.

Я собираюсь заявить, что если я подключу это:

Тогда производная будет равна 0. Ожидаемое значение — это просто сумма всех возможных x вероятности, умноженная на величину внутри ожидания, поэтому ожидаемое значение при p(1/D(x)) — это просто сумма всех x p(x), деленное на D(x):

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

Что равно 0. Это выражение D * (x) на самом деле является оптимальным классификатором, который мы можем получить, если наши точки с положительной меткой происходят из p of x, а наши точки с отрицательной меткой происходят из q из x. Это оптимальный байесовский классификатор.

Приняв p data(x) за p, а pG(x) за q, мы можем вывести оптимальный классификатор D*(G) как функцию от G. Если G фиксировано, и мы оптимизируем сходимость дискриминатора, если наша нейронная сеть очень-очень выразительно, в пределе вот что у нас должно получиться:

Итак, какова цель G? Мы можем просто включить D*G(x) в приведенную выше минимальную оптимизацию и превратить ее просто в минимизацию над G. Я подчеркиваю, что это чисто теоретическое упражнение, единственная причина, по которой мы это делаем, заключается в том, что чтобы понять, какова на самом деле цель GAN. Итак, если мы подставим это в ., нам в основном просто нужно записать:

Что оптимизирует GAN?

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

Хорошо, давайте определим другое распределение, которое мы собираемся назвать q, которое является средним значением данных p и pG. Итак, мы определим q(x) как:

Затем мы можем записать наше выражение для цели генератора как:

Каждое из этих условий является KL-дивергенцией:

Оказывается, это выражение на самом деле является еще одной мерой расхождения, называемой расхождением Дженсена-Шеннона. Он обладает некоторыми интересными свойствами:

  • Он идет к нулю, если распределения совпадают
  • В отличие от дивергенции KL, она симметрична.

Это означает, что GAN действительно пытается соответствовать распределению данных.

Небольшое практическое замечание о GAN

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

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

Что равносильно утверждению: давайте максимизируем вероятность того, что изображение настоящее. Эти две вещи очень похожи и представляют одну и ту же концепцию, но их градиенты выглядят немного по-разному, когда вы очень далеки от оптимума. Причина, по которой лучше максимизировать вероятность того, что изображение реально, связана с формой функции log x и функции log 1 минус x:

  • Если у вас очень плохой генератор, у вас будет большое значение потерь, поэтому вы окажетесь на левой стороне этих двух кривых.
  • Если у вас очень хороший генератор, у вас будет низкое значение потерь, поэтому вы окажетесь справа от этих двух кривых.

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

Несколько практических замечаний по архитектуре GAN: как вы на самом деле настраиваете GAN?

  • У вас может быть полностью подключенная архитектура. Вы можете взять свои z и смонтировать их через два полносвязных слоя по 512 единиц в каждом и получить цифру MNIST. Тогда ваш дискриминатор также может иметь два полносвязных слоя по 512 единиц каждый и выдавать истинный/ложный вывод с сигмовидной нелинейностью в конце.

  • У вас может быть более сложная сверточная GAN, возможно, похожая по духу на некоторые из тех архитектур VAE, где у вас могут быть некоторые транспонированные свертки, фактически создающие изображение с более высоким разрешением, а затем сверточный дискриминатор, который создает вашу истинную/ложную метку.

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

  • Условные GAN генерируют изображения определенного класса. Все, что вам нужно сделать, это соединить вашу метку класса, показанную здесь как y, как с входными данными для вашего генератора, так и с входными данными для вашего дискриминатора. Вы берете свою метку класса, представленную в виде горячего вектора, и просто объединяете ее с z, а затем для дискриминатора объединяете ее с x

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

У нас есть два условных генератора и два дискриминатора:

G превращает X в Y, поэтому он зависит от x, который может быть фотографией лошади, и его задача — создать y, фотографии зебр.

Dx обучается, где позитивы — это настоящие изображения лошадей, а негативы — это поколения от G, а Dy обучается, где позитивы — это настоящие изображения зебр, а негативы — это поколения от F. Но, конечно, для того, чтобы F сгенерировал образец, ему нужно дать некоторый x, поэтому F сгенерирует зебру, учитывая лошадь. Теперь мы можем представить себе обучение этих двух генераторов и двух дискриминаторов, и теперь у нас будет генератор, который превращает лошадей в зебр, и еще один генератор, который превращает зебр в лошадей. Но мы еще не закончили, потому что здесь ничего не говорится о том, какую лошадь или какую зебру вы должны сгенерировать. Итак, проблема в том, почему переведенная зебра должна выглядеть как оригинальная лошадь?

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

Краткое резюме

  • GAN — это игра для двух игроков. Это уравнение для него:

  • Мы можем вывести оптимальный дискриминатор и на его основе определить, какая цель фактически минимизируется при равновесии Нэша.

Это не гарантирует, что мы действительно найдем равновесие Нэша; Итак, этот вывод просто показывает, что если мы найдем равновесие Нэша, фиксированную точку для этой GAN, это минимизирует расхождение.

  • Мы можем обучать либо полностью подключенные, либо сверточные GAN, и у нас есть большая свобода в определении их архитектуры.

Современные методики обучения ГАН

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

Если я обучу свой дискриминатор с помощью pdata и pG, мой дискриминатор может выглядеть примерно так:

Это будет идеальное значение 1,0, близкое к реальным данным, и идеальное 0,0, близкое к сгенерированным данным, и у нас будут некоторые границы решений где-то посередине, очень далеко от любого распределения.

Помните, что генератор полностью учится, используя градиент через дискриминатор, так что же на самом деле будет градиент генератора рядом с сгенерированными данными? Что ж, дискриминатор выдает нуль довольно равномерно по всем этим точкам.

Как мы можем убедиться, что даже когда генератор очень плох по сравнению с дискриминатором и когда сгенерированные данные очень далеки от p-данных, дискриминатор все равно дает нам значимый сигнал градиента?

  • Одна вещь, которую мы можем сделать, это каким-то образом изменить наш дискриминатор; может быть, мы можем каким-то образом изменить способ обучения дискриминатора, так что даже если он может идеально классифицировать p-данные из pG, все равно рекомендуется создавать более плавный наклон между этими двумя распределениями, чтобы дать генератору некоторый градиентный сигнал, чтобы направить его к p-данным.

  • Еще одна вещь, которую мы могли бы сделать, это немного изменить распределения, может быть, мы можем каким-то образом изменить данные p и pG, чтобы они больше перекрывались, и тогда дискриминатор не будет таким резким — тогда мы увидим более сильный сигнал градиента.

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

  • Метод наименьших квадратов GAN — это способ модифицировать дискриминатор GAN для вывода вещественного числа вместо вероятности между нулем и единицей и обучить дискриминатор так, чтобы он давал более плавный наклон.
  • GAN Вассерштейна — это способ изменить дискриминатор, чтобы он был непрерывным по Липшицу, что также способствует более четкому наклону между p-данными и pG.
  • Градиентный штраф — это способ улучшить GAN Вассерштейна, поэтому дискриминатор вынужден быть непрерывным еще сильнее.
  • Спектральная норма - это способ действительно ограничить дискриминатор непрерывностью.
  • Шум экземпляра пытается изменить распределения, добавляя много шума как к p-данным, так и к сгенерированным образцам в надежде, что их распределения будут больше перекрываться.

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

Вассерштейн ГАН

Интуиция высокого уровня заключается в том, что дивергенция Дженсена-Шеннона, используемая классической GAN, не может учитывать расстояние. По сути, если у вас есть этот сценарий:

Можно сказать, что эти два дистрибутива действительно далеки друг от друга. Или у вас может быть такой сценарий, и вы заметите, что эти два дистрибутива очень близки друг к другу:

С точки зрения дивергенции JS эти два случая примерно одинаковы, потому что в обоих случаях перекрытие между распределениями незначительно. Таким образом, JS-дивергенция, KL-дивергенция и большинство других дивергенций, использующих исключительно вероятности, почти идентичны в этих двух случаях, хотя очевидно, что нижняя дивергенция намного лучше. Это причина высокого уровня, почему так сложно получить осмысленный градиент в GAN, когда генератор очень плохой: потому что хороший градиент должен сказать вам, что способ улучшить ситуацию наверху — это двигаться к ситуации вниз. ниже, но если ваша мера дивергенции считает, что эти два распределения почти одинаковы, то, конечно, у нее не будет большого градиента.

Причина, по которой это верно математически, заключается в том, что если вы посмотрите на форму расхождения JS в форме цели GAN, вы увидите, что в основном оно выражается в терминах логарифмических вероятностей при одном распределении в ожидании при другом распределении. Таким образом, если данные p приблизительно равны нулю для всех x, где pG не равно нулю, а pG приблизительно равно нулю для всех x, где данные p не равны нулю, то эти выражения просто не дадут вам никакого значимого градиента. Это в основном объясняет, почему верхняя ситуация считается очень похожей на нижнюю в том, что касается расхождения JS, несмотря на то, что в нижнем случае эти распределения очень близки друг к другу.

Что мы можем сделать, чтобы исправить эту проблему, так это попытаться придумать способ обучения GAN с другой мерой расхождения, которая лучше отражает, насколько далеко друг от друга находятся два распределения. Вот как можно придумать лучшую метрику: подумайте, как далеко друг от друга находятся все биты вероятности в евклидовом пространстве; представьте, что эти распределения в основном представляют собой груды материальных куч. Какое расстояние нужно пройти, чтобы переместить грязь из одной кучи в другую? Эту задачу иногда называют оптимальной транспортной задачей. Как далеко вам нужно пройти, чтобы переместить один дистрибутив в другой? Общее расстояние, которое вам нужно пройти, зависит от того, насколько далеко друг от друга находятся эти два распределения.

Расстояние Вассерштейна формально определяется как ожидаемое значение расстояния между y и x, где y и x распределяются в соответствии с оптимальным выбором гаммы совместного распределения. Таким образом, вы найдете лучшую гамму для минимизации этого ожидаемого значения.

Гамма — это совместное распределение по x и y, где маргинал по x равен p данным, а маргинал по y — pG. Интуиция такова, что корреляции между x и y в gamma(x,y) указывают, какой x должен быть транспортирован к какому y.

Чтобы оценить расстояние Вассерштейна, вы находите гамму, которая минимизирует ожидаемое значение этих расстояний, минимизирует несоответствие между x и y для каждого y, который соответствует этому конкретному x. Интуитивно поиск гаммы подобен поиску оптимального плана для перемещения всех точек данных p в pG.

К сожалению, на самом деле изучение гаммы напрямую, как это, очень и очень сложно, потому что данные p неизвестны, а гамма (x, y) сама по себе может быть очень сложным распределением.

Есть действительно крутая теорема, которую мы можем использовать, основанную на двойственности Канторовича-Рубинштейна, которая дает нам гораздо более удобный способ найти расстояние Вассерштейна. Утверждение состоит в том, что расстояние Вассерштейна также равно супремуму по всем возможным функциям f ожидаемого значения при p данных f минус ожидаемое значение при pG f:

Грубая интуиция подсказывает, что f будет происходить из того же места, где вы получаете множитель Лагранжа. Итак, у вас есть ограничение на гамму, которое заключается в том, что маргиналы гаммы должны соответствовать данным p и pG, и вы, по сути, возьмете двойное из них. Действительно привлекательная вещь в этом выражении для расстояния Вассерштейна заключается в том, что оно выражает разницу ожиданий по данным pG и p, как и обычный GAN.

Супремум был взят за это забавное выражение, которое гласит:

Это сокращение для того, чтобы сказать, что вы должны делать это над функциями, которые являются 1-липшицевыми. Это означает, что разница между f от x и f от y должна быть меньше или равна разнице между x и y. Это эквивалентно утверждению, что функция имеет ограниченный наклон, поэтому она никогда не должна быть слишком крутой, потому что, если f слишком крутая, она может произвольно максимизировать f для x при данных p и произвольно минимизировать ее при pG. Этот предел наклона является своего рода ограничением скорости, с которой вы можете двигаться, когда переносите части одного распределения в другое.

Как добиться, чтобы наклон f был ограничен некоторой константой? Это в основном сложная часть создания экземпляра WGAN. Вот идея, не обязательно лучшая, но все же одна из них:

Если у вас есть только однослойная сеть, то есть один линейный слой, за которым следует нелинейность ReLU, и все ваши записи в W находятся в диапазоне от -0,01 до +0,01, то наклон не может быть больше, чем 0,01 x D.

Если у вас есть двухслойная сеть, вы можете ограничить все записи в обеих весовых матрицах от -0,01 до +0,01. Тогда ваш наклон также будет ограничен; конечно, это намного больше, чем раньше, потому что вы на самом деле перемножаете эти w вместе, но оно по-прежнему ограничено константой, поэтому это не гарантирует, что это 1-липшиц, но гарантирует, что это что-то липшицево для некоторой константы , а значит, наклон будет ограничен, а значит, вы получите некоторое кратное расстоянию Вассерштейна.

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

Но если мы хотим работать с этой процедурой GAN Вассерштейна, вот как она работает. У нас был бы наш генератор, как и раньше, и у нас был бы дискриминатор, который теперь вместо того, чтобы выводить вероятность того, что изображение реальное, на самом деле просто выводит действительное число. Это действительное число не имеет ограничений, поэтому у вас просто есть набор линейных слоев с ReLU и без сигмоиды в конце. Дискриминатор будет использовать отсечение веса, вот как это будет работать.

На каждой итерации вашего алгоритма стохастического градиента вы будете обновлять дискриминатор, используя цель WGAN. Затем вы бы обрезали все матрицы весов внутри тета так, чтобы они находились между некоторыми константами, и это гарантировало бы, что дискриминатор является k-липшицевым для некоторого фиксированного конечного k. Наконец, вы обновите свой генератор, чтобы максимизировать ожидаемое значение f для G (z).

Как нам улучшить регуляризацию дискриминатора, поскольку отсечение веса — ужасный способ принудительного применения константы Липшица?

  • Один очень простой и очень эффективный метод называется градиентным штрафом. Если мы начали с идеи, что мы хотим ограничить наклон, почему бы нам просто не написать выражение для наклона и просто добавить его к нашей цели?

  • Спектральная норма — несколько более сложный подход. Проблема со штрафом за градиент в том, что на самом деле это не ограничение, а просто штраф. Итак, вам действительно нужно ограничение Липшица, но это просто дает вам штраф. Если вам действительно нужно ограничение, вы можете ограничить константу Липшица с точки зрения сингулярных значений каждой весовой матрицы в вашей сети.

Если ваша функция f от x является композицией нескольких функций, константа Липшица может быть записана как композиция трех функций. Все, что нам нужно сделать, это ограничить константу Липшица каждого слоя нашей сети. Максимальный наклон ReLU равен единице, поэтому вам не нужно о них беспокоиться; максимальный наклон линейных слоев - это спектральная норма w, которая определяется как:

Все, что вам нужно сделать, это убедиться, что после каждого шага спектральная норма каждой весовой матрицы в вашей сети меньше или равна единице; чтобы гарантировать это, вы просто делите каждую весовую матрицу на ее спектральную массу

Краткое содержание

Обучение GAN действительно сложно, потому что дискриминатор может давать плохие градиенты. Различные приемы могут сделать это намного более практичным. Например, у вас может быть:

  • Гладкие вещественные дискриминаторы (LSGAN, WGAN, WGAN-GP, спектральная норма)
  • Шум экземпляра