python

Анализ тональности фраз с помощью нейронных сетей

  • воскресенье, 8 марта 2020 г. в 00:25:17
https://habr.com/ru/post/488952/
  • Python
  • Машинное обучение
  • Искусственный интеллект


Всем привет!

Все люди, получающие высшее образование, не отчислившись, все-таки доходят до стадии написания диплома. Не стал исключением и я. Хотелось реализовать что-то интересное и освоить доселе неизученное, поэтому обратил внимание на тему нейронных сетей и искусственного интеллекта в целом. А задачей, которую я решал с помощью нее, является анализ тональности текста, что и так широко применятся в различных системах мониторинга. Процесс ее решения я и попытаюсь описать в данной статье.
Короче говоря, цель — понять присутствует ли у фразы положительный оттенок или отрицательный. Сразу хочу сказать, что эту задачу можно решать несколькими способами, и не только нейросетями. Можем составлять словари в которых отмечены позиции слов и т.д. (все методы есть на хабре в избытке), но на каждый способов может уйти еще по статье, поэтому оставим их обзор на потом.

Данные


Первой задачей на моем пути оказался сбор и предобработка данных для обучения. Хорошим датасетом для такого дела является корпус коротких текстов Рубцовой Ю., предварительно разделенный на негативные и позитивные предложения, собранные на просторах Твиттера. Что особенно удобно — все это существует в формате CSV.

Подготовка к обучению


Обратите внимание, в каком виде представлены данные — куча смайликов, ссылок, ненужных символов, обращений. Все это не является важной информацией и только мешает обучению, к тому же надо убрать все на латинице. Поэтому текст хорошо бы предобработать.
def preprocessText(text):
    text = text.lower().replace("ё", "е")
    text = re.sub('((www\.[^\s]+)|(https?://[^\s]+))', ' ', text)
    text = re.sub('@[^\s]+', ' ', text)
    text = re.sub('[^a-zA-Zа-яА-Я1-9]+', ' ', text)
    text = re.sub(' +', ' ', text)
    return text.strip()

Прогнав так все предложения из файла, приводим их к нижнему регистру, заменяем «ё» на «е», ссылки, упоминания, английские слова просто убираем за неимением смысла. Короче говоря, делаем их однотипными, вычищая «мусор», лишний для обучения.

Инструменты


Конечно, если у вас дома суперкомпьютер, этот раздел можете листать дальше, ища интересную часть. Остальным советую сервис Google Colab, который позволяет запускать Jupyter Notebook'и (кто не слышал и про такое, в помощь поисковик), используя только лишь браузер, а вся работа выполняется на виртуалке в облаке. Временной размер сессии, которая вам предоставляется для работы, ограничен 12 часами — можно закончить и раньше, после чего все сбрасывается.

Пишем наш прекрасный код


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

image

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

image

Наконец-то по существу.
Итак, для чего мы скачивали и импортировали библиотеку Tensorflow-text? Дело в том, что фразы нельзя «скармливать» сетке в том виде, в котором она читаема для нас. Тут в дело вступает Word Embedding, термин, перевода которому я адекватного не нашел, да и вообще сомневаюсь в его существовании. Но грубо говоря речь идет о сопоставлении вектора слову. Об этом хорошо говорится здесь.
Нам нужно преобразовывать в вектор целые предложения, поэтому пользуемся готовым решением от Google — Universal Sentence Encoder.

image

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

image

Все твиты классифицированы по классам — плохой/хороший. Создаем pandas-dataframe, в котором они отсортированы по классам (плохих на картинке не видно, в силу того, что оне влезли).

image

Данные мы подготовили — приступим к самой модели. Для этого воспользуемся фреймворком Keras.

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential, load_model
model = tf.keras.Sequential()

model.add(
  tf.keras.layers.Dense(
    units=256,
    input_shape=(X_train.shape[1], ),
    activation='relu'
  )
)
model.add(
  tf.keras.layers.Dropout(rate=0.5)
)

model.add(
  tf.keras.layers.Dense(
    units=128,
    activation='relu'
  )
)
model.add(
  tf.keras.layers.Dropout(rate=0.5)
)

model.add(tf.keras.layers.Dense(2, activation='softmax'))
model.compile(
    loss='categorical_crossentropy',
    optimizer=tf.keras.optimizers.Adam(0.001),
    metrics=['accuracy']
)

history = model.fit(
    X_train, y_train,
    epochs=10,
    batch_size=16,
    validation_split=0.1,
    verbose=1,
    shuffle=True
)

model.evaluate(X_test, y_test)


Немного о самой модели. В ней есть входные, скрытые и выходные слои.
Каждому слою задаем свою функцию активации.
Небольшое пояснение: В искусственных нейронных сетях функция активации нейрона определяет выходной сигнал, который определяется входным сигналом или набором входных сигналов. Подробнее можно прочесть тут, их кстати немало число существует для разных задач, но мы поработаем лишь с 2.
Первым 2 слоям назначаем функцию активации ReLu. Выходному — Softmax.
Помимо добавления слоев можно заметить слово «Dropout». Что же это такое? Как ни странно, но помимо проблемы недообучения нейронной сети, когда ее предсказания не соответствуют действительности, существует проблема переобучения — модель хорошо объясняет только примеры из обучающей выборки, адаптируясь к обучающим примерам, вместо того чтобы учиться классифицировать примеры, не участвовавшие в обучении. То есть банально на новых данных ваша прекрасная модель, до этого великолепно выполнявшая свою работу, просто «полетит» и начнет вас неприятно удивлять. Так вот Dropout занимается тем, что с некоторой указанной вероятностью «выключает» нейроны из сетки, благодаря чему они перестают участвовать в процессе обучения. Затем результаты от нескольких сетей усредняются (когда нейрон исключается из сети — получается новая).
Кстати, прекрасная статья для тех, кому интересно.

Можно приступать к обучению!

Train on 53082 samples, validate on 5898 samples
Epoch 1/10
53082/53082 [==============================] - 12s 223us/sample - loss: 0.5451 - accuracy: 0.7207 - val_loss: 0.5105 - val_accuracy: 0.7397
Epoch 2/10
53082/53082 [==============================] - 11s 213us/sample - loss: 0.5129 - accuracy: 0.7452 - val_loss: 0.5000 - val_accuracy: 0.7523
Epoch 3/10
53082/53082 [==============================] - 11s 215us/sample - loss: 0.4885 - accuracy: 0.7624 - val_loss: 0.4914 - val_accuracy: 0.7538
Epoch 4/10
53082/53082 [==============================] - 11s 215us/sample - loss: 0.4686 - accuracy: 0.7739 - val_loss: 0.4865 - val_accuracy: 0.7589
Epoch 5/10
53082/53082 [==============================] - 11s 214us/sample - loss: 0.4474 - accuracy: 0.7889 - val_loss: 0.4873 - val_accuracy: 0.7616
Epoch 6/10
53082/53082 [==============================] - 11s 216us/sample - loss: 0.4272 - accuracy: 0.8004 - val_loss: 0.4878 - val_accuracy: 0.7603
Epoch 7/10
53082/53082 [==============================] - 11s 213us/sample - loss: 0.4081 - accuracy: 0.8111 - val_loss: 0.4986 - val_accuracy: 0.7594
Epoch 8/10
53082/53082 [==============================] - 11s 215us/sample - loss: 0.3899 - accuracy: 0.8241 - val_loss: 0.5101 - val_accuracy: 0.7564
Epoch 9/10
53082/53082 [==============================] - 11s 215us/sample - loss: 0.3733 - accuracy: 0.8315 - val_loss: 0.5035 - val_accuracy: 0.7633
Epoch 10/10
53082/53082 [==============================] - 11s 215us/sample - loss: 0.3596 - accuracy: 0.8400 - val_loss: 0.5239 - val_accuracy: 0.7620
6554/6554 [==============================] - 0s 53us/sample - loss: 0.5249 - accuracy: 0.7524
[0.5249265961105736, 0.752365]


Итак, прошло 10 эпох. Для тех, кто совсем не знаком с такими понятиями, поясню определением из Интернета: Эпоха — одна итерация в процессе обучения, включающая предъявление всех примеров из обучающего множества и, возможно, проверку качества обучения на контрольном множестве. Значит все наши данные прошли 10 раз полностью через весь процесс.

Результат



image

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

Используя метод predict попытаемся узнать мнение сети и тональной составляющей 2 очевидно разных в этом плане фраз. Правда предварительно их все равно надо привести к матричному виду — но мы то уже знаем как это сделать, используя готовое решение.
На выходе видим 2 числа — вероятности принадлежности фразы к классам «негативный» / «положительный». Думаю, по картинке явно видно, что разница есть) Значит похожие слова были в дотаяете и сеть прекрасно справилась с их соотношением к своим классам.

Заключение



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

Код можете найти тут, если поставите звездочку проекту, будет здорово. Если понадобятся файлы с весами и структурой сети, а также обработанными данными — пишите к комментарии, добавлю в репозиторий.