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 *