В этой статье мы будем опираться на базовую нейронную сеть, представленную в части 3. Мы добавим несколько драгоценных камней, которые улучшат сеть. Небольшие изменения с большим влиянием. Понятия, которые мы встретим, такие как Инициализация, Мини-пакеты, Распараллеливание, Оптимизаторы и Регуляризация - это, без сомнения, то, с чем вы быстро столкнетесь при изучении нейронных сетей. В этой статье приводится пошаговая инструкция.
Это четвертая часть цикла статей:
- Часть 1: Фундамент.
- Часть 2: Градиентный спуск и обратное распространение.
- Часть 3: Реализация на Java.
- Часть 4: Лучше, быстрее, сильнее.
- Часть 5: Обучение сети чтению рукописных цифр.
- Extra 1: Как я увеличил точность на 1% за счет увеличения данных.
- Экстра 2: площадка MNIST.
Инициализация
До сих пор мы видели, как можно автоматически регулировать веса и смещения с помощью обратного распространения, чтобы улучшить сеть. Итак, каково хорошее начальное значение для этих параметров?
Как правило, достаточно просто рандомизировать параметры от -0,5 до 0,5 со средним значением 0.
Однако в глубоких сетях исследования показали, что мы можем улучшить сходимость, если позволим значениям весов обратно зависеть от количества нейронов в связанном слое (или иногда двух связанных слоях). Другими словами: много нейронов ⇒ начальный вес ближе к 0. Меньшее количество нейронов ⇒ более высокая дисперсия. Вы можете узнать больше о том, почему здесь и здесь.
Я реализовал несколько наиболее популярных из них и сделал возможным внедрить их в качестве стратегии (см. Строку 5) при создании нейронной сети:
Реализованные стратегии инициализации: XavierNormal, XavierUniform, LeCunNormal, LeCunUniform, HeNormal, HeUniform и Random. Вы также можете явно указать веса для слоя, указав весовую матрицу при создании слоя.
Дозирование
В прошлой статье мы кратко коснулись того факта, что мы можем передавать образцы через сеть столько, сколько захотим, а затем, по нашему собственному усмотрению, выбрали когда для обновления веса. Разделение в API между методом Assessment () (который собирает информацию об обучении / впечатлениях) и методом updateFromLearning () (который позволяет этому обучению осваиваться) существует. чтобы позволить именно это. Он оставляет открытым для пользователя API, какую из следующих стратегий использовать:
Стохастический градиентный спуск
В стохастическом градиентном спуске мы обновляем веса и смещения после каждой оценки. Помните из части 2, что функция стоимости зависит от ввода и математического ожидания: C = C (W, b, Sσ, x, exp). Как следствие, ландшафт затрат будет немного отличаться для каждого образца, а отрицательный градиент будет указывать в направлении наискорейшего спуска в этом уникальном ландшафте для каждого образца.
Теперь предположим, что у нас есть ландшафт общих затрат, который будет функцией усредненных затрат для всего набора данных. Все образцы. В этом случае стохастический градиентный спуск выглядел бы как хаотическая прогулка.
Пока скорость обучения достаточно мала, ходьба в среднем, тем не менее, снижается до локального минимума.
Проблема в том, что SGD сложно эффективно распараллелить. Каждое обновление весов должно быть частью следующего расчета.
Пакетный градиентный спуск
Противоположностью стохастического градиентного спуска является пакетный градиентный спуск (или иногда просто градиентный спуск). В этой стратегии все данные обучения оцениваются перед обновлением весов. Рассчитывается средний градиент для всех образцов. Он будет сходиться к локальному минимуму в виде тщательно рассчитанных небольших нисходящих шагов. Недостатком является то, что цикл обратной связи может быть очень длинным для больших наборов данных.
Мини-пакетный градиентный спуск
Мини-пакетный градиентный спуск - это компромисс между SGD и BGD - пакеты из N образцов проходят через сеть до обновления весов. Обычно N находится в диапазоне от 16 до 256. Таким образом мы получаем что-то достаточно стабильное в своем спуске (хотя и не оптимальное):
- По сравнению с пакетным градиентным спуском мы получаем более быструю обратную связь и можем работать с управляемыми наборами данных.
- По сравнению со стохастическим градиентным спуском мы можем запустить весь пакет параллельно, используя все ядра ЦП, графического процессора или TPU. Лучшее из двух миров.
Распараллеливание
В кодовую базу нужно было внести лишь некоторые незначительные изменения, чтобы обеспечить параллельное выполнение метода оценивания (). Как всегда… состояние - враг распараллеливания. В фазе прямой связи единственное состояние, к которому следует относиться с осторожностью, - это тот факт, что исходящее значение от каждого нейрона сохраняется для каждого нейрона. Конкурирующие потоки наверняка перезапишут эти данные до того, как они будут использованы в обратном распространении.
С этим можно справиться, используя ограничение потока.
Также необходимо было синхронизировать ту часть, где накапливаются и в конечном итоге применяются дельты для весов и смещений.
При этом совершенно нормально использовать столько ядер, сколько желательно (или доступно) при обработке данных обучения.
Типичный способ распараллеливания выполнения мини-пакетов в нейронной сети выглядит так:
Особо обратите внимание на конструкцию для распределения обработки всех выборок в пакете в виде параллельного потока в строке 2.
Это все, что нужно.
Лучше всего: значительное ускорение.
Здесь следует иметь в виду, что при таком распределении выполнения (выполнение пакета по нескольким параллельным потокам) к вычислениям добавляется энтропия, которая иногда нежелательна. Для иллюстрации: когда дельты ошибок суммируются в методе addDeltaWeightsAndBiases, они могут добавляться в несколько ином порядке при каждом запуске. Хотя все участвующие в пакете термины одинаковы, измененный порядок их суммирования может давать небольшие небольшие различия, которые со временем начинают проявляться в больших нейронных сетях, что приводит к невоспроизводимым прогонам . Это, вероятно, нормально в такой реализации нейронной сети на игровой площадке ... но если вы хотите провести исследование, вам придется подойти к параллелизму по-другому (обычно создавая большие матрицы входного пакета и распараллеливая все умножения матриц обоих каналов с прямой связью. и обратное распространение на GPU / TPU).
Оптимизаторы
В прошлой статье мы коснулись того факта, что мы сделали актуальное обновление весов и смещений подключаемым в качестве стратегии. Причина в том, что есть другие способы сделать это, кроме базового градиентного спуска.
Помните, это:
Что в коде выглядит так:
Хотя стремление к спуску SGD (и его пакетные варианты) страдает от того факта, что величина уклона пропорциональна тому, насколько крутым является уклон в точке оценки. Более плоская поверхность означает меньшую длину градиента… что дает меньший шаг. Как следствие, он может застрять в седловых точках - например, на полпути по этому пути:
(как видите, есть лучшие локальные минимумы в нескольких направлениях, чем застревание в этой седловой точке)
Один из способов лучше справиться с подобными ситуациями - позволить обновлению весов зависеть не только от вычисленного градиента, но и от градиента, вычисленного на последнем шаге. В чем-то похоже на включение эхо-сигналов из предыдущих расчетов.
Физическая аналогия этого - представить себе мрамор с массой, катящийся с холма. Такой шарик может иметь достаточный импульс, чтобы поддерживать скорость, достаточную для покрытия более плоских участков или даже взбираться на небольшие холмы - таким образом, избегая седловой точки и продолжая движение к лучшим локальным минимумам.
Неудивительно, что один из простейших оптимизаторов называется…
Импульс
В импульс мы вводим другую константу, чтобы указать, сколько эхо мы хотим из предыдущих вычислений. Этот фактор, γ, называется импульсом. Уравнение 1, приведенное выше, теперь принимает следующий вид:
Как видите, оптимизатор Momentum сохраняет последнюю дельту как v и вычисляет новую v на основе предыдущего один. Обычно γ может находиться в диапазоне 0,7–0,9, что означает, что 70–90% предыдущих вычислений градиента учитываются в этом новом вычислении.
В коде он настроен как (строка 4):
И применяется в оптимизаторе Momentum следующим образом:
Это небольшое изменение в способе обновления весов часто улучшает сходимость.
Мы можем сделать даже лучше. Еще один простой, но мощный оптимизатор - это ускоренный градиент Нестерова (NAG).
Нестеров ускоренный градиент
Единственное отличие от импульса состоит в том, что мы в NAG вычисляем Стоимость в позиции, которая уже учла эхо из последнего вычисления градиента.
С точки зрения программирования это плохие новости. До сих пор приятным свойством оптимизаторов было то, что они применялись только при обновлении весов. Здесь внезапно нам нужно включить последний градиент v уже при вычислении функции стоимости. Конечно, мы могли бы расширить эту идею. того, что такое оптимизатор, и позволить ему, например, также увеличивать поиск по весу. Это было бы непоследовательно, поскольку все остальные оптимизаторы (о которых я знаю) сосредоточены только на обновлении.
С помощью небольшого приема мы можем обойти эту проблему. Введя другую переменную x = W - γ v, мы можем переписать уравнения в терминах x. В частности, это введение означает, что проблемная функция стоимости будет зависеть только от x, а не от исторического v -значения ( по крайней мере, не явно зависимым. Часть -γ v фактически все еще будет использоваться, но теперь неявно сохраняется для всех весов).
После переписывания мы можем снова переименовать x обратно в w, чтобы он больше походил на то, что мы узнаем. Уравнение становится:
Намного лучше. Теперь оптимизатор Нестерова можно применять только во время обновления:
Чтобы узнать больше о том, как и почему NAG часто работает лучше, чем Momentum, загляните здесь и здесь.
Конечно, есть много других модных оптимизаторов, которые тоже можно было бы реализовать. Некоторые из них часто работают лучше, чем NAG, но за счет дополнительной сложности. С точки зрения улучшенной сходимости по строкам кода NAG представляет собой золотую середину.
Регуляризация
Когда я только закончил писать первую версию кода нейронной сети, мне не терпелось ее протестировать. Я сразу загрузил набор данных MNIST и начал экспериментировать. Практически сразу я пришел к весьма примечательным цифрам с точки зрения точности. По сравнению с тем, чего достигли другие при создании аналогичных сетей, мой был лучше - намного лучше. Это, конечно, вызвало у меня подозрения.
Потом меня осенило. Я сравнил свою статистику в наборе обучающих данных с их статистикой в тестовом наборе данных. Я обучил свою сеть в бесконечном цикле (почти) на данных обучения и даже не загружал набор данных test. И когда я это сделал, я испытал шок. Моя сеть была полностью испорчена с невидимыми данными, хотя она была близка к полубогу по обучающему набору данных. Я только что проглотил горькую пилюлю переобучения.
Переоснащение
Когда вы тренируете свою сеть слишком агрессивно, вы можете оказаться в ситуации, когда ваша сеть усвоила (почти запомнила) данные обучения, но в качестве побочного эффекта также потеряла общие черты того, что представляют собой данные обучения. Это можно увидеть, построив график точности сети по обучающим данным по сравнению с тестовыми данными во время обучения сети. Обычно вы замечаете, что точность обучающих данных продолжает увеличиваться, в то время как точность тестовых данных сначала увеличивается, после чего она выравнивается, а затем снова начинает снижаться.
Это когда вы зашли слишком далеко.
Одно наблюдение здесь заключается в том, что нам нужно уметь определять, когда это происходит, и прекращать тренировку в этот момент. Это часто называют ранней остановкой, и к этому я еще вернусь в последней статье этой серии Часть 5: Обучение сети чтению рукописных цифр.
Но есть и другие способы снизить риск переобучения. Я завершу эту статью, представив простой, но эффективный подход.
L2 регуляризация
В регуляризации L2 мы стараемся избегать того, чтобы отдельные веса в сети становились слишком большими (или, если быть более точным: слишком далеко от нуля). Идея состоит в том, что вместо того, чтобы позволить нескольким весам иметь очень сильное влияние на результат, мы вместо этого предпочитаем, чтобы множество весов взаимодействовали, каждый и каждый с умеренным вкладом. Основываясь на обсуждении в Части 1: Основа, не кажется слишком большим, что несколько меньших весов ( по сравнению с меньшими крупными) дадут более гладкую и менее резкую / резкое разделение входных данных. Более того, кажется разумным, что более плавное разделение сохранит общие характеристики входных данных и, наоборот: резкое и острое то же самое могло бы очень точно вырезать конкретные характеристики входных данных.
Так как же нам избежать слишком больших индивидуальных весов в сети?
В L2-регуляризации это достигается путем добавления еще одного стоимостного члена к функции стоимости:
Как видите, большие веса будут способствовать более высокой стоимости, поскольку они возведены в квадрат этой дополнительной суммы затрат. Меньший вес вообще не имеет большого значения.
Фактор λ покажет, сколько L2-регуляризации мы хотим. Установка его в ноль полностью отменит эффект L2. Если установить, скажем, 0,5, регуляризация L2 будет составлять существенную часть общей стоимости сети. На практике вам нужно будет найти наилучшую возможную λ с учетом схемы вашей сети и ваших данных.
Откровенно говоря, нас не волнует стоимость как конкретное скалярное значение. Нас больше интересует, что происходит, когда мы обучаем сеть с помощью градиентного спуска. Давайте посмотрим, что произойдет с вычислением градиента этой новой функции стоимости для определенного веса k.
Как видите, мы получаем только дополнительный член λwk, который вычитается из веса wk при каждом обновлении.
В коде регуляризация L2 может быть сконфигурирована как (см. Строку 9):
И применяется во время обновления таким образом (см. Строки 3 и 4):
Это незначительное изменение значительно упрощает управление реализацией нейронной сети, когда дело доходит до переобучения.
Итак, на этом мы завершаем эту статью. Несколько небольших жемчужин были представлены как в теории, так и на практике… все они с помощью всего лишь нескольких строк кода делают нейронную сеть лучше, быстрее и сильнее.
В следующей статье, которая будет последней в этой серии, мы увидим, насколько хорошо сеть работает с классическим набором данных - Mnist: Часть 5: Обучение сети чтению рукописных цифр.
Обратная связь приветствуется!
Первоначально опубликовано на сайте machinelearning.tobiashill.se 17 декабря 2018 г.
Посмотрите мой новый проект Q'n'A-listicle best.ways.to.