Pytorch
TensorFlow

#
使用 Keras 微调一个模型

一旦完成上一节中的所有数据预处理工作后，你只剩下最后的几个步骤来训练模型。 但是请注意，`model.fit()` 命令在 CPU 上运行会非常缓慢。 如果你没有GPU，你可以在 Google Colab（国内网络无法使用） 上使用免费的 GPU 或 TPU。

下面的代码示例假设你已经运行了上一节中的代码示例。 下面是在开始学习这一节之前你需要运行的代码：

    from datasets import load_dataset
    from transformers import AutoTokenizer, DataCollatorWithPadding
    import numpy as np

    raw_datasets = load_dataset("glue", "mrpc")
    checkpoint = "bert-base-uncased"
    tokenizer = AutoTokenizer.from_pretrained(checkpoint)

    def tokenize_function(example):
        return tokenizer(example["sentence1"], example["sentence2"], truncation=True)

    tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)

    data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf")

    tf_train_dataset = tokenized_datasets["train"].to_tf_dataset(
        columns=["attention_mask", "input_ids", "token_type_ids"],
        label_cols=["labels"],
        shuffle=True,
        collate_fn=data_collator,
        batch_size=8,
    )

    tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset(
        columns=["attention_mask", "input_ids", "token_type_ids"],
        label_cols=["labels"],
        shuffle=False,
        collate_fn=data_collator,
        batch_size=8,
    )

##
训练模型

从🤗 Transformers 导入的 TensorFlow 模型已经是 Keras 模型。 下面的视频是对 Keras 的简短介绍。

这意味着一旦我们有了数据，只需要很少的工作就可以开始对其进行训练。

和第二章使用的方法一样, 我们将使用二分类的 `TFAutoModelForSequenceClassification`类，我们将有两个标签:

    from transformers import TFAutoModelForSequenceClassification

    model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)

你会注意到，与 第二章 不同的是，在实例化这个预训练的模型后会收到警告。 这是因为 BERT 没有对句子对的分类进行预训练，所以预训练模型的 head 已经被丢弃，并且插入了一个适合序列分类的新 head。 警告表明，这些权重没有使用（对应于丢弃的预训练 head 权重），而其他一些权重是随机初始化的（对应于新 head 的权重）。 最后它建议你训练模型，这正是我们现在要做的。

为了在我们的数据集上微调模型，我们只需要在我们的模型上调用 `compile()` 方法，然后将我们的数据传递给 `fit()` 方法。 这将启动微调过程（在 GPU 上应该需要几分钟）并输出训练损失，以及每个 epoch 结束时的验证损失。

请注意🤗 Transformers 模型具有大多数 Keras 模型所没有的特殊能力——它们可以自动使用内部计算的损失。 如果你没有在 `compile()` 中设置损失参数，它们可以自动使用适当的损失函数，并在内部计算。 请注意，要使用内部损失，你需要将标签作为输入的一部分传入模型，而不是作为单独的标签（这是在 Keras 模型中使用标签的常规方式）。 你将在课程的第 2 部分中看到这方面的示例，正确定义损失函数可能会有些棘手。 然而对于序列分类来说，标准的 Keras 损失函数效果很好，因此我们将在这里使用它。

    from tensorflow.keras.losses import SparseCategoricalCrossentropy

    model.compile(
        optimizer="adam",
        loss=SparseCategoricalCrossentropy(from_logits=True),
        metrics=["accuracy"],
    )
    model.fit(
        tf_train_dataset,
        validation_data=tf_validation_dataset,
    )

请注意这里有一个非常常见的陷阱——你可以把损失的名称作为一个字符串传递给 Keras，但默认情况下，Keras 会假设你已经对输出进行了 softmax。 然而，许多模型在经过 softmax 函数之前输出的是被称为 `logits` 的值。 我们需要告诉损失函数，我们的模型是否已经使用 softmax 函数进行了处理，唯一的方法是传递一个损失函数并且在参数的部分告诉模型，而不是只传递一个字符串。

##
改善训练的效果

如果你尝试上述代码，它的确可以运行，但你会发现损失下降得很慢或者不规律。 主要原因是`学习率`。 与损失一样，当我们把优化器的名称作为字符串传递给 Keras 时，Keras 会初始化该优化器具有所有参数的默认值，包括学习率。 不过根据长期经验，我们知道Transformer 模型的通常的最佳学习率比 Adam 的默认值（即1e-3，也写成为 10 的 -3 次方，或 0.001）低得多。 5e-5（0.00005），是一个更好的起始点。

除了降低学习率之外，我们还有第二个技巧：我们可以在训练过程中慢慢降低学习率。在文献中，你有时会看到这被称为 学习率的`衰减（decaying）` 或 `退火（annealing）`。 在 Keras 中，最好的方法是使用 `学习率调度器（learning rate scheduler）`。 一个好用的调度器是`PolynomialDecay`——尽管它的名字叫`PolynomialDecay（多项式衰减）`，但在默认设置下，它只是简单将学习率从初始值线性衰减到最终值，这正是我们想要的。不过为了正确使用调度程序，我们需要告诉它训练的次数。我们可以通过下面的`num_train_steps`计算得到。

    from tensorflow.keras.optimizers.schedules import PolynomialDecay

    batch_size = 8
    num_epochs = 3

    # 训练步数是数据集中的样本数量,除以 batch 大小,然后乘以总的 epoch 数。
    # 注意这里的 tf_train_dataset 是 batch 形式的 tf.data.Dataset,
    # 而不是原始的 Hugging Face Dataset ,所以使用 len() 计算它的长度已经是 num_samples // batch_size。
    num_train_steps = len(tf_train_dataset) * num_epochs
    lr_scheduler = PolynomialDecay(
        initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps
    )
    from tensorflow.keras.optimizers import Adam

    opt = Adam(learning_rate=lr_scheduler)

🤗 Transformers 库还有一个 `create_optimizer()` 函数，它将创建一个具有学习率衰减的 `AdamW` 优化器。 这是一个快捷的方式，你将在本课程的后续部分中详细了解。

现在我们有了全新的优化器，我们可以尝试使用它进行训练。 首先，让我们重新加载模型，重新设置刚刚训练时的权重，然后我们可以使用新的优化器对其进行编译：

    import tensorflow as tf

    model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)
    loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
    model.compile(optimizer=opt, loss=loss, metrics=["accuracy"])

现在，我们再次进行fit：

    model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3)

💡 如果你想在训练期间自动将模型上传到 Hub，你可以在 `model.fit()` 方法中传递一个 `PushToHubCallback`。 我们将在 第四章 中进一步了解这个问题。

##
模型预测

训练和观察损失的下降都是非常好，但如果我们想从训练后的模型中得到输出，或者计算一些指标，或者在生产中使用模型，该怎么办呢？为此，我们可以使用`predict()` 方法。 这将返回模型的输出头的`logits`数值，每个类一个。

    preds = model.predict(tf_validation_dataset)["logits"]

我们可以通过使用 argmax 将这些 logits 转换为模型的类别预测，最高的 logits，对应于最有可能的类别

    class_preds = np.argmax(preds, axis=1)
    print(preds.shape, class_preds.shape)

    (408, 2) (408,)

现在，让我们使用这些 `preds` 来计算一些指标！ 我们可以像加载数据集一样轻松地加载与 MRPC 数据集相关的指标，这次使用的是 `evaluate.load()` 函数。 返回的对象有一个 `compute()` 方法，我们可以使用它来进行指标的计算：

    import evaluate

    metric = evaluate.load("glue", "mrpc")
    metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"])

    {'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}

由于模型头的随机初始化可能会改变模型达到的指标，因此你得到的最终结果可能会有所不同。在这里，我们可以看到我们的模型在验证集上的准确率为85.78%，F1分数为89.97。 这就是用于评估 GLUE 基准的 MRPC 数据集上的结果两个指标。 BERT 论文 中的表格报告了基本模型的 F1 分数为 88.9。 那是 `uncased` 模型，而我们目前使用的是 `cased` 模型，这也可以解释为什么我们会得到了更好的结果。

关于使用 Keras API 进行微调的介绍到此结束。 在第七章将给出对大多数常见 NLP 任务微调或训练的示例。如果你想在 Keras API 上磨练自己的技能，请尝试使第2节所学的的数据处理技巧在 GLUE SST-2 数据集上微调模型。

< > Update on GitHub