Курсовая работа. Глубокое обучение
Скачать 4.97 Mb.
|
119 for e in range(epochs): X_train, y_train = permute_data(X_train, y_train) batch_generator = self.generate_batches(X_train, y_train, batch_size) for ii, (X_batch, y_batch) in enumerate(batch_generator): self.net.train_batch(X_batch, y_batch) self.optim.step() if (e+1) % eval_every == 0: test_preds = self.net.forward(X_test) loss = self.net.loss.forward(test_preds, y_test) print(f"Validation loss after {e+1} epochs is {loss:.3f}") В полной версии этой функции в хранилище GitHub книги ( https://oreil. ly/2MV0aZI ) мы также реализовали раннюю остановку, которая выполняет следующие действия: 1. Сохраняет значение потерь каждые eval_every эпох. 2. Проверяет, стали ли потери меньше после настройки. 3. Если потери не стали ниже, модель откатывается на шаг назад. Теперь у нас есть все необходимое для обучения этих моделей! Собираем все вместе Ниже приведен код для обучения сети с использованием всех классов Trainer и Optimizer и двух моделей, определенных ранее, — linear_ regression и neural_network . Мы установим скорость обучения равной 0.01, максимальное количество эпох — 50 и будем оценивать наши модели каждые 10 эпох: 120 Глава 3. Основы глубокого обучения optimizer = SGD(lr=0.01) trainer = Trainer(linear_regression, optimizer) trainer.fit(X_train, y_train, X_test, y_test, epochs = 50, eval_every = 10, seed=20190501); Validation loss after 10 epochs is 30.295 Validation loss after 20 epochs is 28.462 Validation loss after 30 epochs is 26.299 Validation loss after 40 epochs is 25.548 Validation loss after 50 epochs is 25.092 Использование тех же функций оценки моделей из главы 2 и помещение их в функцию eval_regression_model дают нам следующие результаты: eval_regression_model(linear_regression, X_test, y_test) Mean absolute error: 3.52 Root mean squared error 5.01 Это похоже на результаты линейной регрессии, которую мы использовали в предыдущей главе, а это подтверждает, что наша структура работает. Запустив тот же код с моделью neural_network со скрытым слоем с 13 ней- ронами, мы получим следующее: Validation loss after 10 epochs is 27.434 Validation loss after 20 epochs is 21.834 Validation loss after 30 epochs is 18.915 Validation loss after 40 epochs is 17.193 Validation loss after 50 epochs is 16.214 eval_regression_model(neural_network, X_test, y_test) Mean absolute error: 2.60 Root mean squared error 4.03 Опять же, эти результаты похожи на те, что мы видели в предыдущей главе, и они значительно лучше, чем линейная регрессия. Собираем все вместе 121 Наша первая модель глубокого обучения (с нуля) С настройками покончено, запустим первую модель: deep_neural_network = NeuralNetwork( layers=[Dense(neurons=13, activation=Sigmoid()), Dense(neurons=13, activation=Sigmoid()), Dense(neurons=1, activation=LinearAct())], loss=MeanSquaredError(), learning_rate=0.01 ) Пока не будем фантазировать и просто добавим скрытый слой с той же размерностью, что и первый слой, так что наша сеть теперь имеет два скрытых слоя, каждый из которых содержит 13 нейронов. Обучение с использованием той же скорости обучения и периодичности оценки, что и в предыдущих моделях, дает следующий результат: Validation loss after 10 epochs is 44.134 Validation loss after 20 epochs is 25.271 Validation loss after 30 epochs is 22.341 Validation loss after 40 epochs is 16.464 Validation loss after 50 epochs is 14.604 eval_regression_model(deep_neural_network, X_test, y_test) Mean absolute error: 2.45 Root mean squared error 3.82 Наконец-то мы перешли непосредственно к глубокому обучению, но тут уже будут реальные задачи, и без фокусов и хитростей наша модель глубокого обучения будет работать не намного лучше, чем простая ней- ронная сеть с одним скрытым слоем. Что еще более важно, полученная структура легко расширяема. Мы могли бы весьма просто реализовать другие виды Operation , обернуть их в но- вые слои и сразу вставить их, предполагая, что в них определены _output и _input_grad и что размерности данных совпадают с размерностями их 122 Глава 3. Основы глубокого обучения соответствующих градиентов. Аналогично мы могли бы попробовать другие функции активации и посмотреть, уменьшит ли это показатели ошибок. Призываю взять наш код с GitHub ( https://oreil.ly/deep-learning- github ) и попробовать! Заключение и следующие шаги В следующей главе я расскажу о нескольких приемах, которые будут необходимы для правильной тренировки наших моделей, чтобы решать более сложные задачи 1 . Мы попробуем другие функции потерь и оптими- заторы. Я также расскажу о дополнительных приемах настройки скоро- стей обучения и их изменения в процессе обучения, а также покажу, как реализовать это в классах Optimizer и Trainer . Наконец, мы рассмотрим прореживание (Dropout), узнаем, что это такое и зачем оно нужно для повышения устойчивости обучения. Вперед! 1 Даже в этой простой задаче незначительное изменение гиперпараметров может привести к тому, что модель глубокого обучения не справится с двухслойной ней- ронной сетью. Возьмите код с GitHub и попробуйте сами! ГЛАВА 4 Расширения Из первых трех глав мы узнали, что такое модели глубокого обучения и как они должны работать, а затем создали первую модель глубокого обучения и научили ее решать относительно простую задачу прогнозирования цен на жилье на основе некоторых признаков. Однако в реальных задачах успешно обучить модели глубокого обучения не так просто. В теории такие модели действительно позволяют найти оптимальное решение любой задачи, которую можно свести к обучению с учителем, но вот на практике не все так просто. Теория говорит о том, что данная архитектура модели позволяет найти оптимальное решение проблемы. Однако помимо теории есть хорошие и понятные методы, которые повышают вероятность удачного обучения нейронной сети, и эта глава именно об этом. Начнем с математического анализа задачи нейронной сети: поиска мини- мума функции. Затем покажем ряд методов, которые помогут выполнить эту задачу, на классическом примере с рукописными цифрами. Мы нач- нем с функции потерь, которая используется в задачах классификации в глубоком обучении, и продемонстрируем, что она значительно ускоряет обучение (пока мы говорили только о регрессии, а функцию потерь и за- дачу классификации не рассматривали). Рассмотрим новые функции активации и покажем, как они влияют на обучение, а также поговорим об их плюсах и минусах. Далее рассмотрим самое важное (и простое) улучшение для уже знакомого нам метода стохастического градиентного спуска, а также кратко поговорим о возможностях продвинутых опти- мизаторов. В заключение мы рассмотрим еще три метода улучшения работы нашей системы: снижение скорости обучения, инициализация весов и отсев. Как мы увидим, все эти методы помогут нашей нейронной сети находить оптимальные решения. В первой главе мы сначала приводили рисунок со схемой, затем матема- тическую модель, и потом код для рассматриваемой концепции. В этой главе какой-то конкретной схемы у методов не будет, поэтому мы сначала 124 Глава 4 . Расширения дадим краткое описание метода, а затем перейдем к математике (которая будет намного проще, чем в первой главе). Завершим рассмотрение кодом, который реализует метод с использованием введенных нами ранее стро- ительных блоков. Итак, начнем с краткого описания задачи нейронной сети: поиска минимума функции. Немного о понимании нейронных сетей Как мы уже видели, в нейронных сетях содержатся весовые коэффициен- ты, или веса. Значения весов и входные данные X и y позволяют вычислить «потери». На рис. 4.1 показана схема описанной нейронной сети. x Y Потеря L Значения класса W W W W W W W W W W Рис. 4.1. Простое представление нейронной сети с весами На самом деле каждый отдельный вес имеет некоторую сложную нелиней- ную связь с характеристиками X , целью y , другими весами и, в конечном счете, потерей L . Если мы построили график, варьируя значение веса, сохраняя постоянными значения с другими весами, X и y , и вычерчивая полученное значение потери L , мы могли видеть что-то вроде того, что показано на рис. 4.2. Запуская обучение нейронной сети, мы задаем весам начальные значения в пределах, показанных на рис. 4.2. Затем, используя градиенты, которые рассчитываем во время обратного распространения, мы итеративно обнов- ляем значения весов, основываясь на наклоне кривой и текущем значении веса 1 . На рис. 4.3 показана геометрическая интерпретация настройки весов нейронной сети в зависимости от градиента и скорости обучения. Слева стрелками показано, что это правило применяется многократно с меньшей скоростью обучения, чем в области справа. Обратите внима- 1 Кроме того, как мы видели в главе 3, мы умножаем эти градиенты на скорость об- у чения, что позволяет точно контролировать процесс настройки весов. Немного о понимании нейронных сетей 125 ние, что в обоих случаях изменение значения пропорционально наклону кривой при данном значении веса (более крутой наклон означает более сильное изменение). L W Рис. 4.2. Вес нейронной сети против ее потери L W Рис. 4.3. Настройка весов нейронной сети в зависимости от значения градиента и скорости обучения Конечная цель обучения модели — задать весам такие значения, чтобы значение потерь нашло глобальный минимум. Как видно из рис. 4.3, если шаг обучения слишком мал, мы рискуем попасть в локальный минимум, который будет менее оптимален, чем глобальный (этот сценарий показан синими стрелками). Если шаг слишком велик, мы рискуем «перепрыг- нуть» глобальный минимум, даже если находимся рядом с ним (этот сценарий показан красными стрелками). Это самая главная дилемма на- 126 Глава 4 . Расширения стройки скорости обучения: слишком малая скорость ведет к попаданию в локальный минимум, а слишком большая ведет к промаху. На деле все еще сложнее. В нейронной сети могут быть тысячи, а может, и миллионы весов, и в этом случае мы ищем глобальный минимум в тыся- че- или миллионмерном пространстве. Более того, поскольку мы на каждой итерации будем обновлять значения весов, а также передавать различные значения X и y , сама кривая, на которой мы ищем минимум, тоже постоянно меняется! Из-за этого нейронные сети много лет были объектом споров и скепсиса, так как казалось, что оптимальное решение найти невозможно. Ян Лекун и соавт. в статье 2015 года высказались следующим образом: В частности, было принято считать, что простой градиентный спуск оказывается пойман в локальный минимум, который не позволит уменьшить среднюю ошибку. На практике неоптимальные локальные минимумы редко являются проблемой для больших сетей. Незави- симо от начальных условий система почти всегда достигает решений очень похожего качества. Недавние теоретические и эмпирические результаты убедительно свидетельствуют о том, что локальные ми- нимумы — это не такая уж и проблема. На рис. 4.3 хорошо видно, почему скорость обучения не должна быть слишком большой или слишком маленькой, отсюда интуитивно понятно, почему приемы, которые мы собираемся изучить в этой главе, действи- тельно работают. Теперь, понимая цель нейронных сетей, мы приступим к работе. Начнем с многопеременной логистической активационной функции кросс-энтропийных потерь, которая работает в значительной степени благодаря своей способности обеспечивать более крутые гра- диенты весов, чем функция среднеквадратичных потерь, которую мы видели в предыдущей главе. Многопеременная логистическая функция активации с перекрестно-энтропийными потерями В главе 3 в качестве функции потерь мы использовали среднеквадра- тическую ошибку (MSE). Функция обладала свойством выпуклости, Многопеременная логистическая функция активации с перекрестно-энтропийными потерями 127 то есть чем дальше прогноз от цели, тем круче был бы начальный гра- диент, который класс Loss отправлял обратно в слои сети. Из-за этого увеличивались градиенты, полученные параметрами. Но оказывается, что в задачах классификации это не предел мечтаний, поскольку в этом случае значения, которые выводит наша сеть, должны интерпретировать- ся как вероятности в диапазоне от 0 до 1, и вектор вероятностей должен иметь равную 1 сумму для каждого наблюдения, которое мы передали через нашу сеть. Многопеременная логистическая функция активации (будем использовать сокращенное английское название — softmax) с перекрестно-энтропийными потерями позволяет за счет этого полу- чить более крутые градиенты, чем средний квадрат ошибки, при тех же входных данных. У этой функции два компонента: первый — это функция softmax , а второй — это перекрестно-энтропийные потери. Рассмотрим их подробнее. Компонент № 1: функция softmax Для задачи классификации с N возможными категориями нейронная сеть выдаст вектор из N значений для каждого наблюдения. Для задачи с тремя категориями вектор может быть таким: [5, 3, 2] Математическое представление Поскольку речь идет о задаче классификации, мы знаем, что результат следует интерпретировать как вектор вероятностей того, что данное наблюдение относится к категории 1, 2 или 3 соответственно. Один из способов преобразования этих значений в вектор вероятностей — нор- мализация, сложение и деление на сумму: 128 Глава 4 . Расширения Но есть способ, который дает более крутые градиенты и имеет пару тузов в рукаве, — softmax . Эта функция для вектора длины 3 будет иметь вид: Наглядное представление Суть функции softmax заключается в том, что она усиливает максималь- ное значение по сравнению с другими, заставляя нейронную сеть стать «чувствительнее» к прогнозам в задаче классификации. Давайте срав- ним результаты функций normalize и softmax для данного выше вектора вероятностей: normalize(np.array([5,3,2])) array([0.5, 0.3, 0.2]) softmax(np.array([5,3,2])) array([0.84, 0.11, 0.04]) Видно, что исходное максимальное значение 5 выделилось сильнее, а два других стали ниже, чем после нормализации. Таким образом, функция softmax — это нечто среднее между нормализацией значений и факти- ческим применением функции max (которая в данном случае приведет к выводу массива ([1.0, 0.0, 0.0]) ), отсюда и название «softmax» — «мягкий максимум». Компонент № 2: перекрестно-энтропийная потеря Напомним, что любая функция потерь берет на вход вектор вероятностей и вектор фактических значений Многопеременная логистическая функция активации с перекрестно-энтропийными потерями 129 Математическое представление Функция перекрестно-энтропийной потери для каждого индекса i в этих векторах: Наглядное представление Чтобы понять, чем такая функция потерь хороша, отметим, что, посколь- ку каждый элемент y равен 0 или 1, предыдущее уравнение сводится к: Теперь все проще. Если y = 0, то график зависимости значения этой потери от значения среднеквадратичной потери на интервале от 0 до 1 выглядит так, как показано на рис. 4.4. Зн ач ения по терь Прогноз (p) Перекрестно-энтропийная потеря Средняя квадратическая ошибка Рис. 4.4. Сравнение перекрестно-энтропийной функции потерь и СКО при y = 0 Штрафы у перекрестно-энтропийной функции потерь намного выше 1 , и кроме того, они становятся больше с увеличением скорости, стремясь 1 Мы можем быть более конкретными: среднее значение –log(1 – x)за интервал от 0 до 1 оказывается равным 1, тогда как среднее значение x 2 за тот же интервал со- ставляет всего 1/3. 130 Глава 4 . Расширения к бесконечности, когда разница между нашим прогнозом и целью стре- мится к 1! График для случая у = 1 похож, только «перевернут» (то есть он повернут на 180 градусов вокруг линии х = 0.5). Таким образом, для задач, где результат должен лежать между 0 и 1, пере- крестно-энтропийная функция потерь создает более крутые градиенты, чем MSE. Но настоящая магия происходит, когда мы объединяем эту функцию потерь с функцией softmax — сначала проводим выход нейрон- ной сети через функцию softmax , чтобы нормализовать его до нужного диапазона, а затем подаем полученные вероятности в перекрестно-энтро- пийную функцию потерь. Давайте посмотрим, как это выглядит в сценарии с тремя категориями, который мы уже упоминали. Выражение для компонента вектора потерь i = 1, то есть первого компонента потерь для данного наблюдения, которое мы обозначим как SCE 1 , имеет вид: Это выражение дает более сложный градиент для данной функции по- терь. Но есть более элегантное выражение, которое легко и записать, и реализовать: Это означает, что полный градиент перекрестно энтропийной функции с softmax равен: Готово! Как и было обещано, итоговая реализация тоже будет простой: softmax_x = softmax(x, axis = 1) loss_grad = softmax_x — y Теперь будем писать код. Многопеременная логистическая функция активации с перекрестно-энтропийными потерями 131 Код В главе 3 мы говорили, что классу Loss требуются два двумерных массива: один с прогнозами сети, а другой с целевыми значениями. Количество строк в каждом массиве представляет собой размер пакета, а количество столбцов — это число категорий n в задаче классификации. Каждая стро- ка представляет собой наблюдение в наборе данных, причем значения n в строке — это вероятности принадлежности этого наблюдения к каждому из n классов. Таким образом, нам придется применить функцию softmax к каждой строке в массиве прогнозов. Так мы получаем первую потен- циальную проблему: полученные числа мы будем передавать в функцию log , чтобы вычислить потери. Тут есть проблема, так как функция log(x) уходит в отрицательную бесконечность, когда x стремится к 0, а 1 — x стре- мится в бесконечность, когда x стремится к 1. Соберем все вместе! class SoftmaxCrossEntropyLoss(Loss): def __init__(self, eps: float=1e-9) super().__init__() self.eps = eps self.single_output = False def _output(self) -> float: # применение функции softmax к каждой строке (наблюдению) softmax_preds = softmax(self.prediction, axis=1) # захват выхода softmax, чтобы предотвратить неустойчивость self.softmax_preds = np.clip(softmax_preds, self.eps, 1 — self.eps) # вычисление потерь softmax_cross_entropy_loss = ( -1.0 * self.target * np.log(self.softmax_preds) — \ (1.0 — self.target) * np.log(1 — self.softmax_preds) ) return np.sum(softmax_cross_entropy_loss) def _input_grad(self) -> ndarray: return self.softmax_preds — self.target |