Курсовая работа. Глубокое обучение
Скачать 4.97 Mb.
|
144 Глава 4 . Расширения def _decay_lr(self) -> None: if not self.decay_type: return if self.decay_type == 'exponential': self.lr *= self.decay_per_epoch elif self.decay_type == 'linear': self.lr -= self.decay_per_epoch Наконец, мы будем вызывать функцию _decay_lr из класса Trainer в функции fit в конце каждой эпохи: if self.optim.final_lr: self.optim._decay_lr() Теперь проведем несколько экспериментов и посмотрим на результат. Эксперименты: затухание скорости обучения Попробуем обучить ту же модель, добавив затухание скорости обучения. Мы инициализируем скорость обучения таким образом, чтобы «средняя скорость обучения» за цикл была равна предыдущей скорости обучения, равной 0.1. При линейном затухании скорости обучения сделаем снижение с 0.15 до 0.05, а для экспоненциального затухания начальная скорость будет равна 0.2, а снижаться будет до 0.05. Для линейного затухания: optimizer = SGDMomentum(0.15, momentum=0.9, final_lr=0.05, decay_ type='linear') получим: Validation loss after 10 epochs is 0.403 Validation loss after 20 epochs is 0.343 Validation loss after 30 epochs is 0.282 Loss increased after epoch 40, final loss was 0.282, using the model from epoch 30 The model validation accuracy is: 95.91% Для экспоненциального затухания: optimizer = SGDMomentum(0.2, momentum=0.9, final_lr=0.05, decay_ type='exponential') Инициализация весов 145 получим: Validation loss after 10 epochs is 0.461 Validation loss after 20 epochs is 0.323 Validation loss after 30 epochs is 0.284 Loss increased after epoch 40, final loss was 0.284, using the model from epoch 30 The model validation accuracy is: 96.06% Потери в «лучших моделях» были равны 0.282 и 0.284, что значительно ниже, чем значение 0.338, полученное раньше! Далее поговорим о том, как задавать начальные веса. Инициализация весов Как мы упоминали при разговоре о функциях активации, некоторые функции активации, такие как сигмоида и гиперболический тангенс, имеют самые крутые градиенты при нулевых входах, а затем быстро сгла- живаются по мере удаления входов от 0. Это может привести к снижению эффективности этих функций, так как, если у многих входов значения далеки от 0, мы получим очень маленькие градиенты на обратном проходе. Оказывается, это основная проблема в нейронных сетях, с которыми мы работаем. Рассмотрим скрытый слой в сети MNIST, о которой мы говори- ли. На этот слой приходит 784 единицы данных, а затем они умножаются на матрицу весов, в результате чего получается n нейронов (а затем при необходимости добавляется смещение для каждого нейрона). На рис. 4.8 показано распределение этих n значений в скрытом слое нашей нейронной сети (с 784 входами) до и после прохода через функцию активации Tanh После прохода через функцию активации большинство активаций при- няли значения –1 или 1! Это связано с тем, что каждая функция матема- тически определена как: Поскольку мы инициализировали каждый вес с дисперсией 1 (Var(w i,j ) = 1 и Var(b n ) = 1) и Var(X 1 + X 2 ) = Var(X 1 ) + Var(X 2 ) для независимых слу- чайных величин X 1 и X 2 , мы имеем: Var(f n ) = 785. 146 Глава 4 . Расширения Распределение входов по функции активации для слоя с 784 входами Распределение выходов функции tahn для слоя с 784 входами Рис. 4.8. Распределение входов по функции активации Это дает стандартное отклонение ( ) чуть более 28, что отражает разброс значений, который показан в верхней половине рис. 4.8. Кажется, у нас проблема. Но в том ли она заключается, что передаваемые в функции активации признаки не могут быть «слишком распределенны- ми»? Если бы проблема была в этом, мы бы просто поделили признаки на некоторое значение, чтобы уменьшить их дисперсию. Но тогда очевидный вопрос: откуда мы знаем, на сколько делить? Ответ: значения следует масштабировать в зависимости от количества нейронов, передаваемых в слой. Если бы у нас была многослойная нейронная сеть и в одном слое было 200 нейронов, а в следующем 100, слой из 200 нейронов давал бы большее широкое распределение, чем 100 нейронов. Это нежелательно, так как мы не хотим, чтобы масштаб признаков, которые наша нейронная Инициализация весов 147 сеть изучает во время обучения, зависел от их количества. Аналогично мы не хотим, чтобы прогнозы нашей сети зависели от масштаба входных признаков. Предсказания нашей модели не должны изменяться от умно- жения или деления всех признаков на 2. Существует несколько способов исправить это, и здесь мы рассмотрим один наиболее распространенный вариант: мы можем отрегулировать начальную дисперсию весов по количеству нейронов в слоях, чтобы значения, передаваемые вперед следующему слою во время прямого прохода и назад во время обратного прохода, имели примерно одинако- вый масштаб. Нельзя забывать об обратном проходе, поскольку в этом случае у нас та же проблема: дисперсия градиентов, которые слой полу- чает во время обратного распространения, будет напрямую зависеть от количества объектов в следующем слое, поскольку именно он отправляет градиенты назад к рассматриваемому слою. Математическое представление и код Как конкретно решить проблему? Если в каждом слое есть n входных нейронов и n выходных нейронов, дисперсия для каждого веса, которая будет поддерживать постоянство дисперсии результирующих характе- ристик на прямом проходе, будет равна: 1 n in Аналогично для обратного прохода: 1 n out Компромисс, который часто называют инициализацией Глорота 1 , вы- глядит так: Код будет простым — мы добавляем аргумент weight_init к каждому слою и добавляем следующее в нашу функцию _setup_layer : 1 Метод был предложен Глоротом и Бенжио в статье 2010 года Understanding the difficulty of training deep feedforward neural networks. 148 Глава 4 . Расширения if self.weight_init == "glorot": scale = 2/(num_in + self.neurons) else: scale = 1.0 Теперь наши модели будут выглядеть так: model = NeuralNetwork( layers=[Dense(neurons=89, activation=Tanh(), weight_init="glorot"), Dense(neurons=10, activation=Linear(), weight_init="glorot")], loss = SoftmaxCrossEntropy(), seed=20190119) при этом для каждого слоя задается weight_init = "glorot" Эксперименты: инициализация весов Запустим все те же модели, но теперь с инициализацией Глорота: Validation loss after 10 epochs is 0.352 Validation loss after 20 epochs is 0.280 Validation loss after 30 epochs is 0.244 Loss increased after epoch 40, final loss was 0.244, using the model from epoch 30 The model validation accuracy is: 96.71% для модели с линейным затуханием скорости обучения и Validation loss after 10 epochs is 0.305 Validation loss after 20 epochs is 0.264 Validation loss after 30 epochs is 0.245 Loss increased after epoch 40, final loss was 0.245, using the model from epoch 30 The model validation accuracy is: 96.71% для модели с экспоненциальным затуханием скорости обучения. Снова имеет место существенное снижение потерь: с 0.282 и 0.284, которые были ранее, до 0.244 и 0.245! Исключение, или дропаут 149 Обратите внимание, что все эти фокусы не привели к увеличению вре- мени обучения или размера нашей модели. Мы всего лишь подстроили процесс обучения, основываясь на понимании конечной цели нейрон- ной сети, которое мы изложили ранее. В этой главе мы рассмотрим еще один прием. Вы, возможно, заметили, что ни одна из моделей, которые мы использовали в этой главе, не была моделью глубокого обучения. Это были просто нейронные сети с одним скрытым слоем. Дело в том, что без техники дропаута, которую мы сейчас изучим, трудно обучать модели глубокого обучения без переобучения. Исключение, или дропаут В этой главе мы изменили процедуру обучения нейронной сети, что позво- лило приблизиться к глобальному минимуму. Возможно, вы заметили, что мы не попробовали, казалось бы, самую очевидную вещь: добавить в сеть больше слоев или больше нейронов. Дело в том, что простое добавление «ог- невой мощи» в большинстве архитектур нейронных сетей лишь затруднит поиск хорошо обобщенного решения. Простое количественное расшире- ние нейронной сети позволит ей моделировать более сложные отношения между входом и выходом, но также влечет за собой риск пере обучения. Методика исключения, или дропаута, позволила бы нам увеличивать мощь сети, в большинстве случаев снижая вероятность переобучения сети. Что же такое этот дропаут? Определение Дропаут — это случайный выбор некоторой части p нейронов в слое и установка их равными 0 во время прямого прохода. Этот странный трюк снижает мощь сети, но часто позволяет избавиться от переобучения. Это особенно верно в более глубоких сетях, где признаки представляют со- бой несколько уровней абстракции, удаленных из исходных элементов. Несмотря на то что дропаут может помочь нашей сети избежать пере- обучения, мы все же хотим помочь ей делать правильные прогнозы, когда это будет нужно. Таким образом, операция Dropout будет иметь два режима: режим «обучения», в котором применяется дропаут, и режим «вывода», в котором его не будет. Тут будет другая проблема: применение 150 Глава 4 . Расширения дропаута к слою уменьшает число передаваемых вперед значений в 1 – p раз в среднем, то есть веса в следующих слоях получат меньше значений с величиной M, вместо этого они получат величину M * (1 – p). Мы хотим имитировать этот сдвиг при работе сети в режиме вывода, поэтому помимо отключения дропаута мы умножим все значения на 1 – p. В виде кода будет понятнее. Код Мы можем реализовать дропаут как отдельную операцию, которую мы добавим в конец каждого слоя. Это будет выглядеть следующим образом: class Dropout(Operation): def __init__(self, keep_prob: float = 0.8): super().__init__() self.keep_prob = keep_prob def _output(self, inference: bool) -> ndarray: if inference: return self.inputs * self.keep_prob else: self.mask = np.random.binomial(1, self.keep_prob, size=self.inputs.shape) return self.inputs * self.mask def _input_grad(self, output_grad: ndarray) -> ndarray: return output_grad * self.mask На прямом проходе при применении дропаута мы сохраняем «маску», в которой хранится информация об обнуленных нейронах. На обратном проходе мы умножаем градиент, полученный операцией, на эту маску. Это происходит потому, что дропаут делает градиент равным 0 для обнуленных значений (поскольку изменение их значений теперь не будет влиять на потери), а другие градиенты оставим без изменений. Учет дропаута в остальной модели Возможно, вы заметили, что мы включили флаг inference в метод _output , который определяет, применяется ли дропаут. Чтобы этот флаг вызы- Исключение, или дропаут 151 вался правильно, мы должны добавить его в нескольких других местах во время обучения: 1. Методы пересылки Layer и NeuralNetwork будут принимать inference в качестве аргумента (по умолчанию равным False ) и передавать флаг в каждую Operation , и каждая Operation будет вести себя по- разному в зависимости от режима. 2. Напомним, что в классе Trainer мы оцениваем обученную модель на тестовом наборе через каждые eval_every эпох. Во время оценки мы каждый раз будем оценивать флаг inference , равный True : test_preds = self.net.forward(X_test, inference=True) 3. Наконец, мы добавляем ключевое слово dropout в класс Layer , и пол- ная подпись функции __init__ для класса Layer теперь выглядит следующим образом: def __init__(self, neurons: int, activation: Operation = Linear(), dropout: float = 1.0, weight_init: str = "standard") Мы добавляем операцию дропаута с помощью вот такой функции в _setup_layer : if self.dropout < 1.0: self.operations.append(Dropout(self.dropout)) Готово! Давайте посмотрим, как это работает. Эксперимент: дропаут Во-первых, видно, что добавление в модель дропаута действительно сни- жает потери. Добавим дропаут, равный 0.8 (чтобы 20% нейронов обнули- лось), в первый слой, чтобы наша модель выглядела следующим образом: mnist_soft = NeuralNetwork( layers=[Dense(neurons=89, activation=Tanh(), weight_init="glorot", dropout=0.8), Dense(neurons=10, 152 Глава 4 . Расширения activation=Linear(), weight_init="glorot")], loss = SoftmaxCrossEntropy(), seed=20190119) и обучим модель с теми же гиперпараметрами, что и раньше (экспонен- циальное снижение скорости обучения с 0.2 до 0.05): Validation loss after 10 epochs is 0.285 Validation loss after 20 epochs is 0.232 Validation loss after 30 epochs is 0.199 Validation loss after 40 epochs is 0.196 Loss increased after epoch 50, final loss was 0.196, using the model from epoch 40 The model validation accuracy is: 96.95% Мы снова значительно уменьшили потери по сравнению с тем, что мы ви- дели ранее: величина потери стала равна 0.196 по сравнению с 0.244 ранее. Дропаут проявляет себя во всей красе, когда мы добавляем больше слоев. Давайте заменим модель, которую мы использовали в этой главе, на мо- дель глубокого обучения. Пусть в первом скрытом слое будет в два раза больше нейронов, чем в скрытом слое ранее (178), а во втором скрытом слое — вдвое меньше (46). Наша модель выглядит так: model = NeuralNetwork( layers=[Dense(neurons=178, activation=Tanh(), weight_init="glorot", dropout=0.8), Dense(neurons=46, activation=Tanh(), weight_init="glorot", dropout=0.8), Dense(neurons=10, activation=Linear(), weight_init="glorot")], loss = SoftmaxCrossEntropy(), seed=20190119) Обратите внимание на дропаут в первых двух слоях. Обучив модель, увидим еще одно уменьшение потерь и повышение точ- ности! Заключение 153 Validation loss after 10 epochs is 0.321 Validation loss after 20 epochs is 0.268 Validation loss after 30 epochs is 0.248 Validation loss after 40 epochs is 0.222 Validation loss after 50 epochs is 0.217 Validation loss after 60 epochs is 0.194 Validation loss after 70 epochs is 0.191 Validation loss after 80 epochs is 0.190 Validation loss after 90 epochs is 0.182 Loss increased after epoch 100, final loss was 0.182, using the model from epoch 90 The model validation accuracy is: 97.15% Но это улучшение невозможно без дропаута. Ниже приведены обучения той же модели без дропаута: Validation loss after 10 epochs is 0.375 Validation loss after 20 epochs is 0.305 Validation loss after 30 epochs is 0.262 Validation loss after 40 epochs is 0.246 Loss increased after epoch 50, final loss was 0.246, using the model from epoch 40 The model validation accuracy is: 96.52% Без дропаута модель глубокого обучения работает хуже, чем модель с одним скрытым слоем, несмотря на вдвое большее число параметров и время обучения! Это показывает, насколько дропаут важен при обуче- нии моделей глубокого обучения. Он сыграл важную роль в определении модели-победителя на ImageNet 2012 года, что положило начало совре- менной эре глубокого обучения 1 . Без дропаута вы, возможно, не читали бы эту книгу! Заключение В этой главе мы разобрали некоторые из наиболее распространенных методов улучшения обучения нейронной сети, начав с описания ее работы и цели на низком уровне. В конце хотелось бы привести перечень того, 1 Подробнее об этом в статье Г. Хинтона и соавт. Improving neural networks by preventing co-adaptation of feature detectors. 154 Глава 4 . Расширения что вы можете попробовать выжать из своей нейронной сети независимо от задачи: y Учитывайте инерцию — важный метод оптимизации — при определе- нии правила обновления веса. y Скорость обучения лучше снижать постепенно, используя линейное или экспоненциальное затухание, или более современный метод, на- пример косинусоидальное затухание. Наиболее эффективные шаблоны скорости работают не только в зависимости от номера эпохи, но также и от величины потерь, то есть скорость обучения снижается только тогда, когда не удается снизить потери. Попробуйте реализовать это! y Убедитесь, что масштаб инициализации весов является функцией количества нейронов в вашем слое (это делается по умолчанию в боль- шинстве библиотек нейронных сетей). y Добавьте дропаут, особенно если в вашей сети есть несколько полно- стью связанных слоев подряд. Далее мы перейдем к обсуждению архитектур, специально придуманных для конкретных областей, начиная со сверточных нейронных сетей, ис- пользуемых для распознавания изображений. Вперед! ГЛАВА 5 Сверточная нейронная сеть В этой главе мы рассмотрим сверточные нейронные сети (англ.: convo- lutional neural networks — CNN). CNN — это стандартная архитектура нейронной сети, используемая при работе с изображениями в широком спектре задач. Ранее мы работали только с полносвязными нейронными сетями, которые мы реализовали в виде нескольких полносвязных слоев. Начнем с обзора некоторых ключевых элементов этих сетей и объясним, зачем нам для изображений другая архитектура. Затем мы рассмотрим CNN так же, как и другие понятия в этой книге: сначала мы обсудим, как они работают на высоком уровне, затем перейдем к низкому уровню и, наконец, закодируем все это с нуля 1 . К концу этой главы вы получите достаточно полное представление о том, как работают CNN, чтобы уметь использовать их для своих задач, а также для самостоятельного изуче- ния существующих модификаций CNN, таких как ResNets, DenseNets и Octave Convolutions. Нейронные сети и обучение представлениям Нейронные сети получают данные о наблюдениях, причем каждое на- блюдение представлено некоторым числом n признаков. Мы приводили два разных примера. Первый: набор данных о ценах на жилье, где каждое наблюдение состояло из 13 признаков — числовых характеристик этого дома. Второй: набор данных MNIST рукописных цифр; поскольку изображения были пред- ставлены 784 пикселями (28 пикселей в ширину и 28 пикселей в высоту), 1 Наш код для сверточных сетей будет крайне неэффективным. В разделе «Градиент потерь с учетом смещения» приложения А будет предложена более эффективная реализация операции многоканальной свертки, которую мы опишем в этой главе с использованием библиотеки NumPy. |