Let's cook: как приготовить ИИ до золотистой нейронной корочки
Сегодня на нашей кухне необычное блюдо под названием «обучение нейросети». В этом рецепте эксперт в области искусственного интеллекта Андрей Карпатый рассказывает, как избежать часто допускаемых ошибок и сделать процесс обучения более эффективным и понятным.
Мы не будем перечислять распространённые промахи и углубляться в их обсуждение. Вместо этого копнём глубже и поговорим о том, как можно избежать ошибок (или очень быстро их исправить). Основная идея — придерживаться определённой методологии обучения нейросети, которая явно не описывается в профессиональной литературе. Отправными точками в этом подходе являются два важных наблюдения:
1. Нейросети — это чёрные ящики
Вам может показаться, что обучать нейросети легко. Многочисленные библиотеки и фреймворки предлагают сделать это с помощью 30 чудо-строк кода, которые решают все ваши проблемы и создают ошибочное впечатление «обучить нейросеть = включил и играй». Это похоже на:
1 2 3 |
>>> your_data = # укажите ваш замечательный датасет >>> model = SuperCrossValidator(SuperDuper.fit, your_data, ResNet50, SGDOptimizer) # покорение мира -> здесь |
Эти библиотеки активируют ту часть нашего мозга, которая знакома со стандартным ПО — чистыми API и абстракциями. Сделаем запрос для демонстрации:
1 2 3 |
>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass')) >>> r.status_code 200 |
Вот это да! Смелый программист взял на себя бремя понять, как работают запросы GET/POST, URL-адреса, HTTP-соединения и прочие сложности; и скрыл всё от вас за парой строк кода. Это то, с чем мы знакомы и чего ожидаем. К сожалению, с нейронными сетями так не работает. Как только вы немного отклонитесь от стандартной процедуры обучения классификатора ImageNet, он уже перестанет быть простой и готовой технологией. Если вы пытаетесь обучить нейросеть, не зная, как она устроена — скорее всего, вы потерпите неудачу. Из этого следует следующий пункт:
2. Ошибки в обучении нейросети бывают незаметны
Когда вы допускаете ошибку в коде, то часто возникает exception. Например, вы указали целое число вместо строки или два аргумента вместо трёх. Помимо этого, можно создавать юнит-тесты для оценки программы.
Но для нейросетей это только начало. Даже с правильным синтаксисом могут быть неверно настроены какие-нибудь параметры, и иногда сложно сказать, какие именно. Число возможных ошибок довольно велико, и их не всегда можно обнаружить на юнит-тестах. Например, вы забыли перевернуть метки, когда поворачивали изображения во время дополнения данных. Или инициализировали веса значениями предварительно обученной контрольной точки, не использовав исходное среднее значение. Или испортили настройки регуляризации, скорости обучения, размера модели и так далее. Во всех этих случаях будет большой удачей, если на ошибку укажет явное исключение, чаще всего процесс обучения просто будет неэффективным.
Чтобы избежать мучений в поиске ошибок, необходимо иметь терпение и внимание к деталям: выводите в консоль всё, что только можно, чтобы убедиться в корректности данных и настроек.
Процесс обучения нейросети строится от простого к сложному. На каждом этапе следует выдвигать гипотезы о том, каким должен быть результат, а затем проверять их экспериментально. Либо можно проводить исследования до тех пор, пока не обнаружится проблема. Попробуем разобраться, что же нужно для успешной работы.
1. Собираем набор данных: станьте с ним единым целым!
Первый шаг в обучении нейросети — тщательная проверка данных без применения кода. Это очень важный этап. Можно проводить целые часы, просматривая тысячи примеров и пытаясь понять, как выделить из них шаблоны. К счастью, наш мозг справляется с этим очень хорошо. Следует также обращать внимание на то, как вы сами при этом классифицируете данные — это поможет определиться с архитектурой будущей сети.
Фактически нейросеть — сжатая скомпилированная версия вашего датасета. Вы можете взглянуть на её ошибочные прогнозы и понять, откуда они могут исходить. Если модель выводит не соответствующий вашим данным прогноз — что-то явно пошло не так.
Как только вы оцените качество данных, хорошей идеей будет написать простой код для поиска, фильтрации и сортировки (например, типы меток, примечания и так далее) и визуализировать их распределение и выбросы. Поиск выбросов всегда помогает обнаружить ошибки в данных на этапе предварительной обработки. Вы можете почерпнуть практические советы в статье про поиск аномалий в данных.
2. Создаём прямой алгоритм обучения с простыми исходными данными
Теперь, когда у нас есть хороший датасет, можем ли мы сразу обратиться к многомасштабной нейросети ASPP FPN ResNet и начать обучение нашей модели? Конечно нет. Так мы будем только страдать, не понимая, как она работает и какие настройки следует применять. Поэтому следующий шаг — создать простой алгоритм обучения и оценки, а также убедиться в его правильности с помощью серии экспериментов. На этом этапе лучше выбрать тривиальную модель, которую вы не сможете сломать — например, линейный классификатор или ConvNet. Мы обучим его, визуализируем потери, точность и прогнозы, а затем выполним серию экспериментов по поиску ошибок.
Советы и рекомендации для этого шага:
— делайте предсказуемую случайность: всегда задавайте random.seed конкретным числом. Это будет гарантировать, что при повторном запуске кода вы получите тот же результат.
— упрощайте: не пользуйтесь дополнительной функциональностью. Например, отключите увеличение данных на этом этапе (оно понадобится позже).
— оценивайте больше данных: при построении графиков тестовых потерь выполняйте оценку по всей тестовой выборке, а не на отдельных партиях.
— проверьте инициализацию: убедитесь, что ваши потери начинаются с корректных значений. Например, для правильной инициализации последнего слоя с помощью softmax следует измерить −log(1/n_classes). Те же значения по умолчанию можно получить для регрессии L2, потерь Хьюбера и т. д.
— проверьте инициализацию ещё раз: установите правильные веса для последнего слоя. Например, если вы выполняете регрессию для значений, которые имеют среднее 50, инициализируйте окончательное смещение равным 50. Если ваш набор данных не сбалансирован и имеет 1:10 положительных и отрицательных значений, установите смещение так, чтобы сеть прогнозировала вероятность 0.1 при инициализации. Правильная настройка ускорит сходимость и поможет устранить потери.
— сравнивайте с человеком: помимо потерь есть и другие показатели, которые можно сравнить с человеческими (например, точность). Оцените свою (человеческую) точность и сравните её с прогнозируемой. В качестве альтернативы можно дважды использовать тестовую выборку: в первый раз в качестве прогноза, а во второй — в качестве истины.
— обучите независимую от исходных данных модель: проще всего это можно сделать с нулевыми входными данными. Сеть при этом должна работать хуже, чем с обычным датасетом. Проверьте, так ли это? Научилась ли ваша модель извлекать какую-либо информацию?
— переобучитесь на небольшой партии: возьмите для этого всего несколько примеров (два-три). Это поможет увеличить ёмкость (например, добавить слои или фильтры) и проверить, что модель достигает наименьших потерь. Попробуйте отобразить на одном графике метку и прогноз и убедитесь, что при достижении минимальных потерь они совпадают.
— проверьте, что потери уменьшаются: на этом этапе ваша «игрушечная» модель будет недообучена. Попробуйте добавить к ней пару слоёв и посмотрите, уменьшатся ли потери (должны).
— визуализируйте данные: делайте это прямо перед тем, как вызвать y_hat = model(x) (или sess.run в TensorFlow). Это поможет избежать многих проблем с предварительной обработкой и дополнением данных.
— визуализируйте динамику прогнозов: она даст вам хорошее представление о том, как проходит обучение. Слишком высокие или низкие показатели будут заметны на графике и помогут определить, происходит ли переобучение.
— используйте обратное распространение ошибки для построения зависимостей: ваш код часто будет содержать сложные векторные операции. Частая проблема — люди допускают ошибку (например, используют view вместо transpose/permute) и случайно смешивают информацию в пакете. При этом, к сожалению, сеть всё ещё будет нормально обучаться, поскольку сможет игнорировать данные из других примеров. Один из способов исправить это — установить тривиальные потери, например, сумму всех входных данных. Затем выполнить обратный проход и убедиться, что ненулевое значение градиента получается только на i-м входе. Градиенты помогают отслеживать зависимости внутри нейросети, и это очень полезно для отладки.
— обобщите частный случай: этот совет больше относится к программированию в целом. Не пытайтесь откусить больше, чем сможете прожевать. Напишите сначала специфичную функцию для того, над чем работаете в конкретный момент, а затем попытайтесь обобщить её и убедиться, что результат получается тот же.
3. Проверяем модель на переобучение
На этом этапе вы должны хорошо разбираться в своём наборе данных и отладить методы обучения и оценки. У нас есть прямой базовый алгоритм, не зависящая от исходных данных модель, достаточная эффективность и представление о том, к каким показателям надо стремиться (к человеческим). Мы готовы начать обучение.
Дальнейший подход состоит из двух этапов: сначала необходимо получить достаточно большую модель, которую можно переобучить (т. е. сосредоточиться на потерях обучения), а затем соответствующим образом регуляризовать её (сосредоточиться на проверочных потерях). Если мы не сможем достичь низких значений ошибок на этом шаге, скорее всего, причина снова будет в неправильной конфигурации или других проблемах.
Советы и рекомендации для этого шага:
— выберите правильную модель: чтобы достичь хороших потерь при обучении, вы должны выбрать подходящую для исходных данных архитектуру. И вот вам совет: не переусердствуйте. Не стоит собирать лего-замки из экзотических архитектур. Лучше возьмите самую простую из тех, что обеспечивают хорошую эффективность. Например, для классификации изображений выберите обычную ResNet-50. Вы всегда сможете усовершенствовать её.
— Adam = безопасность: на ранних этапах можно использовать оптимизатор Adam со скоростью обучения 3e-4. Он гораздо лучше справляется со своей задачей почти с любыми гиперпараметрами. Для свёрточных сетей подойдёт хорошо настроенный SGD (стохастический градиентный спуск), но область оптимальной скорости обучения будет более узкой и специфичной у конкретных моделей. Для RNN (реккурентных нейросетей) тоже рекомендуется использовать Adam.
— усложняйте за раз что-то одно: например, если вы хотите расширить свой классификатор новыми пакетами данных, лучше подключать их по одному за раз. Так вы обеспечите ожидаемое повышение производительности.
— не верьте значениям скорости обучения по умолчанию: если вы взяли чужой код, всегда будьте осторожны с затуханием скорости обучения. Не забывайте, что она может варьироваться в зависимости от задач, количества эпох обучения и размера набора данных. Ваш алгоритм может слишком рано снижать скорость обучения до нуля, не позволяя модели сходиться. Чтобы избежать этого, можно использовать постоянное значение learning rate и настроить его в самом конце.
4. Делаем регуляризацию
В идеале у нас уже должна быть большая модель, хорошо справляющаяся хотя бы с обучающей выборкой. Теперь пришло время регуляризовать её и оценить точность проверки. Вот несколько советов:
— возьмите больше данных: это лучший способ регуляризации. Очень распространённая ошибка — тратить много итераций на небольшой датасет. Добавление данных — это гарантированный способ увеличения эффективности хорошо настроенной нейросети.
— дополните данные: здесь можно использовать различное искажение (поворот, обрезка, отражение и т. д.) исходных данных.
— дополните данные более креативно: люди находят много интересных путей расширения датасетов. Среди них: рандомизация предметной области, моделирование, GAN и многие другие.
— используйте предварительное обучение: это никогда не будет лишним, даже если у вас достаточно данных.
— придерживайтесь обучения с учителем: обучению без учителя до сих пор не удаётся достичь таких же хороших показателей (за исключением некоторых моделей NLP, например, BERT).
— уменьшите число признаков: удалите экземпляры, которые содержат неверные значения. Любые неправильные данные могут привести к переобучению, особенно для небольшого датасета. Например, можно попробовать уменьшить размер изображений, если несущественные детали на них не играют роли.
— уменьшите размер модели: для этого стоит ограничить предметную область. Например, раньше последние слои ImageNet часто делались полносвязными, но не так давно их заменили на простую субдескритизацию. Это помогло избавиться от настройки большого числа параметров.
— уменьшите размер пакетов: из-за нормализации в пределах одного пакета их меньший размер способствует более эффективной регуляризации. Это связано с тем, что эмпирическое/стандартное среднее пакета более приближено к полному/стандартному среднему.
— отсеивайте: добавьте dropout. Используйте dropout2d для свёрточных сетей. Но будьте осторожны, так как dropout не очень хорошо взаимодействует с пакетной нормализацией.
— увеличьте штраф за затухание весов.
— остановитесь раньше, чем нужно: прекратите обучение, основываясь на измеренных тестовых потерях. Это поможет избежать переобучения.
— попробуйте снова использовать большую модель: она может оказаться лучше, чем та, которую вы попытались регуляризовать.
Наконец, чтобы убедиться в эффективности полученной нейросети, визуализируйте веса первого слоя. Если его фильтры выглядят как шум, что-то может быть не так. Аналогичным образом можно искать проблемы с помощью активации внутри сети.
5. Окончательно всё настраиваем
Теперь настало время исследовать широкий спектр архитектур, которые будут достигать низких значений потерь при проверке. Советы:
— подберите параметры по сетке случайным образом: чтобы установить сразу несколько гиперпараметров, вы наверняка захотите применить поиск по сетке для охвата всех настроек. Но лучше всего использовать случайный поиск. Интуитивно это объясняется тем, что нейросети часто гораздо более чувствительны к определённым гиперпараметрам, и нечувствительны к другим.
— оптимизируйте гиперпараметры: для этого существует большое число байесовских оптимизаторов на ваш выбор.
6. Ещё чуть-чуть
Когда вы найдёте наиболее эффективную архитектуру и настроите гиперпараметры, можно использовать ещё несколько приёмов, чтобы улучшить нейросеть по максимуму:
— ансамбли: это гарантированный способ повысить точность как минимум на 2%.
— дайте сети ещё немного пообучаться: даже если вам кажется, что потери стали минимальными. Автор статьи, Андрей Карпатый, однажды случайно оставил модель обучаться на все зимние праздники. Вернувшись, он обнаружил, что она достигла гораздо лучших результатов.
Заключение
Если вы прошли через все вышеупомянутые этапы — поздравляем! Теперь у вас есть понимание того, как работает нейросеть, как устроен набор данных и какие возникают проблемы. Вы можете создать свой алгоритм обучения и оценки и быть уверенным в его точности. Надеемся, что вы готовы проводить множество экспериментов и получить отличные результаты. Удачи!
С оригинальной статьёй можно ознакомиться в блоге Андрея Карпатого.