.. _sec_dropout: Дропаут ======= Давайте кратко подумаем о том, чего мы ожидаем от хорошей предсказательной модели. Мы хотим, чтобы она хорошо работала на новых, ранее не виденных данных. Классическая теория обобщающей способности предполагает, что для сокращения разрыва между качеством на обучающей и тестовой выборках следует стремиться к использованию простой модели. Простота может проявляться в виде малого числа измерений. Мы исследовали этот аспект, обсуждая мономиальные базисные функции линейных моделей в :numref:`sec_generalization_basics`. Кроме того, как мы видели при рассмотрении затухания весов (:math:`\ell_2` регуляризации) в :numref:`sec_weight_decay`, (обратная) норма параметров также представляет собой полезную меру простоты. Другим полезным понятием простоты является гладкость, то есть функция не должна быть чувствительной к небольшим изменениям своих входных данных. Например, при классификации изображений мы ожидаем, что добавление случайного шума к пикселям будет в основном безвредным. :cite:t:`Bishop.1995` формализовал эту идею, доказав, что обучение с добавлением шума во входные данные эквивалентно регуляризации Тихонова. Эта работа установила чёткую математическую связь между требованием гладкости функции (и, следовательно, её простоты) и требованием её устойчивости к возмущениям во входных данных. Затем :cite:t:`Srivastava.Hinton.Krizhevsky.ea.2014` предложил элегантную идею того, как применить подход Бисхопа и к внутренним слоям сети. Их метод, называемый *дропкаутом*, предполагает внесение шума при вычислении каждого внутреннего слоя в процессе прямого распространения, и он стал стандартным приёмом для обучения нейронных сетей. Метод называется *дропкаутом*, потому что мы буквально *исключаем* некоторые нейроны во время обучения. На протяжении всего процесса обучения, на каждой итерации, стандартный дропаут заключается в обнулении некоторой доли узлов в каждом слое перед вычислением следующего слоя. Чтобы прояснить ситуацию, мы навязываем собственное повествование, связывая дропаут с идеями Бишопа. В оригинальной статье о дропауте интуиция объясняется через удивительную аналогию с половым размножением. Авторы утверждают, что переобучение нейронной сети характеризуется состоянием, в котором каждый слой зависит от определённого паттерна активаций в предыдущем слое, и называют это состояние *коадаптацией*. По их мнению, дропаут разрушает коадаптацию так же, как половое размножение, как считается, разрушает коадаптированные гены. Хотя обоснование этой теории, безусловно, вызывает споры, сам метод дропаута оказался устойчивым, и различные его формы реализованы в большинстве библиотек для глубокого обучения. Ключевая задача заключается в том, как внедрить этот шум. Одна из идей — делать это *несмещённым* образом, так чтобы математическое ожидание каждого слоя (при фиксированных остальных) равнялось тому значению, которое он принял бы без шума. В работе Бишопа он добавил гауссов шум к входам линейной модели. На каждой итерации обучения он добавлял шум, выбранный из распределения с нулевым средним :math:`\epsilon \sim \mathcal{N}(0,\sigma^2)`, к входу :math:`\mathbf{x}`, получая возмущённую точку :math:`\mathbf{x}' = \mathbf{x} + \epsilon`. В математическом ожидании :math:`E[\mathbf{x}'] = \mathbf{x}`. В стандартной регуляризации дропаутом обнуляется некоторая доля узлов в каждом слое, после чего каждый слой *дебиасится* путем нормализации на долю оставшихся (не удаленных) узлов. Иными словами, при *вероятности дропаута* :math:`p` каждое промежуточное значение активации :math:`h` заменяется случайной величиной :math:`h'` следующим образом: .. math:: \begin{aligned} h' = \begin{cases} 0 & \textrm{ with probability } p \\ \frac{h}{1-p} & \textrm{ otherwise} \end{cases} \end{aligned} По замыслу математическое ожидание остаётся неизменным, то есть :math:`E[h'] = h`. .. raw:: latex \diilbookstyleinputcell .. code:: python import torch from torch import nn from d2l import torch as d2l Дропаут на практике ------------------- Вспомним многослойный перцептрон со скрытым слоем и пятью скрытыми единицами из :numref:`fig_mlp`. Когда мы применяем дропаут к скрытому слою, обнуляя каждую скрытую единицу с вероятностью :math:`p`, результат можно рассматривать как сеть, содержащую лишь подмножество исходных нейронов. На рисунке :numref:`fig_dropout2` скрытые единицы :math:`h_2` и :math:`h_5` удалены. В результате вычисление выходных значений больше не зависит от :math:`h_2` или :math:`h_5`, и их градиенты также обращаются в ноль при выполнении обратного распространения ошибки. Таким образом, вычисление выходного слоя не может быть чрезмерно зависимым от какого-либо одного элемента :math:`h_1, \ldots, h_5`. .. _fig_dropout2: .. figure:: ../img/dropout2.svg МЛП до и после применения дропаута. Обычно на этапе тестирования дропаут отключают. Получив обученную модель и новый пример, мы не отбрасываем ни один узел, поэтому нормализация не требуется. Однако есть исключения: некоторые исследователи используют дропаут на этапе тестирования как эвристику для оценки *неопределенности* предсказаний нейронной сети: если предсказания совпадают при различных выходах дропаута, можно считать, что сеть более уверена. Реализация с нуля ----------------- Чтобы реализовать функцию дропаута для одного слоя, нам необходимо сгенерировать столько примеров из биномиальной (двоичной) случайной величины, сколько измерений имеет наш слой. Эта случайная величина принимает значение :math:`1` (сохранить) с вероятностью :math:`1-p` и :math:`0` (отбросить) с вероятностью :math:`p`. Простой способ реализации заключается в том, чтобы сначала получить примеры из равномерного распределения :math:`U[0, 1]`. Затем мы можем сохранить те узлы, для которых соответствующий пример больше :math:`p`, а остальные отбросить. В приведённом ниже коде мы **реализуем функцию ``dropout_layer``, которая отбрасывает элементы в тензоре input ``X`` с вероятностью ``dropout``**, масштабируя оставшиеся значения, как описано выше: деля выжившие на ``1.0-dropout``. .. raw:: latex \diilbookstyleinputcell .. code:: python def dropout_layer(X, dropout): assert 0 <= dropout <= 1 if dropout == 1: return torch.zeros_like(X) mask = (torch.rand(X.shape) > dropout).float() return mask * X / (1.0 - dropout) Мы можем протестировать функцию ``dropout_layer`` на нескольких примерах. В следующих строках кода мы пропускаем наш входной тензор ``X`` через операцию дропаута с вероятностями 0, 0,5 и 1 соответственно. .. raw:: latex \diilbookstyleinputcell .. code:: python X = torch.arange(16, dtype = torch.float32).reshape((2, 8)) print('dropout_p = 0:', dropout_layer(X, 0)) print('dropout_p = 0.5:', dropout_layer(X, 0.5)) print('dropout_p = 1:', dropout_layer(X, 1)) Определение модели ~~~~~~~~~~~~~~~~~~ В приведённой ниже модели дропаут применяется к выходу каждого скрытого слоя (после функции активации). Вероятности дропаута для каждого слоя можно задавать отдельно. Обычно выбирают меньшую вероятность дропаута для слоёв, расположенных ближе к входному слою. Мы обеспечиваем, чтобы дропаут был активен только во время обучения. .. raw:: latex \diilbookstyleinputcell .. code:: python class DropoutMLPScratch(d2l.Classifier): def __init__(self, num_outputs, num_hiddens_1, num_hiddens_2, dropout_1, dropout_2, lr): super().__init__() self.save_hyperparameters() self.lin1 = nn.LazyLinear(num_hiddens_1) self.lin2 = nn.LazyLinear(num_hiddens_2) self.lin3 = nn.LazyLinear(num_outputs) self.relu = nn.ReLU() def forward(self, X): H1 = self.relu(self.lin1(X.reshape((X.shape[0], -1)))) if self.training: H1 = dropout_layer(H1, self.dropout_1) H2 = self.relu(self.lin2(H1)) if self.training: H2 = dropout_layer(H2, self.dropout_2) return self.lin3(H2) Обучение ~~~~~~~~ Следующее аналогично обучению многослойных перцептронов, описанному ранее. .. raw:: latex \diilbookstyleinputcell .. code:: python hparams = {'num_outputs':10, 'num_hiddens_1':256, 'num_hiddens_2':256, 'dropout_1':0.5, 'dropout_2':0.5, 'lr':0.1} model = DropoutMLPScratch(**hparams) data = d2l.FashionMNIST(batch_size=256) trainer = d2l.Trainer(max_epochs=10) trainer.fit(model, data) Краткая реализация ------------------ С помощью высокоуровневых API нам нужно лишь добавить ``Dropout`` слой после каждого полносвязного слоя, передав вероятность дропаута в качестве единственного аргумента его конструктора. В процессе обучения ``Dropout`` слой случайным образом отбрасывает выходы предыдущего слоя (или, что эквивалентно, входы последующего слоя) в соответствии с заданной вероятностью дропаута. В режиме, отличном от обучения, ``Dropout`` слой просто пропускает данные во время тестирования. .. raw:: latex \diilbookstyleinputcell .. code:: python class DropoutMLP(d2l.Classifier): def __init__(self, num_outputs, num_hiddens_1, num_hiddens_2, dropout_1, dropout_2, lr): super().__init__() self.save_hyperparameters() self.net = nn.Sequential( nn.Flatten(), nn.LazyLinear(num_hiddens_1), nn.ReLU(), nn.Dropout(dropout_1), nn.LazyLinear(num_hiddens_2), nn.ReLU(), nn.Dropout(dropout_2), nn.LazyLinear(num_outputs)) Далее мы обучим модель. .. raw:: latex \diilbookstyleinputcell .. code:: python model = DropoutMLP(**hparams) trainer.fit(model, data) Итоги ----- Помимо контроля числа измерений и размера вектора весов, дропаут — ещё один инструмент для предотвращения переобучения. Часто эти инструменты применяются совместно. Обратите внимание, что дропаут используется только на этапе обучения: он заменяет значение активации :math:`h` на случайную величину с математическим ожиданием :math:`h`. Упражнения ---------- 1. Что произойдет, если изменить вероятности дропаута для первого и второго слоёв? В частности, что случится, если поменять их местами? Разработайте эксперимент, чтобы ответить на эти вопросы, опишите результаты количественно и сформулируйте качественные выводы. 2. Увеличьте количество эпох и сравните результаты, полученные при использовании дропаута, с результатами, полученными без него. 3. Какова дисперсия активаций в каждом скрытом слое при применении дропаута и без него? Постройте график, показывающий, как эта величина изменяется со временем для обеих моделей. 4. Почему дропаут обычно не используется во время тестирования? 5. Возьмем модель из этого раздела в качестве примера и сравним эффекты применения дропаута и затухания весов. Что происходит, когда дропаут и затухание весов используются одновременно? Являются ли результаты аддитивными? Наблюдается ли эффект убывающей отдачи (или даже ухудшение)? Или они компенсируют друг друга? 6. Что произойдёт, если мы применим дропаут к отдельным весам матрицы весов, а не к активациям? 7. Придумайте другой способ добавления случайного шума на каждом слое, отличный от стандартного метода дропаут. Можете ли вы разработать метод, который превосходит дропаут на наборе данных Fashion-MNIST (при фиксированной архитектуре)? `Обсуждения `__