<FrameworkSwitchCourse {fw} />

# 使用 Keras 微调一个模型 [[使用 Keras 微调一个模型]]

<CourseFloatingBanner chapter={3}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section3_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section3_tf.ipynb"},
]} />

完成上一节中的所有数据预处理工作后，您只剩下最后的几个步骤来训练模型。 但是请注意，`model.fit()` 命令在 CPU 上运行会非常缓慢。 如果您没有GPU，则可以在 [Google Colab](https://colab.research.google.com/) 上使用免费的 GPU 或 TPU(需要梯子)。

这一节的代码示例假设您已经执行了上一节中的代码示例。 下面一个简短的摘要，包含了在开始学习这一节之前您需要的执行的代码：

```py
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 的简短介绍。

<Youtube id="rnTGBy2ax1c"/>

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

<Youtube id="AUozVp78dhk"/>

和[第二章](/course/chapter2)使用的方法一样, 我们将使用二分类的 `TFAutoModelForSequenceClassification`类: 

```py
from transformers import TFAutoModelForSequenceClassification

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

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

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

<Tip>

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

</Tip>

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

<Tip warning={true}>

请注意这里有一个非常常见的陷阱——你只是*可以*将损失的名称作为字符串传递给 Keras，但默认情况下，Keras 会假设你已经对输出应用了 softmax。 然而，许多模型在应用 softmax 之前就输出，也称为 *logits*。 我们需要告诉损失函数我们的模型是否经过了softmax，唯一的方法是直接调用它，而不是用字符串的名称。

</Tip>


### 提升训练的效果 [[提升训练的效果]]

<Youtube id="cpzq6ESSM5c"/>

如果您尝试上面的代码，它肯定会运行，但您会发现loss只是缓慢或零星地下降。 主要原因是*学习率*。 与loss一样，当我们将优化器的名称作为字符串传递给 Keras 时，Keras 会初始化该优化器具有所有参数的默认值，包括学习率。 但是，根据长期经验，我们知道Transformer 模型更适合使用比 Adam 的默认值（1e-3）也写成为 10 的 -3 次方，或 0.001，低得多的学习率。 5e-5 (0.00005) 比默认值大约低 20 倍，是一个更好的起点。

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

```py
from tensorflow.keras.optimizers.schedules import PolynomialDecay

batch_size = 8
num_epochs = 3
# 训练步数是数据集中的样本数除以batch size再乘以 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)
```

<Tip>

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

</Tip>

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

```py
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：

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

<Tip>

💡 如果您想在训练期间自动将模型上传到 Hub，您可以在 `model.fit()` 方法中传递 `PushToHubCallback`。 我们将在 [第四章](/course/chapter4/3) 中进行介绍

</Tip>

### 模型预测 [[模型预测]]

<Youtube id="nx10eh4CoOs"/>


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

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

我们可以将这些 logit 转换为模型的类别预测，方法是使用 argmax 找到最高的 logit，它对应于最有可能的类别：

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

```python out
(408, 2) (408,)
```

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

```py
import evaluate

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

```python out
{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}
```

您获得的确切结果可能会有所不同，因为模型头的随机初始化可能会改变它获得的指标。 在这里，我们可以看到我们的模型在验证集上的准确率为 85.78%，F1 得分为 89.97。 这些是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。 [BERT 论文](https://arxiv.org/pdf/1810.04805.pdf) 中的表格报告了基本模型的 F1 分数为 88.9。 那是 `uncased` 模型，而我们目前使用的是 `cased` 模型，这解释了为什么我们会获得更好的结果。

使用 Keras API 进行微调的介绍到此结束。 [第 7 章](/course/chapter7)将给出对大多数常见 NLP 任务执行此操作的示例。如果您想在 Keras API 上磨练自己的技能，请尝试使第二节所使用的的数据处理在 GLUE SST-2 数据集上微调模型。