Рубрики
Без рубрики

Python для НЛП: Классификация текста с несколькими метками с помощью Keras

Автор оригинала: Usman Malik.

Вступление

Это 19-я статья в моей серии статей по Python для НЛП. Из последних нескольких статей мы изучали довольно продвинутые концепции НЛП, основанные на методах глубокого обучения. В предыдущей статье мы видели , как создать модель классификации текста, обученную с использованием нескольких входных данных различных типов данных. Мы разработали предиктор настроений текста, используя текстовые входные данные и метаинформацию.

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

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

На этом этапе важно объяснить разницу между проблемой классификации по нескольким классам и классификацией по нескольким меткам. В задаче многоклассовой классификации экземпляр или запись могут принадлежать одному и только одному из нескольких выходных классов. Например, в задаче анализа настроений, которую мы изучали в предыдущей статье, текстовый обзор может быть либо “хорошим”, либо “плохим”, либо “средним”. Он не мог быть одновременно и “хорошим”, и “средним”. С другой стороны, в задачах классификации с несколькими метками экземпляр может иметь несколько выходов одновременно. Например, в задаче классификации текста, которую мы собираемся решить в этой статье, комментарий может иметь несколько тегов. Эти теги включают в себя “токсичные”, “непристойные”, “оскорбительные” и т. Д.

Набор данных

Набор данных содержит комментарии из Страница обсуждения Википедии правки. Существует шесть выходных меток для каждого комментария: токсичный, severe_toxic, непристойный, угроза, оскорбление и identity_hate. Комментарий может принадлежать всем этим категориям или подмножеству этих категорий, что делает его проблемой классификации с несколькими метками.

Набор данных для этой статьи можно скачать по этой ссылке Kaggle . Мы будем использовать только файл “train.csv”, содержащий 160 000 записей.

Загрузите CSV – файл в свой локальный каталог. Я переименовал файл в “toxic_comments.csv”. Вы можете дать ему любое имя, но только обязательно используйте это имя в своем коде.

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

from numpy import array
from keras.preprocessing.text import one_hot
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers.core import Activation, Dropout, Dense
from keras.layers import Flatten, LSTM
from keras.layers import GlobalMaxPooling1D
from keras.models import Model
from keras.layers.embeddings import Embedding
from sklearn.model_selection import train_test_split
from keras.preprocessing.text import Tokenizer
from keras.layers import Input
from keras.layers.merge import Concatenate

import pandas as pd
import numpy as np
import re

import matplotlib.pyplot as plt

Теперь давайте загрузим набор данных в память:

toxic_comments = pd.read_csv("/content/drive/My Drive/Colab Datasets/toxic_comments.csv")

Следующий сценарий отображает форму набора данных, а также печатает заголовок набора данных:

print(toxic_comments.shape)

toxic_comments.head()

Выход:

(159571,8)

Набор данных содержит 159571 запись и 8 столбцов. Заголовок набора данных выглядит следующим образом:

img1

Давайте удалим все записи, где любая строка содержит нулевое значение или пустую строку.

filter = toxic_comments["comment_text"] != ""
toxic_comments = toxic_comments[filter]
toxic_comments = toxic_comments.dropna()

Столбец comment_text содержит текстовые комментарии. Давайте напечатаем случайный комментарий, а затем посмотрим метки для комментариев.

print(toxic_comments["comment_text"][168])

Выход:

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.

Это явно ядовитый комментарий. Давайте посмотрим связанные метки с этим комментарием:

print("Toxic:" + str(toxic_comments["toxic"][168]))
print("Severe_toxic:" + str(toxic_comments["severe_toxic"][168]))
print("Obscene:" + str(toxic_comments["obscene"][168]))
print("Threat:" + str(toxic_comments["threat"][168]))
print("Insult:" + str(toxic_comments["insult"][168]))
print("Identity_hate:" + str(toxic_comments["identity_hate"][168]))

Выход:

Toxic:1
Severe_toxic:0
Obscene:0
Threat:0
Insult:1
Identity_hate:0

Теперь давайте построим график количества комментариев для каждой метки. Для этого мы сначала отфильтруем все метки или выходные столбцы.

toxic_comments_labels = toxic_comments[["toxic", "severe_toxic", "obscene", "threat", "insult", "identity_hate"]]
toxic_comments_labels.head()

Выход:

img2

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

fig_size = plt.rcParams["figure.figsize"]
fig_size[0] = 10
fig_size[1] = 8
plt.rcParams["figure.figsize"] = fig_size

toxic_comments_labels.sum(axis=0).plot.bar()

Выход:

img3

Вы можете видеть, что “токсичный” комментарий имеет самую высокую частоту встречаемости, за которым следуют “непристойный” и “оскорбительный” соответственно.

Мы успешно проанализировали наш набор данных, и в следующем разделе мы создадим модели классификации с несколькими метками, используя этот набор данных.

Создание Моделей Классификации Текста с Несколькими метками

Существует два способа создания моделей классификации с несколькими метками: использование одного плотного выходного слоя и использование нескольких плотных выходных слоев.

В первом подходе мы можем использовать один плотный слой с шестью выходами с сигмоидными функциями активации и бинарными функциями кросс-энтропийных потерь. Каждый нейрон в выходном плотном слое будет представлять одну из шести выходных меток. Сигмовидная функция активации возвращает значение от 0 до 1 для каждого нейрона. Если выходное значение какого-либо нейрона больше 0,5, предполагается, что комментарий принадлежит классу, представленному этим конкретным нейроном.

Во втором подходе мы создадим один плотный выходной слой для каждой метки. На выходе мы получим в общей сложности 6 плотных слоев. Каждый слой будет иметь свою сигмовидную функцию.

Модель классификации текста с несколькими метками и Одним Выходным слоем

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

def preprocess_text(sen):
    # Remove punctuations and numbers
    sentence = re.sub('[^a-zA-Z]', ' ', sen)

    # Single character removal
    sentence = re.sub(r"\s+[a-zA-Z]\s+", ' ', sentence)

    # Removing multiple spaces
    sentence = re.sub(r'\s+', ' ', sentence)

    return sentence

На следующем шаге мы создадим наш входной и выходной набор. Входными данными являются комментарии из столбца comment_text . Мы очистим все комментарии и сохраним их в переменной X . Метки или выходные данные уже были сохранены в фрейме данных toxic_comments_labels . Мы будем использовать эти значения фрейма данных для хранения выходных данных в переменной y . Посмотрите на следующий сценарий:

X = []
sentences = list(toxic_comments["comment_text"])
for sen in sentences:
    X.append(preprocess_text(sen))

y = toxic_comments_labels.values

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

На следующем этапе мы разделим наши данные на обучающие и тестовые наборы:

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)

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

tokenizer = Tokenizer(num_words=5000)
tokenizer.fit_on_texts(X_train)

X_train = tokenizer.texts_to_sequences(X_train)
X_test = tokenizer.texts_to_sequences(X_test)

vocab_size = len(tokenizer.word_index) + 1

maxlen = 200

X_train = pad_sequences(X_train, padding='post', maxlen=maxlen)
X_test = pad_sequences(X_test, padding='post', maxlen=maxlen)

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

from numpy import array
from numpy import asarray
from numpy import zeros

embeddings_dictionary = dict()

glove_file = open('/content/drive/My Drive/Colab Datasets/glove.6B.100d.txt', encoding="utf8")

for line in glove_file:
    records = line.split()
    word = records[0]
    vector_dimensions = asarray(records[1:], dtype='float32')
    embeddings_dictionary[word] = vector_dimensions
glove_file.close()

embedding_matrix = zeros((vocab_size, 100))
for word, index in tokenizer.word_index.items():
    embedding_vector = embeddings_dictionary.get(word)
    if embedding_vector is not None:
        embedding_matrix[index] = embedding_vector

Следующий сценарий создает модель. Наша модель будет иметь один входной слой, один слой встраивания, один слой LSTM со 128 нейронами и один выходной слой с 6 нейронами, так как у нас есть 6 меток на выходе.

deep_inputs = Input(shape=(maxlen,))
embedding_layer = Embedding(vocab_size, 100, weights=[embedding_matrix], trainable=False)(deep_inputs)
LSTM_Layer_1 = LSTM(128)(embedding_layer)
dense_layer_1 = Dense(6, activation='sigmoid')(LSTM_Layer_1)
model = Model(inputs=deep_inputs, outputs=dense_layer_1)

model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc'])

Давайте распечатаем сводку модели:

print(model.summary())

Выход:

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
input_1 (InputLayer)         (None, 200)               0
_________________________________________________________________
embedding_1 (Embedding)      (None, 200, 100)          14824300
_________________________________________________________________
lstm_1 (LSTM)                (None, 128)               117248
_________________________________________________________________
dense_1 (Dense)              (None, 6)                 774
=================================================================
Total params: 14,942,322
Trainable params: 118,022
Non-trainable params: 14,824,300

Следующий скрипт печатает архитектуру нашей нейронной сети:

from keras.utils import plot_model
plot_model(model, to_file='model_plot4a.png', show_shapes=True, show_layer_names=True)

Выход:

img4

Из рисунка выше видно, что выходной слой содержит только 1 плотный слой с 6 нейронами. Давайте теперь потренируем нашу модель:

history = model.fit(X_train, y_train, batch_size=128, epochs=5, verbose=1, validation_split=0.2)

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

Результат для всех 5 эпох выглядит следующим образом:

rain on 102124 samples, validate on 25532 samples
Epoch 1/5
102124/102124 [==============================] - 245s 2ms/step - loss: 0.1437 - acc: 0.9634 - val_loss: 0.1361 - val_acc: 0.9631
Epoch 2/5
102124/102124 [==============================] - 245s 2ms/step - loss: 0.0763 - acc: 0.9753 - val_loss: 0.0621 - val_acc: 0.9788
Epoch 3/5
102124/102124 [==============================] - 243s 2ms/step - loss: 0.0588 - acc: 0.9800 - val_loss: 0.0578 - val_acc: 0.9802
Epoch 4/5
102124/102124 [==============================] - 246s 2ms/step - loss: 0.0559 - acc: 0.9807 - val_loss: 0.0571 - val_acc: 0.9801
Epoch 5/5
102124/102124 [==============================] - 245s 2ms/step - loss: 0.0528 - acc: 0.9813 - val_loss: 0.0554 - val_acc: 0.9807

Давайте теперь оценим нашу модель на тестовом наборе:

score = model.evaluate(X_test, y_test, verbose=1)

print("Test Score:", score[0])
print("Test Accuracy:", score[1])

Выход:

31915/31915 [==============================] - 108s 3ms/step
Test Score: 0.054090796736467786
Test Accuracy: 0.9810642735274182

Наша модель достигает точности около 98%, что довольно впечатляет.

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

import matplotlib.pyplot as plt

plt.plot(history.history['acc'])
plt.plot(history.history['val_acc'])

plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train','test'], loc='upper left')
plt.show()

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])

plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train','test'], loc='upper left')
plt.show()

Выход:

5

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

Модель классификации текста с несколькими метками и несколькими выходными слоями

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

def preprocess_text(sen):
    # Remove punctuations and numbers
    sentence = re.sub('[^a-zA-Z]', ' ', sen)

    # Single character removal
    sentence = re.sub(r"\s+[a-zA-Z]\s+", ' ', sentence)

    # Removing multiple spaces
    sentence = re.sub(r'\s+', ' ', sentence)

    return sentence

Второй шаг-создание входных и выходных данных для модели. Входными данными для модели будут текстовые комментарии, а выходными-шесть меток. Следующий сценарий создает входной слой и объединенный выходной слой:

X = []
sentences = list(toxic_comments["comment_text"])
for sen in sentences:
    X.append(preprocess_text(sen))

y = toxic_comments[["toxic", "severe_toxic", "obscene", "threat", "insult", "identity_hate"]]

Давайте разделим данные на обучающие и тестовые наборы:

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)

Переменная y содержит комбинированный вывод из 6 меток. Однако мы хотим создать отдельный выходной слой для каждой метки. Мы создадим 6 переменных, которые хранят отдельные метки из обучающих данных, и 6 переменных, которые хранят отдельные значения меток для тестовых данных.

Посмотрите на следующий сценарий:

# First output
y1_train = y_train[["toxic"]].values
y1_test =  y_test[["toxic"]].values

# Second output
y2_train = y_train[["severe_toxic"]].values
y2_test =  y_test[["severe_toxic"]].values

# Third output
y3_train = y_train[["obscene"]].values
y3_test =  y_test[["obscene"]].values

# Fourth output
y4_train = y_train[["threat"]].values
y4_test =  y_test[["threat"]].values

# Fifth output
y5_train = y_train[["insult"]].values
y5_test =  y_test[["insult"]].values

# Sixth output
y6_train = y_train[["identity_hate"]].values
y6_test =  y_test[["identity_hate"]].values

Следующим шагом является преобразование текстовых входных данных во встроенные векторы. Это делает следующий сценарий:

tokenizer = Tokenizer(num_words=5000)
tokenizer.fit_on_texts(X_train)

X_train = tokenizer.texts_to_sequences(X_train)
X_test = tokenizer.texts_to_sequences(X_test)

vocab_size = len(tokenizer.word_index) + 1

maxlen = 200

X_train = pad_sequences(X_train, padding='post', maxlen=maxlen)
X_test = pad_sequences(X_test, padding='post', maxlen=maxlen)

Здесь мы снова будем использовать перчатку слово вложения:

glove_file = open('/content/drive/My Drive/Colab Datasets/glove.6B.100d.txt', encoding="utf8")

for line in glove_file:
    records = line.split()
    word = records[0]
    vector_dimensions = asarray(records[1:], dtype='float32')
    embeddings_dictionary[word] = vector_dimensions
glove_file.close()

embedding_matrix = zeros((vocab_size, 100))
for word, index in tokenizer.word_index.items():
    embedding_vector = embeddings_dictionary.get(word)
    if embedding_vector is not None:
        embedding_matrix[index] = embedding_vector

Сейчас самое время создать нашу модель. Наша модель будет иметь один входной слой, один слой встраивания, за которым последует один слой LSTM со 128 нейронами. Выходные данные слоя LSTM будут использоваться в качестве входных данных для 6 плотных выходных слоев. Каждый выходной слой будет иметь 1 нейрон с сигмовидной активационной функцией. Каждый вывод будет предсказывать целочисленное значение от 1 до 0 для соответствующей метки.

Следующий скрипт создает нашу модель:

input_1 = Input(shape=(maxlen,))
embedding_layer = Embedding(vocab_size, 100, weights=[embedding_matrix], trainable=False)(input_1)
LSTM_Layer1 = LSTM(128)(embedding_layer)

output1 = Dense(1, activation='sigmoid')(LSTM_Layer1)
output2 = Dense(1, activation='sigmoid')(LSTM_Layer1)
output3 = Dense(1, activation='sigmoid')(LSTM_Layer1)
output4 = Dense(1, activation='sigmoid')(LSTM_Layer1)
output5 = Dense(1, activation='sigmoid')(LSTM_Layer1)
output6 = Dense(1, activation='sigmoid')(LSTM_Layer1)

model = Model(inputs=input_1, outputs=[output1, output2, output3, output4, output5, output6])
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['acc'])

Следующий сценарий печатает сводку модели:

print(model.summary())

Выход:

__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to
==================================================================================================
input_1 (InputLayer)            (None, 200)          0
__________________________________________________________________________________________________
embedding_1 (Embedding)         (None, 200, 100)     14824300    input_1[0][0]
__________________________________________________________________________________________________
lstm_1 (LSTM)                   (None, 128)          117248      embedding_1[0][0]
__________________________________________________________________________________________________
dense_1 (Dense)                 (None, 1)            129         lstm_1[0][0]
__________________________________________________________________________________________________
dense_2 (Dense)                 (None, 1)            129         lstm_1[0][0]
__________________________________________________________________________________________________
dense_3 (Dense)                 (None, 1)            129         lstm_1[0][0]
__________________________________________________________________________________________________
dense_4 (Dense)                 (None, 1)            129         lstm_1[0][0]
__________________________________________________________________________________________________
dense_5 (Dense)                 (None, 1)            129         lstm_1[0][0]
__________________________________________________________________________________________________
dense_6 (Dense)                 (None, 1)            129         lstm_1[0][0]
==================================================================================================
Total params: 14,942,322
Trainable params: 118,022
Non-trainable params: 14,824,300

И следующий скрипт печатает архитектуру нашей модели:

from keras.utils import plot_model
plot_model(model, to_file='model_plot4b.png', show_shapes=True, show_layer_names=True)

Выход:

img6

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

Давайте теперь потренируем нашу модель:

history = model.fit(x=X_train, y=[y1_train, y2_train, y3_train, y4_train, y5_train, y6_train], batch_size=8192, epochs=5, verbose=1, validation_split=0.2)

Я пытался запустить модель в течение пяти эпох, но она ужасно переоснащала набор валидации. Я увеличил размер партии, но все равно точность теста была не так хороша. Одна из возможных причин переоснащения заключается в том, что в данном случае мы имеем индивидуальный выходной слой для каждой метки, что увеличивает сложность нашей модели. Увеличение сложности модели часто приводит к переоснащению.

Результат для каждой эпохи показан ниже:

Выход:

Train on 102124 samples, validate on 25532 samples
Epoch 1/5
102124/102124 [==============================] - 24s 239us/step - loss: 3.5116 - dense_1_loss: 0.6017 - dense_2_loss: 0.5806 - dense_3_loss: 0.6150 - dense_4_loss: 0.5585 - dense_5_loss: 0.5828 - dense_6_loss: 0.5730 - dense_1_acc: 0.9029 - dense_2_acc: 0.9842 - dense_3_acc: 0.9444 - dense_4_acc: 0.9934 - dense_5_acc: 0.9508 - dense_6_acc: 0.9870 - val_loss: 1.0369 - val_dense_1_loss: 0.3290 - val_dense_2_loss: 0.0983 - val_dense_3_loss: 0.2571 - val_dense_4_loss: 0.0595 - val_dense_5_loss: 0.1972 - val_dense_6_loss: 0.0959 - val_dense_1_acc: 0.9037 - val_dense_2_acc: 0.9901 - val_dense_3_acc: 0.9469 - val_dense_4_acc: 0.9966 - val_dense_5_acc: 0.9509 - val_dense_6_acc: 0.9901
Epoch 2/5
102124/102124 [==============================] - 20s 197us/step - loss: 0.9084 - dense_1_loss: 0.3324 - dense_2_loss: 0.0679 - dense_3_loss: 0.2172 - dense_4_loss: 0.0338 - dense_5_loss: 0.1983 - dense_6_loss: 0.0589 - dense_1_acc: 0.9043 - dense_2_acc: 0.9899 - dense_3_acc: 0.9474 - dense_4_acc: 0.9968 - dense_5_acc: 0.9510 - dense_6_acc: 0.9915 - val_loss: 0.8616 - val_dense_1_loss: 0.3164 - val_dense_2_loss: 0.0555 - val_dense_3_loss: 0.2127 - val_dense_4_loss: 0.0235 - val_dense_5_loss: 0.1981 - val_dense_6_loss: 0.0554 - val_dense_1_acc: 0.9038 - val_dense_2_acc: 0.9900 - val_dense_3_acc: 0.9469 - val_dense_4_acc: 0.9965 - val_dense_5_acc: 0.9509 - val_dense_6_acc: 0.9900
Epoch 3/5
102124/102124 [==============================] - 20s 199us/step - loss: 0.8513 - dense_1_loss: 0.3179 - dense_2_loss: 0.0566 - dense_3_loss: 0.2103 - dense_4_loss: 0.0216 - dense_5_loss: 0.1960 - dense_6_loss: 0.0490 - dense_1_acc: 0.9043 - dense_2_acc: 0.9899 - dense_3_acc: 0.9474 - dense_4_acc: 0.9968 - dense_5_acc: 0.9510 - dense_6_acc: 0.9915 - val_loss: 0.8552 - val_dense_1_loss: 0.3158 - val_dense_2_loss: 0.0566 - val_dense_3_loss: 0.2074 - val_dense_4_loss: 0.0225 - val_dense_5_loss: 0.1960 - val_dense_6_loss: 0.0568 - val_dense_1_acc: 0.9038 - val_dense_2_acc: 0.9900 - val_dense_3_acc: 0.9469 - val_dense_4_acc: 0.9965 - val_dense_5_acc: 0.9509 - val_dense_6_acc: 0.9900
Epoch 4/5
102124/102124 [==============================] - 20s 198us/step - loss: 0.8442 - dense_1_loss: 0.3153 - dense_2_loss: 0.0570 - dense_3_loss: 0.2061 - dense_4_loss: 0.0213 - dense_5_loss: 0.1952 - dense_6_loss: 0.0493 - dense_1_acc: 0.9043 - dense_2_acc: 0.9899 - dense_3_acc: 0.9474 - dense_4_acc: 0.9968 - dense_5_acc: 0.9510 - dense_6_acc: 0.9915 - val_loss: 0.8527 - val_dense_1_loss: 0.3156 - val_dense_2_loss: 0.0558 - val_dense_3_loss: 0.2074 - val_dense_4_loss: 0.0226 - val_dense_5_loss: 0.1951 - val_dense_6_loss: 0.0561 - val_dense_1_acc: 0.9038 - val_dense_2_acc: 0.9900 - val_dense_3_acc: 0.9469 - val_dense_4_acc: 0.9965 - val_dense_5_acc: 0.9509 - val_dense_6_acc: 0.9900
Epoch 5/5
102124/102124 [==============================] - 20s 197us/step - loss: 0.8410 - dense_1_loss: 0.3146 - dense_2_loss: 0.0561 - dense_3_loss: 0.2055 - dense_4_loss: 0.0213 - dense_5_loss: 0.1948 - dense_6_loss: 0.0486 - dense_1_acc: 0.9043 - dense_2_acc: 0.9899 - dense_3_acc: 0.9474 - dense_4_acc: 0.9968 - dense_5_acc: 0.9510 - dense_6_acc: 0.9915 - val_loss: 0.8501 - val_dense_1_loss: 0.3153 - val_dense_2_loss: 0.0553 - val_dense_3_loss: 0.2069 - val_dense_4_loss: 0.0226 - val_dense_5_loss: 0.1948 - val_dense_6_loss: 0.0553 - val_dense_1_acc: 0.9038 - val_dense_2_acc: 0.9900 - val_dense_3_acc: 0.9469 - val_dense_4_acc: 0.9965 - val_dense_5_acc: 0.9509 - val_dense_6_acc: 0.9900

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

Теперь давайте оценим производительность нашей модели на тестовом наборе:

score = model.evaluate(x=X_test, y=[y1_test, y2_test, y3_test, y4_test, y5_test, y6_test], verbose=1)

print("Test Score:", score[0])
print("Test Accuracy:", score[1])

Выход:

31915/31915 [==============================] - 111s 3ms/step
Test Score: 0.8471985269747015
Test Accuracy: 0.31425264998511726

Точность всего 31% достигается на тестовом наборе с помощью нескольких выходных слоев.

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

import matplotlib.pyplot as plt

plt.plot(history.history['dense_1_acc'])
plt.plot(history.history['val_dense_1_acc'])

plt.title('model accuracy')
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.legend(['train','test'], loc='upper left')
plt.show()

plt.plot(history.history['dense_1_loss'])
plt.plot(history.history['val_dense_1_loss'])

plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train','test'], loc='upper left')
plt.show()

Выход:

img7

Из выходных данных видно, что точность для тестового (валидационного) набора не сходится после первых эпох. Кроме того, разница между точностью обучения и валидации очень мала. Таким образом, модель начинает перестраиваться после первых эпох, и, следовательно, мы получаем плохую производительность на невидимом тестовом наборе.

Вывод

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

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

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