ปรับแต่งโมเดล BERT อย่างละเอียด

ดูบน TensorFlow.org ทำงานใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดโน๊ตบุ๊ค ดูรุ่น TF Hub

ในตัวอย่างนี้ เราจะดำเนินการปรับแต่งโมเดล BERT โดยใช้แพ็คเกจ PIP รุ่นเทนเซอร์โฟลว์

โมเดล BERT ที่ฝึกไว้ล่วงหน้าในบทช่วยสอนนี้ยังมีอยู่ใน TensorFlow Hub เพื่อดูวิธีใช้งานโดยอ้างอิงจาก ภาคผนวกของ Hub

ติดตั้ง

ติดตั้งแพ็คเกจ pip ของ TensorFlow Model Garden

  • tf-models-official คือแพ็คเกจ Model Garden ที่เสถียร โปรดทราบว่าอาจไม่รวมการเปลี่ยนแปลงล่าสุดใน tensorflow_models github repo หากต้องการรวมการเปลี่ยนแปลงล่าสุด คุณสามารถติดตั้ง tf-models-nightly ซึ่งเป็นแพ็คเกจ Model Garden ทุกคืนที่สร้างขึ้นทุกวันโดยอัตโนมัติ
  • pip จะติดตั้งทุกรุ่นและการอ้างอิงโดยอัตโนมัติ
pip install -q -U tensorflow-text
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 Datasets

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 tokenizer ที่ใช้ในบทช่วยสอนนี้เขียนด้วย Python แท้ (ไม่ได้สร้างจาก TensorFlow ops) ดังนั้นคุณจึงไม่สามารถเสียบเข้ากับโมเดลของคุณเป็น keras.layer เหมือนกับที่ทำกับ preprocessing.TextVectorization

รหัสต่อไปนี้สร้าง tokenizer ที่ใช้โดยรุ่นพื้นฐาน:

# 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 ประโยค:

tokens = tokenizer.tokenize("Hello TensorFlow!")
print(tokens)
ids = tokenizer.convert_tokens_to_ids(tokens)
print(ids)
0860538800

ประมวลผลข้อมูลล่วงหน้า

ส่วนนี้ประมวลผลชุดข้อมูลล่วงหน้าด้วยตนเองในรูปแบบที่โมเดลคาดหวัง

ชุดข้อมูลนี้มีขนาดเล็ก ดังนั้นการประมวลผลล่วงหน้าจึงสามารถทำได้อย่างรวดเร็วและง่ายดายในหน่วยความจำ สำหรับชุดข้อมูลขนาดใหญ่ ไลบรารี tf_models มีเครื่องมือบางอย่างสำหรับการประมวลผลล่วงหน้าและการจัดลำดับชุดข้อมูลใหม่ ดู ภาคผนวก: เข้ารหัสชุดข้อมูลขนาดใหญ่อีกครั้ง สำหรับรายละเอียด

เข้ารหัสประโยค

ตัวแบบคาดว่าประโยคอินพุตสองประโยคจะถูกต่อเข้าด้วยกัน อินพุตนี้คาดว่าจะเริ่มต้นด้วยโทเค็น [CLS] "นี่คือปัญหาการจัดหมวดหมู่" และแต่ละประโยคควรลงท้ายด้วยโทเค็น "Separator" [SEP] :

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

เริ่มต้นด้วยการเข้ารหัสประโยคทั้งหมดในขณะที่ต่อท้ายโทเค็น [SEP] และบรรจุลงใน ragged-tensor:

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_mask = tf.ones_like(input_word_ids).to_tensor()

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

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

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 กำหนด core BERT Model ซึ่งเป็นโมเดล Keras เพื่อทำนายผลลัพธ์ของ num_classes จากอินพุตที่มีความยาวลำดับสูงสุด max_seq_length

ฟังก์ชันนี้ส่งคืนทั้งตัวเข้ารหัสและตัวแยกประเภท

bert_classifier, bert_encoder = bert.bert_models.classifier_model(
    bert_config, num_labels=2)
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/array_ops.py:5049: calling gather (from tensorflow.python.ops.array_ops) with validate_indices is deprecated and will be removed in a future version.
Instructions for updating:
The `validate_indices` argument has no effect. Indices are always validated on CPU and never validated on GPU.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/ops/array_ops.py:5049: calling gather (from tensorflow.python.ops.array_ops) with validate_indices is deprecated and will be removed in a future version.
Instructions for updating:
The `validate_indices` argument has no effect. Indices are always validated on CPU and never validated on GPU.

ลักษณนามมีสามอินพุตและหนึ่งเอาต์พุต:

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.07727347, -0.04238038],
       [-0.05732805, -0.04752263],
       [-0.06592445, -0.13929501],
       [-0.02577273, -0.11711894],
       [-0.00461444, -0.00049782],
       [-0.05748309, -0.07496218],
       [-0.19308081,  0.01129638],
       [-0.138049  , -0.18596742],
       [ 0.04161626, -0.06026747],
       [-0.1499812 ,  0.06032516]], 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 0x7f1ca3f94790>

ตั้งค่าเครื่องมือเพิ่มประสิทธิภาพ

BERT นำเครื่องมือเพิ่มประสิทธิภาพ Adam มาใช้พร้อมกับการลดน้ำหนัก (aka " 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 เพิ่มประสิทธิภาพ 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 [==============================] - 36s 218ms/step - loss: 0.5971 - accuracy: 0.6848 - val_loss: 0.5176 - val_accuracy: 0.7549
Epoch 2/3
115/115 [==============================] - 24s 209ms/step - loss: 0.4374 - accuracy: 0.8018 - val_loss: 0.4405 - val_accuracy: 0.8211
Epoch 3/3
115/115 [==============================] - 24s 209ms/step - loss: 0.3200 - accuracy: 0.8675 - val_loss: 0.4162 - val_accuracy: 0.8333
<tensorflow.python.keras.callbacks.History at 0x7f1b6c48f1d0>

ตอนนี้ เรียกใช้แบบจำลองที่ปรับแต่งอย่างละเอียดในตัวอย่างที่กำหนดเองเพื่อดูว่ามันใช้งานได้

เริ่มต้นด้วยการเข้ารหัสคู่ประโยคบางคู่:

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)

โมเดลควรรายงาน class 1 "match" สำหรับตัวอย่างแรกและ class 0 "no-match" สำหรับวินาที:

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_and_return_conditional_losses, self_attention_layer_call_fn, dropout_layer_call_and_return_conditional_losses, dropout_layer_call_fn, self_attention_layer_norm_layer_call_and_return_conditional_losses while saving (showing 5 of 900). These functions will not be directly callable after loading.
WARNING:tensorflow:FOR KERAS USERS: The object that you are saving contains one or more Keras models or layers. If you are loading the SavedModel with `tf.keras.models.load_model`, continue reading (otherwise, you may ignore the following instructions). Please change your code to save with `tf.keras.models.save_model` or `model.save`, and confirm that the file "keras.metadata" exists in the export directory. In the future, Keras will only load the SavedModels that have this file. In other words, `tf.saved_model.save` will no longer write SavedModels that can be recovered as Keras models (this will apply in TF 2.5).

FOR DEVS: If you are overwriting _tracking_metadata in your class, this property has been used to save metadata in the SavedModel. The metadta field will be deprecated soon, so please move the metadata to a different file.
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/keras/utils/generic_utils.py:497: CustomMaskWarning: Custom mask layers require a config and must override get_config. When loading, the custom mask layer must be passed to the custom_objects argument.
  category=CustomMaskWarning)
WARNING:tensorflow:FOR KERAS USERS: The object that you are saving contains one or more Keras models or layers. If you are loading the SavedModel with `tf.keras.models.load_model`, continue reading (otherwise, you may ignore the following instructions). Please change your code to save with `tf.keras.models.save_model` or `model.save`, and confirm that the file "keras.metadata" exists in the export directory. In the future, Keras will only load the SavedModels that have this file. In other words, `tf.saved_model.save` will no longer write SavedModels that can be recovered as Keras models (this will apply in TF 2.5).

FOR DEVS: If you are overwriting _tracking_metadata in your class, this property has been used to save metadata in the SavedModel. The metadta field will be deprecated soon, so please move the metadata to a different file.
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.466311    1.644745  ]
 [ 0.43884796 -1.0189507 ]]

[[-1.4663109  1.6447448]
 [ 0.4388481 -1.0189508]]

ภาคผนวก

เข้ารหัสชุดข้อมูลขนาดใหญ่อีกครั้ง

บทแนะนำนี้ให้คุณเข้ารหัสชุดข้อมูลในหน่วยความจำอีกครั้งเพื่อความชัดเจน

สิ่งนี้เป็นไปได้เพียงเพราะ 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 จากไฟล์ 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 ส่งคืนคู่ (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.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 บน TFHub

คุณสามารถ นำโมเดล BERT ออกจากชั้นวางได้จาก TFHub การเพิ่มหัวการจำแนกที่ด้านบนของ hub.KerasLayer นี้ hub.KerasLayer เรื่อง 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 ยังสามารถสร้าง classifier บนตัวเข้ารหัสจาก 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 ใช้ก่อนหน้านี้เป็นเพียง wrapper แบบบางบน 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 0x7f1ac821b850>,
 '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 0x7f1ac3767050>

ทดสอบใช้งาน:

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.5267108 ,  0.1713907 ],
       [ 0.12431717,  0.14409517]], dtype=float32)

เครื่องมือเพิ่มประสิทธิภาพและกำหนดการ

เครื่องมือเพิ่มประสิทธิภาพที่ใช้ในการฝึกโมเดลถูกสร้างขึ้นโดยใช้ฟังก์ชัน nlp.optimization.create_optimizer :

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

Wrapper ระดับสูงนั้นกำหนดตารางอัตราการเรียนรู้และเครื่องมือเพิ่มประสิทธิภาพ

ตารางอัตราการเรียนรู้พื้นฐานที่ใช้ในที่นี้คือการลดลงเชิงเส้นเป็นศูนย์ตลอดช่วงการฝึก:

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

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

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