"Машинное обучение"
Классификация текста с несколькими метками с использованием Scikit-multilearn: пример из практики с вопросами StackOverflow
Разработка модели классификации текста с несколькими метками, которая помогает помечать вопросы stackoverflow.com по разным темам.
Ежедневные пользователи stackoverflow.com задают множество технических вопросов, и все они помечаются разными темами. В этой статье мы обсудим модель классификации, которая может автоматически определять, какие теги могут быть прикреплены к оставшемуся без ответа вопросу.
Очевидно, что с вопросом можно связать несколько тегов. Итак, в конечном итоге эта проблема сводится к «классификации вопроса и прикреплению к нему ярлыков класса». Согласно теории машинного обучения, это проблема «классификации по нескольким меткам».
Мы уже обсуждали различные теоретические методы и показатели точности, необходимые для моделей с несколькими метками, в статье ниже.
Вышеупомянутое является предпосылкой для текущего обсуждения. Читателям предлагается пройти через это перед этой текущей статьей.
В своей работе мы будем использовать scikit-multilearn, gensim и scikit-learn.
Получение данных и исследование
Данные для этой статьи можно найти на сайте Kaggle. Он содержит Questions.csv и Tags.csv, необходимые для нашего обсуждения.
Давайте изучим эти файлы
import pandas as pd tag_df = pd.read_csv('../data/Tags.csv') tag_df.head()
questions_df = pd.read_csv('../data/Questions.csv', encoding = "ISO-8859-1") questions_df.head()
Таким образом, каждый вопрос имеет «Тело» и «Заголовок» в качестве основного содержания. Нам нужно понять это, и оттуда мы должны предсказать теги оставшегося без ответа вопроса.
Мы видим, что данные этих файлов связаны с идентификатором «Id», который является «идентификатором вопроса». Итак, мы можем объединить эти двое, чтобы получить единое представление. Мы будем объединять теги через запятую для каждого «тела» и «заголовка».
Всего существует 1316 различных типов тегов (анализ приведен в предыдущей статье).
Теперь давайте посмотрим, какие теги используются реже всего с минимальным количеством 3 (т. Е. Тег появился как минимум в трех вопросах).
tags_count_df = tag_df.groupby(['Tag']).count() tags_count_df_asc = tags_count_df.sort_values(by=['Id']) tags_count_df_asc.query('Id >= 3').head()
И обратные или самые частые теги
tags_count_df_desc = tags_count_df.sort_values(by=['Id'], ascending=False) tags_count_df_desc.head()
Визуализация содержания "Body" и "Title"
Теперь давайте посмотрим на содержание "Body" и "Title" вопросов в виде облака слов. Для этого мы можем определить функцию
Изучение содержания тега в облаке слов
tags = '' for index, row in input_df.iterrows(): tags = tags + ',' + row['Tag'] plot_word_cloud(tags)
Мы видим, что чаще всего используются такие теги, как "машинное обучение", "временные ряды" и т. Д.
Предварительная обработка данных для дальнейшего изучения
Мы должны разделить данные на входной контент и целевую переменную. В нашем случае целевая переменная - «Тег».
df_x = input_df[['Title','Body']] df_y = input_df[['Tag']]
Чтобы проанализировать содержимое «Body» и «Tag», нам нужно выполнить базовую предварительную обработку текста.
Шаги приведены ниже
- Преобразование в нижний регистр
- Удаление знаков препинания
- Удаление целых чисел, чисел
- Удаление лишних пробелов
- Удаление тегов (например, ‹html›, ‹p› и т. Д.)
- Удаление стоп-слов (например, "и", "к", "то" и т. Д.)
- Stemming (преобразование слов в корневую форму)
Мы будем использовать библиотеку Python «gensim» для очистки всего текста.
Давайте сначала напишем функции для этого
Мы увидим эффект применения этой функции к "Body" и "Title" на примере.
Содержание «Заголовка»
input_df.iloc[0,0]
После очистки
clean_text(input_df.iloc[0,0])
Содержание "Body"
input_df.iloc[0,1]
После очистки
clean_text(input_df.iloc[0,1])
Очищенное содержимое хоть и выглядит немного коряво, но оно необходимо для дальнейшей обработки.
Теперь давайте посмотрим на облако слов всех очищенных «заголовков». Для очистки мы будем использовать функцию «clean_text», как определено выше.
titles = '' for index, row in input_df.iterrows(): titles = titles + ' ' + clean_text(row['Title']) plot_word_cloud(titles)
Таким образом, "данные", "отличаются", "ценность" и т. Д. Являются наиболее частыми токенами в содержании "Заголовок".
Одинаковый график облака слов для всего очищенного содержимого "Body"
bodies = '' for index, row in input_df.iterrows(): bodies = bodies + ' ' + clean_text(row['Body']) plot_word_cloud(bodies)
"Know", "instancepl", "case" и т. Д. - самые частые токены в "Body".
Мы также можем видеть содержимое «Body», соответствующее определенному тегу. Для этого определим функцию
Облако слов для вопросов с меткой «matlab»
plot_word_cloud_of_body_for_tag('matlab')
Совершенно очевидно, что «matlab» будет там одним из наиболее часто используемых токенов.
Аналогично для тега «вероятность»
plot_word_cloud_of_body_for_tag('probability')
Построение модели и конвейера машинного обучения
После изучения данных давайте сконцентрируемся на построении реальной модели. Поскольку это классификация с несколькими метками, нам нужно преобразовать нашу целевую метку в бинаризованный вектор с несколькими битами, установленными как 1.
«MultiLabelBinarizer» из «scikit-learn» может это сделать.
Поскольку мы имеем дело с текстовыми данными. Нам нужно преобразовать это в модель векторного пространства. Для трансформации будем использовать модель Doc2Vec.
Мы напишем собственный «Doc2VecTransformer», используя библиотеку python «gensim».
Этот класс принимает имя поля в качестве входных данных (в нашем случае «Body» и «Title» соответственно) и преобразует текст в массив числовых функций.
Мы разделим данные на «поезд» и «тест».
from sklearn.model_selection import train_test_split train_x, test_x, train_y, test_y = train_test_split(df_x, encoded_y)
Нам нужно объединить два поля «Body» и «Title» в одну функцию после преобразования «Doc2Vec» с помощью FeatureUnion из scikit-learn.
Исходя из концепции моделей «с несколькими метками», мы попробуем использовать «Двоичная релевантность» и «цепочка классификаторов» в качестве общей схемы классификации (см. Предыдущий статья, указанная вверху) и будет использовать "RandomForest" в качестве нашего базового двоичного классификатора.
Поскольку нет 1316 различных тегов, поэтому внутри не будет 1316 разложенных и отдельных двоичных классификаторов.
Мы сравним точность для обеих зонтичных схем.
Двоичная релевантность
Наш конвейер должен состоять из шагов FeatureUnion и BinaryRelevance поверх классификатора RandomForest. Мы будем использовать класс BinaryRelevance из библиотеки scikit-multilearn.
Для проверки точности нашей модели давайте определим функцию для расчета «потери Хэмминга» (см. Предыдущую статью)
Давайте обучим и протестируем модель «двоичной релевантности».
multi_label_rf_br_model.fit(train_x, train_y) print('Hamming loss for test data :', hamming_loss(multi_label_rf_br_model,train_x,train_y,test_x,test_y))
Это означает потерю почти 0,02% или, можно сказать, точность 99,98% !!
Цепочка классификатора
Таким же образом мы можем обучить и протестировать «цепочку классификаторов». «scikit-multilearn» предоставляет для этого один класс.
Почти такая же точность !!
Дальнейший анализ точности отдельного классификатора
Поскольку существует два классификатора, невозможно проверить эффективность и точность каждого отдельного человека, но мы увидим кривую ROC классификаторов для трех из них. Напишем для этого функцию.
Теперь мы увидим «кривую ROC» для трех тегов: «нормальное распределение», «визуализация данных» и «оценка».
plot_roc_curve(x=test_x, y=test_y, classes=['normal-distribution','data-visualization','estimation'], title='ROC curve')
Аналогичные кривые ROC могут быть построены этой функцией и для других тегов. Читатели могут попробовать это самостоятельно.
Заключение
Это все о решении проблемы. Дальнейшее улучшение может быть выполнено с помощью другого двоичного классификатора или настройки его с помощью гиперпараметров. Читатели этой статьи могут попробовать это самостоятельно. Источник этой статьи можно найти здесь, в Github.
Недавно я написал книгу по ML (https://twitter.com/bpbonline/status/1256146448346988546)