Пошаговое руководство в записной книжке Jupyter по оптимизации гиперпараметров.

Это четвертая статья из моей серии о полностью связанных (ванильных) нейронных сетях. В этой статье мы оптимизируем нейронную сеть и проведем настройку гиперпараметров, чтобы получить высокопроизводительную модель на функции Била - одной из многих тестовых функций, обычно используемых для изучения эффективности различные методы оптимизации. Этот анализ можно повторно использовать для любой функции, но я рекомендую попробовать его самостоятельно на другой распространенной функции тестирования, чтобы проверить свои навыки. Лично я считаю, что оптимизация нейронной сети может быть невероятно сложной (хотя и не такой плохой, как GAN, если вы знакомы с ними ...), если у вас нет четкой и четко определенной процедуры, которой нужно следовать. Надеюсь, вам понравится эта статья и вы найдете в ней полезные сведения.

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







Весь связанный код теперь можно найти в моем репозитории GitHub:



Функция Била

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

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

  • Конвергенция (как быстро они приходят к ответу)
  • Точность (насколько они приблизительно соответствуют точному ответу)
  • Надежность (хорошо ли они работают для всех функций или только для небольшого подмножества)
  • Общая производительность (например, вычислительная сложность)

Просто прокрутив статью в Википедии о тестовых функциях оптимизации, вы увидите, что некоторые функции довольно неприятны. Многие из них были выбраны, поскольку они подчеркивают конкретные проблемы, которые могут мешать алгоритмам оптимизации. В этой статье мы рассмотрим относительно безобидную на вид функцию, называемую функцией Била.

Функция Била выглядит так:

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

Остальная часть этой статьи будет следовать руководству по записной книжке Jupyter в моем репозитории GitHub. Мы обсудим, как можно было бы справиться с подобным искусственным ландшафтом. Этот ландшафт аналогичен поверхности потерь нейронной сети. При обучении нейронной сети цель состоит в том, чтобы найти глобальный минимум на поверхности потерь путем выполнения некоторой формы оптимизации - обычно стохастического градиентного спуска.

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

Для тех, кто читает, но не знаком с записной книжкой Jupyter, не стесняйтесь читать о нем подробнее здесь.

Прежде чем мы коснемся каких-либо нейронных сетей, мы сначала должны определить функцию и найти ее минимум (иначе как мы узнаем, что получили правильный ответ?). Первым шагом (после импорта соответствующих пакетов) является определение функции Била в нашей записной книжке:

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

Затем мы создаем сетку из точек на основе этой информации и готовы найти минимум.

Теперь мы делаем (ужасное) первоначальное предположение.

Затем мы используем функцию scipy.optimize и смотрим, какой ответ появится.

Вот результат:

Похоже, что ответ (3, 0,5), и, если вы подставите эти значения в уравнение, вы действительно обнаружите, что это минимум (это также сказано на странице Википедии).

В следующем разделе мы начнем с нашей нейронной сети.

Оптимизация в нейронных сетях

Нейронную сеть можно определить как структуру, которая объединяет входные данные и пытается угадать результат. Если нам посчастливится получить какие-то результаты, называемые «истиной», для сравнения выходных данных, произведенных сетью, мы можем вычислить ошибку. Итак, сеть угадывает, вычисляет некоторую функцию ошибки, снова догадывается, пытаясь минимизировать эту ошибку, снова догадывается, пока ошибка больше не исчезнет. Это оптимизация.

В нейронных сетях наиболее часто используемые алгоритмы оптимизации представляют собой разновидности GD (градиентный спуск). целевая функция, используемая в градиентном спуске, - это функция потерь, которую мы хотим минимизировать.

Этот учебник будет в значительной степени опираться на Keras, поэтому я дам краткое освежение Keras.

Keras Refresher

Keras - это библиотека Python для глубокого обучения, которая может работать поверх Theano или TensorFlow, двух мощных библиотек Python для быстрых численных вычислений, созданных и выпущенных Facebook и Google соответственно.

Keras был разработан, чтобы максимально упростить и ускорить разработку моделей глубокого обучения для исследований и практических приложений. Он работает на Python 2.7 или 3.5 и может без проблем работать на графических процессорах и процессорах.

Керас построен на идее модели. По сути, у нас есть последовательность слоев, называемая Sequential моделью, которая представляет собой линейный набор слоев. Keras также предоставляет функциональный API, способ определения сложных моделей, таких как модели с несколькими выходами, ориентированные ациклические графы или модели с общими слоями.

Мы можем резюмировать построение моделей глубокого обучения в Keras с использованием последовательной модели следующим образом:

  1. Определите свою модель: создайте Sequential модель и добавьте слои.
  2. Скомпилируйте модель: укажите функцию потерь и оптимизаторы и вызовите функцию .compile().
  3. Подгоните под свою модель: обучите модель на данных, вызвав функцию .fit().
  4. Делайте прогнозы: используйте модель для создания прогнозов на основе новых данных, вызывая такие функции, как .evaluate() или .predict().

Вы можете спросить себя - как вы можете проверить производительность модели во время ее работы? Это хороший вопрос, и ответ на него - использование обратных вызовов.

Обратные вызовы: заглянем в нашу модель во время обучения

Вы можете посмотреть, что происходит на различных этапах вашей модели, используя callbacks. Обратный вызов - это набор функций, которые будут применяться на определенных этапах процедуры обучения. Вы можете использовать обратные вызовы, чтобы получить представление о внутренних состояниях и статистике модели во время обучения. Вы можете передать список обратных вызовов (как обратные вызовы аргументов ключевого слова) методу .fit() классов Sequential или Model. Соответствующие методы обратных вызовов будут затем вызываться на каждом этапе обучения.

  • Вы уже знакомы с функцией обратного вызова keras.callbacks.History(). Это автоматически включается в .fit().
  • Еще один очень полезный - keras.callbacks.ModelCheckpoint, который сохраняет модель с ее весами в определенный момент обучения. Это может оказаться полезным, если ваша модель работает долгое время и происходит сбой системы. Тогда не все потеряно. Рекомендуется сохранять веса модели только в том случае, если наблюдается улучшение, измеренное, например, acc.
  • keras.callbacks.EarlyStopping останавливает обучение, когда отслеживаемое количество перестает улучшаться.
  • keras.callbacks.LearningRateScheduler изменит скорость обучения во время обучения.

Позже мы применим некоторые обратные вызовы. Полную документацию по callbacks см. Https://keras.io/callbacks/.

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

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

Шаг 1. Выбор топологии сети (оптимизация не рассматривается, но, очевидно, очень важна)

Мы будем использовать набор данных MNIST, который состоит из изображений рукописных цифр (0–9) в оттенках серого, размер которых составляет 28x28 пикселей. Каждый пиксель состоит из 8 бит, поэтому его значение варьируется от 0 до 255.

Получить набор данных очень просто, поскольку для этого есть функция, встроенная в Keras.

Наши выходные данные для наших данных X и Y равны (60000, 28, 28) и (60000,1) соответственно. Всегда рекомендуется распечатать некоторые данные для проверки значений (и, если необходимо, типа данных).

Мы можем проверить данные обучения, посмотрев на одно изображение каждой цифры, чтобы убедиться, что ни одна из них не отсутствует в наших данных.

Последняя проверка касается размеров обучающего и тестового наборов, что можно сделать относительно легко:

Мы обнаружили, что у нас есть 60 000 обучающих изображений и 10 000 тестовых изображений. Следующее, что нужно сделать, - это предварительно обработать данные.

Предварительная обработка данных

Чтобы запустить нашу NN, нам нужно предварительно обработать данные (эти шаги могут быть выполнены взаимозаменяемо):

  • Во-первых, нам нужно преобразовать массивы 2D-изображений в 1D (сгладить их). Мы можем выполнить это, используя для этого изменение формы массива с помощью numpy.reshape() или keras ': слой с именем keras.layers.Flatten, который преобразует формат изображений из 2d-массива (размером 28 на 28 пикселей) в 1D-массив из 28 пикселей. * 28 = 784 пикселя.
  • Затем нам нужно нормализовать значения пикселей (дать им значения от 0 до 1), используя следующее преобразование:

В нашем случае минимум равен нулю, а максимум равен 255, поэтому формула принимает вид просто 𝑥: = 𝑥 / 255.

Теперь мы хотим сразу закодировать наши данные.

Теперь мы, наконец, готовы построить нашу модель!

Шаг 2 - Регулировка learning rate

Одним из наиболее распространенных алгоритмов оптимизации является стохастический градиентный спуск (SGD). В SGD можно оптимизировать следующие гиперпараметры: learning rate, momentum, decay и nesterov.

Learning rate контролирует вес в конце каждой партии, а momentum контролирует, насколько предыдущее обновление влияет на текущее обновление веса. Decay указывает на снижение скорости обучения при каждом обновлении, а nesterov принимает значение «Истина» или «Ложь» в зависимости от того, хотим ли мы применить импульс Нестерова.

Типичные значения для этих гиперпараметров: lr = 0,01, распад = 1e-6, импульс = 0,9 и нестеров = True.

Гиперпараметр скорости обучения входит в функцию optimizer, которую мы увидим ниже. У Keras есть планировщик скорости обучения по умолчанию в SGDoptimizer, который снижает скорость обучения во время алгоритма оптимизации стохастического градиентного спуска. Скорость обучения снижается по следующей формуле:

lr = lr × 1 / (1 + эпоха распада *)

Давайте реализуем график адаптации скорости обучения в Keras. Мы начнем с SGD и значения скорости обучения 0,1. Затем мы обучим модель для 60 эпох и установим аргумент затухания на 0,0016 (0,1 / 60). Мы также включаем значение импульса 0,8, поскольку оно, кажется, хорошо работает при использовании адаптивной скорости обучения.

Далее строим архитектуру нейронной сети:

Теперь мы можем запустить модель и посмотреть, насколько хорошо она работает. На моей машине это заняло около 20 минут и может быть быстрее или медленнее, в зависимости от вашей машины.

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

График функции потерь выглядит следующим образом:

А вот точность:

Теперь мы рассмотрим применение индивидуализированной скорости обучения.

Примените индивидуальное изменение скорости обучения с помощью LearningRateScheduler

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

𝑙𝑟=𝑙𝑟₀ × 𝑒^(−𝑘𝑡)

Это очень похоже на предыдущее, поэтому я сделаю это в одном блоке кода и опишу различия.

Здесь мы видим, что единственное, что здесь изменилось, - это наличие функции exp_decay, которую мы определили, и ее использование в функции LearningRateScheduler. Обратите внимание, что на этот раз мы также решили добавить несколько обратных вызовов в нашу модель.

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

Функция потерь теперь выглядит более гладкой по сравнению с предыдущей.

Это показывает, что разработка планировщика скорости обучения может быть полезным способом улучшить производительность нейронной сети.

Шаг 3 - Выбор optimizer и loss function

При построении модели и использовании ее для прогнозирования, например, для присвоения оценок изображениям (кошка, самолет и т. Д.), Мы хотим измерить наш успех или неудачу, определив функцию потерь (или целевая функция). Цель оптимизации - эффективно вычислить параметры / веса, которые минимизируют эту функцию потерь. keras предоставляет различные типы функций потерь.

Иногда функция «потери» измеряет «расстояние». Мы можем определить это «расстояние» между двумя точками данных различными способами, подходящими для задачи или набора данных. Используемое расстояние зависит от типа данных и конкретной решаемой проблемы. Например, при обработке естественного языка (которая анализирует текстовые данные) гораздо чаще используется расстояние Хэмминга.

Расстояние

  • Евклидово
  • Манхэттен
  • другие, например, Hamming, который измеряет расстояния между струнами. Расстояние Хэмминга для слов «каролина» и «катрин» равно 3.

Функции потерь

  • MSE (для регрессии)
  • категориальная кросс-энтропия (для классификации)
  • бинарная кросс-энтропия (для классификации)

Шаг 4 - Выбор batch size и number of epochs

Размер пакета определяет количество образцов, которые будут распространяться по сети.

Например, предположим, что у вас есть 1000 обучающих выборок, и вы хотите установить batch_size равным 100. Алгоритм берет первые 100 выборок (от 1-го до 100-го) из набора обучающих данных и обучает сеть. Затем он берет вторые 100 выборок (с 101-й по 200-ю) и снова обучает сеть. Мы можем продолжать выполнять эту процедуру, пока не распространим все образцы по сети.

Преимущества использования размера партии ‹количества всех образцов:

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

Недостатки использования размера партии ‹количества всех образцов:

  • Чем меньше размер партии, тем менее точной будет оценка градиента.

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

Одна эпоха означает, что каждая выборка в наборе обучающих данных имела возможность обновить внутренние параметры модели. Эпоха состоит из одной или нескольких партий.

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

Шаг 5 - Случайные перезапуски

Этот метод, похоже, не реализован в keras. Это легко сделать, изменив keras.callbacks.LearningRateScheduler. Я оставлю это как упражнение для читателя, но, по сути, оно включает в себя сброс скорости обучения после определенного количества эпох на конечное количество раз.

Настройка гиперпараметров с использованием перекрестной проверки

Теперь вместо того, чтобы пробовать разные значения вручную, мы будем использовать GridSearchCV из Scikit-Learn, чтобы опробовать несколько значений для наших гиперпараметров и сравнить результаты.

Для перекрестной проверки с keras мы будем использовать оболочки для Scikit-Learn API. Они предоставляют способ использовать последовательные модели Keras (только с одним входом) как часть рабочего процесса Scikit-Learn.

Доступны две обертки:

keras.wrappers.scikit_learn.KerasClassifier(build_fn=None, **sk_params), который реализует интерфейс классификатора Scikit-Learn,

keras.wrappers.scikit_learn.KerasRegressor(build_fn=None, **sk_params), который реализует интерфейс регрессора Scikit-Learn.

Попытка инициализации с разным весом

Первый гиперпараметр, который мы попытаемся оптимизировать с помощью перекрестной проверки, - это инициализация различных весов.

Результаты нашего GridSearch:

Мы видим, что наилучшие результаты получаются либо из модели, использующей инициализацию lecun_uniform, либо инициализацию glorot_uniform, и что мы можем достичь точности, близкой к 97%, с нашей сетью.

Сохраните модель нейронной сети в JSON

Формат иерархических данных (HDF5) - это формат хранения данных для хранения больших массивов данных, включая значения весов в нейронной сети.

Вы можете установить модуль HDF5 Python: pip install h5py

Keras дает вам возможность описывать и сохранять любую модель в формате JSON.

Перекрестная проверка с более чем одним гиперпараметром

Обычно нас интересует не то, как изменяется только один параметр, а то, как изменение нескольких параметров может повлиять на наши результаты. Мы можем проводить перекрестную проверку с более чем одним параметром одновременно, эффективно пробуя их комбинации.

Примечание. Перекрестная проверка в нейронных сетях требует больших вычислительных ресурсов. Подумайте, прежде чем экспериментировать! Умножьте количество проверяемых функций, чтобы узнать, сколько существует комбинаций. Каждая комбинация оценивается с помощью k -кратной перекрестной проверки (k - это параметр, который мы выбираем).

Например, мы можем выбрать поиск различных значений:

  • размер партии
  • количество эпох
  • режим инициализации

Варианты выбора указываются в словаре и передаются в GridSearchCV.

Теперь мы выполним GridSearch для batch size, number of epochs и initializer вместе.

Последний вопрос перед тем, как мы закончим: что нам делать, если количество параметров и количество значений, которые мы должны циклически перебирать в нашем GridSearchCV, особенно велико?

Это может быть особенно неприятной проблемой - представьте себе ситуацию, когда выбрано 5 параметров и 10 возможных значений, которые мы выбрали для каждого параметра. Количество уникальных комбинаций этого равно 10⁵, что означает, что нам придется обучать смехотворно большое количество сетей. Ясно, что было бы безумием делать это таким образом, поэтому обычно используют RandomizedCV в качестве альтернативы.

RandomizedCV позволяет нам указать все наши потенциальные параметры, а затем для каждой кратности перекрестной проверки выбирает случайное подмножество параметров для использования в текущей модели. В итоге пользователь может выбрать оптимальный набор параметров и использовать их как приблизительное решение.

Заключительные комментарии

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

Новостная рассылка

Чтобы получать обновления о новых сообщениях в блогах и дополнительном контенте, подпишитесь на мою рассылку.



Дополнительная информация

Курсы глубокого обучения:

НЛП-ориентированные:

Ориентировано на видение:

Важные статьи о нейронных сетях: