Есть вопрос? Присоединяйтесь к сообществу на форуме TensorFlow. Посетите форум.

Тонкая настройка модели BERT

Посмотреть на TensorFlow.org Запустить в Google Colab Посмотреть исходный код на GitHub Скачать блокнот См. Модель TF Hub

В этом примере мы проработаем тонкую настройку модели BERT с помощью пакета PIP tensorflow-models.

Предварительно обученная модель BERT, на которой основано это руководство, также доступна на TensorFlow Hub , чтобы узнать, как ее использовать, см. Приложение Hub.

Настраивать

Установите пакет пипсов TensorFlow Model Garden

  • tf-models-official - это стабильный пакет Model Garden. Обратите внимание, что он может не включать последние изменения в tensorflow_models github. Чтобы включить последние изменения, вы можете установить tf-models-nightly , ночной пакет Model Garden, который создается ежедневно автоматически.
  • pip автоматически установит все модели и зависимости.
pip install -q tf-models-official==2.4.0

Импорт

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

Ресурсы

Этот каталог содержит конфигурацию, словарь и предварительно обученную контрольную точку, используемую в этом руководстве:

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

Вы можете получить предварительно обученный кодировщик BERT в TensorFlow Hub :

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

Данные

В этом примере мы использовали набор данных GLUE MRPC от TFDS .

Этот набор данных не настроен таким образом, чтобы его можно было напрямую передать в модель BERT, поэтому в этом разделе также выполняется необходимая предварительная обработка.

Получите набор данных из наборов данных TensorFlow

Microsoft Research Paraphrase Corpus (Dolan & Brockett, 2005) - это корпус пар предложений, автоматически извлекаемых из сетевых источников новостей, с человеческими аннотациями для определения того, являются ли предложения в паре семантически эквивалентными.

  • Количество этикеток: 2.
  • Размер обучающего набора: 3668.
  • Размер набора оценочных данных: 408.
  • Максимальная длина последовательности набора данных для обучения и оценки: 128.
glue, info = tfds.load('glue/mrpc', with_info=True,
                       # It's small, load the whole dataset
                       batch_size=-1)
list(glue.keys())
['test', 'train', 'validation']

info объект описывает набор данных и его особенности:

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),
})

Эти два класса:

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

Вот один пример из обучающей выборки:

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

Токенизатор BERT

Чтобы точно настроить предварительно обученную модель, вы должны быть уверены, что используете точно такие же токенизацию, словарь и сопоставление индексов, которые вы использовали во время обучения.

Токенизатор BERT, используемый в этом руководстве, написан на чистом Python (он не построен на основе операций TensorFlow). Таким образом, вы не можете просто подключить его к своей модели как keras.layer как это можно сделать с помощью preprocessing.TextVectorization .

Следующий код восстанавливает токенизатор, который использовался базовой моделью:

# 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

Обозначьте предложение:

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

Предварительно обработать данные

Раздел вручную предварительно обработал набор данных в формате, ожидаемом моделью.

Этот набор данных невелик, поэтому предварительная обработка может выполняться быстро и легко в памяти. Для больших наборов данных библиотека tf_models включает некоторые инструменты для предварительной обработки и повторной сериализации набора данных. Подробности см. В Приложении: Перекодирование большого набора данных.

Закодируйте предложения

Модель ожидает, что два входных предложения будут объединены вместе. Ожидается, что этот ввод будет начинаться с токена [CLS] «Это проблема классификации», а каждое предложение должно заканчиваться токеном [SEP] «Разделитель»:

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

Начните с кодирования всех предложений, добавляя токен [SEP] и упаковывая их в рваные тензоры:

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]

Теперь добавьте токен [CLS] и input_word_ids рваные тензоры, чтобы сформировать один тензор input_word_ids для каждого примера. RaggedTensor.to_tensor() самую длинную последовательность нулями.

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

Маска и тип ввода

Модель предполагает два дополнительных входа:

  • Маска ввода
  • Тип ввода

Маска позволяет модели четко различать содержимое и отступ. Маска имеет ту же форму, что и input_word_ids , и содержит 1 везде, где input_word_ids не является input_word_ids .

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

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

PNG

«Тип ввода» также имеет ту же форму, но внутри незаполненной области содержит 0 или 1 указывающие, частью какого предложения является токен.

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 0x7fe3400d82b0>

PNG

Положил все это вместе

Соберите приведенный выше код синтаксического анализа текста в одну функцию и примените его к каждому разделению набора данных glue/mrpc .

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

Каждое подмножество данных было преобразовано в словарь функций и набор меток. Каждая функция во входном словаре имеет одинаковую форму, и количество меток должно совпадать:

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

Модель

Построить модель

Первый шаг - загрузить конфигурацию для предварительно обученной модели.

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}

config определяет базовую модель BERT, которая является моделью num_classes для прогнозирования выходных данных num_classes из входов с максимальной длиной последовательности max_seq_length .

Эта функция возвращает кодировщик и классификатор.

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

Классификатор имеет три входа и один выход:

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

PNG

Запустите его на тестовом пакете данных 10 примеров из обучающего набора. Результатом являются логиты для двух классов:

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

bert_classifier(
    glue_batch, training=True
).numpy()
array([[-0.3999117 ,  0.19228943],
       [-0.48039404,  0.49550664],
       [-0.4205317 ,  0.4514861 ],
       [-0.46268317,  0.24971014],
       [-0.24856849,  0.29781285],
       [-0.20492092,  0.33435237],
       [-0.16171221,  0.12575442],
       [-0.17115599,  0.40965632],
       [-0.23386969,  0.41947454],
       [-0.5728958 ,  0.40995434]], dtype=float32)

TransformerEncoder в центре классификатора выше - это bert_encoder .

Осматривая кодировщик, мы видим его стек слоев Transformer подключенный к тем же трем входам:

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

PNG

Восстановить веса кодировщика

При построении кодировщик инициализируется случайным образом. Восстановить веса энкодера из контрольной точки:

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 0x7fe2e00765f8>

Настроить оптимизатор

BERT использует оптимизатор Adam с уменьшением веса (он же AdamW ). Он также использует график скорости обучения, который сначала нагревается от 0, а затем уменьшается до 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)

Это возвращает оптимизатор AdamWeightDecay с установленным расписанием скорости обучения:

type(optimizer)
official.nlp.optimization.AdamWeightDecay

Чтобы увидеть пример того, как настроить оптимизатор и его расписание, см. Приложение «Расписание оптимизатора» .

Обучите модель

Показателем является точность, и мы используем разреженную категориальную кросс-энтропию в качестве потерь.

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 229ms/step - loss: 0.6175 - accuracy: 0.6844 - val_loss: 0.4610 - val_accuracy: 0.7892
Epoch 2/3
115/115 [==============================] - 25s 215ms/step - loss: 0.4207 - accuracy: 0.8125 - val_loss: 0.3859 - val_accuracy: 0.8211
Epoch 3/3
115/115 [==============================] - 25s 215ms/step - loss: 0.2990 - accuracy: 0.8867 - val_loss: 0.3759 - val_accuracy: 0.8407

<tensorflow.python.keras.callbacks.History at 0x7fe2e03ba0f0>

Теперь запустите настроенную модель на настраиваемом примере, чтобы убедиться, что она работает.

Начните с кодирования пар предложений:

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)

Модель должна сообщать о «совпадении» класса 1 для первого примера и «несоответствия» класса 0 для второго:

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

Сохраните модель

Часто цель обучения модели - использовать ее для чего-то, поэтому экспортируйте модель, а затем восстановите ее, чтобы убедиться, что она работает.

export_dir='./saved_model'
tf.saved_model.save(bert_classifier, export_dir=export_dir)
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.
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.5500499   1.4857253 ]
 [ 0.72138155 -0.6029598 ]]

[[-1.5500501   1.4857253 ]
 [ 0.72138053 -0.6029588 ]]

Приложение

Перекодирование большого набора данных

В этом руководстве для наглядности вы перекодировали набор данных в памяти.

Это было возможно только потому, что glue/mrpc - очень маленький набор данных. Для работы с большими наборами данных библиотека tf_models включает некоторые инструменты для обработки и перекодирования набора данных для эффективного обучения.

Первый шаг - описать, какие функции набора данных следует преобразовать:

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)

Затем примените преобразование для создания новых файлов 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))

Наконец создайте tf.data ввода tf.data из этих файлов 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)()

Полученные в результате tf.data.Datasets возвращают tf.data.Datasets (features, labels) , как и ожидалось 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))

Создать tf.data.Dataset для обучения и оценки

Если вам нужно изменить загрузку данных, вот какой-то код для начала:

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.experimental.AUTOTUNE)
  dataset = dataset.map(
      _select_data_from_record,
      num_parallel_calls=tf.data.experimental.AUTOTUNE)
  dataset = dataset.batch(batch_size, drop_remainder=is_training)
  dataset = dataset.prefetch(tf.data.experimental.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 на TFHub

Вы можете получить модель BERT с полки на TFHub . Нетрудно добавить классификационную головку поверх этого 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

Тестовый запуск на пакете данных:

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)

На этом этапе было бы просто добавить классификационную головку самостоятельно.

Функция bert_models.classifier_model также может построить классификатор на кодировщике из 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))

Единственным недостатком загрузки этой модели из TFHub является то, что структура внутренних слоев keras не восстанавливается. Так что проверить или изменить модель сложнее. Модель BertEncoder теперь является однослойной:

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'

Построение модели низкого уровня

Если вам нужен больший контроль над построением модели, стоит отметить, что использованная ранее функция classifier_model самом деле является лишь тонкой оболочкой над nlp.modeling.networks.BertEncoder и nlp.modeling.models.BertClassifier . Просто помните, что если вы начнете изменять архитектуру, возможно, будет некорректно или невозможно перезагрузить предварительно обученную контрольную точку, поэтому вам придется переучиваться с нуля.

Соберите кодировщик:

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': <tensorflow.python.keras.initializers.initializers_v2.TruncatedNormal at 0x7fe203c32d68>,
 'max_sequence_length': 512,
 'num_layers': 12}
manual_encoder = nlp.modeling.networks.BertEncoder(**bert_encoder_config)

Восстановите веса:

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 0x7fe20392a198>

Тестовый запуск:

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)

Оберните это в классификатор:

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.1309041 , -0.20986415],
       [-0.09952673,  0.05040173]], dtype=float32)

Оптимизаторы и расписания

Оптимизатор, используемый для обучения модели, был создан с nlp.optimization.create_optimizer функции nlp.optimization.create_optimizer :

optimizer = nlp.optimization.create_optimizer(
    2e-5, num_train_steps=num_train_steps, num_warmup_steps=warmup_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
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 0x7fe203c1ac50>]

PNG

Это, в свою очередь, заключено в расписание WarmUp которое линейно увеличивает скорость обучения до целевого значения в течение первых 10% обучения:

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

PNG

Затем создайте nlp.optimization.AdamWeightDecay используя это расписание, настроенное для модели 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'])