Прогнозирование оценок в обзорах Amazon с использованием иерархических сетей внимания с PyTorch и Apache Mxnet

Этот пост и код здесь являются частью более крупного репо, которое я (очень творчески) назвал НЛП-материал. Как видно из названия, я включаю в это репо проекты, которые я делаю, и / или идеи, которые у меня есть - пока есть код, связанный с этими идеями, - которые связаны с НЛП. В каждый каталог я включил файл README и серию пояснительных блокнотов, которые, надеюсь, помогут объяснить код. Я намерен продолжать добавлять проекты в течение 2020 года, не обязательно последние и / или самые популярные выпуски, а просто статьи или алгоритмы, которые я считаю интересными и полезными. В частности, код, связанный с этим постом, находится в каталоге amazon_reviews_classification_HAN.

Прежде всего, давайте начнем с признания соответствующих людей, которые проделали тяжелую работу. Этот пост и сопутствующее репо основаны на статье Иерархические сети внимания для классификации документов (Zichao Yang, et al, 2016). Кроме того, я также использовал в своей реализации результаты и код, представленные в Регуляризация и оптимизация языковых моделей LSTM (Стивен Мерити, Нитиш Шириш Кескар и Ричард Сохер, 2017). Набор данных, который я использовал для этого и других экспериментов в репо, - это данные о продуктах Amazon (J. McAuley et al., 2015 и R. He, J. McAuley, 2016), в частности, одежда, Набор данных "Обувь и ювелирные изделия". Я настоятельно рекомендую взглянуть на эти статьи и ссылки в них.

1. Сетевая архитектура

Как только это будет сделано, давайте начнем с описания сетевой архитектуры, которую мы будем здесь реализовывать. Следующий рисунок Figure 2 в статье Zichao Yang et al.

Мы рассматриваем документ, состоящий из L предложений s , и каждое предложение содержит T слова. w_it с t ∈ [1, T] представляет слова в i-м предложении. Как показано на рисунке, авторы использовали кодировщик слов (двунаправленный GRU, Bahdanau et al., 2014) вместе с механизмом внимания к словам для кодирования каждого предложения в векторное представление. Эти представления предложений передаются через кодировщик предложений с механизмом внимания предложения, в результате чего получается векторное представление документа. Это окончательное представление передается на полностью связанный уровень с соответствующей функцией активации для прогнозирования. Слово иерархический относится здесь к процессу кодирования первых предложений из слов, а затем документов из предложений, естественно следуя семантической иерархии в документе.

1.1 Механизм внимания

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

Где x_it - вектор встраивания слова t в предложении i. Векторы h _it - это прямая и обратная выходные характеристики двунаправленного ГРУ, которые объединяются перед обращением внимания. Механизм внимания формулируется следующим образом:

Сначала функции h_it проходят однослойный MLP с функцией гиперболического тангенса. Это приводит к скрытому представлению h_it, u_it. Затем важность каждого слова измеряется как скалярное произведение между u_it и вектором контекста u_w, получая так называемый нормализованный весовой коэффициент важности. α_it. После этого вектор предложения s вычисляется как взвешенная сумма функций h_it на основе нормализованных весов важности. Подробнее читайте в статье, раздел 2.2 «Иерархический Внимание». Как упоминалось ранее, механизм внимания предложения идентичен, но на уровне предложения.

Слово и предложение внимание можно закодировать как:

Pytorch:

Mxnet:

где inp относится к h_it и h_i для слов и предложений внимания соответственно.

Как видите, реализация Mxnet почти идентична реализации в Pytorch, хотя и с некоторыми небольшими отличиями. Это будет происходить на протяжении всей реализации HAN. Однако я хотел бы добавить несколько строк, чтобы прояснить следующее: это мое второе «серьезное» погружение в Mxnet и Gluon. Чем больше я его использую, тем больше мне он нравится, но я почти уверен, что мог бы написать лучший и более эффективный код. Имея это в виду, если вы, читатель, являетесь пользователем Mxnet и имеете предложения и комментарии, я хотел бы их услышать.

1.1.1 Word Encoder + Word Attention

Когда у нас есть класс AttentionWithContext, кодирование WordAttnNet (Word Encoder + Word Attention) становится простым. Приведенный ниже фрагмент представляет собой упрощенную версию фрагмента репо, но содержит основные компоненты. Для полной версии посмотрите код в репозитории.

Pytorch

Mxnet

Вы заметите наличие трех параметров, связанных с выпадением: embed_drop, weight_drop и locked_drop. Я опишу их подробно в Разделе 2. Пока не будем их игнорировать и сосредоточимся на остальных компонентах модуля.

Просто входные токены (X) проходят через поисковую таблицу вложений (word_embed). Результирующие встраивания токенов проходят двунаправленный GRU (rnn), а выходные данные GRU поступают в AttentionWithContext (word_attn), который возвращает веса важности (α), представление предложения (s) и скрытые состояние h_n.

Обратите внимание, что возврат скрытого состояния необходим, поскольку документ (здесь обзор Amazon) состоит из серии предложений. Следовательно, начальное скрытое состояние предложения i + 1 будет последним скрытым состоянием предложения i. Можно сказать, что мы будем рассматривать сами документы как «с сохранением состояния». Я вернусь к этому позже в этом посте.

1.1.2 Кодировщик предложений + внимание предложения

Учитывая тот факт, что нам не нужна таблица поиска встраивания для кодировщика предложений, SentAttnNet (Sentence Encoder + Sentence Attention) просто:

Pytorch

Mxnet

Здесь сеть получит вывод WordAttnNet (X), который затем будет проходить через двунаправленный GRU (rnn), а затем через AttentionWithContext (sent_attn).

На данный момент у нас есть все строительные блоки для кодирования HAN.

1.1.3 Иерархические сети внимания (HAN)

Pytorch

Mxnet

Я считаю, что здесь может быть полезно проиллюстрировать поток данных через сеть с некоторыми числами, связанными с размерами тензоров, когда они перемещаются по сети. Предположим, мы используем размер пакета (bsz) из 32, встраивание токенов dim (embed_dim) 100 и GRU со скрытым размером (hidden_dim) 64.

Входными данными для HierAttnNet во фрагменте перед X является тензор dim (bzs, maxlen_doc, maxlen_sent), где maxlen_doc и maxlen_sent - максимальное количество предложений в документе и слов в предложении. Предположим, что это числа 5 и 20. Следовательно, X здесь тензор dim (32, 5, 20).

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

В этом цикле мы будем обрабатывать одно предложение за раз, то есть тензор dim (32, 20), содержащий i-е предложение для всех 32 отзывов в пакете. Этот тензор затем передается в wordattnnet, который представляет собой просто Word Encoder + Word Attention, как описано ранее. Там он сначала проходит слой встраивания, в результате чего получается тензор dim (32, 20, 100). Затем через двунаправленный ГРУ, в результате чего получается тензор тусклого (32, 20, 128) и, наконец, через механизм внимания, в результате получается тензор тусклого (32, 1, 128). Этот последний тензор равен sᵢ в уравнении 7 в статье Zichao Yang и др. И соответствует векторному представлению предложения i-го.

После выполнения цикла у нас будет maxlen_doc (т.е. 5) тензоров dim (32, 1, 128), которые будут объединены по 2-му измерению, в результате чего получится тензор dim (32, 5, 128)(bsz, maxlen_doc, hidden_dim*2). Этот тензор затем передается через sentattnnet, который представляет собой просто Sentence Encoder + Sentence Attention, как описано ранее. Там он сначала проходит через двунаправленный GRU, в результате чего получается тензор dim (32, 5, 128) и, наконец, через механизм внимания, в результате чего получается тензор dim (32, 128). Этот последний тензор будет v в уравнении 10 в их статье.

Наконец, , v затем передается через полностью связанный слой и функцию Softmax для прогнозирования.

2. Встраивание, блокировка и снижение веса

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

  • Снижение сложности модели. Я исследую это, прогоняя несколько моделей с небольшим количеством вложений и / или скрытых размеров.
  • Ранняя остановка: всегда используется с помощью early_stop функции.
  • Дополнительная регуляризация, например исключение, сглаживание меток (Christian Szegedy et al, 2015) или увеличение данных. Я пишу дополнительно, потому что уже использовал снижение веса.

В этом упражнении я не исследовал сглаживание меток или увеличение данных. Если вы хотите больше узнать, как реализовать сглаживание меток в Pytorch, взгляните на это репо. В случае Mxnet API gluonnlp имеет собственный класс LabelSmoothing.

Что касается увеличения данных, правда в том, что я не пробовал здесь, и, возможно, мне стоит. Не только потому, что это обычно приводит к заметным улучшениям с точки зрения обобщения модели, но более того, потому что у меня уже есть большая часть кода из другого эксперимента, в котором я реализовал EDA: простые методы увеличения данных для повышения производительности при выполнении задач классификации текста (Джейсон Вэй и Кай Дзоу 2019). Тем не менее, нужно где-то остановиться, и я решил сосредоточиться на изучении различных механизмов отсева.

Я использовал 3 различных формы отсева: встраиваемый отсев, заблокированный отсев и отсев веса. Код, который я использовал, взят непосредственно из репозитория отдела продаж, соответствующего реализации AWD-LTSM (Merity, hirish Keskar and Socher, 2017). В этом разделе я сосредоточусь на обсуждении реализации Pytorch, но я также включу информацию о реализации Mxnet. Обратите внимание, что эти механизмы отсева изначально были задуманы и реализованы в контексте языковых моделей. Однако нет причины, по которой они не должны работать здесь (или, по крайней мере, нет причины, по которой мы не должны их пробовать).

2.1 Прекращение встраивания

Это подробно обсуждается в разделе 4.3. в статье Merity et al и основан на работе Gal & Ghahramani (2016). Никто лучше самих авторов не объяснит это. По их собственным словам: Это эквивалентно выполнению исключения в матрице внедрения на уровне слова, где исключение распространяется на все вложения вектора слова. […]

В коде (код ниже является упрощенной версией исходного репо):

По сути, мы создаем маску из нулей и единиц вдоль 1-го измерения тензора вложений (измерение «word»), а затем расширяем эту маску вдоль второго измерения («embedding ”), соответственно масштабируя оставшиеся веса. Как сказали авторы, мы отбрасываем слова.

2.2 Заблокированный отсев

Это также основано на работе Gal & Ghahramani (2016). И снова словами авторов: […] сэмплируйте двоичную маску отключения только один раз при первом вызове, а затем повторно используйте эту заблокированную маску отключения для всех повторяющихся соединений в прямом и обратном проходе .

В коде:

Просто LockedDropout получит трехмерный тензор, затем сгенерирует маску по второму измерению и расширит эту маску по первому измерению. Например, при применении к тензору, подобному (batch_size, seq_length, embed_dim), он создаст маску тусклого (1, seq_length, embed_dim) и применит ее ко всему пакету. Модуль nn.Dropout Mxnet имеет параметр axes, который напрямую реализует этот тип исключения.

И наконец…

2.3. Снижение веса

Об этом говорится в разделе 2 их статьи. Еще раз, их собственными словами: Мы предлагаем использовать DropConnect (« Wan et al., 2013 ) для повторяющихся скрытых и скрытых весовых матриц, которые не требуют каких-либо изменений в формулировке RNN».

В коде (код ниже является упрощенной версией исходного репо):

WeightDrop сначала скопирует и зарегистрирует веса от скрытого до скрытого (или, в общем, веса в List весах) с суффиксом _raw (строка 14). Затем он применит исключение и снова присвоит веса module (строка 25, если variational, или 27 в противном случае). Как показано во фрагменте, параметр variational делает то же самое, что обсуждалось ранее в случае исключения внедрения, т.е. генерирует маску по первому измерению тензора и расширяет (или передает) по второму измерению.

У этой реализации есть пара недостатков. Во-первых, с учетом некоторых входных весов окончательная модель будет содержать исходные веса (обозначенные как weight_name_raw) и те, у которых нет данных (обозначение weight_name), что не очень эффективно. Во-вторых, он изменяет имя параметров, добавляя «module» к исходному имени.

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

Еще одна хорошая реализация - это функция apply_weight_drop в API Mxnet gluonnlp, которую я здесь использовал. Фактически, в их реализации языковой модели AWDRNN эта функция используется как для встраивания, так и для исключения скрытого веса. Он доступен через их модуль utils:

from gluonnlp.model.utils import apply_weight_drop

Что касается реализации, вот и все. Пришло время провести несколько экспериментов.

3. Результаты и визуализация внимания

3.1. Результаты

В итоге я записал 59 экспериментов (я провел еще несколько), 40 из них с использованием реализации Pytorch и 19 с использованием Mxnet. На протяжении экспериментов я использовал разные размеры пакетов, скорость обучения, размеры встраивания, скрытые размеры GRU, частоту отсева, планировщики скорости обучения, оптимизаторы и т. Д. Все они показаны в таблицах 1 и 2 в записной книжке 04_Review_Score_Prediction_Results.ipynb. Лучшие результаты на тестовом наборе данных для каждой реализации показаны в таблице ниже, вместе с лучшим результатом, полученным мною из предыдущих попыток с использованием tf-idf вместе с LightGBM и Hyperopt для классификации и гипер -задачи по оптимизации параметров.

Прежде всего, стоит повторить, что я провел всего 19 экспериментов с реализацией Mxnet. Частично это связано с тем, что, как я упоминал ранее в этой статье, у меня больше опыта работы с Pytorch, чем с Mxnet и Gluon, что повлияло на соответствующие эксперименты. Поэтому вполне возможно, что я пропустил небольшую настройку моделей Mxnet, которая привела бы к лучшим результатам, чем те, что указаны в таблице.

В остальном мы видим, что модель HAN-Pytorch работает лучше, чем тщательно настроенная модель tf-idf + LighGBM на тестовом наборе данных для всех, точности, оценки F1 и точности. Поэтому следующий ближайший вопрос, который возникнет у большинства, таков: стоит ли использовать HAN вместо tf-idf + LightGBM (или вашего любимого классификатора)? И ответ, как и в большинстве случаев в жизни, «это зависит».

Это правда, что сети HAN работают лучше, но прирост относительно невелик. В общем, если оставить в стороне частный случай обзоров Amazon, если для вашего бизнеса важен показатель F1 ~ 3% (т. Е. Приводит к значительному увеличению доходов, экономии или некоторым другим преимуществам), то нет никаких сомнений, что можно было бы использовать подход DL. Вдобавок к этому механизмы внимания могут предоставить вам некоторую дополнительную полезную информацию (например, выражения в тексте, которые приводят к определенной классификации) помимо ключевых слов, которые можно получить с помощью таких подходов, как tf-idf (или моделирование темы).

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

3.2 Визуализация внимания

Теперь давайте посмотрим на веса внимания, в частности на весы важности слов и предложений (α).

На рис. 2 показаны веса слов и предложений для двух правильно классифицированных отзывов. Токен xxmaj - это специальный токен, введенный токенизатором fastai, чтобы указать, что следующий токен начинается с заглавной буквы. Кроме того, стоит отметить, что в исходном наборе данных оценки варьируются от 1 до 5 звезд. Во время предварительной обработки я объединяю обзоры с 1 и 2 запусками в один класс и меняю метки классов, начиная с 0 (подробности см. Здесь). Таким образом, окончательное количество классов равно 4: {0, 1, 2, 3}.

На рисунке показано, как при прогнозировании оценки в обзоре HAN обращает внимание на такие фразы и конструкции, как «идеально подошло», «очень хорошо» или «потертости». […] Неправильные места », а также отдельные слова, такие как« куплено »или« нет ». Кроме того, мы видим, что на верхнем графике третьему предложению уделено немного больше внимания по сравнению с другими тремя.

На рисунке 3 показаны веса внимания как слов, так и предложений для двух неправильно классифицированных обзоров. Для лучшего обзора был предсказан 0, в то время как истинная оценка была 3 (реальная оценка в исходном наборе данных равна 5). Кто-то нашел эти ботинки «противно», «разочаровывающими» и «плохими», но дал им 5 звезд. Обзор внизу был предсказан как 3, тогда как истинный балл был равен 0 (реальный балл в исходном наборе данных равен 1). Легко понять, почему HAN неправильно классифицировал этот обзор, главным образом, на основании первого предложения, которому уделяется наибольшее внимание.

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

4. Обсуждение

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

С другой стороны, глядя на рисунки 2 и 3, можно увидеть, что внимание обычно сосредоточено на отдельных словах или конструкциях и фразах или на 2 или 3 словах. Следовательно, можно подумать, что использование подхода без DL вместе с n-граммами может улучшить результаты в таблице. Я действительно сделал это в этой записной книжке, и разница между использованием или неиспользованием n-граммов (в частности, биграмм через gensim.models.phrases) незначительна.

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

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

5. Резюме и выводы

Я реализовал Hierarchical Attention Networks for Document Classification (Zichao Yang, et al, 2016), используя Pytorch и Mxnet для прогнозирования оценок в обзорах Amazon, и сравнил результаты с результатами предыдущих реализаций, в которых не задействовать глубокое обучение. Сети HAN лучше работают по всем оценочным метрикам, их относительно легко внедрить и быстро обучить. Поэтому я считаю, что этот алгоритм стоит иметь в репертуаре для задач классификации текста.

В остальном, как всегда, я надеюсь, что этот пост был вам полезен.

Любые комментарии, предложения, пожалуйста, напишите мне по адресу: [email protected] или, что еще лучше, откройте вопрос в репо.

Ссылки

Дмитрий Богданов, KyungHyun Cho, Yoshua Bengio 2016. нейронный машинный перевод путем совместного обучения выравниванию и переводу. https://arxiv.org/abs/1409.0473

Ярин Гал, Зубин Гахрамани 2015. Теоретически обоснованное применение Dropout в рекуррентных нейронных сетях. https://arxiv.org/abs/1512.05287.

Разрушая его, Джулиан Маколи 2016. Взлеты и падения: моделирование визуальной эволюции модных тенденций с помощью одноклассной совместной фильтрации. https://arxiv.org/abs/1602.01585

Джулиан Маколи, Кристофер Таргетт, Циньфэн (Джавен) Ши и Антон ван ден Хенгель, 2015. Рекомендации по стилям и заменителям на основе изображений. https://arxiv.org/abs/1506.04757

Стивен Мерити, Нитиш Шириш Кескар, Ричард Сочер 2017. Регуляризация и оптимизация языковых моделей LSTM. https://arxiv.org/abs/1708.02182

Кристиан Сегеди, Винсент Ванхаук, Сергей Иоффе, Джонатон Шленс, Збигнев Война 2015. Переосмысление начальной архитектуры компьютерного зрения. https://arxiv.org/abs/1512.00567

Ли Ван, Мэтью Цайлер, Сиксин Чжан, Ян Лекун, Роб Фергус 2013. Регуляризация нейронных сетей с помощью DropConnect. http://proceedings.mlr.press/v28/wan13.html

Джейсон Вей, Кай Цзоу, 2019. EDA: простые методы увеличения данных для повышения производительности при выполнении задач классификации текста. https://arxiv.org/abs/1901.11196

Цзычао Ян, Дийи Ян, Крис Дайер, Сяодун Хэ, Алекс Смола, Эдуард Хови, 2016. Иерархические сети внимания для классификации документов. https://www.cs.cmu.edu/~./hovy/papers/16HLT-hierarchical-attention-networks.pdf