La journée communautaire ML est le 9 novembre ! Rejoignez - nous pour les mises à jour de tensorflow, JAX et plus En savoir plus

Ajuster un modèle BERT

Voir sur TensorFlow.org Exécuter dans Google Colab Voir la source sur GitHub Télécharger le cahier Voir le modèle TF Hub

Dans cet exemple, nous allons affiner un modèle BERT à l'aide du package PIP tensorflow-models.

Le modèle BERT ce pré - entraîné tutoriel est basé sur est également disponible sur tensorflow Hub , pour voir comment l'utiliser se référer à la Annexe Hub

Installer

Installer le package de pépins TensorFlow Model Garden

  • tf-models-official est le modèle stable package jardin. Notez qu'il peut ne pas inclure les derniers changements dans les tensorflow_models de repo. Pour inclure les dernières modifications, vous pouvez installer tf-models-nightly tous les tf-models-nightly , ce qui est le forfait de nuit Modèle jardin créé par jour automatiquement.
  • pip installera automatiquement tous les modèles et dépendances.
pip install -q -U tensorflow-text
pip install -q tf-models-official==2.4.0

Importations

import os

import numpy as np
import matplotlib.pyplot as plt

import tensorflow as tf

import tensorflow_hub as hub
import tensorflow_datasets as tfds
tfds.disable_progress_bar()

from official.modeling import tf_utils
from official import nlp
from official.nlp import bert

# Load the required submodules
import official.nlp.optimization
import official.nlp.bert.bert_models
import official.nlp.bert.configs
import official.nlp.bert.run_classifier
import official.nlp.bert.tokenization
import official.nlp.data.classifier_data_lib
import official.nlp.modeling.losses
import official.nlp.modeling.models
import official.nlp.modeling.networks
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow_addons/utils/ensure_tf_install.py:67: UserWarning: Tensorflow Addons supports using Python ops for all Tensorflow versions above or equal to 2.3.0 and strictly below 2.6.0 (nightly versions are not supported). 
 The versions of TensorFlow you are currently using is 2.6.0 and is not supported. 
Some things might work, some things might not.
If you were to encounter a bug, do not file an issue.
If you want to make sure you're using a tested and supported configuration, either change the TensorFlow version or the TensorFlow Addons's version. 
You can find the compatibility matrix in TensorFlow Addon's readme:
https://github.com/tensorflow/addons
  UserWarning,

Ressources

Ce répertoire contient la configuration, le vocabulaire et un point de contrôle pré-entraîné utilisé dans ce tutoriel :

gs_folder_bert = "gs://cloud-tpu-checkpoints/bert/v3/uncased_L-12_H-768_A-12"
tf.io.gfile.listdir(gs_folder_bert)
['bert_config.json',
 'bert_model.ckpt.data-00000-of-00001',
 'bert_model.ckpt.index',
 'vocab.txt']

Vous pouvez obtenir un codeur BERT pré-formé à partir tensorflow Hub :

hub_url_bert = "https://tfhub.dev/tensorflow/bert_en_uncased_L-12_H-768_A-12/3"

Les données

Pour cet exemple , nous avons utilisé l' ensemble de données COLLE MRPC de TFDS .

Cet ensemble de données n'est pas configuré de manière à pouvoir être directement introduit dans le modèle BERT. Cette section gère donc également le prétraitement nécessaire.

Obtenir l'ensemble de données à partir des ensembles de données TensorFlow

Le Microsoft Research Paraphrase Corpus (Dolan & Brockett, 2005) est un corpus de paires de phrases extraites automatiquement de sources d'information en ligne, avec des annotations humaines pour savoir si les phrases de la paire sont sémantiquement équivalentes.

  • Nombre d'étiquettes : 2.
  • Taille de l'ensemble de données d'entraînement : 3668.
  • Taille de l'ensemble de données d'évaluation : 408.
  • Longueur de séquence maximale de l'ensemble de données d'entraînement et d'évaluation : 128.
glue, info = tfds.load('glue/mrpc', with_info=True,
                       # It's small, load the whole dataset
                       batch_size=-1)
WARNING:tensorflow:From /home/kbuilder/.local/lib/python3.7/site-packages/tensorflow_datasets/core/dataset_builder.py:622: get_single_element (from tensorflow.python.data.experimental.ops.get_single_element) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.get_single_element()`.
2021-08-19 11:26:56.046711: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 11:26:56.055242: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 11:26:56.056228: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 11:26:56.057937: I tensorflow/core/platform/cpu_feature_guard.cc:142] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX2 AVX512F FMA
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
2021-08-19 11:26:56.058570: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 11:26:56.059629: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 11:26:56.060526: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 11:26:56.661471: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 11:26:56.662451: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 11:26:56.663279: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:937] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2021-08-19 11:26:56.664195: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1510] Created device /job:localhost/replica:0/task:0/device:GPU:0 with 14648 MB memory:  -> device: 0, name: Tesla V100-SXM2-16GB, pci bus id: 0000:00:05.0, compute capability: 7.0
WARNING:tensorflow:From /home/kbuilder/.local/lib/python3.7/site-packages/tensorflow_datasets/core/dataset_builder.py:622: get_single_element (from tensorflow.python.data.experimental.ops.get_single_element) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.data.Dataset.get_single_element()`.
2021-08-19 11:26:57.012380: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:185] None of the MLIR Optimization Passes are enabled (registered 2)
list(glue.keys())
['train', 'validation', 'test']

L' info objet décrit l'ensemble de données et ses caractéristiques:

info.features
FeaturesDict({
    'idx': tf.int32,
    'label': ClassLabel(shape=(), dtype=tf.int64, num_classes=2),
    'sentence1': Text(shape=(), dtype=tf.string),
    'sentence2': Text(shape=(), dtype=tf.string),
})

Les deux classes sont :

info.features['label'].names
['not_equivalent', 'equivalent']

Voici un exemple de l'ensemble de formation :

glue_train = glue['train']

for key, value in glue_train.items():
  print(f"{key:9s}: {value[0].numpy()}")
idx      : 1680
label    : 0
sentence1: b'The identical rovers will act as robotic geologists , searching for evidence of past water .'
sentence2: b'The rovers act as robotic geologists , moving on six wheels .'

Le tokenizer BERT

Pour affiner un modèle pré-entraîné, vous devez vous assurer que vous utilisez exactement la même tokenisation, le même vocabulaire et le mappage d'index que ceux que vous avez utilisés pendant la formation.

Le tokenizer BERT utilisé dans ce didacticiel est écrit en Python pur (il n'est pas construit à partir des opérations TensorFlow). Donc , vous ne pouvez pas brancher simplement dans votre modèle en tant que keras.layer comme vous pouvez avec preprocessing.TextVectorization .

Le code suivant reconstruit le tokenizer qui a été utilisé par le modèle de base :

# Set up tokenizer to generate Tensorflow dataset
tokenizer = bert.tokenization.FullTokenizer(
    vocab_file=os.path.join(gs_folder_bert, "vocab.txt"),
     do_lower_case=True)

print("Vocab size:", len(tokenizer.vocab))
Vocab size: 30522

Tokenize une phrase :

tokens = tokenizer.tokenize("Hello TensorFlow!")
print(tokens)
ids = tokenizer.convert_tokens_to_ids(tokens)
print(ids)
['hello', 'tensor', '##flow', '!']
[7592, 23435, 12314, 999]

Prétraiter les données

La section a prétraité manuellement l'ensemble de données dans le format attendu par le modèle.

Cet ensemble de données est petit, donc le prétraitement peut être effectué rapidement et facilement en mémoire. Pour les ensembles de données plus la tf_models bibliothèque comprend des outils pour prétraiter et re-sérialisation d' un ensemble de données. Voir Annexe: Réencodage un grand ensemble de données pour plus de détails.

Encoder les phrases

Le modèle s'attend à ce que ses deux phrases d'entrée soient concaténées. Cette entrée devrait commencer par [CLS] « Ceci est un problème de classification » jeton, et chaque phrase doit se terminer par un [SEP] jeton « séparateur »:

tokenizer.convert_tokens_to_ids(['[CLS]', '[SEP]'])
[101, 102]

Commencez par coder toutes les phrases en ajoutant un [SEP] jeton, et les emballer dans-tenseurs Ragged:

def encode_sentence(s):
   tokens = list(tokenizer.tokenize(s.numpy()))
   tokens.append('[SEP]')
   return tokenizer.convert_tokens_to_ids(tokens)

sentence1 = tf.ragged.constant([
    encode_sentence(s) for s in glue_train["sentence1"]])
sentence2 = tf.ragged.constant([
    encode_sentence(s) for s in glue_train["sentence2"]])
print("Sentence1 shape:", sentence1.shape.as_list())
print("Sentence2 shape:", sentence2.shape.as_list())
Sentence1 shape: [3668, None]
Sentence2 shape: [3668, None]

Maintenant préfixer un [CLS] jeton et concaténer les tenseurs haillons pour former un seul input_word_ids tenseur pour chaque exemple. RaggedTensor.to_tensor() zéro des tampons à la plus longue séquence.

cls = [tokenizer.convert_tokens_to_ids(['[CLS]'])]*sentence1.shape[0]
input_word_ids = tf.concat([cls, sentence1, sentence2], axis=-1)
_ = plt.pcolormesh(input_word_ids.to_tensor())

png

Masque et type d'entrée

Le modèle attend deux entrées supplémentaires :

  • Le masque de saisie
  • Le type d'entrée

Le masque permet au modèle de différencier clairement entre le contenu et le rembourrage. Le masque a la même forme que les input_word_ids , et contient un 1 où les input_word_ids ne sont pas rembourrage.

input_mask = tf.ones_like(input_word_ids).to_tensor()

plt.pcolormesh(input_mask)
<matplotlib.collections.QuadMesh at 0x7f05206b9790>

png

Le « type d'entrée » a également la même forme, mais à l' intérieur de la région non rembourré, contient un 0 ou un 1 indiquant quelle phrase le jeton est une partie de.

type_cls = tf.zeros_like(cls)
type_s1 = tf.zeros_like(sentence1)
type_s2 = tf.ones_like(sentence2)
input_type_ids = tf.concat([type_cls, type_s1, type_s2], axis=-1).to_tensor()

plt.pcolormesh(input_type_ids)
<matplotlib.collections.QuadMesh at 0x7f0520116d90>

png

Mets le tout ensemble

Recueillir le code d'analyse de texte ci - dessus en une seule fonction, et l' appliquer à chaque division de la glue/mrpc ensemble de données.

def encode_sentence(s, tokenizer):
   tokens = list(tokenizer.tokenize(s))
   tokens.append('[SEP]')
   return tokenizer.convert_tokens_to_ids(tokens)

def bert_encode(glue_dict, tokenizer):
  num_examples = len(glue_dict["sentence1"])

  sentence1 = tf.ragged.constant([
      encode_sentence(s, tokenizer)
      for s in np.array(glue_dict["sentence1"])])
  sentence2 = tf.ragged.constant([
      encode_sentence(s, tokenizer)
       for s in np.array(glue_dict["sentence2"])])

  cls = [tokenizer.convert_tokens_to_ids(['[CLS]'])]*sentence1.shape[0]
  input_word_ids = tf.concat([cls, sentence1, sentence2], axis=-1)

  input_mask = tf.ones_like(input_word_ids).to_tensor()

  type_cls = tf.zeros_like(cls)
  type_s1 = tf.zeros_like(sentence1)
  type_s2 = tf.ones_like(sentence2)
  input_type_ids = tf.concat(
      [type_cls, type_s1, type_s2], axis=-1).to_tensor()

  inputs = {
      'input_word_ids': input_word_ids.to_tensor(),
      'input_mask': input_mask,
      'input_type_ids': input_type_ids}

  return inputs
glue_train = bert_encode(glue['train'], tokenizer)
glue_train_labels = glue['train']['label']

glue_validation = bert_encode(glue['validation'], tokenizer)
glue_validation_labels = glue['validation']['label']

glue_test = bert_encode(glue['test'], tokenizer)
glue_test_labels  = glue['test']['label']

Chaque sous-ensemble de données a été converti en un dictionnaire de caractéristiques et un ensemble d'étiquettes. Chaque entité du dictionnaire d'entrée a la même forme et le nombre d'étiquettes doit correspondre :

for key, value in glue_train.items():
  print(f'{key:15s} shape: {value.shape}')

print(f'glue_train_labels shape: {glue_train_labels.shape}')
input_word_ids  shape: (3668, 103)
input_mask      shape: (3668, 103)
input_type_ids  shape: (3668, 103)
glue_train_labels shape: (3668,)

Le modèle

Construire le modèle

La première étape consiste à télécharger la configuration du modèle pré-entraîné.

import json

bert_config_file = os.path.join(gs_folder_bert, "bert_config.json")
config_dict = json.loads(tf.io.gfile.GFile(bert_config_file).read())

bert_config = bert.configs.BertConfig.from_dict(config_dict)

config_dict
{'attention_probs_dropout_prob': 0.1,
 'hidden_act': 'gelu',
 'hidden_dropout_prob': 0.1,
 'hidden_size': 768,
 'initializer_range': 0.02,
 'intermediate_size': 3072,
 'max_position_embeddings': 512,
 'num_attention_heads': 12,
 'num_hidden_layers': 12,
 'type_vocab_size': 2,
 'vocab_size': 30522}

La config définit le noyau BERT modèle, qui est un modèle Keras pour prédire les sorties de num_classes des entrées avec une longueur de séquence maximale max_seq_length .

Cette fonction renvoie à la fois l'encodeur et le classificateur.

bert_classifier, bert_encoder = bert.bert_models.classifier_model(
    bert_config, num_labels=2)

Le classificateur a trois entrées et une sortie :

tf.keras.utils.plot_model(bert_classifier, show_shapes=True, dpi=48)

png

Exécutez-le sur un lot de test de 10 exemples de données de l'ensemble d'apprentissage. La sortie est les logits pour les deux classes :

glue_batch = {key: val[:10] for key, val in glue_train.items()}

bert_classifier(
    glue_batch, training=True
).numpy()
array([[-0.2307785 ,  0.14490062],
       [-0.09524915,  0.1295139 ],
       [-0.14503807,  0.19079085],
       [-0.2493319 ,  0.29745924],
       [-0.25108814, -0.09775029],
       [-0.02554443, -0.07443134],
       [-0.34417343,  0.00068308],
       [-0.13155738,  0.10524555],
       [-0.39608416, -0.14741066],
       [-0.2702508 , -0.02493864]], dtype=float32)

Le TransformerEncoder au centre du classificateur ci - dessus est le bert_encoder .

Inspectant le codeur, on voit la pile de Transformer couches reliées à ces mêmes trois entrées:

tf.keras.utils.plot_model(bert_encoder, show_shapes=True, dpi=48)

png

Restaurer les poids de l'encodeur

Une fois construit, l'encodeur est initialisé de manière aléatoire. Restaurez les poids de l'encodeur à partir du point de contrôle :

checkpoint = tf.train.Checkpoint(encoder=bert_encoder)
checkpoint.read(
    os.path.join(gs_folder_bert, 'bert_model.ckpt')).assert_consumed()
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f047c7d6e50>

Configurer l'optimiseur

BERT adopte l'optimiseur Adam avec la désintégration de poids (alias « AdamW »). Il utilise également un programme de taux d'apprentissage qui s'échauffe d'abord à partir de 0, puis décroît jusqu'à 0.

# Set up epochs and steps
epochs = 3
batch_size = 32
eval_batch_size = 32

train_data_size = len(glue_train_labels)
steps_per_epoch = int(train_data_size / batch_size)
num_train_steps = steps_per_epoch * epochs
warmup_steps = int(epochs * train_data_size * 0.1 / batch_size)

# creates an optimizer with learning rate schedule
optimizer = nlp.optimization.create_optimizer(
    2e-5, num_train_steps=num_train_steps, num_warmup_steps=warmup_steps)

Cette fonction renvoie un AdamWeightDecay optimiseur avec le calendrier des taux d'apprentissage: ensemble

type(optimizer)
official.nlp.optimization.AdamWeightDecay

Pour voir un exemple de la façon de personnaliser l'optimiseur et le calendrier de, voir l' annexe de calendrier Optimizer .

Former le modèle

La métrique est la précision et nous utilisons une entropie croisée catégorique clairsemée comme perte.

metrics = [tf.keras.metrics.SparseCategoricalAccuracy('accuracy', dtype=tf.float32)]
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)

bert_classifier.compile(
    optimizer=optimizer,
    loss=loss,
    metrics=metrics)

bert_classifier.fit(
      glue_train, glue_train_labels,
      validation_data=(glue_validation, glue_validation_labels),
      batch_size=32,
      epochs=epochs)
Epoch 1/3
115/115 [==============================] - 38s 222ms/step - loss: 0.6067 - accuracy: 0.6737 - val_loss: 0.5166 - val_accuracy: 0.7574
Epoch 2/3
115/115 [==============================] - 24s 211ms/step - loss: 0.4362 - accuracy: 0.8062 - val_loss: 0.3850 - val_accuracy: 0.8137
Epoch 3/3
115/115 [==============================] - 24s 211ms/step - loss: 0.2994 - accuracy: 0.8912 - val_loss: 0.3774 - val_accuracy: 0.8309
<keras.callbacks.History at 0x7f04c010b210>

Exécutez maintenant le modèle affiné sur un exemple personnalisé pour voir qu'il fonctionne.

Commencez par encoder quelques paires de phrases :

my_examples = bert_encode(
    glue_dict = {
        'sentence1':[
            'The rain in Spain falls mainly on the plain.',
            'Look I fine tuned BERT.'],
        'sentence2':[
            'It mostly rains on the flat lands of Spain.',
            'Is it working? This does not match.']
    },
    tokenizer=tokenizer)

Le modèle devrait faire rapport classe 1 « match » pour le premier exemple et la classe 0 « sans correspondance » pour le second:

result = bert_classifier(my_examples, training=False)

result = tf.argmax(result).numpy()
result
array([1, 0])
np.array(info.features['label'].names)[result]
array(['equivalent', 'not_equivalent'], dtype='<U14')

Enregistrer le modèle

Souvent , l'objectif de la formation d' un modèle est de l' utiliser pour quelque chose, alors exporter le modèle puis restaurer pour être sûr que cela fonctionne.

export_dir='./saved_model'
tf.saved_model.save(bert_classifier, export_dir=export_dir)
2021-08-19 11:30:12.946332: W tensorflow/python/util/util.cc:348] 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 self_attention_layer_call_fn, self_attention_layer_call_and_return_conditional_losses, dropout_layer_call_fn, dropout_layer_call_and_return_conditional_losses, self_attention_layer_norm_layer_call_fn while saving (showing 5 of 900). These functions will not be directly callable after loading.
INFO:tensorflow:Assets written to: ./saved_model/assets
INFO:tensorflow:Assets written to: ./saved_model/assets
reloaded = tf.saved_model.load(export_dir)
reloaded_result = reloaded([my_examples['input_word_ids'],
                            my_examples['input_mask'],
                            my_examples['input_type_ids']], training=False)

original_result = bert_classifier(my_examples, training=False)

# The results are (nearly) identical:
print(original_result.numpy())
print()
print(reloaded_result.numpy())
[[-1.1162528   1.014004  ]
 [ 0.9412503  -0.72278297]]

[[-1.1162527  1.0140039]
 [ 0.94125   -0.7227829]]

annexe

Réencodage d'un grand ensemble de données

Ce tutoriel vous a ré-encodé le jeu de données en mémoire, pour plus de clarté.

Cela n'a été possible que la glue/mrpc est un ensemble de données très faible. Pour faire face à grands ensembles de données tf_models la bibliothèque comprend des outils pour le traitement et réencoder un ensemble de données pour une formation efficace.

La première étape consiste à décrire quelles fonctionnalités de l'ensemble de données doivent être transformées :

processor = nlp.data.classifier_data_lib.TfdsProcessor(
    tfds_params="dataset=glue/mrpc,text_key=sentence1,text_b_key=sentence2",
    process_text_fn=bert.tokenization.convert_to_unicode)

Appliquez ensuite la transformation pour générer de nouveaux fichiers TFRecord.

# Set up output of training and evaluation Tensorflow dataset
train_data_output_path="./mrpc_train.tf_record"
eval_data_output_path="./mrpc_eval.tf_record"

max_seq_length = 128
batch_size = 32
eval_batch_size = 32

# Generate and save training data into a tf record file
input_meta_data = (
    nlp.data.classifier_data_lib.generate_tf_record_from_data_file(
      processor=processor,
      data_dir=None,  # It is `None` because data is from tfds, not local dir.
      tokenizer=tokenizer,
      train_data_output_path=train_data_output_path,
      eval_data_output_path=eval_data_output_path,
      max_seq_length=max_seq_length))

Enfin créer tf.data pipelines d'entrée de ces fichiers TFRecord:

training_dataset = bert.run_classifier.get_dataset_fn(
    train_data_output_path,
    max_seq_length,
    batch_size,
    is_training=True)()

evaluation_dataset = bert.run_classifier.get_dataset_fn(
    eval_data_output_path,
    max_seq_length,
    eval_batch_size,
    is_training=False)()

La résultante tf.data.Datasets retour (features, labels) paires, comme prévu par keras.Model.fit :

training_dataset.element_spec
({'input_word_ids': TensorSpec(shape=(32, 128), dtype=tf.int32, name=None),
  'input_mask': TensorSpec(shape=(32, 128), dtype=tf.int32, name=None),
  'input_type_ids': TensorSpec(shape=(32, 128), dtype=tf.int32, name=None)},
 TensorSpec(shape=(32,), dtype=tf.int32, name=None))

Créer tf.data.Dataset pour la formation et l'évaluation

Si vous devez modifier le chargement des données, voici un code pour vous aider à démarrer :

def create_classifier_dataset(file_path, seq_length, batch_size, is_training):
  """Creates input dataset from (tf)records files for train/eval."""
  dataset = tf.data.TFRecordDataset(file_path)
  if is_training:
    dataset = dataset.shuffle(100)
    dataset = dataset.repeat()

  def decode_record(record):
    name_to_features = {
      'input_ids': tf.io.FixedLenFeature([seq_length], tf.int64),
      'input_mask': tf.io.FixedLenFeature([seq_length], tf.int64),
      'segment_ids': tf.io.FixedLenFeature([seq_length], tf.int64),
      'label_ids': tf.io.FixedLenFeature([], tf.int64),
    }
    return tf.io.parse_single_example(record, name_to_features)

  def _select_data_from_record(record):
    x = {
        'input_word_ids': record['input_ids'],
        'input_mask': record['input_mask'],
        'input_type_ids': record['segment_ids']
    }
    y = record['label_ids']
    return (x, y)

  dataset = dataset.map(decode_record,
                        num_parallel_calls=tf.data.AUTOTUNE)
  dataset = dataset.map(
      _select_data_from_record,
      num_parallel_calls=tf.data.AUTOTUNE)
  dataset = dataset.batch(batch_size, drop_remainder=is_training)
  dataset = dataset.prefetch(tf.data.AUTOTUNE)
  return dataset
# Set up batch sizes
batch_size = 32
eval_batch_size = 32

# Return Tensorflow dataset
training_dataset = create_classifier_dataset(
    train_data_output_path,
    input_meta_data['max_seq_length'],
    batch_size,
    is_training=True)

evaluation_dataset = create_classifier_dataset(
    eval_data_output_path,
    input_meta_data['max_seq_length'],
    eval_batch_size,
    is_training=False)
training_dataset.element_spec
({'input_word_ids': TensorSpec(shape=(32, 128), dtype=tf.int64, name=None),
  'input_mask': TensorSpec(shape=(32, 128), dtype=tf.int64, name=None),
  'input_type_ids': TensorSpec(shape=(32, 128), dtype=tf.int64, name=None)},
 TensorSpec(shape=(32,), dtype=tf.int64, name=None))

TFModels BERT sur TFHub

Vous pouvez obtenir le modèle BERT sur l'étagère de TFHub . Il ne serait pas difficile d'ajouter une tête de classement en haut de cette hub.KerasLayer

# Note: 350MB download.
import tensorflow_hub as hub

hub_encoder = hub.KerasLayer(f"https://tfhub.dev/tensorflow/{hub_model_name}/3",
                             trainable=True)

print(f"The Hub encoder has {len(hub_encoder.trainable_variables)} trainable variables")
The Hub encoder has 199 trainable variables

Testez-le sur un lot de données :

result = hub_encoder(
    inputs=dict(
        input_word_ids=glue_train['input_word_ids'][:10],
        input_mask=glue_train['input_mask'][:10],
        input_type_ids=glue_train['input_type_ids'][:10],),
    training=False,
)

print("Pooled output shape:", result['pooled_output'].shape)
print("Sequence output shape:", result['sequence_output'].shape)
Pooled output shape: (10, 768)
Sequence output shape: (10, 103, 768)

À ce stade, il serait simple d'ajouter vous-même une tête de classification.

La bert_models.classifier_model fonction peut également construire un classificateur sur l'encodeur de tensorflow Hub:

hub_classifier = nlp.modeling.models.BertClassifier(
    bert_encoder,
    num_classes=2,
    dropout_rate=0.1,
    initializer=tf.keras.initializers.TruncatedNormal(
        stddev=0.02))

Le seul inconvénient du chargement de ce modèle à partir de TFHub est que la structure des couches internes de keras n'est pas restaurée. Il est donc plus difficile d'inspecter ou de modifier le modèle. Le BertEncoder modèle est maintenant une seule couche:

tf.keras.utils.plot_model(hub_classifier, show_shapes=True, dpi=64)

png

try:
  tf.keras.utils.plot_model(hub_encoder, show_shapes=True, dpi=64)
  assert False
except Exception as e:
  print(f"{type(e).__name__}: {e}")
AttributeError: 'KerasLayer' object has no attribute 'layers'

Maquette de bas niveau

Si vous avez besoin d' un plus grand contrôle sur la construction du modèle , il convient de noter que la classifier_model fonction utilisée précédemment est vraiment juste une enveloppe mince sur les nlp.modeling.networks.BertEncoder et nlp.modeling.models.BertClassifier classes. N'oubliez pas que si vous commencez à modifier l'architecture, il peut ne pas être correct ou possible de recharger le point de contrôle pré-entraîné, vous devrez donc recommencer à zéro.

Construisez l'encodeur :

bert_encoder_config = config_dict.copy()

# You need to rename a few fields to make this work:
bert_encoder_config['attention_dropout_rate'] = bert_encoder_config.pop('attention_probs_dropout_prob')
bert_encoder_config['activation'] = tf_utils.get_activation(bert_encoder_config.pop('hidden_act'))
bert_encoder_config['dropout_rate'] = bert_encoder_config.pop('hidden_dropout_prob')
bert_encoder_config['initializer'] = tf.keras.initializers.TruncatedNormal(
          stddev=bert_encoder_config.pop('initializer_range'))
bert_encoder_config['max_sequence_length'] = bert_encoder_config.pop('max_position_embeddings')
bert_encoder_config['num_layers'] = bert_encoder_config.pop('num_hidden_layers')

bert_encoder_config
{'hidden_size': 768,
 'intermediate_size': 3072,
 'num_attention_heads': 12,
 'type_vocab_size': 2,
 'vocab_size': 30522,
 'attention_dropout_rate': 0.1,
 'activation': <function official.modeling.activations.gelu.gelu(x)>,
 'dropout_rate': 0.1,
 'initializer': <keras.initializers.initializers_v2.TruncatedNormal at 0x7f006c251d10>,
 'max_sequence_length': 512,
 'num_layers': 12}
manual_encoder = nlp.modeling.networks.BertEncoder(**bert_encoder_config)

Restaurer les poids :

checkpoint = tf.train.Checkpoint(encoder=manual_encoder)
checkpoint.read(
    os.path.join(gs_folder_bert, 'bert_model.ckpt')).assert_consumed()
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7f0064723dd0>

Testez-le :

result = manual_encoder(my_examples, training=True)

print("Sequence output shape:", result[0].shape)
print("Pooled output shape:", result[1].shape)
Sequence output shape: (2, 23, 768)
Pooled output shape: (2, 768)

Enveloppez-le dans un classificateur :

manual_classifier = nlp.modeling.models.BertClassifier(
        bert_encoder,
        num_classes=2,
        dropout_rate=bert_encoder_config['dropout_rate'],
        initializer=bert_encoder_config['initializer'])
manual_classifier(my_examples, training=True).numpy()
array([[ 0.19574928, -0.09590703],
       [-0.3455575 ,  0.12091695]], dtype=float32)

Optimiseurs et horaires

L'optimiseur utilisé pour former le modèle a été créée en utilisant nlp.optimization.create_optimizer fonction:

optimizer = nlp.optimization.create_optimizer(
    2e-5, num_train_steps=num_train_steps, num_warmup_steps=warmup_steps)

Ce wrapper de haut niveau configure les programmes de taux d'apprentissage et l'optimiseur.

Le programme de taux d'apprentissage de base utilisé ici est une décroissance linéaire jusqu'à zéro au cours de la course d'entraînement :

epochs = 3
batch_size = 32
eval_batch_size = 32

train_data_size = len(glue_train_labels)
steps_per_epoch = int(train_data_size / batch_size)
num_train_steps = steps_per_epoch * epochs
decay_schedule = tf.keras.optimizers.schedules.PolynomialDecay(
      initial_learning_rate=2e-5,
      decay_steps=num_train_steps,
      end_learning_rate=0)

plt.plot([decay_schedule(n) for n in range(num_train_steps)])
[<matplotlib.lines.Line2D at 0x7f0065da3c50>]

png

Ceci, à son tour , est enveloppé dans un WarmUp horaire qui augmente de façon linéaire le taux d'apprentissage à la valeur cible sur les 10 premiers% de la formation:

warmup_steps = num_train_steps * 0.1

warmup_schedule = nlp.optimization.WarmUp(
        initial_learning_rate=2e-5,
        decay_schedule_fn=decay_schedule,
        warmup_steps=warmup_steps)

# The warmup overshoots, because it warms up to the `initial_learning_rate`
# following the original implementation. You can set
# `initial_learning_rate=decay_schedule(warmup_steps)` if you don't like the
# overshoot.
plt.plot([warmup_schedule(n) for n in range(num_train_steps)])
[<matplotlib.lines.Line2D at 0x7f0064d33fd0>]

png

Ensuite , créez le nlp.optimization.AdamWeightDecay en utilisant ce calendrier, configuré pour le modèle BERT:

optimizer = nlp.optimization.AdamWeightDecay(
        learning_rate=warmup_schedule,
        weight_decay_rate=0.01,
        epsilon=1e-6,
        exclude_from_weight_decay=['LayerNorm', 'layer_norm', 'bias'])