Как мы учили машину распознавать посты противников вакцинации
В Тинькофф—журнале вышла статья «Что и зачем пишут в интернете противники вакцинации», в которой мы рассказали о результатах исследования почти трёх миллионов постов и комментариев из ВКонтакте на тему вакцинации. Самая сложная часть работы — определить, как авторы сообщений относятся к прививкам. В этой статье мы хотим рассказать о том, как мы это делали.
Общее количество постов и комментариев со словом «вакцина»
У трёх миллионов постов почти невозможно вручную определить отношение их авторов к вакцинации. На то, чтобы разметить выборку из 10 тысяч текстов, у нас ушло примерно 50 часов. Такими темпами весь корпус мы бы размечали 15 тысяч часов! Можно было бы отдать разметку на аутсорс, но мы пошли по более дешёвому и быстрому пути — использовали машинное обучение.
Классификация — это один из методов машинного обучения, и одна из стандартных задач обработки естественного языка: она применяется для того, чтобы распознать, например, негативные или позитивные отзывы, популярные темы в соцсетях или спам в почте.
Хотя это и часто используемый инструмент в анализе текстов, было неясно, насколько хорошо модель сможет выявить именно мнение человека по поводу вакцины. Даже при ручной разметке определить пост в ту или иную категорию было не так просто. Пока мы вручную классифицировали десять тысяч постов и комментариев, выяснилось, что чёткого разделения на противников и сторонников вакцинации не существует.
Разбивка постов
Полярность мнений по поводу вакцин оказалась велика: существует целая шкала, где на одной стороне находятся те, кто крайне негативно относится к идее «колоть себе эту гадость» , а на другой — те, кто считает, что «вакцинация — один из самых эффективных методов борьбы с эпидемиями, который есть у нашей цивилизации». Между двумя полюсами расположено множество групп: те, кто не против вакцинации, но против посягательства на личную свободу со стороны государства; те, кто согласен привиться, но зарубежной вакциной; те, кто привился, но не хочет вступать в полемику на столь чувствительную тему.
Однако сообщений, в которых вакцину прямо-таки хвалили, в интернете оказалось немного. Так немного, что мы начали относить к про-вакцинной группе посты тех, кто ничего не написал о самой вакцине, но из контекста было ясно, что человек привился. Мы отнесли к этой группе даже тех, кто упоминает, что вакцины нет в их районной поликлинике или медучреждении — это указывает на то, что человек хотел привиться. Тем не менее, анализа различий в аргументах сторонников и противников вакцинации у нас бы просто не получилось — постов, где ярко выражено позитивное мнение относительно вакцинации, было очень мало. Кроме того, сообщения сторонников вакцин плохо поддавались автоматической классификации.
По итогам ручной разметки обучающей выборки мы получили следующее соотношение записей:
Распределение классов публикаций по итогам ручной разметки
Тогда мы решили более подробно рассмотреть, чем тексты противников вакцинации отличаются от всех остальных. Осталось научить этому разделению модель.
Очистка данных
При ручной разметке мы заметили, что в данных содержится существенная доля нерелевантных постов: сообщения о вакцинации животных от различных заболеваний, информация от турагентств. От таких постов нашу выборку точно следовало очистить.
Здесь и далее мы будем приводить фрагменты кода на языке Python, который использовали для анализа.
Примеры текста до очистки
Первым шагом мы удалили нерелевантные символы, слова и посты. Из исходных данных мы убрали всё, что не несет дополнительной информации — пунктуацию, теги пользователей, числа, латинские слова, ссылки, служебные символы. Также мы убрали посты, которые содержали меньше 40 символов: часто это были неинформативные записи, по которым невозможно было определить мнение автора без полного контекста.
Убираем служебные символы (теги пользователей в квадратных скобках) при помощи регулярных выражений:
Убираем пропуски в столбце [«text»]:
Убираем слишком короткие неинформативные посты до 40 символов:
Убираем знаки препинания, числа, лишние пробелы, латинские слова. Также подготовим список стоп-слов русского языка для дальнейшей очистки текстов. Стоп-слова — это часто повторяющиеся слова, которые не несут смысла для модели, так как повторяются во всех текстах: предлоги, союзы, междометия. В некоторых библиотеках уже есть есть готовые списки стоп-слов — мы воспользовались списком из библиотеки NLTK:
Мы убрали также посты, которые определили как нерелевантные на этапе ручной разметки. Мы сделали это простым исключением строк, которые содержат ключевые слова «кошка», «собака», «родословная», «передержка» и «турагенство», «все включено».
Очищенные тексты мы сохранили отдельно. Пример такого текста:
Подготовка данных
В русском языке одно и тоже слово может иметь множество форм — число, падеж, род. Такое многообразие мешает поиску самых популярных слов, и, соответственно, изучению контекста массива сообщений. Перед тем, как применять машинное обучение, мы привели все слова к начальному виду. Для этого мы использовали следующие операции:
Исходная строка: | @id197263123, За побочки и смерти людей после этой непроверенной жижи! |
Очистили от знаков препинания, латиницы и лишних символов: | За побочки смерти людей после этой непроверенной жижи |
Привели к нижнему регистру: | за побочки смерти людей после этой непроверенной жижи |
Разбили предложение на слова (это называется токенизация): | за, побочки, смерти, людей, после, этой, непроверенной, жижи |
Привели все слова к начальной форме (лемматизация): | за, побочки, смерть, люди, после, это, непроверенный, жижа |
Каждое сообщение из столбца «text_prep» мы разбили на слова (токенизировали) при помощи функции word_tokenize из библиотеки NLTK, отфильтровали по списку стоп-слов и собрали заново в строки в столбце «text_sw»:
Вот пример текста из столбца «text_sw»:
Для лемматизации мы использовали русскоязычную библиотеку PyMystem3 от Яндекса. Лемматизированные тексты сохранили в новый столбец [«text-lemm»].
Сравните написание слов в столбцах [«text_sw»] и [«text_lemm»]:
Обучение модели
После первых двух этапов мы получили набор токенов для каждого сообщения, а также результаты ручной классификации 10 тысяч постов. Все сообщения получили один из следующих классов:
0 | антивакцинационный |
1 | провакцинационный |
2 | нейтральный |
3 | нерелевантный |
Для оптимального результата мы тестировали работу нескольких алгоритмов классификации. Подробнее рассмотрим два из них — Naive Bayes Classifier и Logistic Regression.
Вместе с каждым из алгоритмов мы использовали CountVectorizer и TfidfTransformer из библиотеки scikit-learn для того, чтобы представить текст в понятном для алгоритмов виде, а также тестировали два сценария классификации: с выделением трех классов сообщений (против вакцин, за вакцины и остальные) и двух классов сообщений (против вакцин и остальные). Ни один из результатов разбиения на три класса не оказался приемлемым, поэтому мы рассмотрим только результаты разбиения на два класса.
Перед началом обучения модели мы выделили в отдельные переменные признаки — X — и целевую переменную — Y. Признаки — это данные, которые нужны модели для того, чтобы научиться предсказывать значения. В нашем случае это столбец [«text_lemm»], в котором хранятся приведенные к начальной форме тексты. Целевая переменная — это то, что мы хотим получить в результате работы модели, то есть, класс поста: написан он противником вакцинации или нет.
Сообщения, которые мы разметили вручную, мы разделили на обучающую и тестовую выборки. Обучающая выборка нужна для того, чтобы научить модель угадывать отношение автора поста к вакцинации, а тестовая — для того, чтобы проверить, насколько адекватно модель её угадывает.
Далее рассмотрим несколько алгоритмов машинного обучения и несколько вариантов подготовки данных для них.
Naive Bayes Classifier (Наивный Байесовский Классификатор)
Задали алгоритм для модели (по умолчанию векторизатор CountVectorizer работает с униграммами — отдельными словами):
Затем обучили модель и воспользовались уже обученной моделью, чтобы сделать прогноз на тестовой выборке:
Посчитали метрики качества модели:
Видим, что модель плохо себя показывает на «анти»-постах: показатель recall равен 0,37. Это значит, что из всех постов, которые были вручную размечены как «антивакцинационные», она смогла определить правильно всего 37%.
Эту же модель мы протестировали и для биграмм — сочетаний из двух слов. Такие сочетания позволяют сохранить связь между словами. Например, из текста «никто не может заставить человек делать прививки», можно получить следующие биграммы: «никто не», «не может», «может заставить», «заставить человек», «человек делать», «делать прививки».
Видим, что качество модели даже ухудшилось по сравнению с первым вариантом, показатель recall опустился до 20%.
2. Logistic Regression (Логистическая регрессия).
Посмотрим на результаты модели Logistic Regression (Логистической регрессии).
Униграммы:
Видим, что логистическая регрессия показала себя гораздо лучше Наивного Баейсовского классификатора на униграммах. Посмотрим, каких результатов нам удалось добиться при тестировании с точностью до биграмм.
При тестировании модели Логистической Регрессии с точностью до биграмм мы получили показатель Accuracy (Точность) = 0.75 — это означает, что в 75% случаев модель смогла верно распределить посты на группы в сравнении с ручной разметкой.
Параметр Precision отвечает на вопрос: какая часть текстов, которые были классифицированы в определенную группу, действительно принадлежит этой группе. В нашем случае 67% текстов, которые модель определила как антивакционные действительно являются антивакцинационными, и 81% «остальных» (не антивакционных) текстов действительно являются не антивакцинационными.
По параметру Recall можно оценить сколько постов из тех, которые действительно являются антивакцинационными, наша модель смогла определить как таковые. В целом и здесь мы получили достаточно хорошие результаты — модель определила 71% антивакцинационных и 78% не антивакционных постов.
В целом модель логистической регрессии с векторизацией по биграммам показала достаточно хорошие результаты, и именно её мы использовали для разметки нашего основного датасета, состоящего из 3 млн постов.
Выводы
На выборке, которую мы просмотрели вручную, у модели получилось достаточно хорошо угадать к какой группе относится пост, даже в не самых очевидных случаях.
Примеры правильно размеченных постов:
text | class |
Он поймал коронавирус? Ну всё, теперь мы в безопасности, уносите вакцины |