Рецепт приготовления нейронных сетей

Года три назад в своих Записках я опубликовал историю из научно-популярной лекции «ШАМАНСТВО» В АНАЛИЗЕ ДАННЫХ доцента ВМК МГУ имени М.В. Ломоносова, д.ф.-м.н. А.Г. Дьяконова. В ней внятно объясняется слово «шаманство» по отношении к обработке больших данных и необходимость наличия у исследователя некоторого эмпирического опыта, а не только знания строгостей математики. Вопрос соотношения детерминизма и хаоса в любых природных процессах волновал меня ещё со студенческой скамьи, а что-бы преодалеть хаос в нейронных сетях и заставить их работать должным образом творец должен их одухотворить.

С тех пор много воды утекло и накопился некоторый опыт по поводу эксплуатации и приготовления нейронных сетей, а на глаза случайно попалось эссе A Recipe for Training Neural Networks Andrej Karpathy, мысли которого с некоторыми дополнениями и комментариями созвучны моим, а выпускница магистратуры пожаловалась на низкую вероятность прогноза дефектов керамических изоляторов высоковольтных линий электропередач и всё сложилось в кучку. Так и появилась эта записка с рецептом практического приготовления нейронных сетей. Начинаем… Вперёд и вниз ↓

Есть пара фактов, которые подвигли написать этот рецепт.

1) Обучение нейронной сети — довольно слабая абстракция

Сегодня утверждается, что начать обучать нейронные сети несложно. Многочисленные библиотеки и фреймворки гордятся тем, что показывают 30-строчные чудо-фрагменты, которые решают все ваши проблемы с данными, создавая ложное впечатление работы по принципу plug and play. На многочисленных дорогостоящих курсах подготовки Data Scientists (Исследователи данных) за полгода, доверчивостью курсантов которых остаётся только удивляться, довольно часто встречаются такие вещи, как:

>>> 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

О, как круто! ВЕЛИКОЛЕПНЫЙ PROGRAMMER взял на себя бремя понимания строки запросов, URL-адресов, запросов GET/POST, HTTP-соединений и т.д. И в значительной степени скрыл всю сложность проблем за несколькими строчками кода. Это то, с чем все знакомы и чего от них ожидают. К сожалению, нейронные сети это совсем не то. Стоит только немного отклонится от обучения классификатора ImageNet, вы поймёте, что нейронная сеть есть не «off-the-shelf» (англ., достать с полки). Это великолепно показано в статье «Yes you should understand backprop», «Да, вы должны понимать, что такое обратное распространение» (англ.), где обсуждается обратное распространение и эта концепция названа «худой абстракцией», ведь, к сожалению, ситуация гораздо более сложная и ужасная. Backprop (метод обратного распространения ошибки) + SGD ни каким волшебным образом не заставят вашу сеть работать. Норма пакета не может волшебным образом ускорить сходимость. RNN не позволяют волшебным образом «вставлять» текст. И то, что вы можете сформулировать свою проблему как RL (обучение с подкреплением), не означает, что следует это делать. Если вы настаиваете на использовании технологии, не понимая, как она работает, то, скорее всего, потерпите неудачу. И это привело меня к следующей мысли…

2) Молчаливое обучение нейронной сети ведёт к неудаче

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

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

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

Рецепт

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

1. Вникните в свои данные

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

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

2. Настройте конвейер сквозного обучения/оценки + получите ключевые показатели

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

Вот советы и хитрости для этого этапа:

  • исключите случайность порождающую процесс обучения. Всегда используйте фиксированное случайное начальное число для гарантии, что при повторном запуске кода вы получите тот же результат. Это устраняет вариативность и поможет вам оставаться в здравом уме.
  • упрощайте. Обязательно отключите все ненужные изящности. Например, на этом этапе обязательно исключить любую генерацию данных. Генерация данных — это стратегия регуляризации, которую мы можем использовать позже, но сейчас это просто еще одна возможность внести какую-то глупую ошибку.
  • добавьте значащие цифры к вашей оценке. При нанесении на график потерь при тестировании проведите оценку по всему (наибольшему) набору тестов. Не следует просто отображать потери при выборочном тестировании, а затем полагаться на их сглаживание в TensorFlow. Мы стремимся к правильности и очень готовы тратить время на то, чтобы оставаться в здравом уме.
  • проверить потерю @init. Убедитесь, что ваши потери начинаются с правильной суммы потерь. Например. если вы правильно инициализируете свой последний слой, то должны измерить -\log (1/n_{classes}) на softmax при инициализации. Те же значения по умолчанию могут быть получены для регрессии L2, потерь Хьюбера и т.д.
  • хорошая инициализация. Правильно инициализируйте веса последнего слоя. Например. если вы регрессируете некоторые значения со средним значением 50, инициализируйте окончательное смещение до 50. Если у вас есть несбалансированный набор данных с соотношением 1:10 положительных:отрицательных чисел, установите смещение в логитах так, чтобы сеть предсказывала вероятность 0,1 при инициализации. Такая правильная установка ускорит сходимость и устранит кривые потерь «хоккейной клюшки», когда на первых нескольких итерациях сеть в основном просто ищет смещение.
  • используйте свою человечий опыт. Следите за другими показателями, кроме потерь, которые можно интерпретировать и проверить (например, точность). По возможности оценивайте собственную (человечью) точность и сравнивайте с ней. В качестве альтернативы, аннотируйте тестовые данные дважды и для каждого примера рассматривайте одну аннотацию как прогноз, а вторую как главную истину.
  • базовый отсчёт должен быть независимым от входа. Найдите базовый отсчёт, не зависимый от входа, например, проще всего просто установить все ваши входы на ноль, и это должно работать хуже, чем при фактическом подключении реальных данных. Имеет ли место такой факт? Научилась ли ваша модель вообще извлекать какую-либо ценную информацию из входных данных?
  • сделайте частичное переобучение. Переобучите одну партию всего нескольких примеров (например, всего два). Для этого увеличиваем избыточность нашей модели (например, добавляем нового слоя или фильтра) и проверяем, можно ли достичь минимума потерь (например, нуля). Мне нравится показывать на одном и том же графике метку вместе с прогнозом и следить за тем, как они идеально совпадали, когда достигается минимум потерь. Если этого не получается, то где-то кроется ошибка, и к следующему этапу переходить нельзя.
  • убедитесь в уменьшение потерь при обучении. Надеемся, что к этому этапу вы не можете полностью соответствовать своему набору данных, потому как работаете с игрушечной моделью. Попробуйте немного увеличить её сложность. Ваши потери в тренировках снизились? — так, как должны!
  • визуализируйте всё прямо перед входом в сеть. Однозначно правильное место для визуализации ваших данных — непосредственно перед y_hat = model(x) (или sessions.run в TensorFlow). То есть — вы хотите показать именно то, что входит в вашу сеть, декодируя этот необработанный тензор данных и меток визуализацией. Это единственный «источник истины». Я не могу посчитать, сколько раз это спасало меня и выявляло проблемы с предварительной обработкой и увеличением количества данных.
  • визуализируйте динамику прогнозов. Мне нравится визуализировать прогнозы модели на фиксированном наборе тестов при обучении. Динамика изменений этих прогнозов даст вам невероятно хорошую подсказку относительно того, куда и как продвигается тренировка. Часто можно почувствовать, что сеть «борется» за соответствие вашим данным, если она каким-то образом слишком сильно колеблется, обнаруживая нестабильность. Очень низкие или очень высокие скорости обучения также легко заметны по величине джиттер.
  • используйте метод обратного распространения ошибки для построения графиков зависимостей. Ваш код глубокого обучения часто будет содержать сложные, векторизованные и транслируемые операции. Относительно распространенная ошибка, с которой я сталкивался несколько раз, заключается в том, что люди ошибаются (например, где-то используется view вместо transpose/permute) и непреднамеренно смешивают информацию по пакетному измерению. Удручает тот факт, что ваша сеть, как правило, будет нормально тренироваться, потому что она научится игнорировать данные из других примеров. Один из способов отладки этой (и других похожих ошибок) — установить для потерь что-то тривиальное, например, сумму всех выходных данных примера i, выполнить обратный проход до самого входа и убедиться, что вы получаете не-Нулевой градиент только на i-м входе. Эту же стратегию можно использовать, например, для проверки модели авторегрессии в момент времени t, зависящей только от 1..t-1. В более общем плане градиенты дают вам информацию о том, что от чего зависит в вашей сети, и что может быть полезно для отладки.
  • обобщайте частные случай. Это немного больше общего совета по кодированию, но я часто видел, как люди создают ошибки, когда они откусывают больше, чем могут проглотить, создавая относительно общие функции с нуля. Мне нравится писать очень конкретную функцию для того, что я делаю прямо сейчас, заставлять это работать, а затем обобщать это позже, чтобы получить тот же результат. Часто это относится к векторизованному коду, где я почти всегда сначала записываю полностью зацикленную версию и только затем преобразую ее в векторизованный код по одному циклу за проход.

3. Переобучение

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

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

Несколько советов и приемов для этого этапа:

  • подбирайте модели. Чтобы добиться хороших результатов при обучении, вам нужно выбрать подходящую архитектуру для данных. Когда дело доходит до выбора, мой совет №1: не геройствуйте. Я видел многих, которые, стремясь сойти с ума и проявить творческий подход, складывают блоки lego из набора инструментов нейронных сетей в различные экзотические архитектуры, имеющие для них смысл. Сильно старайтесь избежать этого искушения на ранних этапах своего проекта. Я всегда советую людям просто найти наиболее подходящую статью и скопировать простейшую архитектуру, которая обеспечивает хорошую производительность. Например. если вы классифицируете изображения, не будьте героем и просто скопируйте и вставьте ResNet-50 для первого запуска. Позже вы можете сделать что-то более индивидуальное и превзойти скопированный чужой вариант.
  • оптимизатор Adam в безопасности. На ранних этапах установки базовых показателей мне нравится использовать оптимизатор Adam со скоростью обучения 3e^{-4}. По моему опыту, Adam гораздо более снисходительно относится к гиперпараметрам, включая плохую скорость обучения. Для ConvNets хорошо настроенный SGD почти всегда немного превосходит Adam, но оптимальная область скорости обучения намного более узкая и специфичная для конкретной задачи. (Примечание: если вы используете RNN и связанные модели последовательностей, чаще всего используется оптимизатор Adam. На начальном этапе вашего проекта, опять же, не будьте героем и следуйте тому, что уже рассказано в наиболее внятных статьях.)
  • усложнять только по одному. Если есть несколько сигналов для подключения к классификатору, то я бы посоветовал подключать их по одному и каждый раз обеспечивать ожидаемый прирост производительности. Не выбрасывайте с самого начала свою модель в мусорное ведро. Есть и другие способы усложнения — например, можно попробовать сначала вставить изображения меньшего размера, а затем увеличить их и т.д.
  • не доверяйте значениям по умолчанию для снижения скорости обучения. Если вы перенаправляете код из какой-то другой области, всегда будьте очень осторожны со снижением скорости обучения. Вы не только захотите использовать разные графики сходимости для разных задач, но, что еще хуже, в типичной реализации расписание будет основано на текущем номере эпохи, который может широко варьироваться в зависимости от размера вашего набора данных. Например, ImageNet расходится на 10 к 30-й эпохе. Если вы не тренируете ImageNet, то почти наверняка не захотите этого. Если вы не будете осторожны, код может не заметно и слишком рано снизить скорость обучения до нуля, не позволяя модели сходиться. В своей собственной работе я всегда полностью исключаю падение скорости обучения (использую постоянный LR) и полностью настраиваю его в самом конце.

4. Упорядочить

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

  • найдите больше данных. Во-первых, безусловно, лучший и предпочтительный способ упорядочить модель в любых практических условиях — это добавить больше реальных обучающих данных. Очень распространенная ошибка — повторять множество циклов, пытаясь выжать сок из небольшого набора данных, когда вместо этого надо просто собрать больше данных. Насколько мне известно, добавление дополнительных данных — это практически единственный гарантированный способ монотонно улучшить производительность хорошо настроенной нейронной сети практически на неопределенный срок. Ансамбль будет другой (если вы сможете себе это позволить), но это максимум после ~5 моделей.
  • пополнение данных. Следующее, что лучше настоящих данных — это наполовину искусственные, сгенерированные, данные, попробуйте более агрессивное способы увеличения набора данных.
  • творческое приращение. Если наполовину искусственные данные этого не делают, то полностью искусственные данные тоже могут что-то сделать. Люди находят творческие способы расширения наборов данных; Например, рандомизация предметной области, использование моделирования, умные гибриды, такие как вставка (потенциально смоделированных) данных в сцены или даже GAN (генеративно-состязательная сеть).
  • предварительный тренинг. Использование предварительно обученной сети редко когда может повредить, даже если у вас достаточно данных.
  • придерживайтесь обучения с учителем. Не переживайте из-за предтренинга без присмотра.
  • уменьшайте размерность входа. Удалите функции, которые могут содержать ложный сигнал. Любой добавленный ложный вход — это просто еще одна возможность переобучения при не большом наборе данных. Точно так же, если низкоуровневые детали не имеют большого значения, попробуйте ввести изображение меньшего размера.
  • уменьшайте размер модели. Во многих случаях вы можете использовать ограничения знания предметной области в сети, чтобы уменьшить ее размер. В качестве примера, раньше было модно использовать полностью подключенные уровни в верхней части магистралей для ImageNet, но с тех пор они были заменены простым средним пулом, устраняющим массу параметров в процессе.
  • уменьшите размер пакета для нормализации. При пакетной нормализации нормы пакетов меньших размеров в некоторой степени соответствуют более сильной регуляризации. Это связано с тем, что эмпирическое среднее/стандартное значение пакета являются более приблизительными версиями полного среднего/стандартного значения, поэтому большие масштабы и смещения «раскачивают» ваши пакеты.
  • отбросьте. Добавить отсев. Используйте dropout2d (пространственное исключение) для ConvNets (свёрточной нейронной сети). Используйте это умеренно и осторожно, потому что отсев, похоже, не очень хорошо сочетается с пакетной нормализацией.
  • снижение весов. Увеличьте штраф за снижение веса.
  • ранняя остановка. Прекратите обучение на основе измеренных вами потерь при проверке, чтобы ухватить свою модель за хвост прежде, чем она вот-вот начнёт переобучаться.
  • попробуйте модель размером побольше. Я упоминаю об этом в последнюю очередь и только после ранней остановки, но я несколько раз обнаруживал в прошлом, что более крупные модели, конечно, в конечном итоге будут намного лучше подходить, однако, их производительность при «ранней остановке» часто может быть намного лучше, чем у моделей меньшего размера.

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

5. Настройте

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

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

6. Давите сок до конца

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

  • ансамбли. Модельные ансамбли — это почти гарантированный способ повысить точность на 2% на любом предмете. Если вы не можете позволить себе вычисления во время тестирования, попробуйте преобразовать свой ансамбль в сеть, используя темные знания.
  • прекращай обучение. Я часто видел, как у людей возникает соблазн прекратить обучение модели, когда кажется, что потеря валидации выравнивается. По моему практическому опыту, сети тренируются долго. Однажды я случайно оставил обучение модели на зимние каникулы, а когда вернулся в середине января, это была SOTA.

Заключение

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

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

Мотиватор творческого вдохновения A Recipe for Training Neural Networks

Print Friendly, PDF & Email

CC BY-NC 4.0 Рецепт приготовления нейронных сетей, опубликовано К ВВ, лицензия — Creative Commons Attribution-NonCommercial 4.0 International.


Респект и уважуха

Добавить комментарий