Мультиклассификация экстремально коротких текстов классическими методами машинного обучения

—

от автора

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

В ходе работы служб банка накапливается огромное количество текстовой информации,   которую необходимо анализировать и структурировать. В нашем случае классифицировать необходимо обращения. Категории заданы заранее: “Сбой/ошибка в работе приложения”, “Неторговые операции”, “Негатив”, “Налогообложение”, “Отчётность брокера”, “Обновление анкетных данных”, “ИИС”, “Торговые операции”, “Передача выплат инвестору”, “Выбор опций”, “СМС-информирование”, “Отмена заявки”.

Для начала считаем данные:

import pandas as pd raw_data = pd.read_excel('example.xlsx') raw_data.head()

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

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

import stop_words russian_stopwords = stop_words.get_stop_words('ru') russian_stopwords.extend(['...', '«', '»', 'здравствуйте','здравствуй','до свидания', 'добрый день', 'добрый вечер', 'доброе утро']) import string def remove_punctuation(text):     return ''.join([ch if ch not in string.punctuation else ' ' for ch in text]) def remove_numbers(text):     return ''.join([i if not i.isdigit() else ' ' for i in text]) import re def remove_multiple_spaces(text):     return re.sub(r'\s+', ' ', text, flags=re.I)  prep_text = [remove_multiple_spaces(remove_numbers(remove_punctuation(text.lower()))) for text in raw_data['Text'].astype('str')] raw_data['text_prep'] = prep_text raw_data.head()

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

Для этого используем библиотеку pymorphy2.

import pymorphy2 raw_data = raw_data.dropna(subset=['text_prep']) morph = pymorphy2.MorphAnalyzer() lemm_texts_list = [] for text in raw_data['text_prep']:     text_lem = [morph.parse(word)[0].normal_form for word in text.split(' ')]     if len(text_lem) <= 2:         lemm_texts_list.append('')         continue     lemm_texts_list.append(' '.join(text_lem)) raw_data['text_lemm'] = lemm_texts_list raw_data = raw_data[raw_data['text_lemm'] != ''] raw_data.head()

Самое время окунуться в специфику данных. Дело в том, что наши тексты очень короткие, и, помимо этого, пользователи часто могут писать самые разные общие фразы, которые никак не относятся к тематике обращений, например: «Добрый день», «С новым годом» и т.д. Поэтому мы игнорируем тексты, где после удаления стоп-слов остается только 2 токена. Предполагается, что таким образом наши категории станут наиболее выраженными.

Далее – обучение. В статье будет рассмотрено 2 классических метода: градиентный бустинг от CatBoost и логистическая регрессия.

Разбиваем данные на обучающую и тест выборку.

from sklearn.model_selection import train_test_split X = raw_data ['text_lemm'] y = raw_data ['Разметка подробно'] X_train, X_test, y_train, y_test = train_test_split(X, y, random_state= 42, test_size=0.3)

Обучение логистической регрессии.

from sklearn.pipeline import Pipeline from sklearn.feature_extraction.text import TfidfTransformer from sklearn.feature_extraction.text import CountVectorizer from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression  logreg = Pipeline([                 ('vect', CountVectorizer(analyzer='char', ngram_range =([2,10]))),                 ('tfidf', TfidfTransformer()),                 ('clf', LogisticRegression(n_jobs=3,C=1e5, solver='saga',                                             multi_class='multinomial',                                            max_iter=1000,                                            random_state=42)), ])  logreg.fit(X_train, y_train) y_pred = logreg.predict(X_test)  from sklearn.metrics import classification_report from sklearn.metrics import f1_score print(classification_report(y_test, y_pred, target_names=themes)) print(f"F1 Score: {f1_score(y_test, y_pred, average='weighted')}")

Используется CountVectorizer на ngram с analyzer = char. Именно такой подход дал наилучшую точность на маленьких текстах. В зависимости от данных значение ngram можно корректировать. Целевая метрика – F1.

Далее — CatBoost.

Создаем функцию с инициализацией и обучением модели.

from catboost import CatBoostClassifier, Pool def fit_model(train_pool, test_pool, **kwargs):     model = CatBoostClassifier(task_type='CPU', iterations = 5000,                                eval_metric='TotalF1', od_type='Iter',                                 od_wait=500, **kwargs)          return model.fit(train_pool, eval_set=test_pool,                       verbose=100, plot=True,                       use_best_model=True)

Модель будет обучаться «на процессоре», валидационная метрика – F1. В параметрах обучения задаем валидационный пул и использование модели с наибольшим качеством.

Формируем обучающий и валидационный пулы.

train_pool = Pool(data=X_train, label=y_train,                    text_features=['text_lemm',]) valid_pool = Pool(data=X_test, label=y_test,                    text_features=['text_lemm',])

Стоит отметить, что обучение CatBoost с использованием пулов, как правило дает лучшее качество.

Обучаем модель.

model = fit_model(train_pool, valid_pool, learning_rate=0.35,                   dictionaries = [{                       'dictionary_id':'Word',                       'max_dictionary_size': '50000'                   }],                  feature_calcers = ['BoW:top_tokens_count=10000'])

CatBoost дал нам прирост в качестве ~ 2%.

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


ссылка на оригинал статьи https://habr.com/ru/articles/584506/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *