Рецепт обучения нейросетей

в 21:26, , рубрики: AI, data engineering, neural networks, python, TensorFlow, искусственный интеллект, машинное обучение, нейросети, нейросеть, переобучение нейросети, рекуррентная нейросеть, свёрточные сети, статистика

Перевод статьи A Recipe for Training Neural Networks от имени автора (Andrej Karpathy). С некоторыми дополнительными ссылками.

Также доступна версия на украинском языке в личном блоге: Рецепт навчання нейрнонних мереж.

Рецепт обучения нейросетей - 1

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

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

1) Нейронные сети это дырявая абстракция

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

>>> your_data = # подставьте свой датасет здесь
>>> model = SuperCrossValidator(SuperDuper.fit, your_data, ResNet50, SGDOptimizer)
# покорите мир здесь

Эти библиотеки и примеры активируют часть нашего мозга которая привычна к стандартным программам - место где чистые API и абстракции часто достижимы. Например, библиотека requests:

>>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
>>> r.status_code
200

Круто! Смелый разработчик взял на себя бремя понимание строк запросов, URL, GET / POST запросов, HTTP соединений и т.д., и во многом скрыл сложность за несколькими строками кода. Это то, с чем мы знакомы и ожидаем. К сожалению, нейронные сети не похожи на это. Они не "готовая" технология, когда вы немного отклонились от обучения классификатора ImageNet. Я пытался указать на это в своей публикации "Да вы должны понимать метод обратного распространения ошибки" ("Yes you should understand backprop"), выбрав метод обратного распространения ошибки и назвав его "дырявой абстракцией", но ситуация, к сожалению, гораздо сложнее. "Обратное распространение ошибки" + "Стохастический градиентный спуск» не делает вашу нейронную сеть магически работающей. Пакетная нормализация не заставляет ее магически сходиться быстрее. Рекуррентные нейронные сети не позволяют магически "вставить" текст. И только потому, что вы можете сформулировать вашу проблему в форме "обучение с подкреплением" не означает, что вы должны это делать. Если вы настаиваете на использовании технологии, не зная как она работает, вы, вероятно, потерпите неудачу. Что подводит меня к…

2) Обучение нейронных сетей ломается молча

Когда вы неправильно написали или настроили код вы часто получаете определенное исключение. Вы передали целое число там где ожидается строка. Функция ожидает только 3 аргумента. Этот импорт неудачный. Тот ключ не существует. Количество элементов в двух списках не ровен. В довесок, часто возможно написать юнит-тесты для определенного функционала.

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

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

Рецепт

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

1. Cтаньте едиными c данными

Первый шаг к обучению нейронных сетей - это вообще не касаться кода нейронной сети, а взамен начать с тщательной проверки ваших данных. Этот шаг критический. Я люблю тратить много времени (измеряется в часах), проверяя тысячи примеров, понимая их распределение и ища закономерности. К счастью, ваш мозг хорошо с этим справляется. Однажды я обнаружил, что данные содержат примеры которые повторяются. В другой раз я обнаружил поврежденные изображения / разметку. Я ищу дисбаланс данных и смещения. Обычно я также обращаю внимание на свой собственный процесс классификации данных, который намекает на виды архитектур которые мы со временем изучим. В качестве примера - достаточно локальных особенностей, или нам нужен глобальный контекст? Сколько существует вариаций и какую форму они принимают? Какая вариация ошибочная и может быть предварительно обработана? Имеет ли значение пространственное расположение или мы хотим его усреднить (с помощью операции average pool)? Насколько важны детали и насколько сильно мы можем позволить себе уменьшить размер изображений? Насколько зашумленная разметка?

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

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

2. Настройте сквозной скелет обучения / оценки + получите простой базис (базовую модель)

Теперь, когда мы поняли наши данные, можем ли мы добраться до нашей чрезвычайно крупномасштабной ASPP FPN ResNet и начать обучение великолепных моделей? Точно нет. Это путь к страданиям. Наш следующий шаг - создать полный скелет обучение + оценка и завоевать доверие к его правильности путем серии экспериментов. На этом этапе лучше выбрать какую-то простую модель, которую невозможно как-то испортить - например линейный классификатор или очень крошечную сверточную сеть. Мы хотим обучать сеть, визуализировать потери, любые другие показатели (например, точность), моделировать прогнозы и проводить ряд экспериментов по отключению частей сети (при этом выдвигать гипотезы как это повлияет на результаты) на всем пути.

Советы и подсказки на этом этапе:

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

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

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

  • проверяйте потери в начале. Убедитесь, что показатель потери начинается с правильного значения. Например, если вы правильно инициализирует свой конечный слой, то у вас должно получиться -log(1 / n_classes) для функции softmax при инициализации. Те же значения по умолчанию можно получить для регрессии L2, потерь Губера и тому подобное.

  • инициализируйте верно. Правильно инициализируйте веса конечного слоя. Например, если вы регрессируете некоторые значения, которые имеют среднее значение 50, тогда инициализируйте окончательное смещение к 50. Если у вас несбалансированный набор данных с соотношением 1:10, установите смещение на своих логитах так, чтобы ваша сеть давала предсказания 0.1 при инициализации. Правильная их установка ускорит сходимость и устранит кривые потерь в виде "хоккейной клюшки", где в первые несколько итераций ваша сеть в основном лишь изучает смещения.

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

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

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

  • проверяйте уменьшения потерь на тренировочной выборке. Думаю, ваш набор данных будет не очень объемным, так как вы работаете с игрушечной моделью. Попробуйте немного увеличить его объем. Значение потерь на тренировочной выборке снизились как следует?

  • визуализируйте непосредственно перед входом нейросети. Однозначно правильное место для визуализации ваших данных находится непосредственно перед вашим y_hat = model (x) (или sess.run в Tensorflow). То есть - вы должны визуализировать именно то, что попадает в вашу сеть, декодируя этот необработанный тензор данных и меток в виде какой-то визуализации. Это единственный "источник истины". Я не могу сосчитать, сколько раз это меня спасало и проявляло проблемы с предварительной обработкой и аугментацией данных.

  • визуализируйте динамику прогнозов. Мне нравится визуализировать прогнозы моделей на фиксированной тестовой партии во время обучения. "Динамика" движения этих прогнозов даст вам невероятно хорошую интуицию о том, как прогрессирует обучение. В основном можно почувствовать, как сеть «борется» за размещение ваших данных, если она как-то колеблется, показывая нестабильность. Очень низкая или очень высокая скорость обучения также легко различимы по величине дрожи.

  • используйте метод обратного распространения ошибки для отслеживания зависимостей. Ваш код для глубокого обучения часто может содержать сложные, векторизованные и трансляционные операции. Достаточно распространенная ошибка, с которой я сталкивался несколько раз, заключается в том, что люди достигают этого неправильно (например, они используют view, а не transpose / permute) и нечаянно смешивают информацию в измерении размера пакета. Удручает тот факт, что ваша сеть, как правило, все равно способна хорошо учиться, потому что она научится игнорировать данные из других примеров. Одним из способов налаживания этой (и других связанных с этим проблем) является установление функции потери как чего-то тривиального, такого как сумма всех выходов примера i, запуск обратного прохода до входного сигнала и обеспечения получения ненулевого градиента только на i-м входе. Ту же стратегию можно использовать, чтобы убедиться, что ваша авторегресивная модель в момент времени t зависит только от 1..t-1. В общем, градиенты дают вам информацию о том, что и от чего зависит в вашей сети, это может быть полезно для отладки.

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

3. Переобучайте

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

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

Несколько советов и подсказок на этом этапе:

  • подбор модели. Чтобы достичь хороших значений потерь обучающей выборки, вы должны выбрать соответствующую архитектуру данных. Когда дело доходит до ее выбора, мой первый совет: Не будьте героем. Я видел много людей, которые стремятся сойти с ума в креативности подбора лего-блоков из набора инструментов нейронных сетей в процессе создания различных экзотических архитектур, которые имеют смысл только для них. На первых этапах проекта всеми силами сопротивляйтесь этому искушению. Я всегда советую людям просто найти наиболее похожую научную работу и скопировать ее простейшую архитектуру, которая обеспечивает хорошие показатели. Например, если вы классифицируете изображения, не будьте героем, а просто скопируйте ResNet-50 для первого запуска. Вы сможете делать что-то более специфическое позже и победить этот пункт.

  • Adam (метод адаптивной оценки моментов) безопасен. На ранних стадиях установления базиса мне нравится использовать Adam со скоростью обучения 3e-4. По моему опыту, Adam гораздо лояльнее к гиперпараметрам, включая плохую скорость обучения. Для сверточных нейросетей хорошо настроенный метод стохастического градиента (SGD) почти всегда немного превосходит Adam, но область оптимальной скорости обучения гораздо более узкая и зависит от задачи. (Примечание. Если вы используете рекуррентные нейросети и связанные с ними модели обработки последовательностей, то чаще используют Adam. Опять же, на начальном этапе своего проекта не будьте героем и соблюдайте самые популярные статьи.)

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

  • не доверяйте коэффициенту уменьшения скорости обучения по умолчанию. Если вы переделываете код с какой-то другой задачи, всегда будьте очень осторожны со снижением скорости обучения. Вы не только хотели бы использовать различные графики снижения скорости обучения для различных проблем, но - что еще хуже - в типовой реализации снижение будет базироваться на текущем номере эпохи, который может широко варьироваться просто в зависимости от размера вашего набора данных. Например, ImageNet замедлится в 10 раз на 30-й эпохе. Если вы не обучаетесь с ImageNet (имеется в виду размер датасета), вы, почти наверняка, этого не хотите. Если вы не будете осторожны, ваш код может тайком сводить вашу скорость обучения к нулю слишком рано, не позволяя вашей модели сходиться. В своей работе я всегда полностью выключаю уровень снижения скорости обучения (использую постоянную скорость обучения) и настраиваю его в самом конце.

4. Регуляризируйте

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

  • получите больше данных. Во-первых, безусловно лучшим способом регуляризирования модели в любом практической среде является добавление большего количества реальных учебных данных. Очень распространенной ошибкой является проведение многих инженерных циклов, пытаясь выжать сок из небольшого набора данных, когда вместо этого можно было собирать больше данных. Насколько мне известно, добавление дополнительных данных является едва ли не единственным гарантированным способом монотонно улучшать производительность хорошо настроенной нейронной сети почти неограниченно долго. Остальные - это ансамбли нейросетей (если вы можете себе позволить), но это ограничивается ~ 5-ю моделями.

  • аугментация данных. Следующим лучшим способом после реальных данных является полу фальшивые данные - попробуйте более агрессивную аугментацию данных.

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

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

  • придерживайтесь контролируемого обучения (обучение с учителем). Не переоценивайте предварительное обучение без присмотра (без учителя). В отличие от того, что рассказывается в той заметке в блоге от 2008 года [не могу понять о каком сообщении тут идет речь], насколько мне известно, нет версий, которые показывают хорошие результаты на современных задачах компьютерного зрения (хотя NLP, кажется, вполне хорошо справляется вместе с BERT и компанией сегодня, вполне вероятно благодаря умышленному характеру текста и высшему соотношению сигнал / шум).

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

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

  • уменьшайте размер партии. Через нормализацию внутри нормы партии меньшие размеры партии несколько соответствуют сильной регуляризации. Это связано с тем, что эмпирическое среднее / стандартное распределение для партии является более приблизительной версией полного среднего / стандартное распределение, поэтому изменение масштаба и смещения "раскачивают" вашу партию больше.

  • отсеивайте. Добавьте отсеивания. Используйте dropout2d (пространственное отсеивания) для сверточных сетей. Используйте это умеренно / осторожно, поскольку, кажется, отсеивания нехорошо работает при нормализации партии.

  • уменьшение веса. Увеличьте коэффициент уменьшения веса (эффект забывания).

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

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

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

5. Тюнингуйте

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

  • случайный поиск по сетке. Для одновременной настройки нескольких гиперпараметров может показаться соблазнительным использовать поиск по сетке, чтобы обеспечить охват всех настроек, но имейте в виду, что лучше вместо этого использовать случайный поиск. Интуитивно это связано с тем, что нейронные сети часто гораздо более чувствительны к одним параметрам, чем к другим. В общем, если параметр a важен, но изменение b не имеет эффекта, вы подбираете значение a более продуманно, чем в нескольких фиксированных точках несколько раз.

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

6. Выжмите все соки

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

  • ансамбли. Ансамбли моделей - это почти гарантированный способ получить 2% точности на чем-либо. Если вы не можете позволить себе вычисления во время тестирования, посмотрите на перегонку своего ансамбля в сеть, используя темные знания.

  • оставьте ее тренироваться. Я часто видел людей, которые соблазняются прекратить обучение моделей, когда потеря валидации, кажется, выравнивается. По моему опыту, сети продолжают тренироваться не интуитивно долго. Однажды я случайно покинул тренировку модели во время зимних каникул, и когда вернулся в январе, я увидел результат SOTA (state of the art - "современный уровень").

Вывод

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

Автор: Микола Парфенюк

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js