e
В прошлый раз мы обсудили историю возникновения свёрточных архитектур, а также узнали об их устройстве и широких возможностях применения. В течение следующих двух лекций мы поговорим об особенностях обучения нейросетей и разберёмся, как правильно настраивать параметры, выбирать функцию активации, подготавливать данные и добиваться успешных результатов.
Предыдущие лекции:
Лекция 1. Введение
Лекция 2. Классификация изображений
Лекция 3. Функция потерь и оптимизация
Лекция 4. Введение в нейронные сети
Лекция 5. Свёрточные нейронные сети
Обучение нейросети — непредсказуемый и захватывающий процесс, который, однако, требует тщательной подготовки. В целом его можно разделить на три основных этапа:
В этой лекции мы обсудим некоторые детали первых двух пунктов. Если вы уже знакомы со всеми понятиями и имеете опыт работы с нейросетями, рекомендуем нашу статью с полезными советами по обучению моделей.
Обучение нейросети — непредсказуемый и захватывающий процесс, который, однако, требует тщательной подготовки. В целом его можно разделить на три основных этапа:
Сюда входят: выбор функции активации, предварительная обработка данных, инициализация весов, регуляризация, градиентная проверка.
Отслеживание процесса обучения, оптимизация и обновление гиперпараметров.
Использование ансамблевых методов.
В этой лекции мы обсудим некоторые детали первых двух пунктов. Если вы уже знакомы со всеми понятиями и имеете опыт работы с нейросетями, рекомендуем нашу статью с полезными советами по обучению моделей.
Ранее мы выяснили, что в каждый слой нейросети поступают входные данные. Они умножаются на веса полносвязного или свёрточного слоя, а результат передаётся в функцию активации или нелинейность. Мы также говорили о сигмоиде и ReLU, которые часто используются в качестве таких функций. Но список возможных вариантов не ограничивается только ими. Какой же следует выбирать?
Рассмотрим наиболее популярные функции активации и обсудим их преимущества и недостатки.
Функция сигмоиды преобразовывает поступающие в неё значения в вещественный диапазон [0, 1]. То есть, если входные данные окажутся большими положительными значениями, то после преобразования они будут равны примерно единице, а отрицательные числа станут близки к нулю. Это довольно популярная функция, которую можно интерпретировать как частоту возбуждения нейрона.
Но если внимательнее присмотреться к сигмоиде, можно заметить несколько проблем.
1. Насыщенные нейроны могут «убить» градиент. Возьмём сигмоидный узел вычислительного графа и передадим в него входные данные X. Когда мы делаем обратный проход, восходящий градиент равен dL/d𝜎, а локальный — dL/d𝜎 * d𝜎/dx.
Что же произойдёт, если X будет равен −10? Градиент станет нулевым, поскольку все большие отрицательные значения находятся на прямом участке сигмоидной функции. Таким образом, во все последующие узлы будут передаваться нулевые производные — это и «убивает» градиентный поток.
А если X = 0? В этом случае всё будет в порядке, как и для других близких к нулю значений. А вот при X = 10 градиент снова обнулится. Поэтому сигмоида не работает для слишком высоких положительных или отрицательных данных.
2. Выходные значения сигмоиды не центрированы нулем. Пусть исходные данные полностью положительны — что тогда станет с градиентами во время обратного распространения? Они все будут либо положительными, либо отрицательными (в зависимости от градиента f). Это приведёт к тому, что все веса при обновлении также будут либо увеличены, либо уменьшены, и градиентный поток станет зигзагообразным.
Поэтому следует изначально подготавливать данные таким образом, чтобы их средним значением являлся ноль.
3. Функцию exp() достаточно дорого считать. Это не такая существенная проблема, поскольку скалярные произведения во время свёртки тратят гораздо больше вычислительных мощностей, но в сравнении с остальными функциями активации её тоже можно отметить.
Тангенс очень похож на сигмоиду, но обладает двумя существенными отличиями: он преобразует данные в диапазон [-1, 1] и имеет нулевое центрирование, что исключает вторую проблему сигмоиды. Значения градиента при обратном распространении по-прежнему могут обнуляться, тем не менее, использование тангенса обычно более предпочтительно.
ReLU или Rectified Linear Unit стала довольно популярной в последние годы. Она вычисляет функцию f(x) = max(0,x), то есть просто выдаёт значения «ноль» и «не ноль». Это решает проблему обнуления градиента для положительных чисел. Кроме того, ReLU очень просто вычисляется: примерно в шесть раз быстрее сигмоиды и тангенса. Однако, в ней снова отсутствует нулевое центрирование.
Другой очевидный недостаток — градиент по-прежнему «умирает» при отрицательных входных данных. Это может привести к тому, что половина нейронов будет неактивна и не сможет обновляться.
Проблему можно попробовать решить, задав более низкую скорость обучения и подобрав другие весовые коэффициенты. Или использовать модификации ReLU.
Отличие этой функции в том, что она имеет небольшой наклон в левой полуплоскости — значит, при отрицательных входных данных градиент не будет нулевым.
При этом функцию по-прежнему легко вычислить. То есть, она решает практически все перечисленные проблемы. Одной из её разновидностей является PReLU, которая выглядит как f(x) = max(𝛼x, x).
Эта функция похожа на leaky ReLU и обладает всеми её преимуществами, но включает в себя экспоненту, что делает её вычисление дороже. Её стоит использовать в тех случаях, когда вам важна устойчивость к шумовым данным.
Maxout выбирает максимальную сумму из двух наборов весов, умноженных на исходные данные с учётом смещения. Тем самым он обобщает ReLU и leaky ReLU, не обнуляя градиент. Но, как можно догадаться по виду функции, maxout требует удвоения параметров и нейронов.
Подводя итог: используйте ReLU, можете попробовать взять leaky ReLU/Maxout/ELU. На тангенс и сигмоиду лучше не рассчитывать.
Существует три наиболее распространённых способа предварительной обработки данных. Будем полагать, что данные X — это матрица размером [NxD].
1. Вычитание среднего. Чтобы избежать смещения данных и сделать их симметричными относительно нуля, из каждого элемента вычитается среднее значение. Это помогает предотвратить ситуации, когда все исходные числа оказываются только положительными или отрицательными. В NumPy операция выглядит как X -= np.mean(X, axis = 0). В частности, при обработке изображений можно вычитать одно значение из всех пикселей (например, X -= np.mean(X)) или делать это отдельно по каждому из трёх цветовых каналов.
2. Нормализация. Изменение данных таким образом, чтобы они все были приблизительно одного масштаба. Один из вариантов — разделить каждое измерение на его стандартное отклонение: (X /= np.std(X, axis = 0)). Другой вариант — нормализовать каждое значение так, чтобы min и max были равны -1 и 1 соответственно. Нормализацию следует применять только в том случае, если исходные данные имеют разные форматы или единицы измерения. У изображений значения пикселей не выходят за пределы диапазона от 0 до 255, поэтому для них нет необходимости выполнять нормализацию.
Итак, мы построили архитектуру нейронной сети и подготовили данные. Прежде чем начать обучение, необходимо инициализировать параметры (веса).
Как не нужно делать: задавать веса нулевыми. Это приведёт к тому, что абсолютно все нейроны будут вести себя одинаково — совсем не то, что мы хотим получить. Нейросеть должна обучаться разным признакам.
Небольшие случайные величины. Более удачный вариант — присвоить весам маленькие значения. Тогда все нейроны будут уникальными и в процессе обучения постепенно интегрируются в различные части сети. Реализация может выглядеть так: W = 0.01* np.random.randn(D,H). Метод randn(n) формирует массив размера n х n, элементами которого являются случайные величины, распределённые по нормальному закону с математическим ожиданием 0 и среднеквадратичным отклонением 1 (распределение Гаусса). Недостаток этого способа в том, что он неплохо работает для небольших архитектур, но гораздо хуже справляется с громоздкими нейросетями.
Калибровка с помощью 1/sqrt(n). Проблема вышеупомянутого метода состоит в том, что дисперсия случайных величин растёт с числом нейронов. Чтобы избежать этого, можно масштабировать веса, поделив их на корень из количества входов: w = np.random.randn(n) / sqrt(n). Это гарантирует, что все нейроны сети изначально будут иметь примерно одинаковое выходное распределение.
Также можно использовать вариант w = np.random.randn(n) * sqrt(2.0/n), который был предложен в одном из исследований. Он приводит к наиболее удачному распределению нейронов, поэтому на практике рекомендуем использовать именно его.
Метод, известный также как batch normalization, решает множество проблем при инициализации, заставляя все активации (выводы) принимать единичное гауссово распределение в начале обучения.
Как же это работает? Рассмотрим небольшое число выводов нейронов на каком-либо слое. Пусть в функцию активации поступает вектор размерности d: x = (x(1),…,x(d)). Нормализуем его по каждой из размерностей:
Где E(x) — математическое ожидание, D(x) — дисперсия, которые вычисляются по всей обучающей выборке. Таким образом, вместо инициализации весов можно использовать эту простую дифференцируемую функцию и получить нормальное распределение на каждом слое.
Пакетная нормализация обычно применяется между слоями (полносвязными или свёрточными) и функциями активации.
Это очень полезный алгоритм, который часто применяется в современном машинном обучении. Нейросети, использующие batch normalization, значительно более устойчивы к плохой инициализации.
Мы выбрали архитектуру сети, подготовили данные, инициализировали веса и нормализовали их. Пришло время начать обучение! Вернее, попытаться начать. Самый простой способ проверить, что нейросеть готова обучаться — взять совсем немного данных и попробовать переобучить её на них, то есть, добиться очень хорошей точности и малых потерь. Для этого мы убираем регуляризацию, устанавливаем необходимое количество эпох обучения и вычисляем потери (они должны уменьшаться).
Напомним, что эпоха — один «проход» данных через нейросеть, после которого обновляются веса с помощью градиентного спуска. Упрощённо это выглядит следующим образом:
Теперь можно запустить настоящий процесс: взять все данные, добавить регуляризацию и установить начальную скорость обучения. К сожалению, просто выполнить код и оставить нейросеть на пару часов пока не получится. Необходимо убедиться, что потери постепенно уменьшаются после каждой эпохи. Если этого не происходит, скорее всего, скорость обучения слишком маленькая. Стремительный рост потерь наоборот говорит о слишком высоком значении learning rate.
Как мы могли убедиться, обучение нейронных сетей включает множество этапов настройки гиперпараметров. Наиболее распространенными являются:
— начальная скорость обучения;
— график затухания скорости обучения (например, постоянная затухания);
— сила регуляризации.
При желании можно даже модернизировать архитектуру сети, если вам кажется, что она выбрана не слишком удачно.
Learning rate — одно из самых важных значений. Попробуйте поэкспериментировать с различными вариантами и построить графики потерь. На рисунке ниже слева показаны эффекты, возникающие при изменении скорости обучения, а справа — типичная функция потерь при обучении небольшой нейросети на наборе данных CIFAR-10.
Вторая важная вещь, которую следует отслеживать — точность сети на обучающих и оценочных данных. Если поместить их на один график, то можно оценить наличие переобучения, о чём свидетельствуют расходящиеся кривые.
Для поиска оптимальных гиперпараметров стоит написать отдельную функцию, которая будет самостоятельно подбирать их и выполнять оптимизацию. При этом лучше использовать не равномерный поиск (известный также как «перебор по сетке»), а случайный — он чаще всего даёт гораздо более удачные результаты.
Кратко изложим всё, что мы узнали про обучение нейросетей из сегодняшней лекции:
— используйте функцию активации ReLU;
— выполняйте предварительную обработку данных (для изображений: вычитайте среднее значение);
— масштабируйте веса при инициализации;
— применяйте пакетную нормализацию;
— следите за процессом обучения;
— оптимизируйте гиперпараметры с помощью случайного поиска.
⌘⌘⌘
На следующей лекции мы расскажем ещё о нескольких важных шагах обучения, узнаем про ансамблевые методы и разберёмся, как выполнять передачу обучения (transfer learning) и точную настройку (fine tuning). Пробовали ли вы самостоятельно обучать нейросети? Были ли у вас свои хитрости, или вы полагались на установки по умолчанию? Делитесь с нами успехами и не забывайте задавать вопросы, если что-то непонятно.
Следующие лекции (список будет дополняться по мере появления материалов):
Лекция 7. Обучение нейросетей, часть 2
Лекция 8. ПО для глубокого обучения
Лекция 9. Архитектуры CNN
Лекция 10. Рекуррентные нейронные сети
С оригинальной лекцией можно ознакомиться на YouTube.
Один способ позволяет отслеживать каждую деталь изделия, другой – сфокусироваться на брендинге и маркетинге. Разбираемся…
Рассказываем, как помогли российскому разработчику систем аналитики мигрировать в частное облако и сократить затраты на…
Рассказываем, как создать сайт-визитку и какой должна быть структура. Внутри — инструкция, которая поможет предпринимателям.
Онлайн-карты — хорошая площадка для привлечения аудитории в бизнес. Рассказываем об инструментах продвижения в геосервисах.
Можно стартовать с багажом знаний из найма или практически без опыта. Рассказываем, что нужно делать:…
Что такое наука о данных, чем занимается Data Scientist и можно ли обучиться этой специальности…