В этой статье рассмотрим, как можно использовать глубокое обучение для оценки различных негативных окрасок текста, таких как угрозы, непристойности, оскорбления.
![](https://habrastorage.org/getpro/habr/upload_files/0b0/f58/dad/0b0f58dadf9fe16d33bacf79ce757ea5.png)
Данные
Набор данных был создан командой Conversation AI. Тексты взяты из комментариев к правкам на странице обсуждения Википедии. Исходные данные состоят 223 549 примеров с разметкой.
Нужно на основе текста сообщения (comment_text) предсказать тональность (колонки toxic, severe_toxic, obscene, threat, insult, identity_hate). Значение 1 показывает наличие негативной составляющей:
comment_text | toxic | severe_toxic | obscene | threat | insult | identity_hate |
"You should be fired, you're a moronic wimp who is too lazy to do research. It makes me sick that people like you exist in this world." | 1 | 0 | 0 | 0 | 1 | 0 |
"Oh, I didn't know, thanks." | 0 | 0 | 0 | 0 | 0 | 0 |
Модели
Глубокое обучение предлагает следующие модели для определения эмоциональной окраски текста:
сверточные нейронные сети (CNN, convolutional neural network): класс нейронных сетей, часто применяемых в обработке изображений, но также успешно используемых в обработке текстов. В случае сентимент-анализа текста, сверточные слои CNN могут использоваться для извлечения локальных признаков из текста, а последующие полносвязные слои для классификации и определения тональности;
рекуррентные нейронные сети (RNN, Recurrent neural network): класс нейронных сетей, способных моделировать последовательности данных. Сети с долгой краткосрочной памятью (LSTM, long short-term memory) и модель с управляемыми рекуррентными блоками (GRU, Gated Recurrent Unit) являются расширениями RNN, позволяющими более эффективно учитывать долгосрочные зависимости в тексте;
Transformer: модель способна эффективно моделировать зависимости между словами в тексте и учитывать их важность при принятии решения о тональности. Примером такой модели является BERT (Bidirectional Encoder Representations from Transformers).
В статье рассмотрим и сравним CNN, LSTM и GRU.
Обработка данных
Для удобства обработки, тексты должны быть разбиты на одинаковое количество блоков. Далее из них будет составлен словарь, в котором каждому фрагменту будет соответствовать некое число, и каждый комментарий будет преобразован в вектор с различными значениями. Следует понять, какое оптимальное количество блоков нам нужно (слишком маленькое уменьшит точность предсказаний, слишком большое — увеличит время и сложность вычислений). Посмотрим, из скольких слов состоят сообщения в тексте:
![](https://habrastorage.org/getpro/habr/upload_files/c24/93f/937/c2493f9371a40caa851626860d78b9fe.png)
Как видно, в среднем тексты состоят из 68 слов, а также большая часть из них содержит менее 76 слов, но есть и тексты с 2834 словами.
При выборе количества блоков стоит взять такое число, которое покроет большую часть текстов. Рассмотрим график подсчета (count plot) для длины сообщений:
![](https://habrastorage.org/getpro/habr/upload_files/8b7/daa/566/8b7daa56616abd33a9823359b2114cc6.png)
По графику видно, что большая часть текстов содержит примерно до 250-300 слов, также имеется некоторое количество около 500. Попробуем узнать, какую часть текстов покрывают сообщения, содержащие до 250 и до 500 слов.
![](https://habrastorage.org/getpro/habr/upload_files/22e/0f8/482/22e0f8482d8321e7c6787a12944be4f2.png)
250 слов покрывают 95,65% слов, а 500 — 98,63%. Но разница между ними незначительна, поэтому для того, чтобы не увеличивать количество вычислений в будущей нейронной сети, возьмём разбиение на 250 слов.
Посмотрим, что представляет себой сообщение, которое имеет более 1000 слов в своем составе
![](https://habrastorage.org/getpro/habr/upload_files/441/fa7/ce4/441fa7ce4d87ab48a6af8257c9f14c00.png)
Большинство таких комментариев содержат или повторяющие слова, или множество побочных символов (например, знаки препинания или иероглифы). Все лишнее стоит убрать. Также для стандартизации текста все следует перевести к одному регистру. Функция, которую мы будем использовать для представления текста к одному виду:
def custom_standardization(sentence):
# приводим в нижний регистр
sample = tensorflow.strings.lower(sentence)
# убираем не словесные знаки (к словесным относятся буквы, цифры и т.д.)
sample = tensorflow.strings.regex_replace(sample, '\W', ' ')
# убираем цифры
sample = tensorflow.strings.regex_replace(sample, '\d', ' ')
# убираем знаки препинания
return tensorflow.strings.regex_replace(sample,
'[%s]'%re.escape(string.punctuation), '')
После всего мы можем составить слой векторизации, с помощью которого мы приведем сообщения к стандартному виду:
vectorize_layer = tensorflow.keras.layers.TextVectorization(
standardize=custom_standardization,
split='whitespace',
max_tokens=max_features,
output_mode='int',
output_sequence_length=sequence_length,
encoding='utf-8')
vectorize_layer.adapt(train['comment_text'])
Также разделим данные на тренировочную и тестовую выборки, предварительно преобразовав сообщения с помощью слоя векторизации.
X = train["comment_text"]
Y = train[train.columns[2:-1]]
X = np.array(vectorize_layer(X))
X_train, X_test, y_train, y_test = train_test_split(X, Y, test_size=0.2)
Построение моделей
Начнем с свёрточной нейронной сети:
![](https://habrastorage.org/getpro/habr/upload_files/dc8/bd6/8e5/dc8bd68e574deaae3708e0d051fc407e.png)
embedding_dim = 9
cnn_model = Sequential()
# сначала ембединг
cnn_model.add(Embedding(max_features+1, embedding_dim))
cnn_model.add(Dropout(0.2))
# далее уменьшим размерность
cnn_model.add(GlobalAveragePooling1D())
cnn_model.add(Dropout(0.2))
cnn_model.add(Dense(16, activation='relu'))
# слой для вывода результата. сигмоида для того, чтобы попасть в рамки [0, 1]
# в остальных моделях последним слоем также будет сигмоида
cnn_model.add(Dense(6, activation='sigmoid'))
cnn_model.compile(loss='binary_crossentropy',
optimizer='Adam',
metrics=['accuracy'])
Посмотрим, сколько времени требуется на обучение, а также какая точность предсказаний:
![](https://habrastorage.org/getpro/habr/upload_files/57f/67d/8d7/57f67d8d7c6923e97c474197cbe15e3c.png)
Она работает достаточно быстро (обучение одной эпохи занимает 3-5 секунд), обладает высокой точностью.
Аналогично построим модель нейронной сети с долгой краткосрочной памятью (long short-term memory; LSTM) и оценим ее время работы и точность:
lstm_epochs = 5
lstm_model = Sequential()
lstm_model.add(Embedding(max_features+1, embedding_dim))
lstm_model.add(LSTM(16))
lstm_model.add(Dropout(0.2))
lstm_model.add(Dense(16, activation='relu'))
lstm_model.add(Dense(6, activation='sigmoid'))
lstm_model.compile(loss='binary_crossentropy',
optimizer='Adam',
metrics=['accuracy'])
![](https://habrastorage.org/getpro/habr/upload_files/e5d/c9f/c25/e5dc9fc253f61248dafceb7ffb1a7820.png)
Нейронная сеть с долгой краткосрочной памятью работает намного медленнее свёрточной нейронной сети, при этом не обладая сильным преимуществом по точности.
Наконец, построим и оценим модель с управляемыми рекуррентными блоками (gated recurrent units, GRU)
gru_epochs = 5
gru_model = Sequential()
gru_model.add(Embedding(max_features+1, embedding_dim))
gru_model.add(GRU(16))
gru_model.add(Dropout(0.2))
gru_model.add(Dense(16, activation='relu'))
gru_model.add(Dense(6, activation='sigmoid'))
gru_model.compile(loss='binary_crossentropy',
optimizer='Adam',
metrics=['accuracy'])
gru_model.summary()
![](https://habrastorage.org/getpro/habr/upload_files/5b1/5e1/f9f/5b15e1f9f484a5379a8f4ac5e54f142b.png)
Работает медленней, чем свёрточная нейронная сеть, но быстрее чем сеть с долгой краткосрочной памятью, не обладает серьезным преимуществом в точности.
Выводы: из всех трёх моделей наилучшим образом себя показала свёрточная нейронная сеть.