L2-регуляризатор и Dropout

Смотреть материал на YouTube | RuTube

Мы подошли к моменту, когда нужно подробнее познакомиться со способами борьбы с переобучением НС® Как мы говорили в курсе по машинному обучению (МО©, каждая модель немного переобучается. Наша задача, чтобы степень переобучения была как можно меньше. Те, кто проходил курс по МО¬ уже знакомы с двумя техниками борьбы с переобучением: регуляризация настраиваемых параметров и понижение размерности признакового пространства:

https://proproprogs.ru/ml/ml-l2-regulyarizator-matematicheskoe-obosnovanie-i-primer-raboty

При этом часто выделяют L1- и L2-регуляризации. Но при обучении НС¬ как правило, используют L2-регуляризацию весовых коэффициентов, так как L1-регуляризация склонна занулять некоторые веса и это может негативно сказаться на качестве работы НС® Помимо регуляризаторов специально для НС были предложены еще две довольно популярные техники борьбы с переобучением: Dropout и BatchNormalization. Но начнем с классической L2-регуляризации.

Добавление L2-регуляризации для подбираемых параметров

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

Чтобы добавить L2-регуляризатор, в выбранном оптимизаторе достаточно прописать параметр weight_decay, например, так:

optimizer = optim.Adam(params=model.parameters(), lr=0.01, weight_decay=0.001)

Здесь величина weight_decay=0.001 является аналогом коэффициента λ в формуле функции потерь:

Аналогом, но не точным соответствием, т.к. оптимизаторы фреймворка PyTorch каждый по-своему применяют параметр weight_decay. Но одно можно сказать определенно. Если weight_decay не равен нулю, то при обучении будет применяться L2-регуляризатор. Само же значение weight_decay мы выбираем сами в зависимости от типа задач. Обычно оно берется в диапазоне [0.001; 0.00001]. Но это не строго.

После добавления L2-регуляризатора характер графиков заметно меняется:

Уже нет явного расхождения и можно ожидать лучших обобщающих свойств полученной НС®

Идея алгоритма Dropout

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

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

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

Этот метод исходит из того, что корень проблемы переобучения кроется в излишней специализации каждого отдельного нейрона. Цель Dropout сделать из нейронов «специалистов более широкого профиля». Но как уменьшить специализацию, сохраняя прежнее их число? Очень просто. Давайте представим, что в некоторой школе работают учителя по различным предметам: химия, биология, история, математика, физика, география и информатика. Это их специализация. Затем, в какой-то момент директор школы озаботился их приверженностью только одной дисциплине и решил расширить горизонты их профессиональной деятельности. Что он сделал? Он заставил учителя по химии время от времени вести занятия по физике, учителя по физике – химию, математика менялась с информатикой, а биология, история и география – между собой. В итоге, учителям волей-неволей пришлось изучить смежные дисциплины и расширить свою специализацию. Теперь, директор школы был доволен и знал, если какой-либо отдельный учитель заболеет или уволится, у него будет кем его заменить.

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

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

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

В режиме эксплуатации значение на входе нейрона будет z, а в момент обучения – значение:

Как вы понимаете, это приводит к искаженным входным значениям, а значит, и к неверным результатам на выходе всей НС® Как поправить ситуацию, чтобы, в среднем, эти суммы были равны? Для этого нужно вычислить среднее число выключенных нейронов в текущем слое. Учитывая, что понятие среднего в теории вероятностей – это математическое ожидание, которое, в дискретном случае, можно записать в виде:

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

Соответственно, останется, в среднем:

нейронов. Где q – вероятность того, что нейрон останется (не будет исключен). Отсюда получаем, что средний суммарный сигнал на входах нейронов следующего слоя будет меньше на величину:

Из этого выражения хорошо видно, что для сохранения масштаба суммы, ее нужно разделить на величину q:

Во многих фреймворках алгоритм Dropout реализован именно так: суммарный сигнал на входах нейронов масштабируется, эмулируя поведение полной сети со всеми нейронами. Благодаря этому, в среднем, выходной сигнал сети соответствует истинным значениям и при ее эксплуатации (со всеми включенными нейронами) не будет возникать никаких «сюрпризов». Кстати, это одна из причин, почему во фреймворке PyTorch модели работают в двух режимах:

  • model.train() – обучения;
  • model.eval() – эксплуатации.

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

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

Реализация Dropout в PyTorch

Давайте добавим Dropout после первого слоя НС распознавания рукописных цифр. Для этого в класс DigitNN достаточно добавить две строчки:

class DigitNN(nn.Module):
    def __init__(self, input_dim, num_hidden, output_dim):
        super().__init__()
        self.layer1 = nn.Linear(input_dim, num_hidden)
        self.layer2 = nn.Linear(num_hidden, output_dim)
        self.dropout_1 = nn.Dropout1d(0.3)
 
    def forward(self, x):
        x = self.layer1(x)
        x = nn.functional.relu(x)
        x = self.dropout_1(x)
        x = self.layer2(x)
        return x

Команда nn.Dropout1d(0.3) создает объект Dropout для обработки батчей из одномерных тензоров с вероятностью p=0,3 отключения нейронов сети. Именно в таком виде тензоры передаются от слоя к слою в нашей НС® Забегая вперед отмечу, что класс Dropout2d способен применять алгоритм дропаут к двумерным тензорам. Но, что это за двумерные тензоры и откуда они берутся, мы с вами будем говорить на будущих занятиях, когда речь пойдет о сверточных НС®

После создания объекта nn.Dropout1d(0.3) в методе forward пропускаем выходные значения нейронов первого слоя через слой Dropout, в котором имитируется работа отключения части нейронов при каждом новом батче. Обратите внимание, что слой Dropout обычно прописывают после функции активации. Хотя, формально никто не мешает записать его и перед ней. В ряде случаев это может давать даже лучшие результаты.

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

model = DigitNN(28 * 28, 128, 10)

И уберем в оптимизаторе L2-регуляризацию:

optimizer = optim.Adam(params=model.parameters(), lr=0.01)

Оценим работу слоя Dropout при обучении такой НС® После 20 эпох получим следующие графики для обучающей и валидационной выборок:

А вот какой вид принимают те же графики без слоя Dropout:

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

На практике, как правило, при обучении НС всегда применяют L2-регуляризацию для оптимизируемых параметров:

optimizer = optim.Adam(params=model.parameters(), lr=0.01, weight_decay=0.001)

А слой Dropout только по необходимости.

Видео по теме