Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Clasificación de texto básica

Ver en TensorFlow.org Ejecutar en Google Colab Ver código fuente en GitHub Descargar cuaderno

Este tutorial muestra la clasificación de texto a partir de archivos de texto sin formato almacenados en el disco. Entrenarás un clasificador binario para realizar análisis de sentimientos en un conjunto de datos IMDB. Al final del cuaderno, hay un ejercicio para que pruebe, en el que entrenará a un clasificador multiclase para predecir la etiqueta para una pregunta de programación sobre Desbordamiento de pila.

 import matplotlib.pyplot as plt
import os
import re
import shutil
import string
import tensorflow as tf

from tensorflow.keras import layers
from tensorflow.keras import losses
from tensorflow.keras import preprocessing
from tensorflow.keras.layers.experimental.preprocessing import TextVectorization
 
 print(tf.__version__)
 
2.3.0

Análisis de los sentimientos

Este cuaderno entrena un modelo de análisis de sentimientos para clasificar las reseñas de películas como positivas o negativas , según el texto de la reseña. Este es un ejemplo de clasificación binaria, o de dos clases, un tipo de problema de aprendizaje automático importante y ampliamente aplicable.

Utilizará el conjunto de datos de revisión de películas grandes que contiene el texto de 50,000 reseñas de películas de la base de datos de películas de Internet . Estos se dividen en 25,000 revisiones para capacitación y 25,000 revisiones para pruebas. Los conjuntos de entrenamiento y evaluación son equilibrados , lo que significa que contienen el mismo número de críticas positivas y negativas.

Descargue y explore el conjunto de datos IMDB

Descarguemos y extraigamos el conjunto de datos, luego exploremos la estructura de directorios.

 url = "https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz"

dataset = tf.keras.utils.get_file("aclImdb_v1.tar.gz", url,
                                    untar=True, cache_dir='.',
                                    cache_subdir='')

dataset_dir = os.path.join(os.path.dirname(dataset), 'aclImdb')
 
Downloading data from https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz
84131840/84125825 [==============================] - 7s 0us/step

 os.listdir(dataset_dir)
 
['imdb.vocab', 'train', 'test', 'README', 'imdbEr.txt']
 train_dir = os.path.join(dataset_dir, 'train')
os.listdir(train_dir)
 
['urls_pos.txt',
 'neg',
 'labeledBow.feat',
 'pos',
 'urls_neg.txt',
 'unsup',
 'unsupBow.feat',
 'urls_unsup.txt']

Los aclImdb/train/pos y aclImdb/train/neg contienen muchos archivos de texto, cada uno de los cuales es una revisión de una sola película. Echemos un vistazo a uno de ellos.

 sample_file = os.path.join(train_dir, 'pos/1181_9.txt')
with open(sample_file) as f:
  print(f.read())
 
Rachel Griffiths writes and directs this award winning short film. A heartwarming story about coping with grief and cherishing the memory of those we've loved and lost. Although, only 15 minutes long, Griffiths manages to capture so much emotion and truth onto film in the short space of time. Bud Tingwell gives a touching performance as Will, a widower struggling to cope with his wife's death. Will is confronted by the harsh reality of loneliness and helplessness as he proceeds to take care of Ruth's pet cow, Tulip. The film displays the grief and responsibility one feels for those they have loved and lost. Good cinematography, great direction, and superbly acted. It will bring tears to all those who have lost a loved one, and survived.

Cargue el conjunto de datos

A continuación, cargará los datos del disco y los preparará en un formato adecuado para la capacitación. Para hacerlo, utilizará la útil utilidad text_dataset_from_directory , que espera una estructura de directorio de la siguiente manera.

 main_directory/
...class_a/
......a_text_1.txt
......a_text_2.txt
...class_b/
......b_text_1.txt
......b_text_2.txt
 

Para preparar un conjunto de datos para la clasificación binaria, necesitará dos carpetas en el disco, correspondientes a class_a y class_b . Estas serán las críticas de películas positivas y negativas, que se pueden encontrar en aclImdb/train/pos y aclImdb/train/neg . Como el conjunto de datos IMDB contiene carpetas adicionales, las eliminará antes de usar esta utilidad.

 remove_dir = os.path.join(train_dir, 'unsup')
shutil.rmtree(remove_dir)
 

A continuación, utilizará la utilidad text_dataset_from_directory para crear una etiqueta tf.data.Dataset . tf.data es una poderosa colección de herramientas para trabajar con datos.

Al ejecutar un experimento de aprendizaje automático, es una buena práctica dividir su conjunto de datos en tres divisiones: capacitación , validación y prueba .

El conjunto de datos IMDB ya se ha dividido en tren y prueba, pero carece de un conjunto de validación. Creemos un conjunto de validación usando una división 80:20 de los datos de entrenamiento usando el argumento validation_split continuación.

 batch_size = 32
seed = 42

raw_train_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'aclImdb/train', 
    batch_size=batch_size, 
    validation_split=0.2, 
    subset='training', 
    seed=seed)
 
Found 25000 files belonging to 2 classes.
Using 20000 files for training.

Como puede ver arriba, hay 25,000 ejemplos en la carpeta de capacitación, de los cuales utilizará el 80% (o 20,000) para la capacitación. Como verá en un momento, puede entrenar un modelo pasando un conjunto de datos directamente a model.fit . Si es nuevo en tf.data , también puede iterar sobre el conjunto de datos e imprimir algunos ejemplos de la siguiente manera.

 for text_batch, label_batch in raw_train_ds.take(1):
  for i in range(3):
    print("Review", text_batch.numpy()[i])
    print("Label", label_batch.numpy()[i])
 
Review b'"Pandemonium" is a horror movie spoof that comes off more stupid than funny. Believe me when I tell you, I love comedies. Especially comedy spoofs. "Airplane", "The Naked Gun" trilogy, "Blazing Saddles", "High Anxiety", and "Spaceballs" are some of my favorite comedies that spoof a particular genre. "Pandemonium" is not up there with those films. Most of the scenes in this movie had me sitting there in stunned silence because the movie wasn\'t all that funny. There are a few laughs in the film, but when you watch a comedy, you expect to laugh a lot more than a few times and that\'s all this film has going for it. Geez, "Scream" had more laughs than this film and that was more of a horror film. How bizarre is that?<br /><br />*1/2 (out of four)'
Label 0
Review b"David Mamet is a very interesting and a very un-equal director. His first movie 'House of Games' was the one I liked best, and it set a series of films with characters whose perspective of life changes as they get into complicated situations, and so does the perspective of the viewer.<br /><br />So is 'Homicide' which from the title tries to set the mind of the viewer to the usual crime drama. The principal characters are two cops, one Jewish and one Irish who deal with a racially charged area. The murder of an old Jewish shop owner who proves to be an ancient veteran of the Israeli Independence war triggers the Jewish identity in the mind and heart of the Jewish detective.<br /><br />This is were the flaws of the film are the more obvious. The process of awakening is theatrical and hard to believe, the group of Jewish militants is operatic, and the way the detective eventually walks to the final violent confrontation is pathetic. The end of the film itself is Mamet-like smart, but disappoints from a human emotional perspective.<br /><br />Joe Mantegna and William Macy give strong performances, but the flaws of the story are too evident to be easily compensated."
Label 0
Review b'Great documentary about the lives of NY firefighters during the worst terrorist attack of all time.. That reason alone is why this should be a must see collectors item.. What shocked me was not only the attacks, but the"High Fat Diet" and physical appearance of some of these firefighters. I think a lot of Doctors would agree with me that,in the physical shape they were in, some of these firefighters would NOT of made it to the 79th floor carrying over 60 lbs of gear. Having said that i now have a greater respect for firefighters and i realize becoming a firefighter is a life altering job. The French have a history of making great documentary\'s and that is what this is, a Great Documentary.....'
Label 1

Observe que las revisiones contienen texto sin formato (con puntuación y ocasionales etiquetas HTML como <br/> ). Mostrará cómo manejarlos en la siguiente sección.

Las etiquetas son 0 o 1. Para ver cuáles corresponden a críticas de películas positivas y negativas, puede verificar la propiedad class_names en el conjunto de datos.

 print("Label 0 corresponds to", raw_train_ds.class_names[0])
print("Label 1 corresponds to", raw_train_ds.class_names[1])
 
Label 0 corresponds to neg
Label 1 corresponds to pos

A continuación, creará un conjunto de datos de validación y prueba. Utilizará las 5.000 revisiones restantes del conjunto de capacitación para la validación.

 raw_val_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'aclImdb/train', 
    batch_size=batch_size, 
    validation_split=0.2, 
    subset='validation', 
    seed=seed)
 
Found 25000 files belonging to 2 classes.
Using 5000 files for validation.

 raw_test_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'aclImdb/test', 
    batch_size=batch_size)
 
Found 25000 files belonging to 2 classes.

Prepare el conjunto de datos para la capacitación.

A continuación, estandarizará, tokenizará y vectorizará los datos utilizando la útil capa de preprocessing.TextVectorization .

La estandarización se refiere al preprocesamiento del texto, generalmente para eliminar la puntuación o los elementos HTML para simplificar el conjunto de datos. La tokenización se refiere a dividir cadenas en tokens (por ejemplo, dividir una oración en palabras individuales, dividiendo en espacios en blanco). La vectorización se refiere a convertir tokens en números para que puedan ser alimentados a una red neuronal. Todas estas tareas se pueden lograr con esta capa.

Como viste anteriormente, las revisiones contienen varias etiquetas HTML como <br /> . El estandarizador predeterminado no eliminará estas etiquetas en la capa TextVectorization (que convierte el texto en minúsculas y elimina la puntuación de forma predeterminada, pero no elimina HTML). Escribirás una función de estandarización personalizada para eliminar el HTML.

 def custom_standardization(input_data):
  lowercase = tf.strings.lower(input_data)
  stripped_html = tf.strings.regex_replace(lowercase, '<br />', ' ')
  return tf.strings.regex_replace(stripped_html,
                                  '[%s]' % re.escape(string.punctuation),
                                  '')
 

A continuación, creará una capa de TextVectorization . utilizará esta capa para estandarizar, tokenizar y vectorizar nuestros datos. Establece output_mode en int para crear índices enteros únicos para cada token.

Tenga en cuenta que está utilizando la función de división predeterminada y la función de estandarización personalizada que definió anteriormente. También podrá definir algunas constantes para el modelo, como un máximo explícita sequence_length , lo que hará que la capa de secuencias almohadilla o truncar a exactamente sequence_length valores.

 max_features = 10000
sequence_length = 250

vectorize_layer = TextVectorization(
    standardize=custom_standardization,
    max_tokens=max_features,
    output_mode='int',
    output_sequence_length=sequence_length)
 

A continuación, llamará a adapt para ajustar el estado de la capa de preprocesamiento al conjunto de datos. Esto hará que el modelo construya un índice de cadenas a enteros.

 # Make a text-only dataset (without labels), then call adapt
train_text = raw_train_ds.map(lambda x, y: x)
vectorize_layer.adapt(train_text)
 

Creemos una función para ver el resultado del uso de esta capa para preprocesar algunos datos.

 def vectorize_text(text, label):
  text = tf.expand_dims(text, -1)
  return vectorize_layer(text), label
 
 # retrieve a batch (of 32 reviews and labels) from the dataset
text_batch, label_batch = next(iter(raw_train_ds))
first_review, first_label = text_batch[0], label_batch[0]
print("Review", first_review)
print("Label", raw_train_ds.class_names[first_label])
print("Vectorized review", vectorize_text(first_review, first_label))
 
Review tf.Tensor(b'Silent Night, Deadly Night 5 is the very last of the series, and like part 4, it\'s unrelated to the first three except by title and the fact that it\'s a Christmas-themed horror flick.<br /><br />Except to the oblivious, there\'s some obvious things going on here...Mickey Rooney plays a toymaker named Joe Petto and his creepy son\'s name is Pino. Ring a bell, anyone? Now, a little boy named Derek heard a knock at the door one evening, and opened it to find a present on the doorstep for him. Even though it said "don\'t open till Christmas", he begins to open it anyway but is stopped by his dad, who scolds him and sends him to bed, and opens the gift himself. Inside is a little red ball that sprouts Santa arms and a head, and proceeds to kill dad. Oops, maybe he should have left well-enough alone. Of course Derek is then traumatized by the incident since he watched it from the stairs, but he doesn\'t grow up to be some killer Santa, he just stops talking.<br /><br />There\'s a mysterious stranger lurking around, who seems very interested in the toys that Joe Petto makes. We even see him buying a bunch when Derek\'s mom takes him to the store to find a gift for him to bring him out of his trauma. And what exactly is this guy doing? Well, we\'re not sure but he does seem to be taking these toys apart to see what makes them tick. He does keep his landlord from evicting him by promising him to pay him in cash the next day and presents him with a "Larry the Larvae" toy for his kid, but of course "Larry" is not a good toy and gets out of the box in the car and of course, well, things aren\'t pretty.<br /><br />Anyway, eventually what\'s going on with Joe Petto and Pino is of course revealed, and as with the old story, Pino is not a "real boy". Pino is probably even more agitated and naughty because he suffers from "Kenitalia" (a smooth plastic crotch) so that could account for his evil ways. And the identity of the lurking stranger is revealed too, and there\'s even kind of a happy ending of sorts. Whee.<br /><br />A step up from part 4, but not much of one. Again, Brian Yuzna is involved, and Screaming Mad George, so some decent special effects, but not enough to make this great. A few leftovers from part 4 are hanging around too, like Clint Howard and Neith Hunter, but that doesn\'t really make any difference. Anyway, I now have seeing the whole series out of my system. Now if I could get some of it out of my brain. 4 out of 5.', shape=(), dtype=string)
Label neg
Vectorized review (<tf.Tensor: shape=(1, 250), dtype=int64, numpy=
array([[1287,  313, 2380,  313,  661,    7,    2,   52,  229,    5,    2,
         200,    3,   38,  170,  669,   29, 5492,    6,    2,   83,  297,
         549,   32,  410,    3,    2,  186,   12,   29,    4,    1,  191,
         510,  549,    6,    2, 8229,  212,   46,  576,  175,  168,   20,
           1, 5361,  290,    4,    1,  761,  969,    1,    3,   24,  935,
        2271,  393,    7,    1, 1675,    4, 3747,  250,  148,    4,  112,
         436,  761, 3529,  548,    4, 3633,   31,    2, 1331,   28, 2096,
           3, 2912,    9,    6,  163,    4, 1006,   20,    2,    1,   15,
          85,   53,  147,    9,  292,   89,  959, 2314,  984,   27,  762,
           6,  959,    9,  564,   18,    7, 2140,   32,   24, 1254,   36,
           1,   85,    3, 3298,   85,    6, 1410,    3, 1936,    2, 3408,
         301,  965,    7,    4,  112,  740, 1977,   12,    1, 2014, 2772,
           3,    4,  428,    3, 5177,    6,  512, 1254,    1,  278,   27,
         139,   25,  308,    1,  579,    5,  259, 3529,    7,   92, 8981,
          32,    2, 3842,  230,   27,  289,    9,   35,    2, 5712,   18,
          27,  144, 2166,   56,    6,   26,   46,  466, 2014,   27,   40,
        2745,  657,  212,    4, 1376, 3002, 7080,  183,   36,  180,   52,
         920,    8,    2, 4028,   12,  969,    1,  158,   71,   53,   67,
          85, 2754,    4,  734,   51,    1, 1611,  294,   85,    6,    2,
        1164,    6,  163,    4, 3408,   15,   85,    6,  717,   85,   44,
           5,   24, 7158,    3,   48,  604,    7,   11,  225,  384,   73,
          65,   21,  242,   18,   27,  120,  295,    6,   26,  667,  129,
        4028,  948,    6,   67,   48,  158,   93,    1]])>, <tf.Tensor: shape=(), dtype=int32, numpy=0>)

Como puede ver arriba, cada ficha ha sido reemplazada por un número entero. Puede buscar el token (cadena) al que corresponde cada entero llamando a .get_vocabulary() en la capa.

 print("1287 ---> ",vectorize_layer.get_vocabulary()[1287])
print(" 313 ---> ",vectorize_layer.get_vocabulary()[313])
print('Vocabulary size: {}'.format(len(vectorize_layer.get_vocabulary())))
 
1287 --->  silent
 313 --->  night
Vocabulary size: 10000

Estás casi listo para entrenar a tu modelo. Como paso final de preprocesamiento, aplicará la capa TextVectorization que creó anteriormente al conjunto de datos de tren, validación y prueba.

 train_ds = raw_train_ds.map(vectorize_text)
val_ds = raw_val_ds.map(vectorize_text)
test_ds = raw_test_ds.map(vectorize_text)
 

Configurar el conjunto de datos para el rendimiento

Estos son dos métodos importantes que debe usar al cargar datos para asegurarse de que la E / S no se bloquee.

.cache() mantiene los datos en la memoria después de cargarlos del disco. Esto asegurará que el conjunto de datos no se convierta en un cuello de botella mientras entrena a su modelo. Si su conjunto de datos es demasiado grande para caber en la memoria, también puede usar este método para crear una memoria caché en el disco de rendimiento, que es más eficiente de leer que muchos archivos pequeños.

.prefetch() superpone el preprocesamiento de datos y la ejecución del modelo durante el entrenamiento.

Puede obtener más información sobre ambos métodos, así como sobre cómo almacenar en caché los datos en el disco en la guía de rendimiento de datos .

 AUTOTUNE = tf.data.experimental.AUTOTUNE

train_ds = train_ds.cache().prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
test_ds = test_ds.cache().prefetch(buffer_size=AUTOTUNE)
 

Crea el modelo

Es hora de crear nuestra red neuronal:

 embedding_dim = 16
 
 model = tf.keras.Sequential([
  layers.Embedding(max_features + 1, embedding_dim),
  layers.Dropout(0.2),
  layers.GlobalAveragePooling1D(),
  layers.Dropout(0.2),
  layers.Dense(1)])

model.summary()
 
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding (Embedding)        (None, None, 16)          160016    
_________________________________________________________________
dropout (Dropout)            (None, None, 16)          0         
_________________________________________________________________
global_average_pooling1d (Gl (None, 16)                0         
_________________________________________________________________
dropout_1 (Dropout)          (None, 16)                0         
_________________________________________________________________
dense (Dense)                (None, 1)                 17        
=================================================================
Total params: 160,033
Trainable params: 160,033
Non-trainable params: 0
_________________________________________________________________

Las capas se apilan secuencialmente para construir el clasificador:

  1. La primera capa es una capa de Embedding . Esta capa toma las revisiones codificadas con enteros y busca un vector de incrustación para cada índice de palabras. Estos vectores se aprenden a medida que el modelo entrena. Los vectores agregan una dimensión a la matriz de salida. Las dimensiones resultantes son: (batch, sequence, embedding) . Para obtener más información sobre las incrustaciones, consulte el tutorial de incrustación de palabras .
  2. A continuación, una capa GlobalAveragePooling1D devuelve un vector de salida de longitud fija para cada ejemplo promediando la dimensión de secuencia. Esto permite que el modelo maneje la entrada de longitud variable, de la manera más simple posible.
  3. Este vector de salida de longitud fija se canaliza a través de una capa completamente conectada ( Dense ) con 16 unidades ocultas.
  4. La última capa está densamente conectada con un solo nodo de salida.

Función de pérdida y optimizador

Un modelo necesita una función de pérdida y un optimizador para el entrenamiento. Dado que este es un problema de clasificación binaria y el modelo genera una probabilidad (una capa de una sola unidad con una activación sigmoidea), losses.BinaryCrossentropy función loss. losses.BinaryCrossentropy loss.

Ahora, configure el modelo para usar un optimizador y una función de pérdida:

 model.compile(loss=losses.BinaryCrossentropy(from_logits=True), optimizer='adam', metrics=tf.metrics.BinaryAccuracy(threshold=0.0))
 

Entrenar a la modelo

Entrenarás el modelo pasando el objeto del dataset al método de ajuste.

 epochs = 10
history = model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=epochs)
 
Epoch 1/10
625/625 [==============================] - 3s 5ms/step - loss: 0.6632 - binary_accuracy: 0.6931 - val_loss: 0.6135 - val_binary_accuracy: 0.7752
Epoch 2/10
625/625 [==============================] - 3s 4ms/step - loss: 0.5472 - binary_accuracy: 0.8003 - val_loss: 0.4968 - val_binary_accuracy: 0.8220
Epoch 3/10
625/625 [==============================] - 3s 4ms/step - loss: 0.4434 - binary_accuracy: 0.8459 - val_loss: 0.4187 - val_binary_accuracy: 0.8486
Epoch 4/10
625/625 [==============================] - 3s 4ms/step - loss: 0.3770 - binary_accuracy: 0.8660 - val_loss: 0.3726 - val_binary_accuracy: 0.8622
Epoch 5/10
625/625 [==============================] - 2s 4ms/step - loss: 0.3349 - binary_accuracy: 0.8786 - val_loss: 0.3442 - val_binary_accuracy: 0.8678
Epoch 6/10
625/625 [==============================] - 2s 4ms/step - loss: 0.3046 - binary_accuracy: 0.8889 - val_loss: 0.3253 - val_binary_accuracy: 0.8722
Epoch 7/10
625/625 [==============================] - 2s 4ms/step - loss: 0.2807 - binary_accuracy: 0.8977 - val_loss: 0.3118 - val_binary_accuracy: 0.8726
Epoch 8/10
625/625 [==============================] - 2s 4ms/step - loss: 0.2609 - binary_accuracy: 0.9046 - val_loss: 0.3026 - val_binary_accuracy: 0.8762
Epoch 9/10
625/625 [==============================] - 2s 4ms/step - loss: 0.2443 - binary_accuracy: 0.9123 - val_loss: 0.2961 - val_binary_accuracy: 0.8774
Epoch 10/10
625/625 [==============================] - 2s 4ms/step - loss: 0.2309 - binary_accuracy: 0.9163 - val_loss: 0.2915 - val_binary_accuracy: 0.8804

Evaluar el modelo.

Veamos cómo funciona el modelo. Se devolverán dos valores. Pérdida (un número que representa nuestro error, los valores más bajos son mejores) y precisión.

 loss, accuracy = model.evaluate(test_ds)

print("Loss: ", loss)
print("Accuracy: ", accuracy)
 
782/782 [==============================] - 2s 3ms/step - loss: 0.3097 - binary_accuracy: 0.8740
Loss:  0.30967268347740173
Accuracy:  0.8740400075912476

Este enfoque bastante ingenuo logra una precisión de aproximadamente el 86%.

Cree una trama de precisión y pérdida con el tiempo

model.fit() devuelve un objeto History que contiene un diccionario con todo lo que sucedió durante el entrenamiento:

 history_dict = history.history
history_dict.keys()
 
dict_keys(['loss', 'binary_accuracy', 'val_loss', 'val_binary_accuracy'])

Hay cuatro entradas: una para cada métrica monitoreada durante el entrenamiento y la validación. Puede usarlos para trazar la pérdida de capacitación y validación para la comparación, así como la precisión de capacitación y validación:

 acc = history_dict['binary_accuracy']
val_acc = history_dict['val_binary_accuracy']
loss = history_dict['loss']
val_loss = history_dict['val_loss']

epochs = range(1, len(acc) + 1)

# "bo" is for "blue dot"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b is for "solid blue line"
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()
 

png

 plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend(loc='lower right')

plt.show()
 

png

En este gráfico, los puntos representan la pérdida y la precisión del entrenamiento, y las líneas continuas son la pérdida y la precisión de la validación.

Observe que la pérdida de entrenamiento disminuye con cada época y la precisión del entrenamiento aumenta con cada época. Esto se espera cuando se utiliza una optimización de descenso de gradiente: debe minimizar la cantidad deseada en cada iteración.

Este no es el caso de la pérdida y precisión de la validación: parecen alcanzar su punto máximo antes de la precisión del entrenamiento. Este es un ejemplo de sobreajuste: el modelo funciona mejor en los datos de entrenamiento que en los datos que nunca antes había visto. Después de este punto, el modelo optimiza en exceso y aprende representaciones específicas de los datos de entrenamiento que no se generalizan para probar los datos.

Para este caso particular, podría evitar el sobreajuste simplemente deteniendo el entrenamiento cuando la precisión de la validación ya no aumente. Una forma de hacerlo es usar la devolución de llamada EarlyStopping .

Exportar el modelo

En el código anterior, aplicó la capa TextVectorization al conjunto de datos antes de alimentar el texto al modelo. Si desea que su modelo sea capaz de procesar cadenas sin procesar (por ejemplo, para simplificar la implementación), puede incluir la capa TextVectorization dentro de su modelo. Para hacerlo, puede crear un nuevo modelo utilizando los pesos que acaba de entrenar.

 export_model = tf.keras.Sequential([
  vectorize_layer,
  model,
  layers.Activation('sigmoid')
])

export_model.compile(
    loss=losses.BinaryCrossentropy(from_logits=False), optimizer="adam", metrics=['accuracy']
)

# Test it with `raw_test_ds`, which yields raw strings
loss, accuracy = export_model.evaluate(raw_test_ds)
print(accuracy)
 
782/782 [==============================] - 3s 4ms/step - loss: 0.3097 - accuracy: 0.8740
0.8740400075912476

La inclusión de la lógica de preprocesamiento de texto dentro de su modelo le permite exportar un modelo para producción que simplifica la implementación y reduce el potencial de sesgo de entrenamiento / prueba .

Hay una diferencia de rendimiento a tener en cuenta al elegir dónde aplicar su capa TextVectorization. Usarlo fuera de su modelo le permite realizar el procesamiento asíncrono de la CPU y el almacenamiento en búfer de sus datos cuando entrena en GPU. Por lo tanto, si está entrenando su modelo en la GPU, probablemente quiera usar esta opción para obtener el mejor rendimiento mientras desarrolla su modelo, luego cambie a incluir la capa TextVectorization dentro de su modelo cuando esté listo para prepararse para la implementación .

Visite este tutorial para obtener más información sobre cómo guardar modelos.

Ejercicio: clasificación multiclase en preguntas de desbordamiento de pila

Este tutorial mostró cómo entrenar un clasificador binario desde cero en el conjunto de datos IMDB. Como ejercicio, puede modificar este cuaderno para entrenar a un clasificador multiclase para predecir la etiqueta de una pregunta de programación en Stack Overflow .

Hemos preparado un conjunto de datos para su uso que contiene el cuerpo de varios miles de preguntas de programación (por ejemplo, "¿Cómo puedo ordenar un diccionario por valor en Python?") Publicado en Stack Overflow. Cada uno de estos está etiquetado con exactamente una etiqueta (Python, CSharp, JavaScript o Java). Su tarea es tomar una pregunta como entrada y predecir la etiqueta apropiada, en este caso, Python.

El conjunto de datos con el que trabajará contiene varios miles de preguntas extraídas del conjunto de datos público de desbordamiento de pila mucho más grande en BigQuery , que contiene más de 17 millones de publicaciones.

Después de descargar el conjunto de datos, encontrará que tiene una estructura de directorio similar al conjunto de datos IMDB con el que trabajó anteriormente:

 train/
...python/
......0.txt
......1.txt
...javascript/
......0.txt
......1.txt
...csharp/
......0.txt
......1.txt
...java/
......0.txt
......1.txt
 

Para completar este ejercicio, debe modificar este cuaderno para que funcione con el conjunto de datos de desbordamiento de pila realizando las siguientes modificaciones:

  1. En la parte superior de su computadora portátil, actualice el código que descarga el conjunto de datos IMDB con el código para descargar el conjunto de datos Stack Overflow que hemos preparado previamente. Como el conjunto de datos de desbordamiento de pila tiene una estructura de directorio similar, no necesitará realizar muchas modificaciones.

  2. Modifique la última capa de su modelo para leer Dense(4) , ya que ahora hay cuatro clases de salida.

  3. Cuando compile su modelo, cambie la pérdida a SparseCategoricalCrossentropy . Esta es la función de pérdida correcta para usar en un problema de clasificación multiclase, cuando las etiquetas para cada clase son enteros (en nuestro caso, pueden ser 0, 1 , 2 o 3 ).

  4. Una vez que se completen estos cambios, podrá entrenar un clasificador multiclase.

Si te quedas atascado, puedes encontrar una solución aquí .

Aprendiendo más

Este tutorial introdujo la clasificación de texto desde cero. Para obtener más información sobre el flujo de trabajo de clasificación de texto en general, recomendamos leer esta guía de Google Developers.

 
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.