Как обучить модель предсказывать негативные комментарии?

Расскажем про построение модели, способной предсказать негативные комментарии.

Во-первых, подготовим датасет с размеченными данными. Все комментарии мы разделим на два класса: негативные (target — 1) и позитивные (target — 0).

Есть два типа классификации выборки: бинарная и мультиклассовая классификация. Для разметки текста на положительные и отрицательные комментарии мы будем использовать бинарную классификацию.

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

Стемминг – это грубый эвристический процесс, который оставляет начальную форму слова. У слов есть разные окончания, например: «Ручка», «Ручку», «Ручкам» компьютер воспринимает это как 3 разных слова, поэтому от слов нужно удалить концовки.

В-третьих, выберем модель, разделим наш датасет на обучающую и тестовую выборку, обучим и протестируем модель.

По результатам теста определим процент точности работы модели.

Итак, для начала импортируем библиотеки.

import pandas as pd import re from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from nltk.stem.snowball import SnowballStemmer from sklearn.metrics import classification_report import stop_words

Загружаем подготовленный файл «user_comments_with_target.csv» и файл, на котором будем применять модель «isu_2020.csv» с помощью pandas.

df_2019_comment = pd.read_csv('user_comments_with_target.csv', sep=';', encoding='cp1251', low_memory=False) df_2020_comment = pd.read_csv('isu_2020.csv', sep=';', encoding='cp1251', low_memory=False)

Файл содержит лишние столбцы, оставляем только 2 нужных: сами комментарии и разметку. Пустые значения удаляем.

# Оставляем только 2 нужных столбца и удаляем все пустые значения df_2019_comment = df_2019_comment[['user_comments', 'target']].dropna() df_2020_comment = df_2020_comment[['user_comments']].dropna() # Подготовка файла для обучения # 2019 df_2019_comment = df_2019_comment[df_2019_comment['target'].isin(['0', '1'])] df_2019_comment['target'] = df_2019_comment['target'].astype(int) df_2019_comment = df_2019_comment.drop_duplicates('user_comments') df_2019_comment = df_2019_comment[df_2019_comment['user_comments'].apply(lambda x:len(x) > 2)] df_2019_comment = df_2019_comment.sort_values('target').reset_index(drop=True) # Подготовка файла для предсказания # 2020 df_2020_comment = df_2020_comment.drop_duplicates('user_comments') df_2020_comment = df_2020_comment[df_2020_comment['user_comments'].apply(lambda x:len(x) > 2)] df_2020_comment = df_2020_comment.reset_index(drop=True)

Мы создали функцию, которая проводит очистку текста и подготовку его к обучению:

  • переводит текст в нижний регистр;
  • удаляет латиницу, знаки препинания, символы и цифры;
  • применяет Стемминг.
stemmer = SnowballStemmer('russian') stop_words_ru = stop_words.get_stop_words('russian') def clear_txt(txt): txt = txt.lower() txt = re.sub('[/+_!@#$A-Za-z0-9\n.,:()""«»;-]', ' ', txt) new_txt = '' for t in txt.split(' '): if len(t) > 0: new_txt = new_txt + stemmer.stem(t) + ' ' return new_txt[:-1]

Пример работы стемминга:

stemmer = SnowballStemmer('russian') stemmer.stem('Ручкам') 'ручк' stemmer.stem('Ручки') 'ручк'

Как видно из примера, стемминг удаляет концовки слов.

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

Пример работы леммитизации с использованием библиотеки pymorphy2:

import pymorphy2 pm = pymorphy2.MorphAnalyzer() pm.parse('Ручкам') [Parse(word='ручкам', tag=OpencorporaTag('NOUN,inan,femn plur,datv'), normal_form='ручка', score=1.0, methods_stack=((DictionaryAnalyzer(), 'ручкам', 9, 9),))] pm.parse('Ручкам')[0].normal_form 'ручка' pm.parse('Ручки')[0].normal_form 'ручка'

Слова «Ручкам» и «Ручки» pymorphy2 привел к начальной форме в нижнем регистре к слову «ручка».

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

Обучение и тестирование модели.

После обучения модели нам нужно понять насколько верно модель определяет комментарии. Мы будем делать это по фактическим контрольным данным. Для этого мы оставим часть датасета для тестирования.

В переменную «X» мы записываем все наши неочищенные комментарии («user_comments»), а в переменную «Y» правильные ответы/классы («target»).

X = df_2019_comment['user_comments'] y = list(df_2019_comment['target'])

Разделим весь датасет на 2 выборки, одна выборка будет использоваться для обучения (X_ train, y_train), а вторая для тестирования качества предсказания модели (X_test, y_test).

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) print('X train:', X_train.shape[0], '\ny train:', len(y_train), '\nX test:', X_test.shape[0], '\ny test:', len(y_test)) X train: 10942 y train: 10942 X test: 2736 y test: 2736

В Vectorizer в параметры передаем нашу функцию и стоп-слова, в которые мы добавили ФИО сотрудников, встречающихся в комментариях, приведенные к начальной форме, тем самым Vectorizer будет работать с очищенным текстом.

Vectorizer удобен тем, что он при работе применяет токенизацию, векторизацию текста и скоринг TF-IDF.

Токенизация по предложениям – это процесс разделения письменного языка на предложения-компоненты.

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

TF-IDF (term-frequency – inverse document frequency) – это статистическая мера для оценки важности слова.

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

stop_words_ru = stop_words.get_stop_words('russian') tfidf_vectorizer = TfidfVectorizer(max_features=10000, preprocessor=clear_txt, stop_words=stop_words_ru) X_train = tfidf_vectorizer.fit_transform(X_train)

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

В библиотеке scikit-learn есть реализация алгоритма Random Forest, с помощью которого мы будем обучать нашу модель. Random Forest уникальный алгоритм, придуманный еще в прошлом веке, который используется для многих задач и является самым популярным алгоритмом.

rfc = RandomForestClassifier() # Обучение rfc.fit(X_train, y_train) # Предсказание модели на тестовом датасете y_pred = rfc.predict(tfidf_vectorizer.transform(X_test))

Результат точности предсказания негативных комментариев по метрике f1_score (мера точности в статистическом анализе бинарной классификации) равна 84%.

print(classification_report(y_test, y_pred)) precision recall f1-score support 0 0.98 1.00 0.99 2532 1 0.93 0.76 0.84 204 avg / total 0.98 0.98 0.98 2736

При использовании обученной модели на разметку 30 тыс. комментариев ушло всего 25 секунд. Исходя из нашего опыта разметки датасета, на это потребовалось бы примерно 100 человеко-часов.

df_2020_comment['pred_labels'] = \ rfc.predict(tfidf_vectorizer.transform(df_2020_comment['user_comments']))

Мы попросили экспертов провести перепроверку результата работы модели, и в 92% случаев модель смогла верно предсказать негативные комментарии.

22
3 комментария

Спасибо за статью. А почему именно random forest решили использовать?

Автор

Антон, спасибо! Считаем, что random forest - один из лучших алгоритмов машинного обучения. Он универсален, его можно использовать во многих задачах - классификация, кластеризация и других. Поэтому для решения этой задачи остановились на нем.

Можно разшарить датасэт?