Добрый день. Сегодня мы продвинемся в изучении машинного обучения и затронем тему обработки естественного языка (NLP). Сегодня мы будем использовать множество инструментов на примере практической задачи.
Подпишись на группу Вконтакте и Телеграм-канал. Там еще больше полезного контента для программистов.
А на моем YouTube-канале ежедневно по будням проходят обучающие стримы по программированию. Подписывайся!
Задача
Имеется датасет, который вмещает в себя описание и название направления некой математической области.
Сам датасет: https://www.kaggle.com/extralime/math-lectures.
Чтобы скачать, нам необходимо зарегистрироваться на Kaggle (наверняка каждый там есть), скачать архив и распаковать его.
Нам необходимо научиться определять, про какую отросль математики идёт речь. Мы будем использовать метод векторизации, то есть представление предложения в качестве массива, который вмещает в себя кол-во определённых слов. Также присутствует визуализация данных.
Обработка естественного языка. Начало работы

Уже взглянув на список импортов, мы можем понять, что здесь не так всё просто, как в обычных задачах классификации или регрессии.
Рассмотрим самые не очевидные места:
- string — модуль стандартной библиотеки Python для расширенной работы со строками
- seaborn — надстройка над matplotlib (здесь нужен лишь для установки стиля графиков)
- CountVectorizer — класс, с помощью которого мы будем проводить векторизацию
Предобработка данных
Загрузим наш датасет в объект DataFrame:

Убедимся, что у нас отсутствуют пустые значения:

Мы будем предсказывать метку класса, так что закодируем значения столбца label в численные значения:

Создаём хэш-таблицу, а также обратную к ней таблицу (позже она нам пригодится).
Series.unique() — возвращает список уникальных значений столбца Series.
Примечание: в моих фрагментах кода редко можно заметить комментарии, потому что я излагаю свои мысли в статье, однако здесь я закомментировал то, каким именно образом устроен словарь (как в C#, к примеру). Это не обязательно, однако я взял это к себе в привычку, и Вам советую. Значительно помогает облегчить работу с неизвестными словарями, повышая качество кода.
Получаем такой словарь:

С помощью метода Series.map() кодируем значения:

Series.map(dict) — заменяет значения столбца теми значениями, которые указаны в словаре.
Теперь создадим массив через векторизатор, который мы ранее импортирвали:

Также создаём обратную таблицу, дальше нам она понадобится. Векторизатор возвращает CSR-матрицу (документация здесь).
stop_words — список стоп-слов (они просто удалятся). В нашем случае мы указываем список слишком часто встречающихся английских слов. К сожалению, не все такие слова удаляются, об этом далее будет. Хотите установить свой список стоп-слов? В этом вопросе на StackOverFlow (сегодня у него особенный дизайн) описано, как это сделать.
vectorizer.get_feature_names() — получаем список всех слов, которые будут использованы при кодировании.
vectorizer.vocabulary_ — словарь со значениями, на подобии того, что мы делали выше с метками классов.
vectorizer.fit_transform() — метод «обучает» векторизатор и сразу возвращает результат для данного набора слов.
Визуализация NLP
Теперь мы визуализируем самые часто встречающиеся слова (это не совсем так) в разных «категориях» текста.
Создадим функцию, которая будет векторизировать отдельные фрагменты DataFrame’а:

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

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

Основной частью нашей визуализации станет хэш-таблица, ключём которой будет закодированное значение категории, а значениями — список часто-встречающихся слов с 90 по 100 позицию.
Почему с 90 до 100, а не с 0 до 10? Как я указал выше, не все «стоп-слова» удаляются при векторизации. Потому первые 10 слов совпадали на 70-80% для каждой категории. С 90-ой по сотую позиции слов значительно отличаются, потому мы будем использовать именно эти слова.

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

Здесь разные значения «частоты» слов для разных категорий. Это объясняется тем, что датасет не равномерный, и кол-во примеров для одной категории не совпадает с кол-вом примеров для другой.
Теперь построим множество «баров»:

plt.sublots(rows, columns, figsize) — добавляет множество графиков, также можно указать значение размера общей фигуры в дюймах.
axes.flat — т.к. axes изначально имеет форму (5, 2), для облегчения работы с ними мы будем использовать одномерный массив. Получить такой массив из массива с любой формой можно с помощью аттрибута flat.
Axes.set_ylabel(string) — установка подпись для оси y.
Axes.set_xticks(array) — устанавливает нужный нам список значений по оси x.
Axes.set_xticklabels(array) — устанавливает список подписей к оси x.
Axes.set_title(string) — устанавливает заголовок для «графика».
Axes.bar() — построение столбчастой диаграммы.
Магическая команда IPython %matplotlib inline даёт нам вывод графика прямо в блокнот:

К сожалению, в статье ничего подробного не рассмотреть, выполните пример сами, чтобы получить более четкую картинку.
Модель машинного обучения
Сразу хочется сказать, что векторизация — далеко не самый лучший подход при работе с текстом, потому даже на среднюю обобщающую способность модели нам надеяться не приходится.
Т.к. у нас есть значения, далеко отличающихся от нуля (и нормализация может только усугубить ситуацию), то можно предположить, что нам необходимо использовать модель, построенную на основе дерева решений.
Слабый ученик в виде обычного дерева — так себе затея, лучше использовать ансамбль. Мой выбор пал на случайный лес:

Перед этим, конечно же, делим исходные данные на тестовые и тренировочные в пропорции 1:9:

Как мы можем заметить, обобщающая способность модели находится на очень низком уровне.
Но! Т.к. у нас имеются 10 классов, то вероятность чистого угадывания 0.1. Наша точность выше, чем 0.1, что говорит о том, что наша модель всё-таки вывела определённые правила для прогнозирования.
Однако, для многих классов слова часто повторяются, потому спрогнозировать нужный нам класс в такой ситуации очень сложно.
Конечно, мы можем поиграться с параметрами модели, однако значительного прироста это нам не принесёт, к сожалению.
Заключение
Сегодня мы разобрали такой метод работы с текстом, как векторизация. Мы изучили часто встречающиеся слова, визуализировали их, проанализировали некоторые «подводные» камни, а также попытались построит модель машинного обучения, однако это там не удалось в виду неактуальности подхода векторизации в этом контексте.
Весь код из статьи здесь.
Также рекомендую прочитать статью NumPy vs Python и подпишитесь на группу ВКонтакте, Телеграм и YouTube-канал. Там еще больше полезного и интересного для разработчиков.