Добрый день, уважаемые читатели. Сегодня мы реализуем анализ настроения, а именно определим, является ли комментарий на русском языке «токсичным».
Мы будем использовать библиотеки Keras
для построения модели, pandas
для обработки данных, некоторые функции sklearn
и matplotlib
для визуализации процесса обучения нейронной сети.
Наша цель — определить, является ли комментарий агрессивным, то есть наша задача на сегодня — бинарная классификация. Не стоит думать, что это сложно, нам всего лишь необходимо подать текст в приемлемом виде для нейронной сети.
Подпишись на группу Вконтакте и Телеграм-канал. Там еще больше полезного контента для программистов.
А на моем YouTube-канале ты найдешь обучающие видео по программированию. Подписывайся!
Начало работы
Я создал notebook в Kaggle, взяв данные из этого датасета.
Для начала импортируем все необходимые нам модули.
In[1]: import os import numpy as np # For DataFrame object import pandas as pd # Neural Network from keras.models import Sequential from keras.layers import Dense, Dropout from keras.optimizers import RMSprop # Text Vectorizing from keras.preprocessing.text import Tokenizer # Train-test-split from sklearn.model_selection import train_test_split # History visualization %matplotlib inline import matplotlib.pyplot as plt # Normalize from sklearn.preprocessing import normalize print(os.listdir("../input")) ... Using TensorFlow backend. ['labeled.csv']
Начнём с начала:
os
— для работы с файловой системойnumpy
— для работы с массивамиpandas
— для загрузки данных из .csv файла и обработки их с помощьюDataFrame
keras
— для построения моделиkeras.preprocessing.Text
— для обработки текста, чтобы подать его в числовом виде для обучения нейронной сетиsklearn.train_test_split
— для отделения тестовых данных от тренировочныхmatplotlib
— для визуализации процесса обученияsklearn.normalize
— для нормализации тестовых и обучающих данных
Загрузим данные, узнав имя файла благодаря конструкции:
print(os.listdir("../input"))
Сама загрузка осуществляется с помощью функции read_csv
:
In [2]: path = '../input/labeled.csv' df = pd.read_csv(path) df.head()
Обработка данных
Теперь удалим символы новой строки из текстовых данных:
In [3]: def delete_new_line_symbols(text): text = text.replace('\n', ' ') return text
In [4]: df['comment'] = df['comment'].apply(delete_new_line_symbols) df.head()
Из первых пяти строк таблицы стало ясно, что колонка toxic
имеет вещественный тип. А ведь именно это и является нашей «целью». Приведём их к целочисленному типу и сохраним в отдельную переменную.
In [5]: target = np.array(df['toxic'].astype('uint8')) target[:5] Out[5]: array([1, 1, 1, 1, 1], dtype=uint8)
Теперь про обработку текста. Мы могли бы создать кучу функций, которые бы разбивали текст на слова, подсчитывали их кол-во, частоту и т. д. Однако Keras обладает улучшенным программным интерфейсом (хотя отдельные функции в том же подмодуле так же имеются) в виде класса Tokenizer
. Создадим его экземпляр:
In [6]: tokenizer = Tokenizer(num_words=30000, filters='!"#$%&()*+,-./:;<=>?@[\\]^_`{|}~\t\n', lower=True, split=' ', char_level=False)
Кратко пройдёмся по параметрам:
num_words
— кол-во фиксируемых слов (самых часто встречающихся)filters
— последовательность символов, которые будут удаляться.lower
— булевый параметр, отвечающий за то, будет ли переведён текст в нижний регистрsplit
— основной символ разбиения предложенияchar_level
— указывает на то, будет ли считаться отдельный символ словом.
Документацию по этому классу можно найти здесь.
Теперь преобразуем наш набор текста с помощью этого класса:
In [7]: tokenizer.fit_on_texts(df['comment']) matrix = tokenizer.texts_to_matrix(df['comment'], mode='count') matrix.shape Out[7]: (14412, 30000)
14 тысяч строк (образцов) и 30000 столбцов — признаков. Метод text_to_matrix
имеет параметр mode
, который может принимать 4 значения:
binary
— вернёт массив, состоящий из 0 и 1, где каждый флаг будет отвечать за то, присутствует определённое слово в тексте.count
— простой счетчик словtfidf
— текстовая обратная оценка частоты документа (TF-IDF) для каждого словаfreq
— частота каждого слова в соответствии с другими
Теперь построим модель из двух слоёв: Dense
и Dropout
. Про оба эти слоя рассказывалось в предыдущих статья, советую их прочитать, если не ознакомлены с материалом.
In [8]: def get_model(): model = Sequential() model.add(Dense(32, activation='relu')) model.add(Dropout(0.3)) model.add(Dense(16, activation='relu')) model.add(Dropout(0.3)) model.add(Dense(16, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(optimizer=RMSprop(lr=0.0001), loss='binary_crossentropy', metrics=['accuracy']) return model
Нормализируем нашу матрицу и разобьём данные на тестовые и тренировочные:
In [9]: X = normalize(matrix) y = target X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2) X_train.shape, y_train.shape Out[9]: ((11529, 30000), (11529,))
Обучение модели анализ настроения
Теперь приступим к обучению модели, выполняющую анализ настроения, сохранив историю в отдельную переменную. Установим счетчик эпох, равным 150, а размер батча — 500.
In [10]: model = get_model() history = model.fit(X_train, y_train, epochs=150, batch_size=500, validation_data=(X_test, y_test))
Не буду демонстрировать полный отчёт про обучение, покажу лишь сведения о последней итерации:
Epoch 150/150 11529/11529 [==============================] - 5s 434us/step - loss: 0.0562 - acc: 0.9847 - val_loss: 0.4854 - val_acc: 0.8724
Точность на тренировочных данных составила 0.87, т. е. 87%.
Визуализируем процесс обучения с помощью matplotlib
.
In [11]: history = history.history fig = plt.figure(figsize=(20, 10)) ax1 = fig.add_subplot(221) ax2 = fig.add_subplot(223) x = range(150) ax1.plot(x, history['acc'], 'b-', label='Accuracy') ax1.plot(x, history['val_acc'], 'r-', label='Validation accuracy') ax1.legend(loc='lower right') ax2.plot(x, history['loss'], 'b-', label='Losses') ax2.plot(x, history['val_loss'], 'r-', label='Validation losses') ax2.legend(loc='upper right')
Здесь переменная history — словарь с четырьмя ключами:
acc
val_acc
loss
val_loss
По каждому ключу имеется доступ к массиву, который имеет размерность (epochs, )
.
С помощью простых манипуляций получаем вывод:
Out[11]:
<matplotlib.legend.Legend at 0x7f29440d14a8>

Как мы видим, модель вышла примерно на 75-ой эпохе, а дальше кол-во ошибок росло с каждой эпохой. Попробуйте изменить гиперпараметры модели, кол-во слоёв и посмотреть на результат. Так сказать, добавим интерактива.
Заключение
Сегодня мы рассмотрели простейший анализ тональности текста, предварительно обработав его и обучив на полученных данных модель Keras.
Результат не расстроил: точность в 0.85 довольно неплохой результат. Сказать честно, я, прочитав некоторые комментарии из набора, сам не смог определить их к нормальным или токсичным :)
Документ .ipynb с кодом из статьи можно найти здесь.
Также рекомендую прочитать статью Ансамбль с мажоритарным голосованием на Python. Подпишитесь на группу ВКонтакте, Telegram и YouTube-канал. Там еще больше полезного и интересного для разработчиков.