Este es el artículo número 18 de mi serie de artículos sobre Python para PNL. En mi artículo anterior, expliqué cómo crear un modelo de análisis de sentimientos de películas basado en aprendizaje profundo utilizando la biblioteca Keras de Python . En ese artículo, vimos cómo podemos realizar análisis de opinión de las opiniones de los usuarios con respecto a diferentes películas en IMDB. Usamos el texto de la revisión para predecir el sentimiento.
Sin embargo, en las tareas de clasificación de textos, también podemos hacer uso de la información no textual para clasificar el texto. Por ejemplo, el género puede tener un impacto en el sentimiento de la revisión. Además, las nacionalidades pueden afectar la opinión pública sobre una película en particular. Por lo tanto, esta información asociada, también conocida como metadatos, también se puede utilizar para mejorar la precisión del modelo estadístico.
En este artículo, nos basaremos en los conceptos que estudiamos en los dos últimos artículos y veremos cómo crear un sistema de clasificación de texto que clasifique las reseñas de los usuarios sobre diferentes negocios, en una de las tres categorías predefinidas, es decir, «bueno», «malo». «y» promedio «. Sin embargo, además del texto de la revisión, utilizaremos los metadatos asociados de la revisión para realizar la clasificación. Dado que tenemos dos tipos diferentes de entradas, es decir, entrada textual y entrada numérica, necesitamos crear un modelo de entradas múltiples. Utilizaremos la API funcional de Keras, ya que admite múltiples entradas y múltiples modelos de salida.
Después de leer este artículo, podrá crear un modelo de aprendizaje profundo en Keras que sea capaz de aceptar múltiples entradas, concatenar las dos salidas y luego realizar una clasificación o regresión utilizando la entrada agregada.
- El conjunto de datos
- Crear un modelo solo con entradas de texto
- Crear un modelo solo con metainformación
- Crear un modelo con múltiples entradas
- Reflexiones finales y mejoras
Antes de sumergirnos en los detalles de la creación de dicho modelo, primero revisemos brevemente el conjunto de datos que vamos a utilizar.
El conjunto de datos
Contenido
El conjunto de datos de este artículo se puede descargar desde este enlace de Kaggle . El conjunto de datos contiene varios archivos, pero solo nos interesa el yelp_review.csv
archivo. El archivo contiene más de 5.2 millones de reseñas sobre diferentes negocios, incluidos restaurantes, bares, dentistas, médicos, salones de belleza, etc. Para nuestros propósitos, solo usaremos los primeros 50,000 registros para entrenar nuestro modelo. Descargue el conjunto de datos en su máquina local.
Primero importemos todas las bibliotecas que usaremos en este artículo antes de importar el conjunto de datos.
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
Como primer paso, necesitamos cargar el conjunto de datos. El siguiente script hace eso:
yelp_reviews = pd.read_csv("/content/drive/My Drive/yelp_review_short.csv")
El conjunto de datos contiene una columna Stars
que contiene calificaciones para diferentes empresas. La columna «Estrellas» puede tener valores entre 1 y 5. Simplificaremos nuestro problema convirtiendo los valores numéricos de las reseñas en categóricos. Agregaremos una nueva columna reviews_score
a nuestro conjunto de datos. Si la reseña del usuario tiene un valor de 1 en la Stars
columna, la reviews_score
columna tendrá un valor de cadena bad
. Si la calificación es 2 o 3 en la Stars
columna, la reviews_score
columna contendrá un valor average
. Finalmente, la calificación de revisión de 4 o 5 tendrá un valor correspondiente de good
en la reviews_score
columna.
El siguiente script realiza este preprocesamiento:
bins = [0,1,3,5]
review_names = ['bad', 'average', 'good']
yelp_reviews['reviews_score'] = pd.cut(yelp_reviews['stars'], bins, labels=review_names)
A continuación, eliminaremos todos los valores NULL de nuestro marco de datos e imprimiremos la forma y el encabezado del conjunto de datos.
yelp_reviews.isnull().values.any()
print(yelp_reviews.shape)
yelp_reviews.head()
En la salida verá (50000,10)
, lo que significa que nuestro conjunto de datos contiene 50.000 registros con 10 columnas. El encabezado del yelp_reviews
marco de datos se ve así:
Puede ver las 10 columnas que contiene nuestro marco de datos, incluida la reviews_score
columna recién agregada . La text
columna contiene el texto de la reseña, mientras que la useful
columna contiene un valor numérico que representa el recuento de personas que encontraron útil la reseña. De manera similar, las columnas funny
y cool
contienen los recuentos de personas que encontraron reseñas funny
o cool
, respectivamente.
Elijamos una revisión al azar. Si observa la cuarta revisión (revisión con índice 3), tiene 4 estrellas y, por lo tanto, está marcada como good
. Veamos el texto completo de esta revisión:
print(yelp_reviews["text"][3])
La salida se ve así:
Love coming here. Yes the place always needs the floor swept but when you give out peanuts in the shell how won't it always be a bit dirty.
The food speaks for itself, so good. Burgers are made to order and the meat is put on the grill when you order your sandwich. Getting the small burger just means 1 patty, the regular is a 2 patty burger which is twice the deliciousness.
Getting the Cajun fries adds a bit of spice to them and whatever size you order they always throw more fries (a lot more fries) into the bag.
Puede ver claramente que esta es una revisión positiva.
Te puede interesar:Minimax con poda alfa-beta en PythonAhora vamos a trazar el número de good
, average
y bad
comentarios.
import seaborn as sns
sns.countplot(x='reviews_score', data=yelp_reviews)
Es evidente a partir del gráfico anterior que la mayoría de las críticas son buenas, seguidas de las críticas promedio. El número de críticas negativas es muy reducido.
Hemos preprocesado nuestros datos y ahora crearemos tres modelos en este artículo. El primer modelo sólo utiliza las entradas de texto para predecir si una revisión es good
, average
o bad
. En el segundo modelo, no usaremos texto. Sólo utilizaremos la información de metadatos, tales como useful
, funny
y cool
para predecir el sentimiento de la revisión. Finalmente, crearemos un modelo que acepta múltiples entradas, es decir, texto y metainformación para la clasificación de texto.
Crear un modelo solo con entradas de texto
El primer paso es definir una función que limpie los datos textuales.
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
Dado que solo estamos usando texto en este modelo, filtraremos todas las reseñas de texto y las almacenaremos en la lista. Las revisiones de texto se limpiarán mediante la preprocess_text
función, que elimina las puntuaciones y los números del texto.
X = []
sentences = list(yelp_reviews["text"])
for sen in sentences:
X.append(preprocess_text(sen))
y = yelp_reviews['reviews_score']
Nuestra X
variable aquí contiene las revisiones de texto, mientras que la y
variable contiene los reviews_score
valores correspondientes . La reviews_score
columna tiene datos en formato de texto. Necesitamos convertir el texto en un vector codificado en caliente. Podemos usar el to_categorical
método del keras.utils
módulo. Sin embargo, primero tenemos que convertir el texto en etiquetas enteras usando la LabelEncoder
función del sklearn.preprocessing
módulo.
from sklearn import preprocessing
# label_encoder object knows how to understand word labels.
label_encoder = preprocessing.LabelEncoder()
# Encode labels in column 'species'.
y = label_encoder.fit_transform(y)
Dividamos ahora nuestros datos en conjuntos de prueba y entrenamiento:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
Ahora podemos convertir las etiquetas de entrenamiento y de prueba en vectores codificados one-hot:
from keras.utils import to_categorical
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
Expliqué en mi artículo sobre incrustaciones de palabras que los datos textuales deben convertirse en algún tipo de forma numérica antes de que puedan ser utilizados por algoritmos estadísticos como modelos de aprendizaje profundo y de máquina. Una forma de convertir texto en números es mediante incrustaciones de palabras. Si no sabe cómo implementar incrustaciones de palabras a través de Keras, le recomiendo que lea este artículo antes de pasar a las siguientes secciones del código.
El primer paso en las incrustaciones de palabras es convertir las palabras en sus índices numéricos correspondientes. Para hacerlo, podemos usar la Tokenizer
clase del Keras.preprocessing.text
módulo.
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)
Las oraciones pueden tener diferentes longitudes y, por lo tanto, las secuencias devueltas por la Tokenizer
clase también constan de longitudes variables. Especificamos que la longitud máxima de la secuencia será 200 (aunque puede probar con cualquier número). Para las oraciones que tengan una longitud menor a 200, los índices restantes se rellenarán con ceros. Para las oraciones que tengan una longitud superior a 200, los índices restantes se truncarán.
Mira el siguiente guión:
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)
A continuación, necesitamos cargar las incrustaciones de palabras GloVe integradas .
from numpy import array
from numpy import asarray
from numpy import zeros
embeddings_dictionary = dict()
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()
Finalmente, crearemos una matriz de incrustación donde las filas serán iguales a la cantidad de palabras en el vocabulario (más 1). El número de columnas será 100 ya que cada palabra en las incrustaciones de palabras GloVe que cargamos se representa como un vector dimensional 100.
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
Una vez que se completa el paso de incrustación de palabras, estamos listos para crear nuestro modelo. Usaremos la API funcional de Keras para crear nuestro modelo. Aunque los modelos de entrada única como el que estamos creando ahora también se pueden desarrollar usando API secuencial, pero como en la siguiente sección vamos a desarrollar un modelo de entrada múltiple que solo se puede desarrollar usando la API funcional de Keras, nos ceñiremos a funcional API en esta sección también.
Crearemos un modelo muy simple con una capa de entrada (capa de incrustación), una capa LSTM con 128 neuronas y una capa densa que actuará también como capa de salida. Como tenemos 3 salidas posibles, el número de neuronas será 3 y la función de activación será softmax
. Usaremos categorical_crossentropy
como nuestra función de pérdida y adam
como función de optimización.
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(3, activation='softmax')(LSTM_Layer_1)
model = Model(inputs=deep_inputs, outputs=dense_layer_1)
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=['acc'])
Imprimamos el resumen de nuestro modelo:
print(model.summary())
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, 200) 0
_________________________________________________________________
embedding_1 (Embedding) (None, 200, 100) 5572900
_________________________________________________________________
lstm_1 (LSTM) (None, 128) 117248
_________________________________________________________________
dense_1 (Dense) (None, 3) 387
=================================================================
Total params: 5,690,535
Trainable params: 117,635
Non-trainable params: 5,572,900
Finalmente, imprimamos el diagrama de bloques de nuestra red neuronal:
from keras.utils import plot_model
plot_model(model, to_file="model_plot1.png", show_shapes=True, show_layer_names=True)
El archivo model_plot1.png
se creará en su ruta de archivo local. Si abre la imagen, se verá así:
Puede ver que el modelo tiene 1 capa de entrada, 1 capa de incrustación, 1 LSTM y una capa densa que también sirve como capa de salida.
Entrenemos ahora nuestro modelo:
history = model.fit(X_train, y_train, batch_size=128, epochs=10, verbose=1, validation_split=0.2)
El modelo se entrenará en el 80% de los datos del tren y se validará en el 20% de los datos del tren. Los resultados de las 10 épocas son los siguientes:
Train on 32000 samples, validate on 8000 samples
Epoch 1/10
32000/32000 [==============================] - 81s 3ms/step - loss: 0.8640 - acc: 0.6623 - val_loss: 0.8356 - val_acc: 0.6730
Epoch 2/10
32000/32000 [==============================] - 80s 3ms/step - loss: 0.8508 - acc: 0.6618 - val_loss: 0.8399 - val_acc: 0.6690
Epoch 3/10
32000/32000 [==============================] - 84s 3ms/step - loss: 0.8461 - acc: 0.6647 - val_loss: 0.8374 - val_acc: 0.6726
Epoch 4/10
32000/32000 [==============================] - 82s 3ms/step - loss: 0.8288 - acc: 0.6709 - val_loss: 0.7392 - val_acc: 0.6861
Epoch 5/10
32000/32000 [==============================] - 82s 3ms/step - loss: 0.7444 - acc: 0.6804 - val_loss: 0.6371 - val_acc: 0.7311
Epoch 6/10
32000/32000 [==============================] - 83s 3ms/step - loss: 0.5969 - acc: 0.7484 - val_loss: 0.5602 - val_acc: 0.7682
Epoch 7/10
32000/32000 [==============================] - 82s 3ms/step - loss: 0.5484 - acc: 0.7623 - val_loss: 0.5244 - val_acc: 0.7814
Epoch 8/10
32000/32000 [==============================] - 86s 3ms/step - loss: 0.5052 - acc: 0.7866 - val_loss: 0.4971 - val_acc: 0.7950
Epoch 9/10
32000/32000 [==============================] - 84s 3ms/step - loss: 0.4753 - acc: 0.8032 - val_loss: 0.4839 - val_acc: 0.7965
Epoch 10/10
32000/32000 [==============================] - 82s 3ms/step - loss: 0.4539 - acc: 0.8110 - val_loss: 0.4622 - val_acc: 0.8046
Puede ver que la precisión de entrenamiento final del modelo es 81,10% mientras que la precisión de validación es 80,46. La diferencia es muy pequeña y, por lo tanto, asumimos que nuestro modelo no se adapta demasiado a los datos de entrenamiento.
Evaluemos ahora el rendimiento de nuestro modelo en el conjunto de prueba:
score = model.evaluate(X_test, y_test, verbose=1)
print("Test Score:", score[0])
print("Test Accuracy:", score[1])
La salida se ve así:
10000/10000 [==============================] - 37s 4ms/step
Test Score: 0.4592904740810394
Test Accuracy: 0.8101
Finalmente, tracemos los valores de pérdida y precisión para los conjuntos de entrenamiento y prueba:
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()
Debería ver los siguientes dos gráficos:
Puede ver que las líneas para la precisión de entrenamiento y prueba y las pérdidas están bastante cerca entre sí, lo que significa que el modelo no está sobreajustado.
Crear un modelo solo con metainformación
En esta sección, vamos a crear un modelo de clasificación que utiliza la información de los useful
, funny
y cool
columnas de las revisiones Yelp. Dado que los datos para estas columnas están bien estructurados y no contienen ningún patrón secuencial o espacial, podemos usar redes neuronales simples densamente conectadas para hacer predicciones.
Grafiquemos los recuentos promedio de useful
, funny
y cool
comentarios en contra de la puntuación de la crítica.
import seaborn as sns
sns.barplot(x='reviews_score', y='useful', data=yelp_reviews)
En la salida, puede ver que el recuento promedio de reseñas marcadas como useful
es el más alto para las reseñas negativas, seguido de las reseñas promedio y las reseñas positivas.
Tracemos ahora el recuento promedio de funny
reseñas:
sns.barplot(x='reviews_score', y='funny', data=yelp_reviews)
El resultado muestra que, nuevamente, el recuento promedio de reseñas marcadas como funny
es más alto para las reseñas negativas.
Finalmente, grafiquemos el valor promedio de la cool
columna contra la reviews_score
columna. Esperamos que el recuento promedio de la cool
columna sea el más alto para las buenas críticas, ya que las personas a menudo califican las críticas positivas o buenas como interesantes:
sns.barplot(x='reviews_score', y='cool', data=yelp_reviews)
Como era de esperar, el recuento promedio de buenas críticas es el más alto. A partir de esta información, podemos suponer con seguridad que el recuento de valores useful
, funny
y cool
las columnas tienen cierta correlación con las reviews_score
columnas. Por lo tanto, intentaremos usar los datos de estas tres columnas para entrenar nuestro algoritmo que predice el valor de la reviews_score
columna.
Filtremos estas tres columnas del conjunto de datos pur:
yelp_reviews_meta = yelp_reviews[['useful', 'funny', 'cool']]
X = yelp_reviews_meta.values
y = yelp_reviews['reviews_score']
A continuación, convertiremos nuestras etiquetas en valores codificados one-hot y luego dividiremos nuestros datos en conjuntos de prueba y tren:
from sklearn import preprocessing
# label_encoder object knows how to understand word labels.
label_encoder = preprocessing.LabelEncoder()
# Encode labels in column 'species'.
y = label_encoder.fit_transform(y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
from keras.utils import to_categorical
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
El siguiente paso es crear nuestro modelo. Nuestro modelo constará de cuatro capas (puede probar cualquier número): la capa de entrada, dos capas ocultas densas con 10 neuronas y funciones de activación relu, y finalmente una capa densa de salida con 3 neuronas y función de activación softmax. La función de pérdida y el optimizador serán categorical_crossentropy
y adam
, respectivamente.
El siguiente script define el modelo:
input2 = Input(shape=(3,))
dense_layer_1 = Dense(10, activation='relu')(input2)
dense_layer_2 = Dense(10, activation='relu')(dense_layer_1)
output = Dense(3, activation='softmax')(dense_layer_2)
model = Model(inputs=input2, outputs=output)
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=['acc'])
Imprimamos el resumen del modelo:
print(model.summary())
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) (None, 3) 0
_________________________________________________________________
dense_1 (Dense) (None, 10) 40
_________________________________________________________________
dense_2 (Dense) (None, 10) 110
_________________________________________________________________
dense_3 (Dense) (None, 3) 33
=================================================================
Total params: 183
Trainable params: 183
Non-trainable params: 0
Finalmente, el diagrama de bloques para el modelo se puede crear mediante el siguiente script:
from keras.utils import plot_model
plot_model(model, to_file="model_plot2.png", show_shapes=True, show_layer_names=True)
Ahora, si abre el model_plot2.png
archivo desde su ruta de archivo local, se verá así:
Entrenemos ahora el modelo e imprimamos los valores de precisión y pérdida para cada época:
history = model.fit(X_train, y_train, batch_size=16, epochs=10, verbose=1, validation_split=0.2)
Train on 32000 samples, validate on 8000 samples
Epoch 1/10
32000/32000 [==============================] - 8s 260us/step - loss: 0.8429 - acc: 0.6649 - val_loss: 0.8166 - val_acc: 0.6734
Epoch 2/10
32000/32000 [==============================] - 7s 214us/step - loss: 0.8203 - acc: 0.6685 - val_loss: 0.8156 - val_acc: 0.6737
Epoch 3/10
32000/32000 [==============================] - 7s 217us/step - loss: 0.8187 - acc: 0.6685 - val_loss: 0.8150 - val_acc: 0.6736
Epoch 4/10
32000/32000 [==============================] - 7s 220us/step - loss: 0.8183 - acc: 0.6695 - val_loss: 0.8160 - val_acc: 0.6740
Epoch 5/10
32000/32000 [==============================] - 7s 227us/step - loss: 0.8177 - acc: 0.6686 - val_loss: 0.8149 - val_acc: 0.6751
Epoch 6/10
32000/32000 [==============================] - 7s 219us/step - loss: 0.8175 - acc: 0.6686 - val_loss: 0.8157 - val_acc: 0.6744
Epoch 7/10
32000/32000 [==============================] - 7s 216us/step - loss: 0.8172 - acc: 0.6696 - val_loss: 0.8145 - val_acc: 0.6733
Epoch 8/10
32000/32000 [==============================] - 7s 214us/step - loss: 0.8175 - acc: 0.6689 - val_loss: 0.8139 - val_acc: 0.6734
Epoch 9/10
32000/32000 [==============================] - 7s 215us/step - loss: 0.8169 - acc: 0.6691 - val_loss: 0.8160 - val_acc: 0.6744
Epoch 10/10
32000/32000 [==============================] - 7s 216us/step - loss: 0.8167 - acc: 0.6694 - val_loss: 0.8138 - val_acc: 0.6736
En la salida, puede ver que nuestro modelo no converge y los valores de precisión permanecen entre 66 y 67 en todas las épocas.
Veamos cómo funciona el modelo en el conjunto de prueba:
score = model.evaluate(X_test, y_test, verbose=1)
print("Test Score:", score[0])
print("Test Accuracy:", score[1])
10000/10000 [==============================] - 0s 34us/step
Test Score: 0.8206425309181213
Test Accuracy: 0.6669
Podemos imprimir los valores de pérdida y precisión para los conjuntos de entrenamiento y prueba mediante el siguiente script:
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()
En la salida, puede ver que los valores de precisión son relativamente más bajos. Por tanto, podemos decir que nuestro modelo no se adapta bien. La precisión se puede aumentar aumentando el número de capas densas o aumentando el número de épocas, sin embargo, te lo dejo a ti.
Pasemos a la sección final y más importante de este artículo donde utilizaremos múltiples entradas de diferentes tipos para entrenar nuestro modelo.
Te puede interesar:Tareas asíncronas en Django con Redis y CeleryCrear un modelo con múltiples entradas
En las secciones anteriores, vimos cómo entrenar modelos de aprendizaje profundo utilizando datos textuales o metainformación. ¿Qué pasa si queremos combinar información textual con metainformación y usarla como entrada para nuestro modelo? Podemos hacerlo utilizando la API funcional de Keras. En esta sección crearemos dos submodelos.
El primer submodelo aceptará entrada de texto en forma de revisiones de texto. Este submodelo constará de una capa de forma de entrada, una capa de incrustación y una capa LSTM de 128 neuronas. El segundo submodelo aceptará la entrada en forma de meta-información de los useful
, funny
y cool
columnas. El segundo submodelo también consta de tres capas. Una capa de entrada y dos capas densas.
La salida de la capa LSTM del primer submodelo y la salida de la segunda capa densa del segundo submodelo se concatenarán juntas y se utilizarán como entrada concatenada a otra capa densa con 10 neuronas. Finalmente, la capa densa de salida tendrá tres neutros correspondientes a cada tipo de revisión.
Veamos cómo podemos crear un modelo tan concatenado.
Primero tenemos que crear dos tipos diferentes de entradas. Para hacerlo, dividiremos nuestros datos en un conjunto de características y un conjunto de etiquetas, como se muestra a continuación:
X = yelp_reviews.drop('reviews_score', axis=1)
y = yelp_reviews['reviews_score']
La X
variable contiene el conjunto de características, mientras que la y
variable contiene el conjunto de etiquetas. Necesitamos convertir nuestras etiquetas en vectores codificados one-hot. Podemos hacerlo utilizando el codificador de etiquetas y la to_categorical
función del keras.utils
módulo. También dividiremos nuestros datos en entrenamiento y conjunto de funciones.
from sklearn import preprocessing
# label_encoder object knows how to understand word labels.
label_encoder = preprocessing.LabelEncoder()
# Encode labels in column 'species'.
y = label_encoder.fit_transform(y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=42)
from keras.utils import to_categorical
y_train = to_categorical(y_train)
y_test = to_categorical(y_test)
Ahora nuestro conjunto de etiquetas está en la forma requerida. Como solo habrá una salida, no es necesario que procesemos nuestro conjunto de etiquetas. Sin embargo, habrá múltiples entradas al modelo. Por lo tanto, necesitamos preprocesar nuestro conjunto de características.
Primero creemos la preproces_text
función que se utilizará para preprocesar nuestro conjunto de datos:
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
Como primer paso, crearemos una entrada textual para el conjunto de entrenamiento y prueba. Mira el siguiente guión:
X1_train = []
sentences = list(X_train["text"])
for sen in sentences:
X1_train.append(preprocess_text(sen))
Ahora X1_train
contiene la entrada textual para el conjunto de entrenamiento. De manera similar, el siguiente script preprocesa los datos de entrada textuales para el conjunto de prueba:
X1_test = []
sentences = list(X_test["text"])
for sen in sentences:
X1_test.append(preprocess_text(sen))
Ahora necesitamos convertir la entrada textual para los conjuntos de entrenamiento y prueba en forma numérica utilizando incrustaciones de palabras. El siguiente script hace eso:
tokenizer = Tokenizer(num_words=5000)
tokenizer.fit_on_texts(X1_train)
X1_train = tokenizer.texts_to_sequences(X1_train)
X1_test = tokenizer.texts_to_sequences(X1_test)
vocab_size = len(tokenizer.word_index) + 1
maxlen = 200
X1_train = pad_sequences(X1_train, padding='post', maxlen=maxlen)
X1_test = pad_sequences(X1_test, padding='post', maxlen=maxlen)
Usaremos nuevamente incrustaciones de palabras GloVe para crear vectores de palabras:
from numpy import array
from numpy import asarray
from numpy import zeros
embeddings_dictionary = dict()
glove_file = open('/content/drive/My Drive/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
Hemos preprocesado nuestra entrada textual. El segundo tipo de entrada es la meta información en los useful
, funny
y cool
columnas. Filtraremos estas columnas del conjunto de características para crear una meta entrada para entrenar los algoritmos. Mira el siguiente guión:
X2_train = X_train[['useful', 'funny', 'cool']].values
X2_test = X_test[['useful', 'funny', 'cool']].values
Creemos ahora nuestras dos capas de entrada. La primera capa de entrada se usará para ingresar la entrada textual y la segunda capa de entrada se usará para ingresar metainformación de las tres columnas.
input_1 = Input(shape=(maxlen,))
input_2 = Input(shape=(3,))
Puede ver que la primera capa de entrada input_1
se utiliza para la entrada textual. El tamaño de la forma se ha establecido en la forma de la oración de entrada. Para la segunda capa de entrada, la forma corresponde a tres columnas.
Creemos ahora el primer submodelo que acepta datos de la primera capa de entrada:
Te puede interesar:Python para PNL: creación de un chatbot basado en reglasembedding_layer = Embedding(vocab_size, 100, weights=[embedding_matrix], trainable=False)(input_1)
LSTM_Layer_1 = LSTM(128)(embedding_layer)
De manera similar, el siguiente script crea un segundo submodelo que acepta la entrada de la segunda capa de entrada:
dense_layer_1 = Dense(10, activation='relu')(input_2)
dense_layer_2 = Dense(10, activation='relu')(dense_layer_1)
Ahora tenemos dos submodelos. Lo que queremos hacer es concatenar la salida del primer submodelo con la salida del segundo submodelo. La salida del primer submodelo es la salida del LSTM_Layer_1
y, de manera similar, la salida del segundo submodelo es la salida del dense_layer_2
. Podemos usar la Concatenate
clase del keras.layers.merge
módulo para concatenar dos entradas.
El siguiente script crea nuestro modelo final:
concat_layer = Concatenate()([LSTM_Layer_1, dense_layer_2])
dense_layer_3 = Dense(10, activation='relu')(concat_layer)
output = Dense(3, activation='softmax')(dense_layer_3)
model = Model(inputs=[input_1, input_2], outputs=output)
Puede ver que ahora nuestro modelo tiene una lista de entradas con dos elementos. El siguiente script compila el modelo e imprime su resumen:
model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=['acc'])
print(model.summary())
El resumen del modelo es el siguiente:
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_1 (InputLayer) (None, 200) 0
__________________________________________________________________________________________________
input_2 (InputLayer) (None, 3) 0
__________________________________________________________________________________________________
embedding_1 (Embedding) (None, 200, 100) 5572900 input_1[0][0]
__________________________________________________________________________________________________
dense_1 (Dense) (None, 10) 40 input_2[0][0]
__________________________________________________________________________________________________
lstm_1 (LSTM) (None, 128) 117248 embedding_1[0][0]
__________________________________________________________________________________________________
dense_2 (Dense) (None, 10) 110 dense_1[0][0]
__________________________________________________________________________________________________
concatenate_1 (Concatenate) (None, 138) 0 lstm_1[0][0]
dense_2[0][0]
__________________________________________________________________________________________________
dense_3 (Dense) (None, 10) 1390 concatenate_1[0][0]
__________________________________________________________________________________________________
dense_4 (Dense) (None, 3) 33 dense_3[0][0]
==================================================================================================
Total params: 5,691,721
Trainable params: 118,821
Non-trainable params: 5,572,900
Finalmente, podemos trazar el modelo de red completo usando el siguiente script:
from keras.utils import plot_model
plot_model(model, to_file="model_plot3.png", show_shapes=True, show_layer_names=True)
Si abre el model_plot3.png
archivo, debería ver el siguiente diagrama de red:
La figura anterior explica claramente cómo hemos concatenado múltiples entradas en una entrada para crear nuestro modelo.
Entrenemos ahora nuestro modelo y veamos los resultados:
history = model.fit(x=[X1_train, X2_train], y=y_train, batch_size=128, epochs=10, verbose=1, validation_split=0.2)
Aquí está el resultado de las 10 épocas:
Train on 32000 samples, validate on 8000 samples
Epoch 1/10
32000/32000 [==============================] - 155s 5ms/step - loss: 0.9006 - acc: 0.6509 - val_loss: 0.8233 - val_acc: 0.6704
Epoch 2/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.8212 - acc: 0.6670 - val_loss: 0.8141 - val_acc: 0.6745
Epoch 3/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.8151 - acc: 0.6691 - val_loss: 0.8086 - val_acc: 0.6740
Epoch 4/10
32000/32000 [==============================] - 155s 5ms/step - loss: 0.8121 - acc: 0.6701 - val_loss: 0.8039 - val_acc: 0.6776
Epoch 5/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.8027 - acc: 0.6740 - val_loss: 0.7467 - val_acc: 0.6854
Epoch 6/10
32000/32000 [==============================] - 155s 5ms/step - loss: 0.6791 - acc: 0.7158 - val_loss: 0.5764 - val_acc: 0.7560
Epoch 7/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.5333 - acc: 0.7744 - val_loss: 0.5076 - val_acc: 0.7881
Epoch 8/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.4857 - acc: 0.7973 - val_loss: 0.4849 - val_acc: 0.7970
Epoch 9/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.4697 - acc: 0.8034 - val_loss: 0.4709 - val_acc: 0.8024
Epoch 10/10
32000/32000 [==============================] - 154s 5ms/step - loss: 0.4479 - acc: 0.8123 - val_loss: 0.4592 - val_acc: 0.8079
Para evaluar nuestro modelo, tendremos que pasar ambas entradas de prueba a la evaluate
función como se muestra a continuación:
score = model.evaluate(x=[X1_test, X2_test], y=y_test, verbose=1)
print("Test Score:", score[0])
print("Test Accuracy:", score[1])
Aquí está el resultado:
10000/10000 [==============================] - 18s 2ms/step
Test Score: 0.4576087875843048
Test Accuracy: 0.8053
La precisión de nuestra prueba es del 80.53%, que es un poco menos que nuestro primer modelo que usa solo entrada de texto. Esto muestra que la metainformación en yelp_reviews
no es muy útil para la predicción de sentimientos.
De todos modos, ¡ahora sabe cómo crear un modelo de entrada múltiple para la clasificación de texto en Keras!
Finalmente, imprimamos ahora la pérdida y la precisión para los conjuntos de entrenamiento y prueba:
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()
Puede ver que las diferencias en los valores de pérdida y precisión son mínimas entre los conjuntos de entrenamiento y prueba, por lo que nuestro modelo no está sobreajustado.
Te puede interesar:Python para PNL: trabajar con archivos de texto y PDFReflexiones finales y mejoras
En este artículo, construimos una red neuronal muy simple ya que el propósito del artículo es explicar cómo crear un modelo de aprendizaje profundo que acepte múltiples entradas de diferentes tipos.
A continuación, se muestran algunos de los consejos que puede seguir para mejorar aún más el rendimiento del modelo de clasificación de texto:
- Solo usamos 50,000, de los 5.2 millones de registros en este artículo, ya que teníamos restricciones de hardware. Puede intentar entrenar su modelo en una mayor cantidad de registros y ver si puede lograr un mejor rendimiento.
- Intente agregar más LSTM y capas densas al modelo. Si el modelo se adapta demasiado, intente agregar abandono.
- Intente cambiar la función del optimizador y entrene el modelo con mayor número de épocas.
Comparta sus resultados junto con la configuración de la red neuronal en la sección de comentarios. Me encantaría ver qué tan bien te desempeñaste.