Код программных продуктов для машинного обучения часто бывает сложным и довольно запутанным. Обнаружение и ликвидация багов в нем — ресурсоемкая задача. Даже простейшие нейросети с прямой связью требуют серьезного подхода к сетевой архитектуре, инициализации весов, оптимизации сети. Небольшая ошибка может привести к появлению неприятных проблем.
Эта статья посвящена алгоритму отладки ваших нейронных сетей.
Skillbox рекомендует: Практический курс Python-разработчик с нуля.
Напоминаем: для всех читателей «Хабра» — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».
Алгоритм состоит из пяти этапов:
- простой старт;
- подтверждение потерь;
- проверка промежуточных результатов и соединений;
- диагностика параметров;
- контролирование работы.
Если вам что-то кажется более интересным, чем остальное, можете сразу переходить к этим разделам.
Простой старт
Нейронную сеть со сложной архитектурой, регуляризацией и планировщиком скорости обучения дебажить сложнее, чем обычную. Мы тут немного хитрим, поскольку сам пункт к отладке имеет опосредованное отношение, но это все же важная рекомендация.
Простой старт заключается в создании упрощенной модели и обучении ее на одном наборе (точке) данных.
Сначала создаем упрощенную модель
Для быстрого старта создаем небольшую сеть с единственным скрытым слоем и проверяем, чтобы все работало корректно. Затем постепенно усложняем модель, проверяя каждый новый аспект ее структуры (дополнительного слоя, параметра и т.п.), и двигаемся дальше.
Обучаем модель на единственном наборе (точке) данных
В качестве быстрой проверки работоспособности вашего проекта можно использовать для обучения одну или две точки данных, чтобы подтвердить, корректно ли работает система. Нейронная сеть должна показать 100% точности обучения и проверки. Если это не так, то либо модель слишком мала, либо у вас уже есть баг.
Даже если все хорошо, подготовьте модель к проходу одной или нескольких эпох прежде, чем идти дальше.
Оценка потерь
Оценка потерь — основной способ уточнить производительность модели. Вам необходимо убедиться, что потеря соответствует задаче, а функции потерь оцениваются по корректной шкале. Если вы используете больше одного типа потерь, то убедитесь в том, что все они одного порядка и правильно масштабированы.
Важно быть внимательным к начальным потерям. Проверьте, насколько близок реальный результат к ожидаемому, если модель стартовала со случайного предположения. В работе Андрея Карпати предлагается следующее: «Убедитесь, что вы получаете результат, ожидаемый при начале работы с небольшим числом параметров. Лучше сразу проверить потерю данных (с установкой степени регуляризации на ноль). К примеру, для CIFAR-10 с классификатором Softmax мы ожидаем, что начальные потери будут 2.302, потому что ожидаемая диффузная вероятность составляет 0,1 для каждого класса (так как существует 10 классов), а потеря Softmax является отрицательной логарифмической вероятностью корректного класса как –ln (0.1) = 2.302».
Для бинарного примера просто делается аналогичный расчет для каждого из классов. Вот, к примеру, данные: 20% 0's и 80% 1's. Ожидаемая начальная потеря составит до –0,2ln (0,5) –0,8ln (0,5) = 0,693147. Если результат больше 1, это может указывать на то, что веса нейросети не сбалансированы должным образом или данные не нормализованы.
Проверяем промежуточные результаты и соединения
Для отладки нейросети необходимо понимать динамику процессов внутри сети и роль отдельных промежуточных слоев, поскольку они связаны. Вот типичные ошибки, с которыми вы можете столкнуться:
- неправильные выражения для обновлений градиента;
- не применяются обновления веса;
- исчезающие или взрывающиеся градиенты (exploding gradients).
Если значения градиента нулевые, это означает, что скорость обучения в оптимизаторе слишком мала, либо то, что вы столкнулись с некорректным выражением для обновления градиента.
Кроме того, необходимо следить за значениями функций активаций, весов и обновлений каждого из слоев. К примеру, величина обновлений параметров (весов и смещений) должна составлять 1-e3.
Существует явление, которое получило название “Dying ReLU” или «проблема исчезающего градиента», когда нейроны ReLU будут выводить ноль после изучения большого отрицательного значения (bias) смещения для его весов. Эти нейроны больше никогда не активируются ни в одном месте данных.
Вы можете использовать проверку градиента для выявления этих ошибок путем аппроксимации градиента с использованием численного подхода. Если он близок к рассчитанным градиентам, то обратное распространение было реализовано правильно. Чтобы создать проверку градиента, ознакомьтесь с этими замечательными ресурсами из CS231 здесь и здесь, а также с уроком Эндрю Ына (Andrew Nga) по этой теме.
Файзан Шейх указывает три главных метода визуализации нейросети:
- Предварительные — простые методы, которые показывают нам общую структуру обученной модели. Они включают вывод форм или фильтров отдельных слоев нейронной сети и параметров в каждом слое.
- Основанные на активации. В них мы расшифровываем активации отдельных нейронов или группы нейронов, чтобы понять их функции.
- Основанные на градиентах. Эти методы имеют тенденцию манипулировать градиентами, которые формируются из прохода вперед и назад при обучении модели (включая карты значимости и карты активации класса).
Есть несколько полезных инструментов для визуализации активаций и соединений отдельных слоев, например, ConX и Tensorboard.
Диагностика параметров
У нейросетей масса параметров, которые взаимодействуют друг с другом, что усложняет оптимизацию. Собственно, этот раздел — предмет активных исследований специалистов, поэтому предложения ниже должны рассматриваться лишь как советы, начальные точки, от которых можно отталкиваться.
Размер пакета (batch size) — если необходимо, чтобы размер пакета был достаточно большим для получения точных оценок градиента ошибки, но достаточно малым, чтобы стохастический градиентный спуск (SGD) мог упорядочить вашу сеть. Небольшие размеры пакетов приведут к быстрому схождению за счет шума в процессе обучения и в дальнейшем — к трудностям оптимизации. Подробнее это описано здесь.
Скорость обучения — слишком низкая приведет к медленной конвергенции или риску застрять в локальных минимумах. В то же время высокая скорость обучения вызовет расхождение оптимизации, поскольку вы рискуете «прыгнуть» через глубокую, но при этом узкую часть функции потери. Попробуйте использовать планирование скорости, чтобы снизить ее в процессе обучения нейросети. В курсе CS231n есть большой раздел, посвященный этой проблеме.
Gradient clipping? — обрезка градиентов параметров во время обратного распространения по максимальному значению или предельной норме. Полезно для решения проблем с любыми взрывающимися градиентами, с которыми вы можете столкнуться в третьем пункте.
Пакетная нормализация — используется для нормализации входных данных каждого слоя, что позволяет решить проблему внутреннего ковариатного сдвига. Если вы используете Dropout и Batch Norma вместе, ознакомьтесь с этой статьей.
Стохастический градиентный спуск (SGD) — существует несколько разновидностей SGD, которые используют импульс, адаптивные скорости обучения и метод Нестерова. При этом ни у одной из них нет явного преимущества как по эффективности обучения, так и по обобщению (подробности здесь).
Регуляризация — имеет решающее значение для построения обобщаемой модели, поскольку добавляет штраф за сложность модели или экстремальные значения параметров. Это способ снизить дисперсию модели без существенного увеличения ее смещения. Более подробная информация — здесь.
Чтобы самому все оценить, необходимо отключить регуляризацию и проверить градиент потери данных самостоятельно.
Выпадение — еще один метод упорядочения вашей сети для предотвращения перегрузки. Во время обучения выпадение осуществляется только поддержанием активности нейрона с некоторой вероятностью p (гиперпараметр) или установкой его на ноль в обратном случае. В результате сеть должна использовать другое подмножество параметров для каждой обучающей партии, что уменьшает изменения определенных параметров, которые становятся доминирующими.
Важно: если вы используете как выпадение, так и пакетную нормализацию, будьте осторожны с порядком этих операций или даже с их совместным использованием. Все это еще активно обсуждается и дополняется. Вот две важных дискуссии по этой теме на Stackoverflow и Arxiv.
Контроль работы
Речь идет о документировании рабочих процессов и экспериментов. Если ничего не документировать, можно забыть, например, какая используется скорость обучения или вес классов. Благодаря контролю можно без проблем просматривать и воспроизводить предыдущие эксперименты. Это позволяет снизить количество дублирующихся экспериментов.
Правда, ручное документирование может стать сложной задачей в случае большого объема работ. Здесь приходят на помощь такие инструменты, как Comet.ml, помогающие автоматически логировать наборы данных, изменения кода, историю экспериментов и производственные модели, включая ключевые сведения о вашей модели (гиперпараметры, показатели производительности модели и сведения об окружении).
Нейронная сеть может быть весьма чувствительной к небольшим изменениям, а это приведет к падению производительности модели. Отслеживание и документирование работы — первый шаг, который стоит предпринять для стандартизации среды и моделирования.
Надеюсь, что этот пост сможет стать отправной точкой, с которой вы начнете отладку вашей нейросети.
Skillbox рекомендует:
- Двухлетний практический курс «Я — веб-разработчик PRO».
- Онлайн-курс «С#-разработчик с 0».
- Практический годовой курс «PHP-разработчик с 0 до PRO».