Python para PNL: Creación de modelos de clasificación de varios tipos de datos con Keras

    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

    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.csvarchivo. 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 Starsque 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_scorea nuestro conjunto de datos. Si la reseña del usuario tiene un valor de 1 en la Starscolumna, la reviews_scorecolumna tendrá un valor de cadena bad. Si la calificación es 2 o 3 en la Starscolumna, la reviews_scorecolumna contendrá un valor average. Finalmente, la calificación de revisión de 4 o 5 tendrá un valor correspondiente de gooden la reviews_scorecolumna.

    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_reviewsmarco de datos se ve así:

    Puede ver las 10 columnas que contiene nuestro marco de datos, incluida la reviews_scorecolumna recién agregada . La textcolumna contiene el texto de la reseña, mientras que la usefulcolumna contiene un valor numérico que representa el recuento de personas que encontraron útil la reseña. De manera similar, las columnas funnyy coolcontienen los recuentos de personas que encontraron reseñas funnyo 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.

    Ahora vamos a trazar el número de good, averagey badcomentarios.

    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, averageo bad. En el segundo modelo, no usaremos texto. Sólo utilizaremos la información de metadatos, tales como useful, funnyy coolpara 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_textfunció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 Xvariable aquí contiene las revisiones de texto, mientras que la yvariable contiene los reviews_scorevalores correspondientes . La reviews_scorecolumna tiene datos en formato de texto. Necesitamos convertir el texto en un vector codificado en caliente. Podemos usar el to_categoricalmétodo del keras.utilsmódulo. Sin embargo, primero tenemos que convertir el texto en etiquetas enteras usando la LabelEncoderfunción del sklearn.preprocessingmó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 Tokenizerclase del Keras.preprocessing.textmó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 Tokenizerclase 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_crossentropycomo nuestra función de pérdida y adamcomo 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.pngse 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, funnyy coolcolumnas 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, funnyy coolcomentarios 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 usefules 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 funnyreseñas:

    sns.barplot(x='reviews_score', y='funny', data=yelp_reviews)
    

    El resultado muestra que, nuevamente, el recuento promedio de reseñas marcadas como funnyes más alto para las reseñas negativas.

    Finalmente, grafiquemos el valor promedio de la coolcolumna contra la reviews_scorecolumna. Esperamos que el recuento promedio de la coolcolumna 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, funnyy coollas columnas tienen cierta correlación con las reviews_scorecolumnas. Por lo tanto, intentaremos usar los datos de estas tres columnas para entrenar nuestro algoritmo que predice el valor de la reviews_scorecolumna.

    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_crossentropyy 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.pngarchivo 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.

    Crear 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, funnyy coolcolumnas. 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 Xvariable contiene el conjunto de características, mientras que la yvariable contiene el conjunto de etiquetas. Necesitamos convertir nuestras etiquetas en vectores codificados one-hot. Podemos hacerlo utilizando el codificador de etiquetas y la to_categoricalfunción del keras.utilsmó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_textfunció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_traincontiene 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, funnyy coolcolumnas. 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_1se 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:

    embedding_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_1y, de manera similar, la salida del segundo submodelo es la salida del dense_layer_2. Podemos usar la Concatenateclase del keras.layers.mergemó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.pngarchivo, 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 evaluatefunció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_reviewsno 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.

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

     

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *