Machine Learning, NLP

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

Время прочтения: 4 мин.

Сначала стоит определить основные условия задачи. Не волнуйтесь, они будут усложняться по нарастающей.

Тип задачи: классификация.

Датасет: Чистый длинный эмоциональный текст на английском языке в каждой ячейке, плюс оценка автором своего настроения при написании.

Уже можно понять, что рассматриваемый датасет близок к идеалу. Это могут быть, например, обзоры на фильмы и сериалы, после которых автор ставит 1-10 звёзд (а потом специально для нас даёт ещё и словесное описание своей эмоции).

К решению такого кейса можно подойти очень оригинально. Среди оптимальных моделей машинного обучения сразу можно выделить: Наивный байесовский классификатор, Метод опорных векторов или Случайный лес. Если хочется приключений, можно развернуться с масштабными n-граммами или пойти в глубокое обучение. Даже чистая проверка по словарям позитив/негатив даст хороший результат — на длинном тексте будет высокий шанс построения статистических закономерностей в том числе и в русском языке.

По алгоритму предобработки: токенизация -> нормализация -> векторизация — и учить.

import nltk

def clear_text(text):
doc = re.sub(r'\W', ' ', text)
# одиночные символы
doc = re.sub(r'\s+[a-zA-Z ]\s+', ' ', doc)
# одиночные символы с начала
doc = re.sub(r'\^[a-zA-Z ]\s+', ' ', doc)
# заменяем пробелы на один
doc = re.sub(r'\s+', ' ', doc, flags=re.I)
# заменяем префикс
doc = re.sub(r'^b\s+', '', doc)
# в нижний регистр
doc = doc.lower()

clear = doc.split()
return " ".join(clear)

w_tokenizer = nltk.tokenize.WhitespaceTokenizer()
lemmatizer = nltk.stem.WordNetLemmatizer()

def lemmatize_text(text):
return [lemmatizer.lemmatize(w) for w in w_tokenizer.tokenize(text)]

Теперь усложним задачу. Пусть текст теперь русский, но он будет короче (например, твитты). В этот раз придётся сложнее. Для приемлемого качества нужно немного поменять предобработку, выбрать модели посложнее и подобрать им правильные параметры. В идеале стоит обратиться к RuBERT, тогда результат будет впечатляющим, но сама модель довольно капризная, поэтому можно посмотреть другие сложные классификаторы вроде LGBM и CatBoost. Я бы рекомендовал LGBM, она быстрее и качественнее. Для выбранной модели проводим поиск оптимальных параметров по сетке и проводим кросс-валидацию для их проверки (GS + CV). Для оценки модели лучше использовать метрики точности и полноты, например, f1.

from sklearn.model_selection import GridSearchCV

grid1 = {'learning_rate': [.05, .1, .15, .2, .25, .3, .35, .4, .45, .5, .55, .6, .65, .7, .75, .8, .85, .9, .95],
'max_depth': [5, 10, 15],
'n_estimators': [5, 10, 15],
'num_leaves': [10, 20, 30]}

test_model1 = LGBMClassifier(verbose=1)
grid_search_result1 = GridSearchCV(estimator=test_model1, param_grid=grid1, cv=4, n_jobs=4, scoring=‘f1’, verbose=True)
grid_search_result1.fit(features_train, target_train)
print(grid_search_result1.best_params_)

model1 = LGBMClassifier(max_depth=10, learning_rate=.55, n_estimators=10, num_leaves=30)
model1.fit(features_test, target_test)

pred1_t = model1.predict(features_test)
print('LGBM:', f1_score(target_test, pred1_t))
print(‘Acc:’, accuracy_score(target_test, pred1_t))

LGBM: 0.7666263603385731
Acc: 0.9586428571428571

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

Можно догадаться, что теперь нам наконец придётся обратиться к более «словарным» моделям, которые используют уникальные датасеты для словарей. Практически единственным решением в свободном доступе является библиотека Dostoyevsky и её набор аннотированных данных RuSentiment из популярной соцсети (который, если честно, больше нигде не найдёшь). Сама модель имеет в своей основе FastText, которая вместе с BERT’ом эволюционировала из word2vec.

import dostoevsky
from dostoevsky.tokenization import RegexTokenizer
from dostoevsky.models import FastTextSocialNetworkModel

Итак, через данную модель, с помощью анализа на основе полученного словаря, можно получить эмоциональный анализ даже двух слов приветствия типа «Добрый вечер», для которых у модели есть отдельная категория speech наряду с neutral, positive, negative и др.

Единственная сложность, с которой нужно справиться — преобразовать входные данные в формат list и подавать модели батчами малого размера, иначе модель очень любит «ругаться».

привет -> {'speech': 1.0000100135803223, 'skip': 0.0020607432816177607, 'negative': 1.0000003385357559e-05}
Здравствуйте!! -> {'neutral': 0.6442351341247559, 'speech': 0.38492217659950256, 'skip': 0.0014203583123162389}
малолетние дураки -> {'negative': 0.546748161315918, 'neutral': 0.41490885615348816, 'positive': 0.027595279738307}
да вы совсем уже -> {'negative': 0.9875783920288086, 'neutral': 0.29422497749328613, 'skip': 0.004915405530482531}
До свидания -> {'neutral': 0.9965265989303589, 'skip': 0.00942259095609188, 'negative': 0.008071991614997387}
всё очень хорошо -> {'positive': 0.9905974268913269, 'negative': 0.009135636501014233, 'neutral': 0.0008395892800763249}

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

Ссылки на используемые в статье материалы:

Статья по Dostoyevsky

Kaggle tweets 1

Kaggle tweets 2

Советуем почитать