Python para NLP: clasificación de texto multietiqueta con Keras

Este es el artículo 19 de mi serie de artículos sobre Python para PNL. Desde los últimos artículos, hemos estado explorando conceptos de PNL bastante avanzados basados ​​en d...

Introducción

Este es el artículo 19 de mi serie de artículos sobre Python para PNL. Desde los últimos artículos, hemos estado explorando conceptos de PNL bastante avanzados basados ​​en técnicas de aprendizaje profundo. En el último artículo, vimos cómo crear un modelo de clasificación de texto entrenado usando múltiples entradas de diferentes tipos de datos. Desarrollamos un predictor de sentimiento de texto utilizando entradas de texto más metainformación.

En este artículo, veremos cómo desarrollar un modelo de clasificación de texto con múltiples salidas. Desarrollaremos un modelo de clasificación de texto que analice un comentario textual y prediga múltiples etiquetas asociadas con el comentario. El problema de clasificación de etiquetas múltiples es en realidad un subconjunto del modelo de salida múltiple. Al final de este artículo, podrá realizar una clasificación de texto de etiquetas múltiples en sus datos.

El enfoque explicado en este artículo se puede ampliar para realizar una clasificación general de etiquetas múltiples. Por ejemplo, puede resolver un problema de clasificación en el que tiene una imagen como entrada y desea predecir la categoría de la imagen y la descripción de la imagen.

En este punto, es importante explicar la diferencia entre un problema de clasificación multiclase y una clasificación multietiqueta. En un problema de clasificación de clases múltiples, una instancia o un registro puede pertenecer a una y solo una de las múltiples clases de salida. Por ejemplo, en el problema de análisis de sentimientos que estudiamos en el último artículo, una reseña de texto podría ser "buena", "mala" o "promedio". No podía ser "bueno" y "promedio" al mismo tiempo. Por otro lado, en problemas de clasificación de múltiples etiquetas, una instancia puede tener múltiples salidas al mismo tiempo. Por ejemplo, en el problema de clasificación de texto que vamos a resolver en este artículo, un comentario puede tener varias etiquetas. Estas etiquetas incluyen "tóxico", "obsceno", "insultante", etc., al mismo tiempo.

El conjunto de datos

El conjunto de datos contiene comentarios de ediciones de página de discusión de Wikipedia. Hay seis etiquetas de salida para cada comentario: tóxico, grave_tóxico, obsceno, amenaza, insulto e identidad_odio. Un comentario puede pertenecer a todas estas categorías o a un subconjunto de estas categorías, lo que lo convierte en un problema de clasificación de etiquetas múltiples.

El conjunto de datos para este artículo se puede descargar desde este Enlace Kaggle. Solo usaremos el archivo "train.csv" que contiene 160.000 registros.

Descargue el archivo CSV en su directorio local. He renombrado el archivo como "toxic_comments.csv". Puede darle cualquier nombre, pero asegúrese de usar ese nombre en su código.

Importemos ahora las bibliotecas requeridas y carguemos el conjunto de datos en nuestra aplicación. El siguiente script importa las bibliotecas requeridas:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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

Ahora carguemos el conjunto de datos en la memoria:

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

El siguiente script muestra la forma del conjunto de datos y también imprime el encabezado del conjunto de datos:

1
2
3
print(toxic_comments.shape)

toxic_comments.head()

Producción:

1
(159571,8)

El conjunto de datos contiene 159571 registros y 8 columnas. El encabezado del conjunto de datos se ve así:

img1

Eliminemos todos los registros donde cualquier fila contenga un valor nulo o una cadena vacía.

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

La columna comment_text contiene comentarios de texto. Imprimamos un comentario aleatorio y luego veamos las etiquetas de los comentarios.

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

Producción:

1
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.

Este es claramente un comentario tóxico. Veamos las etiquetas asociadas a este comentario:

1
2
3
4
5
6
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]))

Producción:

1
2
3
4
5
6
Toxic:1
Severe_toxic:0
Obscene:0
Threat:0
Insult:1
Identity_hate:0

Ahora tracemos el recuento de comentarios para cada etiqueta. Para ello, primero filtraremos todas las etiquetas o columnas de salida.

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

Producción:

img2

Usando el marco de datos toxic_comments_labels trazaremos diagramas de barras que muestran el recuento total de comentarios para diferentes etiquetas.

1
2
3
4
5
6
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()

Producción:

img3

Puedes ver que el comentario "tóxico" tiene la mayor frecuencia de aparición seguido de "obsceno" e "insulto", respectivamente.

Hemos analizado con éxito nuestro conjunto de datos, en la siguiente sección crearemos modelos de clasificación de etiquetas múltiples utilizando este conjunto de datos.

Creación de modelos de clasificación de texto de etiquetas múltiples

Hay dos formas de crear modelos de clasificación de etiquetas múltiples: usando una sola capa de salida densa y usando varias capas de salida densa.

En el primer enfoque, podemos usar una sola capa densa con seis salidas con funciones de activación sigmoide y funciones de pérdida de entropía cruzada binaria. Cada neurona en la capa densa de salida representará una de las seis etiquetas de salida. La función de activación sigmoidea devolverá un valor entre 0 y 1 para cada neurona. Si el valor de salida de cualquier neurona es superior a 0,5, se supone que el comentario pertenece a la clase representada por esa neurona en particular.

En el segundo enfoque, crearemos una capa de salida densa para cada etiqueta. Tendremos un total de 6 capas densas en la salida. Cada capa tendrá su propia función sigmoidea.

Modelo de clasificación de texto de varias etiquetas con una sola capa de salida

En esta sección, crearemos un modelo de clasificación de texto de múltiples etiquetas con una sola capa de salida. Como siempre, el primer paso en el modelo de clasificación de texto es crear una función responsable de limpiar el texto.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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

En el siguiente paso, crearemos nuestro conjunto de entrada y salida. La entrada es el comentario de la columna comment_text. Limpiaremos todos los comentarios y los guardaremos en la variable X. Las etiquetas o resultados ya se han almacenado en el marco de datos toxic_comments_labels. Usaremos esos valores de marco de datos para almacenar la salida en la variable y. Mira el siguiente guión:

1
2
3
4
5
6
X = []
sentences = list(toxic_comments["comment_text"])
for sen in sentences:
    X.append(preprocess_text(sen))

y = toxic_comments_labels.values

Aquí no necesitamos realizar ninguna codificación one-hot porque nuestras etiquetas de salida ya están en forma de vectores codificados one-hot.

En el siguiente paso, dividiremos nuestros datos en conjuntos de entrenamiento y prueba:

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

We need to convert text inputs into embedded vectors. To understand word embeddings in detail, please refer to mi artículo sobre incrustaciones de palabras.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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)

Usaremos incrustaciones de palabras GloVe para convertir entradas de texto a sus contrapartes numéricas.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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

El siguiente script crea el modelo. Nuestro modelo tendrá una capa de entrada, una capa de incrustación, una capa LSTM con 128 neuronas y una capa de salida con 6 neuronas ya que tenemos 6 etiquetas en la salida.

1
2
3
4
5
6
7
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'])

Imprimamos el resumen del modelo:

1
print(model.summary())

Producción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
_________________________________________________________________
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

El siguiente script imprime la arquitectura de nuestra red neuronal:

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

Producción:

img4

En la figura anterior, puede ver que la capa de salida solo contiene 1 capa densa con 6 neuronas. Ahora entrenemos nuestro modelo:

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

Entrenaremos nuestro modelo durante 5 épocas. Puedes entrenar el modelo con más épocas y ver si obtienes mejores o peores resultados.

El resultado para las 5 épocas es el siguiente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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

Ahora vamos a evaluar nuestro modelo en el conjunto de prueba:

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

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

Producción:

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

Nuestro modelo logra una precisión de alrededor del 98%, lo cual es bastante impresionante.

Finalmente, trazaremos los valores de pérdida y precisión para los conjuntos de entrenamiento y prueba para ver si nuestro modelo se está sobreajustando.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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()

Producción:

5

Puede ver que el modelo no se sobreajusta en el conjunto de validación.

Modelo de clasificación de texto de varias etiquetas con varias capas de salida {#modelo de clasificación de texto de varias etiquetas con varias capas de salida}

En esta sección, crearemos un modelo de clasificación de texto de etiquetas múltiples donde cada etiqueta de salida tendrá una capa densa de salida dedicada. Primero definamos nuestra función de preprocesamiento:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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

El segundo paso es crear entradas y salidas para el modelo. La entrada al modelo serán los comentarios de texto, mientras que la salida serán seis etiquetas. El siguiente script crea la capa de entrada y la capa de salida combinada:

1
2
3
4
5
6
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"]]

Dividamos los datos en conjuntos de entrenamiento y prueba:

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

La variable y contiene la salida combinada de 6 etiquetas. Sin embargo, queremos crear una capa de salida individual para cada etiqueta. Crearemos 6 variables que almacenen etiquetas individuales a partir de los datos de entrenamiento y 6 variables que almacenen valores de etiquetas individuales para los datos de prueba.

Mira el siguiente guión:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 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

El siguiente paso es convertir las entradas de texto en vectores incrustados. El siguiente script hace eso:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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)

Aquí nuevamente usaremos las incrustaciones de palabras GloVe:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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

Ahora es el momento de crear nuestro modelo. Nuestro modelo tendrá una capa de entrada, una capa de incrustación seguida de una capa LSTM con 128 neuronas. La salida de la capa LSTM se utilizará como entrada para las 6 capas de salida densas. Cada capa de salida tendrá 1 neurona con función de activación sigmoidea. Cada salida predecirá un valor entero entre 1 y 0 para la etiqueta correspondiente.

El siguiente script crea nuestro modelo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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'])

El siguiente script imprime el resumen del modelo:

1
print(model.summary())

Producción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
__________________________________________________________________________________________________
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

Y el siguiente script imprime la arquitectura de nuestro modelo:

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

Producción:

img6

Puede ver que tenemos 6 capas de salida diferentes. La figura anterior explica claramente la diferencia entre el modelo con una sola capa de entrada que creamos en la última sección y el modelo con múltiples capas de salida.

Ahora entrenemos nuestro modelo:

1
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)

Traté de ejecutar el modelo durante cinco épocas, pero se sobreajustaba terriblemente en el conjunto de validación. Aumenté el tamaño del lote, pero aún así la precisión de la prueba no fue tan buena. Una de las posibles razones del sobreajuste es que aquí, en este caso, tenemos una capa de salida individual para cada etiqueta, lo que aumenta la complejidad de nuestro modelo. El aumento de la complejidad del modelo a menudo conduce a un sobreajuste.

El resultado para cada época se muestra a continuación:

Producción:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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

Puede ver que para cada época, tenemos valores de pérdida, pérdida de valor, precisión y precisión de valor para las 6 capas densas en la salida.

Ahora vamos a evaluar el rendimiento de nuestro modelo en el conjunto de prueba:

1
2
3
4
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])

Producción:

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

Se logra una precisión de solo el 31% en el equipo de prueba a través de múltiples capas de salida.

El siguiente script traza los valores de pérdida y precisión para conjuntos de entrenamiento y validación para la primera capa densa.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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()

Producción:

img7

A partir de la salida, puede ver que la precisión para el conjunto de prueba (validación) no converge después de las primeras épocas. Además, la diferencia entre la precisión del entrenamiento y la validación es mínima. Por lo tanto, el modelo comienza a sobreajustarse después de las primeras épocas y, por lo tanto, obtenemos un rendimiento deficiente en el conjunto de prueba no visto.

Yendo más lejos: proyecto de extremo a extremo portátil

¿Tu naturaleza inquisitiva te hace querer ir más allá? Recomendamos consultar nuestro Proyecto guiado: ["Subtítulos de imágenes con CNN y Transformers con Keras"](https://wikihtp.com/courses/image-captioning-with-cnns-and -transformadores-con-keras/#cta){target="_blank"}.

En este proyecto guiado, aprenderá a crear un modelo de subtítulos de imágenes, que acepta una imagen como entrada y produce un subtítulo de texto como salida.

Aprenderás a:

  • Preprocesar texto
  • Vectorizar la entrada de texto fácilmente
  • Trabaje con la API tf.data y cree conjuntos de datos de alto rendimiento
  • Cree Transformers desde cero con TensorFlow/Keras y KerasNLP: la adición horizontal oficial a Keras para crear modelos NLP de última generación
  • Cree arquitecturas híbridas donde la salida de una red se codifica para otra

¿Cómo enmarcamos los subtítulos de las imágenes? La mayoría lo considera un ejemplo de aprendizaje profundo generativo, porque estamos enseñando a una red a generar descripciones. Sin embargo, me gusta verlo como una instancia de traducción automática neuronal: estamos traduciendo las características visuales de una imagen en palabras. A través de la traducción, estamos generando una nueva representación de esa imagen, en lugar de simplemente generar un nuevo significado. Verlo como traducción, y solo por generación de extensión, enfoca la tarea bajo una luz diferente y la hace un poco más intuitiva.

Enmarcar el problema como uno de traducción hace que sea más fácil averiguar qué arquitectura querremos usar. Los transformadores solo de codificador son excelentes para comprender el texto (análisis de opinión, clasificación, etc.) porque los codificadores codifican representaciones significativas. Los modelos de solo decodificador son excelentes para la generación (como GPT-3), ya que los decodificadores pueden inferir representaciones significativas en otra secuencia con el mismo significado. La traducción generalmente se realiza mediante una arquitectura de codificador-decodificador, donde los codificadores codifican una representación significativa de una oración (o imagen, en nuestro caso) y los decodificadores aprenden a convertir esta secuencia en otra representación significativa que es más interpretable para nosotros (como una oración).

Conclusión

La clasificación de texto de etiquetas múltiples es uno de los problemas de clasificación de texto más comunes. En este artículo, estudiamos dos enfoques de aprendizaje profundo para la clasificación de texto de etiquetas múltiples. En el primer enfoque, usamos una sola capa de salida densa con múltiples neuronas donde cada neurona representaba una etiqueta.

En el segundo enfoque, creamos capas densas separadas para cada etiqueta con una neurona. Los resultados muestran que, en nuestro caso, una sola capa de salida con varias neuronas funciona mejor que varias capas de salida.

Como siguiente paso, le aconsejo que cambie la función de activación y la división de prueba del tren para ver si puede obtener mejores resultados que los presentados en este artículo.