Привет всем моим энтузиастам глубокого обучения.

Недавно я реализовал модель QANet для системы Q / A с использованием набора данных SQuAD. И столкнулся со многими проблемами при реализации того же самого.

Я не буду объяснять, как я реализовал эту модель.

Но я проведу вас через шаги, которые я выполнил для решения проблем и оптимизации модели.

Основная причина для объяснения самого процесса оптимизации заключается в приведенных ниже причинах.

1. Было доступно много материалов по тому, как реализовать QANet с помощью Pytorch. Но когда я столкнулся с небольшими проблемами при реализации модели, было очень мало материалов для их исправления, и мне потребовалось много дней исследований, чтобы решить проблему, но все это не было собрано в одном месте.

2. Как уменьшить объем памяти модели QANet.

3. Как эффективно снизить размер убытков.

Введение в модель QANet

Модель QANet - это преобразованная версия метода BIDAF (BID, направленного A ttention F low).
Как следует из названия, модели BIDAF были в основном созданы для использования механизма потока внимания не только от контекста к вопросу, но и от вопроса к контексту.

В традиционной системе ответов на вопросы поток внимания использовался только от вопроса к контексту. Но здесь все идет в обоих направлениях.

Этот базовый метод BIDAF первоначально был предложен только для сети RNN (LSTM), но для того, чтобы извлечь выгоду из вычислительных и параллельных вычислительных мощностей трансформатора, тот же метод позже был распространен на трансформаторы.

Многие модели QANet лидировали в рейтинге набора данных SQuAD на официальном сайте Стэнфорда до появления модели BERT, которая в основном обогнала все другие модели.

Я использовал код некоторых дополнительных функций из этих ссылок GITHUB. Но я в основном писал код для части фреймворка модели и других функций с нуля.

  1. Модель BiDAF LSTM
  2. Трансформатор

Теперь переходим к проблемам, с которыми я столкнулся при реализации модели.

Вызов 1

Большинство людей, пытающихся реализовать модель из исследовательской работы, прошли бы через это.

«С чего начать?».

Я бы посоветовал начать писать код для базовой структуры модели с более крупными компонентами, такими как кодировщик модели, многоголовое внимание и т. Д.,

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

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

Вызов 2

Проблема нехватки памяти

После того, как я завершил кодирование, модель была правильно инициализирована с правильными размерами.

Я столкнулся с серьезной проблемой ошибки нехватки памяти во время выполнения модели.

Эта ошибка возникает из-за того, что общий размер выполняемой памяти превышает размер вашей оперативной памяти.
И эти модели становятся довольно большими, когда вы используете большой набор данных, такой как SQuAD 2.0.

Поверьте мне, у меня на моем компьютере было 16 ГБ ОЗУ, но все равно возникала ошибка «нехватка памяти». И даже при использовании графического процессора, предоставленного Google colab, я столкнулся с той же проблемой.

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

Самый простой способ решить эту проблему - уменьшить размер набора данных, что позволяет также уменьшить размер модели.

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

Поэтому я решил не изменять размер набора данных, а вместо этого хотел уменьшить размер обработки.

Но в случае, если вы не уверены, какой процесс занимает больше места в ОЗУ,
самый простой метод, которым я воспользовался, заключался в том, чтобы просто добавить оператор печати после каждого основного компонента в модели и запустить программу.

Откройте диспетчер задач (ctrl + shift + Esc) в Windows и щелкните вкладку производительности, чтобы проверить использование памяти RAM.

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



Решение 1

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

Решение 2: -

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

Для реализации решения я использовал следующие функции:

del и gc.collect ()

Функция del удалит переменную.

gc.collect () освободит память для этой удаленной переменной.

Внимание: -
Не используйте del и gc.collect () внутри модели (т.е. компоненты, которые будут иметь require_grad как true), делая это, это не только освободит значение тензора / переменной, но также освобождает градиент тензора,
в результате чего модель имеет неустойчивый градиентный спуск, а также значительно увеличивает время выполнения более чем на 200%. Я использовал эту функцию здесь, только на этапе обработки данных.

Если вы хотите узнать больше о сборке мусора в Python, загляните здесь

Я буквально целую неделю ломал голову над решением этого вопроса. Поверьте, использование этого избавит вас от боли.

Решение 3: -

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

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

Замена 2D CNN на одномерную разделяемую по глубине CNN уменьшила размер модели.
Мы поговорим об этой одномерной разделяемой по глубине CNN в следующей части, так как это решение также сократило время выполнения и повысило производительность .

После применения всех этих трех решений я смог запустить программу с использованием графического процессора с размером пакета 50 без каких-либо ошибок нехватки памяти.

Вызов 3

Срок исполнения модели был высоким.

Я использовал Google colab для запуска модели с использованием графического процессора с размером пакета 50.

Но на каждую сотню пакетов, даже после использования графического процессора, требовалось почти 10 минут, а существует почти 1200 пакетов с размером каждого пакета, равным 50 для каждой эпохи.

Таким образом, на каждую эпоху уходило почти 2 часа. Google colab обеспечивает только 12 часов работы графического процессора в день.
Так что, даже сохранив эпохи до 8, я не смог запустить модель вовремя.
А также весь ваш день будет потрачен впустую, если у вас возникнут проблемы в коде
, или произойдет сбой, или вы захотите внести изменения в код.

Решение 1

Pytorch предоставляет вам функцию torch.autograd.profiler.profile (), которая помогает профилировать процессы, выполняемые в модели, и сколько времени занимает каждый процесс и сколько раз каждый процесс запускается.

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

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

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

Вы также можете проверить реализацию torch.autograd.profiler.profile () здесь.

Решение 2

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

Поэтому выберите измерение word и char, которое может сократить время выполнения, но в то же время точность не снижается в значительной степени.

Вызов 4

Колебание значения потерь для каждой эпохи во время задней стойки.

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

Даже после увеличения эпох до 12, значения потерь были очень высокими, что было необычно.
а также значение потерь не уменьшалось, так как было более беспорядочно изучать что-либо.

Решение для уменьшения колебаний стоимости убытков: -

Я изменил оптимизаторы Adadelta, которые были указаны в базовом документе BIDAF, и заменил его оптимизатором ADAM
и удалил модуль EMA (Exponential Moving Average). Колебания немного уменьшились, но разница была незначительной.

Поэтому я провел очень долгое исследование, чтобы выяснить основную причину такого поведения.

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

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

Перекрестная потеря энтропии - это комбинация softmax с потерями NLL, поэтому нам нужно просто передать необработанные логиты из полностью подключенного слоя.

После удаления функции softmax потери начинают уменьшаться после каждой эпохи без колебаний .

Уф !!!

Решение для уменьшения огромных потерь: -

После анализа и отладки кода построчно я смог выяснить основную причину такого поведения в программе.

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

И в этом модуле он увеличивает входное значение на квадратный корень из измерения.

Это нормально, если это для слоя внедрения, так как значения для слоя внедрения будут очень низкими. А увеличение его значения только увеличит производительность.

Но когда вы используете ту же функцию в блоке Encoder, конечные размеры которого могут увеличиваться до 16 раз,
В моем случае размер слова был 100, а квадратный корень из 1600 - 40, что является огромным значением для масштабирования вверх.

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

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

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

После реализации всех вышеперечисленных решений моя оценка EM достигла 37,534, а оценка F1 - 49,802.

Это по-прежнему средний балл F1 и EM по сравнению с базовым документом. Но в базовом документе QANet требуется значение измерения для встраивания слов, поскольку в качестве кодировщика модели использовалось 7 кодировщиков с накоплением, тогда как в кодировщике модели использовалось только 100 измерений и 4 кодировщика с накоплением.
и максимальный контекст длина была сохранена на 150 для сокращения времени обучения.

Поскольку для реализации такой огромной модели требуется графический процессор с большим объемом оперативной памяти и возможность использовать более длительное время обработки.
С моими 12 часами графического процессора, которые предоставляет Google colab, я смог реализовать только более легкую модель.

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

Хотя это немного свело меня с ума и потребовалось несколько недель, чтобы завершить этот проект.

Конечный результат был удовлетворительным !!!!.

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

Посмотреть весь мой код на GITHUB здесь

Хлопайте, если вам это нравится, и комментируйте, если вам это нравится.

Мир !!!!

Получите доступ к экспертному обзору - Подпишитесь на DDI Intel