開發者指南 / KerasNLP / 使用 KerasNLP 從頭開始預先訓練 Transformer

使用 KerasNLP 從頭開始預先訓練 Transformer

**作者:** Matthew Watson
建立日期 2022/04/18
上次修改日期 2023/07/15
**說明:**使用 KerasNLP 從頭開始訓練 Transformer 模型。

**在 Colab 中檢視** **GitHub 來源**

KerasNLP 旨在讓建立最先進的文字處理模型變得容易。在本指南中,我們將展示程式庫元件如何簡化從頭開始預先訓練和微調 Transformer 模型的過程。

本指南分為三個部分

  1. 「設定」、任務定義和建立基準。
  2. 預先訓練 Transformer 模型。
  3. 在我們的分類任務上微調 Transformer 模型。

設定

以下指南使用 Keras 3 在「tensorflow」、「jax」或「torch」中工作。我們在下方選擇「jax」後端,這將為我們提供特別快的訓練步驟,但您可以隨意混合使用。

!pip install -q --upgrade keras-nlp
!pip install -q --upgrade keras  # Upgrade to Keras 3.
import os

os.environ["KERAS_BACKEND"] = "jax"  # or "tensorflow" or "torch"


import keras_nlp
import tensorflow as tf
import keras

接下來,我們可以下載兩個數據集。

  • SST-2 是一個文字分類資料集,也是我們最終目標。這個資料集經常被用來評估語言模型的基準。
  • WikiText-103:一個中等大小的英文維基百科精選文章集合,我們將使用它進行預訓練。

最後,我們將下載一個 WordPiece 詞彙表,以便在本指南的稍後部分進行子詞詞元化。

# Download pretraining data.
keras.utils.get_file(
    origin="https://s3.amazonaws.com/research.metamind.io/wikitext/wikitext-103-raw-v1.zip",
    extract=True,
)
wiki_dir = os.path.expanduser("~/.keras/datasets/wikitext-103-raw/")

# Download finetuning data.
keras.utils.get_file(
    origin="https://dl.fbaipublicfiles.com/glue/data/SST-2.zip",
    extract=True,
)
sst_dir = os.path.expanduser("~/.keras/datasets/SST-2/")

# Download vocabulary data.
vocab_file = keras.utils.get_file(
    origin="https://storage.googleapis.com/tensorflow/keras-nlp/examples/bert/bert_vocab_uncased.txt",
)

接下來,我們定義一些在訓練期間將使用的超參數。

# Preprocessing params.
PRETRAINING_BATCH_SIZE = 128
FINETUNING_BATCH_SIZE = 32
SEQ_LENGTH = 128
MASK_RATE = 0.25
PREDICTIONS_PER_SEQ = 32

# Model params.
NUM_LAYERS = 3
MODEL_DIM = 256
INTERMEDIATE_DIM = 512
NUM_HEADS = 4
DROPOUT = 0.1
NORM_EPSILON = 1e-5

# Training params.
PRETRAINING_LEARNING_RATE = 5e-4
PRETRAINING_EPOCHS = 8
FINETUNING_LEARNING_RATE = 5e-5
FINETUNING_EPOCHS = 3

載入資料

我們使用 tf.data 載入資料,這將允許我們定義用於詞元化和預處理文字的輸入管道。

# Load SST-2.
sst_train_ds = tf.data.experimental.CsvDataset(
    sst_dir + "train.tsv", [tf.string, tf.int32], header=True, field_delim="\t"
).batch(FINETUNING_BATCH_SIZE)
sst_val_ds = tf.data.experimental.CsvDataset(
    sst_dir + "dev.tsv", [tf.string, tf.int32], header=True, field_delim="\t"
).batch(FINETUNING_BATCH_SIZE)

# Load wikitext-103 and filter out short lines.
wiki_train_ds = (
    tf.data.TextLineDataset(wiki_dir + "wiki.train.raw")
    .filter(lambda x: tf.strings.length(x) > 100)
    .batch(PRETRAINING_BATCH_SIZE)
)
wiki_val_ds = (
    tf.data.TextLineDataset(wiki_dir + "wiki.valid.raw")
    .filter(lambda x: tf.strings.length(x) > 100)
    .batch(PRETRAINING_BATCH_SIZE)
)

# Take a peak at the sst-2 dataset.
print(sst_train_ds.unbatch().batch(4).take(1).get_single_element())
(<tf.Tensor: shape=(4,), dtype=string, numpy=
array([b'hide new secretions from the parental units ',
       b'contains no wit , only labored gags ',
       b'that loves its characters and communicates something rather beautiful about human nature ',
       b'remains utterly satisfied to remain the same throughout '],
      dtype=object)>, <tf.Tensor: shape=(4,), dtype=int32, numpy=array([0, 0, 1, 0], dtype=int32)>)

您可以看到我們的 SST-2 資料集包含相對較短的電影評論文字片段。我們的目標是預測片段的情感。標籤 1 表示正面情感,標籤 0 表示負面情感。

建立基準

作為第一步,我們將建立一個良好的效能基準。我們實際上不需要 KerasNLP 來做到這一點,我們只需要使用核心 Keras 層。

我們將訓練一個簡單的詞袋模型,在這個模型中,我們為詞彙表中的每個詞學習一個正面或負面的權重。樣本的分數只是樣本中出現的所有詞的權重之和。

# This layer will turn our input sentence into a list of 1s and 0s the same size
# our vocabulary, indicating whether a word is present in absent.
multi_hot_layer = keras.layers.TextVectorization(
    max_tokens=4000, output_mode="multi_hot"
)
multi_hot_layer.adapt(sst_train_ds.map(lambda x, y: x))
multi_hot_ds = sst_train_ds.map(lambda x, y: (multi_hot_layer(x), y))
multi_hot_val_ds = sst_val_ds.map(lambda x, y: (multi_hot_layer(x), y))

# We then learn a linear regression over that layer, and that's our entire
# baseline model!

inputs = keras.Input(shape=(4000,), dtype="int32")
outputs = keras.layers.Dense(1, activation="sigmoid")(inputs)
baseline_model = keras.Model(inputs, outputs)
baseline_model.compile(loss="binary_crossentropy", metrics=["accuracy"])
baseline_model.fit(multi_hot_ds, validation_data=multi_hot_val_ds, epochs=5)
Epoch 1/5
 2105/2105 ━━━━━━━━━━━━━━━━━━━━ 2s 698us/step - accuracy: 0.6421 - loss: 0.6469 - val_accuracy: 0.7567 - val_loss: 0.5391
Epoch 2/5
 2105/2105 ━━━━━━━━━━━━━━━━━━━━ 1s 493us/step - accuracy: 0.7524 - loss: 0.5392 - val_accuracy: 0.7868 - val_loss: 0.4891
Epoch 3/5
 2105/2105 ━━━━━━━━━━━━━━━━━━━━ 1s 513us/step - accuracy: 0.7832 - loss: 0.4871 - val_accuracy: 0.7991 - val_loss: 0.4671
Epoch 4/5
 2105/2105 ━━━━━━━━━━━━━━━━━━━━ 1s 475us/step - accuracy: 0.7991 - loss: 0.4543 - val_accuracy: 0.8069 - val_loss: 0.4569
Epoch 5/5
 2105/2105 ━━━━━━━━━━━━━━━━━━━━ 1s 476us/step - accuracy: 0.8100 - loss: 0.4313 - val_accuracy: 0.8036 - val_loss: 0.4530

<keras.src.callbacks.history.History at 0x7f13902967a0>

詞袋方法可能是一種快速且出奇強大的方法,尤其是在輸入範例包含大量詞彙時。對於較短的序列,它可能會遇到效能上限。

為了做得更好,我們希望建立一個可以評估*上下文*中詞彙的模型。我們需要使用*整個有序序列*中包含的資訊,而不是在真空中評估每個詞。

這讓我們遇到了一個問題。 SST-2 是一個非常小的資料集,沒有足夠的範例文字來嘗試建立一個更大、參數更多的模型來學習序列。我們很快就會開始過度擬合並記憶我們的訓練集,而無法提高我們對未見範例的泛化能力。

進入**預訓練**,這將允許我們在更大的語料庫上學習,並將我們的知識轉移到 SST-2 任務。並進入**KerasNLP**,這將允許我們輕鬆地預訓練一個特別強大的模型,即 Transformer。


預訓練

為了超越我們的基準,我們將利用 WikiText103 資料集,這是一個未標記的維基百科文章集合,比 SST-2 大得多。

我們將訓練一個*transformer*,這是一個高度表達的模型,它將學習將我們輸入中的每個詞嵌入到一個低維向量中。我們的維基百科資料集沒有標籤,因此我們將使用一個稱為*遮罩語言建模*(MaskedLM)目標的無監督訓練目標。

從本質上講,我們將玩一個大型的「猜測缺失詞」遊戲。對於每個輸入樣本,我們將遮蔽 25% 的輸入資料,並訓練我們的模型來預測我們遮蔽的部分。

為 MaskedLM 任務預處理資料

我們對 MaskedLM 任務的文字預處理將分為兩個階段。

  1. 將輸入文字詞元化為詞元 ID 的整數序列。
  2. 遮蔽輸入中的某些位置以進行預測。

要進行詞元化,我們可以使用 keras_nlp.tokenizers.Tokenizer - KerasNLP 建構塊,用於將文字轉換為整數詞元 ID 的序列。

特別是,我們將使用 keras_nlp.tokenizers.WordPieceTokenizer 進行*子詞*詞元化。子詞詞元化在訓練大型文字語料庫上的模型時很流行。從本質上講,它允許我們的模型從不常見的詞彙中學習,而不需要在我們的訓練集中包含每個詞彙的大量詞彙表。

我們需要做的第二件事是為 MaskedLM 任務屏蔽輸入。為此,我們可以使用 keras_nlp.layers.MaskedLMMaskGenerator,它會在每個輸入中隨機選擇一組標記並將其屏蔽。

標記器和遮罩層都可以用於呼叫 tf.data.Dataset.map。我們可以使用 tf.data 在 CPU 上有效地預先計算每個批次,而我們的 GPU 或 TPU 則使用之前的批次進行訓練。因為我們的遮罩層每次都會選擇新的詞彙進行遮罩,所以我們資料集中的每個時期都會為我們提供一組全新的標籤來訓練。

# Setting sequence_length will trim or pad the token outputs to shape
# (batch_size, SEQ_LENGTH).
tokenizer = keras_nlp.tokenizers.WordPieceTokenizer(
    vocabulary=vocab_file,
    sequence_length=SEQ_LENGTH,
    lowercase=True,
    strip_accents=True,
)
# Setting mask_selection_length will trim or pad the mask outputs to shape
# (batch_size, PREDICTIONS_PER_SEQ).
masker = keras_nlp.layers.MaskedLMMaskGenerator(
    vocabulary_size=tokenizer.vocabulary_size(),
    mask_selection_rate=MASK_RATE,
    mask_selection_length=PREDICTIONS_PER_SEQ,
    mask_token_id=tokenizer.token_to_id("[MASK]"),
)


def preprocess(inputs):
    inputs = tokenizer(inputs)
    outputs = masker(inputs)
    # Split the masking layer outputs into a (features, labels, and weights)
    # tuple that we can use with keras.Model.fit().
    features = {
        "token_ids": outputs["token_ids"],
        "mask_positions": outputs["mask_positions"],
    }
    labels = outputs["mask_ids"]
    weights = outputs["mask_weights"]
    return features, labels, weights


# We use prefetch() to pre-compute preprocessed batches on the fly on the CPU.
pretrain_ds = wiki_train_ds.map(
    preprocess, num_parallel_calls=tf.data.AUTOTUNE
).prefetch(tf.data.AUTOTUNE)
pretrain_val_ds = wiki_val_ds.map(
    preprocess, num_parallel_calls=tf.data.AUTOTUNE
).prefetch(tf.data.AUTOTUNE)

# Preview a single input example.
# The masks will change each time you run the cell.
print(pretrain_val_ds.take(1).get_single_element())
({'token_ids': <tf.Tensor: shape=(128, 128), dtype=int32, numpy=
array([[7570, 7849, 2271, ..., 9673,  103, 7570],
       [7570, 7849,  103, ..., 1007, 1012, 2023],
       [1996, 2034, 3940, ...,    0,    0,    0],
       ...,
       [2076, 1996, 2307, ...,    0,    0,    0],
       [3216,  103, 2083, ...,    0,    0,    0],
       [ 103, 2007, 1045, ...,    0,    0,    0]], dtype=int32)>, 'mask_positions': <tf.Tensor: shape=(128, 32), dtype=int64, numpy=
array([[  5,   6,   7, ..., 118, 120, 126],
       [  2,   3,  14, ..., 105, 106, 113],
       [  4,   9,  10, ...,   0,   0,   0],
       ...,
       [  4,  11,  19, ..., 117, 118,   0],
       [  1,  14,  17, ...,   0,   0,   0],
       [  0,   3,   6, ...,   0,   0,   0]])>}, <tf.Tensor: shape=(128, 32), dtype=int32, numpy=
array([[ 1010,  2124,  2004, ...,  2095, 11300,  1012],
       [ 2271, 13091,  2303, ...,  2029,  2027,  1010],
       [23976,  2007,  1037, ...,     0,     0,     0],
       ...,
       [ 1010,  1996,  1010, ...,  1999,  7511,     0],
       [ 2225,  1998, 10722, ...,     0,     0,     0],
       [ 9794,  1030,  2322, ...,     0,     0,     0]], dtype=int32)>, <tf.Tensor: shape=(128, 32), dtype=float32, numpy=
array([[1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 1., 1., 1.],
       [1., 1., 1., ..., 0., 0., 0.],
       ...,
       [1., 1., 1., ..., 1., 1., 0.],
       [1., 1., 1., ..., 0., 0., 0.],
       [1., 1., 1., ..., 0., 0., 0.]], dtype=float32)>)

上面的區塊將我們的資料集排序成一個 (特徵、標籤、權重) 元組,可以直接傳遞給 keras.Model.fit()

我們有兩個特徵

  1. "token_ids",其中一些標記已被替換為我們的遮罩標記 ID。
  2. "mask_positions",它會追蹤我們遮罩了哪些標記。

我們的標籤就是我們遮罩掉的 ID。

因為並非所有序列都具有相同數量的遮罩,所以我們還保留了一個 sample_weight 張量,它通過賦予填充標籤零權重來將其從我們的損失函數中移除。

建立 Transformer 編碼器

KerasNLP 提供了所有構建塊,可以快速構建 Transformer 編碼器。

我們使用 keras_nlp.layers.TokenAndPositionEmbedding 首先嵌入我們的輸入標記 ID。該層同時學習兩種嵌入 - 一種用於句子中的詞彙,另一種用於句子中的整數位置。輸出嵌入只是兩者的總和。

然後我們可以添加一系列 keras_nlp.layers.TransformerEncoder 層。這些是 Transformer 模型的基礎,使用注意力機制來關注輸入句子的不同部分,然後是一個多層感知器區塊。

該模型的輸出將是每個輸入標記 ID 的編碼向量。與我們用作基準的詞袋模型不同,該模型將嵌入每個標記,並考慮其出現的上下文。

inputs = keras.Input(shape=(SEQ_LENGTH,), dtype="int32")

# Embed our tokens with a positional embedding.
embedding_layer = keras_nlp.layers.TokenAndPositionEmbedding(
    vocabulary_size=tokenizer.vocabulary_size(),
    sequence_length=SEQ_LENGTH,
    embedding_dim=MODEL_DIM,
)
outputs = embedding_layer(inputs)

# Apply layer normalization and dropout to the embedding.
outputs = keras.layers.LayerNormalization(epsilon=NORM_EPSILON)(outputs)
outputs = keras.layers.Dropout(rate=DROPOUT)(outputs)

# Add a number of encoder blocks
for i in range(NUM_LAYERS):
    outputs = keras_nlp.layers.TransformerEncoder(
        intermediate_dim=INTERMEDIATE_DIM,
        num_heads=NUM_HEADS,
        dropout=DROPOUT,
        layer_norm_epsilon=NORM_EPSILON,
    )(outputs)

encoder_model = keras.Model(inputs, outputs)
encoder_model.summary()
Model: "functional_3"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Layer (type)                     Output Shape                  Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ input_layer_1 (InputLayer)      │ (None, 128)               │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ token_and_position_embedding    │ (None, 128, 256)          │  7,846,400 │
│ (TokenAndPositionEmbedding)     │                           │            │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ layer_normalization             │ (None, 128, 256)          │        512 │
│ (LayerNormalization)            │                           │            │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ dropout (Dropout)               │ (None, 128, 256)          │          0 │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ transformer_encoder             │ (None, 128, 256)          │    527,104 │
│ (TransformerEncoder)            │                           │            │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ transformer_encoder_1           │ (None, 128, 256)          │    527,104 │
│ (TransformerEncoder)            │                           │            │
├─────────────────────────────────┼───────────────────────────┼────────────┤
│ transformer_encoder_2           │ (None, 128, 256)          │    527,104 │
│ (TransformerEncoder)            │                           │            │
└─────────────────────────────────┴───────────────────────────┴────────────┘
 Total params: 9,428,224 (287.73 MB)
 Trainable params: 9,428,224 (287.73 MB)
 Non-trainable params: 0 (0.00 B)

預訓練 Transformer

您可以將 encoder_model 視為它自己的模組化單元,它是我們模型中我們真正感興趣的部分,用於我們的下游任務。但是,我們仍然需要設置編碼器以在 MaskedLM 任務上進行訓練;為此,我們附加了一個 keras_nlp.layers.MaskedLMHead

該層將標記編碼作為一個輸入,並將我們在原始輸入中遮罩的位置作為另一個輸入。它將收集我們遮罩的標記編碼,並將其轉換回我們整個詞彙表的預測。

這樣,我們就可以編譯並運行預訓練了。如果您在 Colab 中運行此程序,請注意這大約需要一個小時。訓練 Transformer 以計算量大而聞名,因此即使是這個相對較小的 Transformer 也需要一些時間。

# Create the pretraining model by attaching a masked language model head.
inputs = {
    "token_ids": keras.Input(shape=(SEQ_LENGTH,), dtype="int32", name="token_ids"),
    "mask_positions": keras.Input(
        shape=(PREDICTIONS_PER_SEQ,), dtype="int32", name="mask_positions"
    ),
}

# Encode the tokens.
encoded_tokens = encoder_model(inputs["token_ids"])

# Predict an output word for each masked input token.
# We use the input token embedding to project from our encoded vectors to
# vocabulary logits, which has been shown to improve training efficiency.
outputs = keras_nlp.layers.MaskedLMHead(
    token_embedding=embedding_layer.token_embedding,
    activation="softmax",
)(encoded_tokens, mask_positions=inputs["mask_positions"])

# Define and compile our pretraining model.
pretraining_model = keras.Model(inputs, outputs)
pretraining_model.compile(
    loss="sparse_categorical_crossentropy",
    optimizer=keras.optimizers.AdamW(PRETRAINING_LEARNING_RATE),
    weighted_metrics=["sparse_categorical_accuracy"],
    jit_compile=True,
)

# Pretrain the model on our wiki text dataset.
pretraining_model.fit(
    pretrain_ds,
    validation_data=pretrain_val_ds,
    epochs=PRETRAINING_EPOCHS,
)

# Save this base model for further finetuning.
encoder_model.save("encoder_model.keras")
Epoch 1/8
 5857/5857 ━━━━━━━━━━━━━━━━━━━━ 242s 41ms/step - loss: 5.4679 - sparse_categorical_accuracy: 0.1353 - val_loss: 3.4570 - val_sparse_categorical_accuracy: 0.3522
Epoch 2/8
 5857/5857 ━━━━━━━━━━━━━━━━━━━━ 234s 40ms/step - loss: 3.6031 - sparse_categorical_accuracy: 0.3396 - val_loss: 3.0514 - val_sparse_categorical_accuracy: 0.4032
Epoch 3/8
 5857/5857 ━━━━━━━━━━━━━━━━━━━━ 232s 40ms/step - loss: 3.2609 - sparse_categorical_accuracy: 0.3802 - val_loss: 2.8858 - val_sparse_categorical_accuracy: 0.4240
Epoch 4/8
 5857/5857 ━━━━━━━━━━━━━━━━━━━━ 233s 40ms/step - loss: 3.1099 - sparse_categorical_accuracy: 0.3978 - val_loss: 2.7897 - val_sparse_categorical_accuracy: 0.4375
Epoch 5/8
 5857/5857 ━━━━━━━━━━━━━━━━━━━━ 235s 40ms/step - loss: 3.0145 - sparse_categorical_accuracy: 0.4090 - val_loss: 2.7504 - val_sparse_categorical_accuracy: 0.4419
Epoch 6/8
 5857/5857 ━━━━━━━━━━━━━━━━━━━━ 252s 43ms/step - loss: 2.9530 - sparse_categorical_accuracy: 0.4157 - val_loss: 2.6925 - val_sparse_categorical_accuracy: 0.4474
Epoch 7/8
 5857/5857 ━━━━━━━━━━━━━━━━━━━━ 232s 40ms/step - loss: 2.9088 - sparse_categorical_accuracy: 0.4210 - val_loss: 2.6554 - val_sparse_categorical_accuracy: 0.4513
Epoch 8/8
 5857/5857 ━━━━━━━━━━━━━━━━━━━━ 236s 40ms/step - loss: 2.8721 - sparse_categorical_accuracy: 0.4250 - val_loss: 2.6389 - val_sparse_categorical_accuracy: 0.4548

微調

預訓練後,我們現在可以在 SST-2 資料集上微調我們的模型。我們可以利用我們構建的編碼器在上下文中預測詞彙的能力來提高我們在下游任務中的性能。

預處理分類資料

微調的預處理比我們的預訓練 MaskedLM 任務簡單得多。我們只需對輸入句子進行標記化,就可以開始訓練了!

def preprocess(sentences, labels):
    return tokenizer(sentences), labels


# We use prefetch() to pre-compute preprocessed batches on the fly on our CPU.
finetune_ds = sst_train_ds.map(
    preprocess, num_parallel_calls=tf.data.AUTOTUNE
).prefetch(tf.data.AUTOTUNE)
finetune_val_ds = sst_val_ds.map(
    preprocess, num_parallel_calls=tf.data.AUTOTUNE
).prefetch(tf.data.AUTOTUNE)

# Preview a single input example.
print(finetune_val_ds.take(1).get_single_element())
(<tf.Tensor: shape=(32, 128), dtype=int32, numpy=
array([[ 2009,  1005,  1055, ...,     0,     0,     0],
       [ 4895, 10258,  2378, ...,     0,     0,     0],
       [ 4473,  2149,  2000, ...,     0,     0,     0],
       ...,
       [ 1045,  2018,  2000, ...,     0,     0,     0],
       [ 4283,  2000,  3660, ...,     0,     0,     0],
       [ 1012,  1012,  1012, ...,     0,     0,     0]], dtype=int32)>, <tf.Tensor: shape=(32,), dtype=int32, numpy=
array([1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0,
       0, 1, 1, 0, 0, 1, 0, 0, 1, 0], dtype=int32)>)

微調 Transformer

要從我們的編碼標記輸出轉到分類預測,我們需要在我們的 Transformer 模型中附加另一個“頭”。我們可以在這裡簡單一點。我們將編碼的標記匯集在一起,並使用單個密集層進行預測。

# Reload the encoder model from disk so we can restart fine-tuning from scratch.
encoder_model = keras.models.load_model("encoder_model.keras", compile=False)

# Take as input the tokenized input.
inputs = keras.Input(shape=(SEQ_LENGTH,), dtype="int32")

# Encode and pool the tokens.
encoded_tokens = encoder_model(inputs)
pooled_tokens = keras.layers.GlobalAveragePooling1D()(encoded_tokens[0])

# Predict an output label.
outputs = keras.layers.Dense(1, activation="sigmoid")(pooled_tokens)

# Define and compile our fine-tuning model.
finetuning_model = keras.Model(inputs, outputs)
finetuning_model.compile(
    loss="binary_crossentropy",
    optimizer=keras.optimizers.AdamW(FINETUNING_LEARNING_RATE),
    metrics=["accuracy"],
)

# Finetune the model for the SST-2 task.
finetuning_model.fit(
    finetune_ds,
    validation_data=finetune_val_ds,
    epochs=FINETUNING_EPOCHS,
)
Epoch 1/3
 2105/2105 ━━━━━━━━━━━━━━━━━━━━ 21s 9ms/step - accuracy: 0.7500 - loss: 0.4891 - val_accuracy: 0.8036 - val_loss: 0.4099
Epoch 2/3
 2105/2105 ━━━━━━━━━━━━━━━━━━━━ 16s 8ms/step - accuracy: 0.8826 - loss: 0.2779 - val_accuracy: 0.8482 - val_loss: 0.3964
Epoch 3/3
 2105/2105 ━━━━━━━━━━━━━━━━━━━━ 16s 8ms/step - accuracy: 0.9176 - loss: 0.2066 - val_accuracy: 0.8549 - val_loss: 0.4142

<keras.src.callbacks.history.History at 0x7f12d85c21a0>

預訓練足以將我們的效能提升至 84%,而這遠非 Transformer 模型的極限。您可能在預訓練期間注意到,我們的驗證效能仍在穩定提升。我們的模型顯然仍未充分訓練。訓練更多時期、訓練更大的 Transformer 以及使用更多未標記的文字進行訓練,都會繼續顯著提升效能。

KerasNLP 的主要目標之一是提供一種模組化的 NLP 模型建構方法。我們在這裡展示了一種建構 Transformer 的方法,但 KerasNLP 支援越來越多用於預處理文字和建構模型的元件。我們希望它能讓您更容易嘗試解決自然語言問題的方法。