Как создать мульти-меточный классификатор НЛП с нуля

Kaggle - хорошее место, чтобы изучить и попрактиковать свои навыки машинного обучения. Это также отличное место для поиска подходящего набора данных для ваших учебных проектов. Мне нужен хороший набор данных НЛП для классификации, чтобы практиковать недавно изученный урок fast.ai, и я наткнулся на Задачу классификации токсичных комментариев. Конкурс проводится два года назад и давно завершился, но мне не помешает отправить свои оценки и посмотреть, насколько хорошо я выступила. Это одна из вещей, в которой Kaggle отлично подходит, поскольку в реальном мире обычно намного сложнее узнать, насколько хороша или плоха ваша модель, тогда как в Kaggle вы четко увидите, где ваша производительность в таблице лидеров.

Набор данных

Этот конкурс проводится командой Conversation AI, исследовательской инициативой, основанной Jigsaw и Google (обе являются частью Alphabet). Его цель - найти лучшую модель, которая может классифицировать несколько типов токсичности в комментариях. Типы токсичности:

ядовитый

серьезный_отоксичный

непристойный

угроза

оскорблять

indentity_hate

Комментарии даны в обучающем файле train.cvs и тестовом файле test.csv. И вам нужно будет предсказать вероятность каждого типа токсичности для каждого комментария в test.csv. Это проблема классификации НЛП с несколькими метками.

Посмотрите на данные

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

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
from fastai.text import *
from fastai import *

Обратите внимание, здесь мы импортировали все из модулей fastai.text и fastai. Мы против передовой практики разработки программного обеспечения? На самом деле не совсем так. Это скорее преднамеренный шаг в сторону более итеративного и интерактивного подхода к науке о данных. Имея всю доступную библиотеку, я могу легко тестировать и пробовать различные функции / модули без необходимости каждый раз возвращаться и импортировать их. Это сделает процесс исследования / эксперимента более плавным. Но я отвлекся, давайте загрузим данные и посмотрим на них:

# Kaggle store dataset in the /kaggle/input/ folder,
path = Path('/kaggle/input/jigsaw-toxic-comment-classification-challenge/')
path.ls()
# the /kaggle/input/ folder is read-only, copy away so I can also write to the folder. 
!mkdir data
!cp -a {path}/*.* ./data/
!ls data
# make sure everything is correctly copied over
path = Path('/kaggle/working/data/')
path.ls()
# read in the data and have a peak
df = pd.read_csv(path/'train.csv')
df.head()

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

Трансферное обучение: точная настройка нашей языковой модели

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

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

bs = 64   # set batch size to 64, works for Kaggle Kernels
data_lm = (TextList.from_df(df, path, cols='comment_text')
                .split_by_rand_pct(0.1)
                .label_for_lm()
                .databunch(bs=bs))

Для этой задачи мы используем Data Block API fast.ai. Это очень гибкий и эффективный способ решения сложной задачи построения конвейера: загрузки данных в модель. Он изолирует весь процесс на разные части / шаги, каждый шаг с множеством методов / функций для адаптации к различным типам данных и способам хранения данных. Эта концепция очень похожа на философию Linux, сильно модулирована и с каждым модулем делает только одно, но действительно очень хорошо. Вы можете исследовать замечательный API здесь, однако для приведенного выше кода он выполняет следующие функции:

  1. Импортируйте данные из Pandas DataFrame с именем df, скажите модели использовать comment_text в качестве входных данных (TextList.from_df(df, path, cols=’comment_text’)) Обратите внимание, здесь я также могу включить test.csv в языковую модель. Это не считается "обманом", поскольку мы не используем ярлыки, а просто обучаем языковой модели.
  2. Разделите набор обучающих данных на обучающий / проверочный набор случайных 10/90 процентов. (.split_by_rand_pct(0.1))
  3. Игнорируйте указанные метки (поскольку мы только настраиваем языковую модель, а не обучаем классификатор) и используем «предсказать следующее слово» языковой модели в качестве меток. (.label_for_lm())
  4. Объедините данные в databunch с размером пакета bs. (.databunch(bs=bs))

Теперь давайте посмотрим на databunch, который мы только что построили:

Обратите внимание, что databunch не имеет всех меток типа токсичности, поскольку мы только настраиваем языковую модель.

Хорошо, время для некоторых типичных корректировок скорости обучения и обучения fast.ai:

Мы помещаем наш databunch в language_model_learner, указываем ему базу языковой модели, которую мы хотим использовать (AWD_LSTM), и назначаем коэффициент отсева по умолчанию, равный 0,3. На графике LR Finder найдите самый большой наклон вниз и выберите среднюю точку в качестве скорости обучения. (Для более подробного объяснения того, как осуществляется эта магия fit_one_cycle, обратитесь к этой статье. Это метод SOTA для fast.ai, который сочетает в себе скорость обучения и отжиг импульса). Теперь мы можем разморозить модель и обучить всю модель пару эпох:

Мы можем посмотреть на один пример того, насколько хороша модель:

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

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

# save the encoder for next step use
learn.save_encoder('fine_tuned_enc')

Трансферное обучение: обучение классификатора

Давайте прочитаем в тестовом наборе данных:

test = pd.read_csv(path/"test.csv")
test_datalist = TextList.from_df(test, cols='comment_text')

Снова строим наш databunch:

data_cls = (TextList.from_csv(path, 'train.csv', cols='comment_text', vocab=data_lm.vocab)
                .split_by_rand_pct(valid_pct=0.1)
                .label_from_df(cols=['toxic', 'severe_toxic','obscene', 'threat', 'insult', 'identity_hate'], label_cls=MultiCategoryList, one_hot=True)
                .add_test(test_datalist)
                .databunch())
data_cls.save('data_clas.pkl')

Обратите внимание на разницу на этот раз:

  1. При создании TextList мы указали vocab=data_lm.vocab, чтобы убедиться, что мы используем тот же словарь, и наше обучение языковой модели может быть правильно применено к модели классификатора.
  2. Теперь мы используем все наши ярлыки стилей токсичности (.label_from_df(cols=[‘toxic’, ‘severe_toxic’,’obscene’, ‘threat’, ‘insult’, ‘identity_hate’],label_cls=MultiCategoryList, one_hot=True),)
  3. Мы добавили сюда наш набор тестов. (.add_test(test_datalist))

Теперь посмотрим на наш классификатор databunch:

Наконец, пора собрать все вместе! Мы поместим databunch в модель text_classifier_learner и загрузим кодировщик, который мы узнали из языковой модели.

learn = text_classifier_learner(data_clas, AWD_LSTM, drop_mult=0.5)
learn.load_encoder('fine_tuned_enc')

Опять же, найдите лучшую скорость обучения и тренируйте один цикл:

Тренируйте еще немного циклов и разморозьте:

Смотрите результаты:

Снижается на единицу, но в целом прогноз верен. Для справки я отправил прогноз в Kaggle и получил оценку публики 0,98098 (место в середине таблицы лидеров общественности). Результат не оптимален, но, как я уже сказал, я не тренировался полностью из-за ограниченного количества графических процессоров. Цель этой статьи - показать вам весь процесс использования fast.ai для решения проблемы классификации текста с несколькими метками. Настоящая проблема здесь - загрузить данные в модель с помощью API блока данных.

Заключение

Надеюсь, вы кое-что узнали из этой статьи. Fast.ai - действительно компактная, гибкая и мощная библиотека. Что касается вещей, которые он может делать (например, классификация изображений / текста, табличные данные, совместная фильтрация и т. Д.), Он делает это очень хорошо. Он не такой обширный, как Керас, но очень резкий и сфокусированный. Вроде как Vim и Emacs, если вы знакомы с войной текстовых редакторов командной строки. 😜

Вы можете найти ядро ​​Kaggle здесь.

Любые отзывы или конструктивная критика приветствуются. Вы можете найти меня в Twitter @lymenlee или в моем блоге wayofnumbers.com.