Classificare il testo con BERT

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza su GitHub Scarica taccuino Vedi il modello del mozzo TF

Questo tutorial contiene il codice completo per mettere a punto BERT per eseguire l'analisi del sentiment su un set di dati di recensioni di film IMDB in testo normale. Oltre ad addestrare un modello, imparerai come preelaborare il testo in un formato appropriato.

In questo quaderno dovrai:

  • Carica il set di dati IMDB
  • Carica un modello BERT da TensorFlow Hub
  • Costruisci il tuo modello combinando BERT con un classificatore
  • Allena il tuo modello, mettendo a punto BERT come parte di questo
  • Salva il tuo modello e usalo per classificare le frasi

Se siete nuovi a lavorare con il set di dati IMDB, vedere la classificazione di base del testo per maggiori dettagli.

A proposito di BERT

BERT e altre architetture encoder Transformer sono stati di grande successo su una varietà di compiti in PNL (elaborazione del linguaggio naturale). Calcolano rappresentazioni dello spazio vettoriale del linguaggio naturale adatte per l'uso nei modelli di deep learning. La famiglia di modelli BERT utilizza l'architettura dell'encoder Transformer per elaborare ogni token del testo di input nel contesto completo di tutti i token prima e dopo, da cui il nome: Bidirectional Encoder Representations from Transformers.

I modelli BERT sono solitamente pre-addestrati su un ampio corpus di testi, quindi perfezionati per compiti specifici.

Impostare

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

Si utilizzerà l'ottimizzatore AdamW da tensorflow / modelli .

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')
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/pkg_resources/__init__.py:119: PkgResourcesDeprecationWarning: 0.18ubuntu0.18.04.1 is an invalid version and will not be supported in a future release
  PkgResourcesDeprecationWarning,

Analisi del sentimento

Questo notebook si allena un modello di sentiment analysis per recensioni di film classificare come positivo o negativo, in base al testo della revisione.

Potrai utilizzare il Grande Movie Review dataset che contiene il testo di 50.000 recensioni di film dalla Internet Movie Database .

Scarica il set di dati IMDB

Scarichiamo ed estraiamo il set di dati, quindi esploriamo la struttura delle directory.

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 [==============================] - 7s 0us/step
84140032/84125825 [==============================] - 7s 0us/step

Successivamente, si utilizzerà il text_dataset_from_directory utility per creare una etichetta tf.data.Dataset .

Il set di dati IMDB è già stato suddiviso in treno e test, ma manca di un set di convalida. Creiamo un set di validazione con un 80:20 spaccatura dei dati di addestramento utilizzando il validation_split argomento di seguito.

AUTOTUNE = tf.data.AUTOTUNE
batch_size = 32
seed = 42

raw_train_ds = tf.keras.utils.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.utils.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.utils.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.

Diamo un'occhiata ad alcune recensioni.

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)
2021-12-01 12:17:32.795514: W tensorflow/core/kernels/data/cache_dataset_ops.cc:768] The calling iterator did not fully read the dataset being cached. In order to avoid unexpected truncation of the dataset, the partially cached contents of the dataset  will be discarded. This can happen if you have an input pipeline similar to `dataset.cache().take(k).repeat()`. You should use `dataset.take(k).cache().repeat()` instead.

Caricamento di modelli da TensorFlow Hub

Qui puoi scegliere quale modello BERT caricherai da TensorFlow Hub e perfezionarlo. Sono disponibili più modelli BERT.

  • BERT-Base , uncased e altri sette modelli con pesi addestrati rilasciate dagli autori BERT originali.
  • Piccole BERT hanno la stessa architettura generale, ma meno e / o blocchi trasformatore più piccolo, che ti permette di esplorare compromessi tra velocità, dimensioni e qualità.
  • ALBERT : quattro diverse dimensioni di "A Lite BERT" che riduce la dimensione modello (ma non il tempo di calcolo) ripartendo parametri tra gli strati.
  • BERT Gli esperti : otto modelli che tutti hanno l'architettura BERT-base, ma offrono una scelta tra diversi domini di pre-formazione, per allineare più strettamente con il compito di destinazione.
  • Electra ha la stessa architettura BERT (in tre diverse dimensioni), ma viene pre-formato come discriminatore in un set-up che assomiglia a un contraddittorio rete Generativa (GAN).
  • BERT con Talking teste Attenzione e gated GELU [ basamento , grande ] ha due miglioramenti al nucleo dell'architettura trasformatore.

La documentazione del modello su TensorFlow Hub contiene maggiori dettagli e riferimenti alla letteratura di ricerca. Segui i link di cui sopra, oppure fare clic sul tfhub.dev URL stampata dopo la successiva esecuzione delle cellule.

Il suggerimento è di iniziare con un BERT Small (con meno parametri) poiché sono più veloci da mettere a punto. Se ti piace un modello piccolo ma con una precisione maggiore, ALBERT potrebbe essere la tua prossima opzione. Se desideri una precisione ancora migliore, scegli una delle classiche taglie BERT o le loro recenti rifiniture come Electra, Talking Heads o un BERT Expert.

A parte i modelli disponibili di seguito, ci sono più versioni dei modelli che sono più grandi e possono produrre anche una maggiore precisione, ma sono troppo grandi per essere messo a punto su una singola GPU. Sarete in grado di farlo sui compiti COLLA Risolvi utilizzando BERT su un CoLab TPU .

Vedrai nel codice seguente che cambiare l'URL tfhub.dev è sufficiente per provare uno di questi modelli, perché tutte le differenze tra loro sono incapsulate nei SavedModels da TF Hub.

Scegli un modello BERT per la messa a punto

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

Il modello di preelaborazione

Gli input di testo devono essere trasformati in ID token numerici e organizzati in diversi tensori prima di essere inseriti in BERT. TensorFlow Hub fornisce un modello di preelaborazione corrispondente per ciascuno dei modelli BERT discussi sopra, che implementa questa trasformazione utilizzando le operazioni TF dalla libreria TF.text. Non è necessario eseguire codice Python puro al di fuori del modello TensorFlow per preelaborare il testo.

Il modello di preelaborazione deve essere quello a cui fa riferimento la documentazione del modello BERT, che puoi leggere all'URL stampato sopra. Per i modelli BERT dal menu a discesa sopra, il modello di preelaborazione viene selezionato automaticamente.

bert_preprocess_model = hub.KerasLayer(tfhub_handle_preprocess)

Proviamo il modello di preelaborazione su del testo e vediamo l'output:

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_word_ids', 'input_mask', 'input_type_ids']
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]

Come si può vedere, ora avete le 3 uscite dalla pre-elaborazione che un modello BERT userebbe ( input_words_id , input_mask e input_type_ids ).

Alcuni altri punti importanti:

  • L'input viene troncato a 128 token. Il numero di token può essere personalizzato, e si possono vedere maggiori dettagli sui compiti Solve Adesiva usando BERT su un CoLab TPU .
  • Le input_type_ids hanno solo valore (0) perché questo è un singolo ingresso frase. Per un input di più frasi, avrebbe un numero per ogni input.

Poiché questo preprocessore di testo è un modello TensorFlow, può essere incluso direttamente nel modello.

Utilizzando il modello BERT

Prima di inserire BERT nel tuo modello, diamo un'occhiata ai suoi output. Lo caricherai da TF Hub e vedrai i valori restituiti.

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]]

I modelli BERT restituiscono una mappa con 3 chiavi importanti: pooled_output , sequence_output , encoder_outputs :

  • pooled_output rappresenta ogni sequenza di ingresso nel suo complesso. La forma è [batch_size, H] . Puoi pensare a questo come a un incorporamento per l'intera recensione del film.
  • sequence_output rappresenta ogni ingresso token nel contesto. La forma è [batch_size, seq_length, H] . Puoi pensare a questo come a un incorporamento contestuale per ogni token nella recensione del film.
  • encoder_outputs sono le attivazioni intermedie delle L blocchi trasformatore. outputs["encoder_outputs"][i] è un tensore di forma [batch_size, seq_length, 1024] con le uscite del i-esimo blocco trasformatore, per 0 <= i < L . L'ultimo valore della lista è pari a sequence_output .

Per la messa a punto che si sta per utilizzare il pooled_output array.

Definisci il tuo modello

Creerai un modello molto semplice e perfezionato, con il modello di preelaborazione, il modello BERT selezionato, un livello Dense e un Dropout.

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)

Verifichiamo che il modello venga eseguito con l'output del modello di preelaborazione.

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

L'output è privo di significato, ovviamente, perché il modello non è stato ancora addestrato.

Diamo un'occhiata alla struttura del modello.

tf.keras.utils.plot_model(classifier_model)

png

Formazione modello

Ora hai tutti i pezzi per addestrare un modello, incluso il modulo di preelaborazione, l'encoder BERT, i dati e il classificatore.

Funzione di perdita

Dal momento che questo è un problema di classificazione binaria e il modello emette una probabilità (uno strato singolo-unità), si utilizzerà losses.BinaryCrossentropy funzione di perdita.

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

Ottimizzatore

Per la messa a punto, utilizziamo lo stesso ottimizzatore con cui BERT è stato originariamente addestrato: gli "Adaptive Moments" (Adam). Questo ottimizzatore minimizza la perdita previsione e fa regolarizzazione per decadimento peso (non utilizzando momenti), che è anche conosciuto come AdamW .

Per il tasso di apprendimento ( init_lr ), si utilizzerà la stessa pianificazione come BERT pre-formazione: decadimento lineare di un tasso di apprendimento iniziale nozionale, preceduto da una fase lineare di warm-up rispetto al primo 10% della Formazione Iter ( num_warmup_steps ). In linea con il documento BERT, il tasso di apprendimento iniziale è inferiore per la messa a punto (meglio di 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')

Caricamento del modello BERT e formazione

Utilizzando il classifier_model creato in precedenza, è possibile compilare il modello con la perdita, metrica e ottimizzatore.

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 [==============================] - 91s 138ms/step - loss: 0.4776 - binary_accuracy: 0.7513 - val_loss: 0.3791 - val_binary_accuracy: 0.8380
Epoch 2/5
625/625 [==============================] - 85s 136ms/step - loss: 0.3266 - binary_accuracy: 0.8547 - val_loss: 0.3659 - val_binary_accuracy: 0.8486
Epoch 3/5
625/625 [==============================] - 86s 138ms/step - loss: 0.2521 - binary_accuracy: 0.8928 - val_loss: 0.3975 - val_binary_accuracy: 0.8518
Epoch 4/5
625/625 [==============================] - 86s 137ms/step - loss: 0.1910 - binary_accuracy: 0.9269 - val_loss: 0.4180 - val_binary_accuracy: 0.8522
Epoch 5/5
625/625 [==============================] - 86s 137ms/step - loss: 0.1509 - binary_accuracy: 0.9433 - val_loss: 0.4641 - val_binary_accuracy: 0.8522

Valuta il modello

Vediamo come si comporta il modello. Verranno restituiti due valori. Perdita (un numero che rappresenta l'errore, valori più bassi sono migliori) e precisione.

loss, accuracy = classifier_model.evaluate(test_ds)

print(f'Loss: {loss}')
print(f'Accuracy: {accuracy}')
782/782 [==============================] - 61s 78ms/step - loss: 0.4495 - binary_accuracy: 0.8554
Loss: 0.4494614601135254
Accuracy: 0.8553599715232849

Traccia la precisione e la perdita nel tempo

Sulla base della History oggetto restituito da model.fit() . È possibile tracciare la perdita di formazione e convalida per il confronto, nonché l'accuratezza della formazione e della convalida:

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)
# r is for "solid red line"
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 0x7fee7cdb4450>

png

In questo grafico, le linee rosse rappresentano la perdita e l'accuratezza dell'allenamento e le linee blu rappresentano la perdita e l'accuratezza della convalida.

Esporta per inferenza

Ora devi solo salvare il tuo modello messo a punto per un uso successivo.

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

classifier_model.save(saved_model_path, include_optimizer=False)
2021-12-01 12:26:06.207608: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
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.

Ricarichiamo il modello, così puoi provarlo fianco a fianco con il modello che è ancora in memoria.

reloaded_model = tf.saved_model.load(saved_model_path)

Qui puoi testare il tuo modello su qualsiasi frase che desideri, basta aggiungere alla variabile di esempio di seguito.

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.999521
input: The movie was great!           : score: 0.997015
input: The movie was meh.             : score: 0.988535
input: The movie was okish.           : score: 0.079138
input: The movie was terrible...      : score: 0.001622

Results from the model in memory:
input: this is such an amazing movie! : score: 0.999521
input: The movie was great!           : score: 0.997015
input: The movie was meh.             : score: 0.988535
input: The movie was okish.           : score: 0.079138
input: The movie was terrible...      : score: 0.001622

Se si desidera utilizzare il modello sul Servire TF , ricordate che esso chiamerà il SavedModel attraverso una delle sue firme di nome. In Python, puoi testarli come segue:

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.999521
input: The movie was great!           : score: 0.997015
input: The movie was meh.             : score: 0.988535
input: The movie was okish.           : score: 0.079138
input: The movie was terrible...      : score: 0.001622

Prossimi passi

Come passo successivo, si può provare a risolvere i compiti Adesiva usando BERT su un tutorial TPU , che gira su un TPU e mostra come lavorare con più ingressi.