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

ดูใน TensorFlow.org เรียกใช้ใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดสมุดบันทึก ดูโมเดล TF Hub

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

แบบจำลอง BERT ที่ได้รับการฝึกฝนมาแล้วในบทช่วยสอนนี้ยังมีอยู่ใน TensorFlow Hub เพื่อดูวิธีการใช้งานอ้างอิงจาก Hub Appendix

ติดตั้ง

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

  • tf-models-official คือแพ็คเกจ Model Garden ที่เสถียร โปรดทราบว่าอาจไม่รวมการเปลี่ยนแปลงล่าสุดใน repo 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 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 keras.layer

รหัสต่อไปนี้สร้างโทเค็นไนเซอร์ที่ใช้โดยโมเดลพื้นฐาน:

# 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)
['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 เดียวสำหรับแต่ละตัวอย่าง 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 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 รุ่นซึ่งเป็นรูปแบบ Keras ในการทำนายผลของ 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 ที่มีการสลายน้ำหนัก (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 [==============================] - 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 จากไฟล์ 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.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 นี้คงไม่ใช่เรื่องยาก 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 :

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