¿Tengo una pregunta? Conéctese con la comunidad en el Foro de visita del foro de TensorFlow

Clasificar texto con BERT

Ver en TensorFlow.org Ejecutar en Google Colab Ver en GitHub Descargar cuaderno Ver modelo TF Hub

Este tutorial contiene código completo para ajustar BERT a fin de realizar análisis de sentimientos en un conjunto de datos de reseñas de películas IMDB de texto sin formato. Además de entrenar un modelo, aprenderá a preprocesar texto en un formato apropiado.

En este cuaderno, podrá:

  • Cargar el conjunto de datos de IMDB
  • Cargar un modelo BERT desde TensorFlow Hub
  • Construya su propio modelo combinando BERT con un clasificador
  • Entrene su propio modelo, afinando BERT como parte de eso
  • Guarde su modelo y utilícelo para clasificar oraciones

Si es nuevo en el trabajo con el conjunto de datos de IMDB, consulte Clasificación de texto básica para obtener más detalles.

Sobre BERT

BERT y otras arquitecturas de codificador Transformer han tenido un gran éxito en una variedad de tareas en NLP (procesamiento de lenguaje natural). Calculan representaciones en el espacio vectorial del lenguaje natural que son adecuadas para su uso en modelos de aprendizaje profundo. La familia de modelos BERT utiliza la arquitectura de codificador Transformer para procesar cada token de texto de entrada en el contexto completo de todos los tokens antes y después, de ahí el nombre: Representaciones de codificador bidireccional de Transformers.

Los modelos BERT generalmente se entrenan previamente en un gran corpus de texto y luego se ajustan para tareas específicas.

Configuración

# A dependency of the preprocessing for BERT inputs
pip install -q -U tensorflow-text

Utilizará el optimizador AdamW de tensorflow / models .

pip install -q tf-models-official
import os
import shutil

import tensorflow as tf
import tensorflow_hub as hub
import tensorflow_text as text
from official.nlp import optimization  # to create AdamW optimizer

import matplotlib.pyplot as plt

tf.get_logger().setLevel('ERROR')

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.

Utilizará el conjunto de datos de reseñas 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 .

Descarga el conjunto de datos de IMDB

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

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

train_dir = os.path.join(dataset_dir, 'train')

# remove unused folders to make it easier to load the data
remove_dir = os.path.join(train_dir, 'unsup')
shutil.rmtree(remove_dir)
Downloading data from https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz
84131840/84125825 [==============================] - 10s 0us/step

A continuación, utilizará la utilidad text_dataset_from_directory para crear untf.data.Dataset etiquetado.

El conjunto de datos de IMDB ya se ha dividido en entrenamiento 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.

AUTOTUNE = tf.data.AUTOTUNE
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)

class_names = raw_train_ds.class_names
train_ds = raw_train_ds.cache().prefetch(buffer_size=AUTOTUNE)

val_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'aclImdb/train',
    batch_size=batch_size,
    validation_split=0.2,
    subset='validation',
    seed=seed)

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

test_ds = tf.keras.preprocessing.text_dataset_from_directory(
    'aclImdb/test',
    batch_size=batch_size)

test_ds = test_ds.cache().prefetch(buffer_size=AUTOTUNE)
Found 25000 files belonging to 2 classes.
Using 20000 files for training.
Found 25000 files belonging to 2 classes.
Using 5000 files for validation.
Found 25000 files belonging to 2 classes.

Echemos un vistazo a algunas reseñas.

for text_batch, label_batch in train_ds.take(1):
  for i in range(3):
    print(f'Review: {text_batch.numpy()[i]}')
    label = label_batch.numpy()[i]
    print(f'Label : {label} ({class_names[label]})')
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 (neg)
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 (neg)
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 (pos)

Carga de modelos desde TensorFlow Hub

Aquí puede elegir qué modelo BERT cargará desde TensorFlow Hub y ajustarlo. Hay varios modelos BERT disponibles.

  • BERT-Base , Uncased y siete modelos más con pesos entrenados lanzados por los autores originales de BERT.
  • Los BERT pequeños tienen la misma arquitectura general pero menos bloques Transformer o más pequeños, lo que le permite explorar las compensaciones entre velocidad, tamaño y calidad.
  • ALBERT : cuatro tamaños diferentes de "A Lite BERT" que reduce el tamaño del modelo (pero no el tiempo de cálculo) al compartir parámetros entre capas.
  • Expertos en BERT : ocho modelos que tienen todos la arquitectura basada en BERT, pero ofrecen una opción entre diferentes dominios de preentrenamiento, para alinearse más estrechamente con la tarea objetivo.
  • Electra tiene la misma arquitectura que BERT (en tres tamaños diferentes), pero se entrena previamente como discriminador en una configuración que se asemeja a una Red Adversaria Generativa (GAN).
  • BERT con Talking-Heads Attention y Gated GELU [ base , large ] tiene dos mejoras en el núcleo de la arquitectura Transformer.

La documentación del modelo en TensorFlow Hub tiene más detalles y referencias a la literatura de investigación. Siga los enlaces anteriores o haga clic en la URL tfhub.dev impresa después de la siguiente ejecución de celda.

La sugerencia es comenzar con un Small BERT (con menos parámetros) ya que son más rápidos de ajustar. Si le gusta un modelo pequeño pero con mayor precisión, ALBERT podría ser su próxima opción. Si desea una precisión aún mayor, elija uno de los tamaños clásicos de BERT o sus mejoras recientes como Electra, Talking Heads o BERT Expert.

Aparte de los modelos disponibles a continuación, hay varias versiones de los modelos que son más grandes y pueden ofrecer una precisión aún mejor, pero son demasiado grandes para ajustarlos en una sola GPU. Podrá hacer eso en las tareas de Resolver GLUE usando BERT en un colab de TPU .

Verá en el código siguiente que cambiar la URL tfhub.dev es suficiente para probar cualquiera de estos modelos, porque todas las diferencias entre ellos están encapsuladas en los modelos guardados de TF Hub.

Elija un modelo BERT para ajustarlo

BERT model selected           : https://tfhub.dev/tensorflow/small_bert/bert_en_uncased_L-4_H-512_A-8/1
Preprocess model auto-selected: https://tfhub.dev/tensorflow/bert_en_uncased_preprocess/3

El modelo de preprocesamiento

Las entradas de texto deben transformarse en identificadores de token numéricos y organizarse en varios tensores antes de ingresar a BERT. TensorFlow Hub proporciona un modelo de preprocesamiento coincidente para cada uno de los modelos BERT discutidos anteriormente, que implementa esta transformación mediante operaciones TF de la biblioteca TF.text. No es necesario ejecutar código Python puro fuera de su modelo de TensorFlow para preprocesar el texto.

El modelo de preprocesamiento debe ser el referenciado por la documentación del modelo BERT, que puede leer en la URL impresa arriba. Para los modelos BERT del menú desplegable anterior, el modelo de preprocesamiento se selecciona automáticamente.

bert_preprocess_model = hub.KerasLayer(tfhub_handle_preprocess)

Probemos el modelo de preprocesamiento en algún texto y veamos el resultado:

text_test = ['this is such an amazing movie!']
text_preprocessed = bert_preprocess_model(text_test)

print(f'Keys       : {list(text_preprocessed.keys())}')
print(f'Shape      : {text_preprocessed["input_word_ids"].shape}')
print(f'Word Ids   : {text_preprocessed["input_word_ids"][0, :12]}')
print(f'Input Mask : {text_preprocessed["input_mask"][0, :12]}')
print(f'Type Ids   : {text_preprocessed["input_type_ids"][0, :12]}')
Keys       : ['input_type_ids', 'input_word_ids', 'input_mask']
Shape      : (1, 128)
Word Ids   : [ 101 2023 2003 2107 2019 6429 3185  999  102    0    0    0]
Input Mask : [1 1 1 1 1 1 1 1 1 0 0 0]
Type Ids   : [0 0 0 0 0 0 0 0 0 0 0 0]

Como puede ver, ahora tiene las 3 salidas del preprocesamiento que usaría un modelo BERT ( input_words_id , input_mask y input_type_ids ).

Algunos otros puntos importantes:

  • La entrada se trunca a 128 tokens. La cantidad de tokens se puede personalizar y puede ver más detalles sobre las tareas de Resolver GLUE usando BERT en un colab de TPU .
  • Los input_type_ids solo tienen un valor (0) porque se trata de una entrada de una sola oración. Para una entrada de oración múltiple, tendría un número para cada entrada.

Dado que este preprocesador de texto es un modelo de TensorFlow, se puede incluir directamente en su modelo.

Usando el modelo BERT

Antes de poner BERT en su propio modelo, echemos un vistazo a sus resultados. Lo cargará desde TF Hub y verá los valores devueltos.

bert_model = hub.KerasLayer(tfhub_handle_encoder)
bert_results = bert_model(text_preprocessed)

print(f'Loaded BERT: {tfhub_handle_encoder}')
print(f'Pooled Outputs Shape:{bert_results["pooled_output"].shape}')
print(f'Pooled Outputs Values:{bert_results["pooled_output"][0, :12]}')
print(f'Sequence Outputs Shape:{bert_results["sequence_output"].shape}')
print(f'Sequence Outputs Values:{bert_results["sequence_output"][0, :12]}')
Loaded BERT: https://tfhub.dev/tensorflow/small_bert/bert_en_uncased_L-4_H-512_A-8/1
Pooled Outputs Shape:(1, 512)
Pooled Outputs Values:[ 0.76262873  0.99280983 -0.1861186   0.36673835  0.15233682  0.65504444
  0.9681154  -0.9486272   0.00216158 -0.9877732   0.0684272  -0.9763061 ]
Sequence Outputs Shape:(1, 128, 512)
Sequence Outputs Values:[[-0.28946388  0.3432126   0.33231565 ...  0.21300787  0.7102078
  -0.05771166]
 [-0.28742015  0.31981024 -0.2301858  ...  0.58455074 -0.21329722
   0.7269209 ]
 [-0.66157013  0.6887685  -0.87432927 ...  0.10877253 -0.26173282
   0.47855264]
 ...
 [-0.2256118  -0.28925604 -0.07064401 ...  0.4756601   0.8327715
   0.40025353]
 [-0.29824278 -0.27473143 -0.05450511 ...  0.48849759  1.0955356
   0.18163344]
 [-0.44378197  0.00930723  0.07223766 ...  0.1729009   1.1833246
   0.07897988]]

Los modelos BERT devuelven un mapa con 3 claves importantes: pooled_output , sequence_output , encoder_outputs :

  • pooled_output representa cada secuencia de entrada como un todo. La forma es [batch_size, H] . Puede pensar en esto como una incrustación para toda la revisión de la película.
  • sequence_output representa cada token de entrada en el contexto. La forma es [batch_size, seq_length, H] . Puede pensar en esto como una inserción contextual para cada token en la reseña de la película.
  • encoder_outputs son las activaciones intermedias de los bloques L Transformer. outputs["encoder_outputs"][i] es un tensor de forma [batch_size, seq_length, 1024] con las salidas del i-ésimo bloque Transformer, para 0 <= i < L . El último valor de la lista es igual a sequence_output .

Para el ajuste fino, utilizará la matriz pooled_output .

Define tu modelo

Creará un modelo muy simple y afinado, con el modelo de preprocesamiento, el modelo BERT seleccionado, una capa densa y una de abandono.

def build_classifier_model():
  text_input = tf.keras.layers.Input(shape=(), dtype=tf.string, name='text')
  preprocessing_layer = hub.KerasLayer(tfhub_handle_preprocess, name='preprocessing')
  encoder_inputs = preprocessing_layer(text_input)
  encoder = hub.KerasLayer(tfhub_handle_encoder, trainable=True, name='BERT_encoder')
  outputs = encoder(encoder_inputs)
  net = outputs['pooled_output']
  net = tf.keras.layers.Dropout(0.1)(net)
  net = tf.keras.layers.Dense(1, activation=None, name='classifier')(net)
  return tf.keras.Model(text_input, net)

Comprobemos que el modelo se ejecuta con la salida del modelo de preprocesamiento.

classifier_model = build_classifier_model()
bert_raw_result = classifier_model(tf.constant(text_test))
print(tf.sigmoid(bert_raw_result))
tf.Tensor([[0.27966386]], shape=(1, 1), dtype=float32)

El resultado no tiene sentido, por supuesto, porque el modelo aún no se ha entrenado.

Echemos un vistazo a la estructura del modelo.

tf.keras.utils.plot_model(classifier_model)

png

Entrenamiento de modelos

Ahora tiene todas las piezas para entrenar un modelo, incluido el módulo de preprocesamiento, el codificador BERT, los datos y el clasificador.

Función de pérdida

Dado que se trata de un problema de clasificación binaria y el modelo genera una probabilidad (una capa de una sola unidad), utilizará la función de pérdida losses.BinaryCrossentropy .

loss = tf.keras.losses.BinaryCrossentropy(from_logits=True)
metrics = tf.metrics.BinaryAccuracy()

Optimizador

Para un ajuste fino, usemos el mismo optimizador con el que se entrenó originalmente BERT: los "Momentos adaptativos" (Adam). Este optimizador minimiza la pérdida de predicción y realiza la regularización por caída de peso (sin usar momentos), lo que también se conoce como AdamW .

Para la tasa de aprendizaje ( init_lr ), utilizará el mismo programa que el preentrenamiento BERT: decadencia lineal de una tasa de aprendizaje inicial teórica, precedida de una fase de calentamiento lineal sobre el primer 10% de los pasos de entrenamiento ( num_warmup_steps ). De acuerdo con el documento BERT, la tasa de aprendizaje inicial es menor para el ajuste fino (mejor de 5e-5, 3e-5, 2e-5).

epochs = 5
steps_per_epoch = tf.data.experimental.cardinality(train_ds).numpy()
num_train_steps = steps_per_epoch * epochs
num_warmup_steps = int(0.1*num_train_steps)

init_lr = 3e-5
optimizer = optimization.create_optimizer(init_lr=init_lr,
                                          num_train_steps=num_train_steps,
                                          num_warmup_steps=num_warmup_steps,
                                          optimizer_type='adamw')

Carga del modelo BERT y formación

Con el classifier_model que creó anteriormente, puede compilar el modelo con la pérdida, la métrica y el optimizador.

classifier_model.compile(optimizer=optimizer,
                         loss=loss,
                         metrics=metrics)
print(f'Training model with {tfhub_handle_encoder}')
history = classifier_model.fit(x=train_ds,
                               validation_data=val_ds,
                               epochs=epochs)
Training model with https://tfhub.dev/tensorflow/small_bert/bert_en_uncased_L-4_H-512_A-8/1
Epoch 1/5
625/625 [==============================] - 88s 132ms/step - loss: 0.4826 - binary_accuracy: 0.7490 - val_loss: 0.4323 - val_binary_accuracy: 0.8298
Epoch 2/5
625/625 [==============================] - 83s 132ms/step - loss: 0.3281 - binary_accuracy: 0.8537 - val_loss: 0.3690 - val_binary_accuracy: 0.8438
Epoch 3/5
625/625 [==============================] - 81s 129ms/step - loss: 0.2471 - binary_accuracy: 0.8972 - val_loss: 0.4079 - val_binary_accuracy: 0.8456
Epoch 4/5
625/625 [==============================] - 82s 131ms/step - loss: 0.1921 - binary_accuracy: 0.9255 - val_loss: 0.4533 - val_binary_accuracy: 0.8432
Epoch 5/5
625/625 [==============================] - 84s 134ms/step - loss: 0.1504 - binary_accuracy: 0.9441 - val_loss: 0.4877 - val_binary_accuracy: 0.8498

Evaluar el modelo

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

loss, accuracy = classifier_model.evaluate(test_ds)

print(f'Loss: {loss}')
print(f'Accuracy: {accuracy}')
782/782 [==============================] - 61s 78ms/step - loss: 0.4634 - binary_accuracy: 0.8538
Loss: 0.463392972946167
Accuracy: 0.8538399934768677

Trace la precisión y la pérdida a lo largo del tiempo

Basado en el objeto History devuelto por model.fit() . Puede trazar la pérdida de entrenamiento y validación para comparar, así como la precisión de entrenamiento y validación:

history_dict = history.history
print(history_dict.keys())

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)
fig = plt.figure(figsize=(10, 6))
fig.tight_layout()

plt.subplot(2, 1, 1)
# "bo" is for "blue dot"
plt.plot(epochs, loss, 'r', 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.subplot(2, 1, 2)
plt.plot(epochs, acc, 'r', 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')
dict_keys(['loss', 'binary_accuracy', 'val_loss', 'val_binary_accuracy'])
<matplotlib.legend.Legend at 0x7f3fe1284a90>

png

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

Exportar para inferencia

Ahora, simplemente guarde su modelo ajustado para usarlo más adelante.

dataset_name = 'imdb'
saved_model_path = './{}_bert'.format(dataset_name.replace('/', '_'))

classifier_model.save(saved_model_path, include_optimizer=False)
WARNING:absl:Found untraced functions such as restored_function_body, restored_function_body, restored_function_body, restored_function_body, restored_function_body while saving (showing 5 of 310). These functions will not be directly callable after loading.

Recarguemos el modelo, para que pueda probarlo junto con el modelo que todavía está en la memoria.

reloaded_model = tf.saved_model.load(saved_model_path)

Aquí puede probar su modelo en cualquier oración que desee, solo agregue a la variable de ejemplos a continuación.

def print_my_examples(inputs, results):
  result_for_printing = \
    [f'input: {inputs[i]:<30} : score: {results[i][0]:.6f}'
                         for i in range(len(inputs))]
  print(*result_for_printing, sep='\n')
  print()


examples = [
    'this is such an amazing movie!',  # this is the same sentence tried earlier
    'The movie was great!',
    'The movie was meh.',
    'The movie was okish.',
    'The movie was terrible...'
]

reloaded_results = tf.sigmoid(reloaded_model(tf.constant(examples)))
original_results = tf.sigmoid(classifier_model(tf.constant(examples)))

print('Results from the saved model:')
print_my_examples(examples, reloaded_results)
print('Results from the model in memory:')
print_my_examples(examples, original_results)
Results from the saved model:
input: this is such an amazing movie! : score: 0.999455
input: The movie was great!           : score: 0.993913
input: The movie was meh.             : score: 0.944821
input: The movie was okish.           : score: 0.052513
input: The movie was terrible...      : score: 0.001723

Results from the model in memory:
input: this is such an amazing movie! : score: 0.999455
input: The movie was great!           : score: 0.993913
input: The movie was meh.             : score: 0.944821
input: The movie was okish.           : score: 0.052513
input: The movie was terrible...      : score: 0.001723

Si desea utilizar su modelo en TF Serving , recuerde que llamará a su SavedModel a través de una de sus firmas nombradas. En Python, puede probarlos de la siguiente manera:

serving_results = reloaded_model \
            .signatures['serving_default'](tf.constant(examples))

serving_results = tf.sigmoid(serving_results['classifier'])

print_my_examples(examples, serving_results)
input: this is such an amazing movie! : score: 0.999455
input: The movie was great!           : score: 0.993913
input: The movie was meh.             : score: 0.944821
input: The movie was okish.           : score: 0.052513
input: The movie was terrible...      : score: 0.001723

Próximos pasos

Como siguiente paso, puede probar Resolver tareas GLUE usando BERT en un tutorial de TPU , que se ejecuta en una TPU y le muestra cómo trabajar con múltiples entradas.