HuggingfaceNLP笔记7.1Token classification

我们将首先探讨的任务是词元分类。这个通用任务包括任何可以表述为“为句子中的每个词元分配一个标签”的问题,例如:

  • 命名实体识别(NER):在句子中找到实体(如人名、地名或组织)。这可以通过为每个实体分配一个类,以及一个表示“无实体”的类来实现。
  • 词性标注(POS):标记句子中每个词对应的具体词性(如名词、动词、形容词等)。
  • 切分:找出属于同一实体的词元。这个任务(可以与POS或NER结合)可以被表述为为任何属于实体的词元分配一个标签(通常为B-),为内部词元分配另一个标签(通常为I-),并将不属于任何实体的词元标记为O

当然,还有许多其他类型的词元分类问题;这些只是几个代表性的例子。在本节中,我们将使用BERT模型进行NER任务的微调,然后它将能够生成这样的预测:

在这里插入图片描述

您可以在这里查看我们将训练和上传到Hub的模型,并验证其预测。

数据准备

首先,我们需要一个适合词元分类的数据集。在本节中,我们将使用CoNLL-2003数据集,它包含来自路透社的新闻故事。

💡只要您的数据集包含分词后的文本及其相应的标签,您就可以根据这里描述的数据处理流程调整到自己的数据集。如果您需要复习如何加载自定义数据到Dataset,请参考第5章

CoNLL-2003数据集

要加载CoNLL-2003数据集,我们使用🤗 Datasets库的load_dataset()方法:

from datasets import load_dataset

raw_datasets = load_dataset("conll2003")

这将下载并缓存数据集,就像我们在第3章中为GLUE MRPC数据集所做的那样。查看这个对象,我们可以看到存在的列以及训练、验证和测试集的划分:

raw_datasets
DatasetDict({
    'train': Dataset({
        'features': ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
        'num_rows': 14041
    }),
    'validation': Dataset({
        'features': ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
        'num_rows': 3250
    }),
    'test': Dataset({
        'features': ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'],
        'num_rows': 3453
    })
})

特别是,我们可以看到数据集中包含了我们之前提到的三个任务的标签:NER、POS和切分。与其它数据集的一个显著区别是,输入文本不是以句子或文档的形式呈现,而是单词列表(最后一列称为tokens,但它包含的是预分词的输入,仍然需要经过分词器进行子词分割)。

让我们看看训练集的第一个元素:

raw_datasets["train"][0]["tokens"]
['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.']

由于我们要进行命名实体识别,我们将查看NER标签:

raw_datasets["train"][0]["ner_tags"]
[3, 0, 7, 0, 0, 0, 7, 0, 0]

这些是用于训练的整数标签,但当我们想要查看数据时,它们可能并不直观。就像对于文本分类一样,我们可以通过查看数据集的features属性来获取这些整数标签与标签名称之间的对应关系:

ner_feature = 原始数据集中的"train"特征["ner_tags"]
ner_feature
Sequence(feature=ClassLabel(num_classes=9, names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'], names_file=None, id=None), length=-1, id=None)

这个列包含元素,这些元素是ClassLabel序列。序列元素的类型存储在ner_featurefeature属性中,我们可以通过查看该featurenames属性来访问标签列表:

label_names = ner_feature.feature.names
label_names
['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC']

在第6章中,当我们深入研究token-classification管道时,我们已经见过这些标签,但为了快速回顾:

  • O表示单词不属于任何实体。
  • B-PER/I-PER表示单词对应于一个人名实体的开始或内部。
  • B-ORG/I-ORG表示单词对应于一个组织实体的开始或内部。
  • B-LOC/I-LOC表示单词对应于一个位置实体的开始或内部。
  • B-MISC/I-MISC表示单词对应于一个杂项实体的开始或内部。

现在,我们可以解码我们之前看到的标签,得到:

words = raw_datasets["train"][0]["tokens"]
labels = raw_datasets["train"][0]["ner_tags"]
line1 = ""
line2 = ""
for word, label in zip(words, labels):
    full_label = label_names[label]
    max_length = max(len(word), len(full_label))
    line1 += word + " " * (max_length - len(word) + 1)
    line2 += full_label + " " * (max_length - len(full_label) + 1)

print(line1)
print(line2)
'EU    rejects German call to boycott British lamb .'
'B-ORG O       B-MISC O    O  O       B-MISC  O    O'

对于混合B-I-标签的示例,对于索引为4的训练集元素,代码会显示:

'Germany \'s representative to the European Union \'s veterinary committee Werner Zwingmann said on Wednesday consumers should buy sheepmeat from countries other than Britain until the scientific advice was clearer .'
'B-LOC   O  O              O  O   B-ORG    I-ORG O  O          O         B-PER  I-PER     O    O  O         O         O      O   O         O    O         O     O    B-LOC   O     O   O          O      O   O       O'

我们可以看到,像“European Union”和“Werner Zwingmann”这样的跨越两个单词的实体,被赋予了第一个单词B-标签和第二个单词I-标签。

✍️ 你的任务! 打印带有它们的POS或依存关系标签的同一两个句子。

处理数据

如往常一样,我们的文本在模型能够理解它们之前需要转换为token ID。在第6章中,我们看到对于token分类任务的一个重要区别是,我们已经有了预处理的输入。幸运的是,tokenizer API可以轻松处理这种情况;我们只需要用一个特殊的标志警告tokenizer

首先,让我们创建我们的tokenizer对象。如前所述,我们将使用预训练的BERT模型,所以首先下载并缓存相关的tokenizer:

from transformers import AutoTokenizer

model_checkpoint = "bert-base-cased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)

您可以将model_checkpoint替换为Hugging Face Hub上的任何其他模型,或者替换为本地保存的预训练模型和分词器。唯一的限制是分词器需要由🤗 Tokenizers库支持,这意味着它有一个“fast”版本。您可以在这个大表格中查看所有带有fast版本的架构。要检查您使用的分词器是否由🤗 Tokenizers支持,可以查看其is_fast属性:

tokenizer.is_fast
True

对于预分词输入,我们可以像平常一样使用分词器,并添加is_split_into_words=True

inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True)
inputs.tokens()
['[CLS]', 'EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'la', '##mb', '.', '[SEP]']

如我们所见,分词器添加了模型使用的特殊标记(如[CLS]在开头,[SEP]在结尾),并保留了大部分单词。然而,单词lamb被分成了两个子词la##mb。这导致输入和标签之间存在不匹配:标签列表只有9个元素,而我们的输入现在有12个令牌。考虑到特殊标记很容易(我们知道它们在开头和结尾),但我们还需要确保所有标签都与正确的单词对齐。

幸运的是,由于我们使用的是fast分词器,我们能够利用🤗 Tokenizers的强大功能,这意味着我们可以轻松地将每个令牌映射到相应的单词(如第6章所示):

inputs.word_ids()
[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None]

稍加努力,我们就可以扩展标签列表以匹配令牌。我们将应用的第一个规则是,特殊标记的标签为-100。这是因为在我们将使用的损失函数(交叉熵)中,默认情况下-100是一个被忽略的索引。然后,每个令牌获得与它所在的单词开始的令牌相同的标签,因为它们属于同一个实体。对于单词内的令牌但不在开头,我们将B-更改为I-(因为令牌不开始实体):

def align_labels_with_tokens(labels, word_ids):
    new_labels = []
    current_word = None
    for word_id in word_ids:
        if word_id != current_word:
            # 新单词的开始!
            current_word = word_id
            label = -100 if word_id is None else labels[word_id]
            new_labels.append(label)
        elif word_id is None:
            # 特殊标记
            new_labels.append(-100)
        else:
            # 同一个单词,与前一个令牌相同
            label = labels[word_id]
            # 如果标签是B-XXX,我们将其改为I-XXX
            if label % 2 == 1:
                label += 1
            new_labels.append(label)

    return new_labels

让我们在第一个句子上试一试:

labels = raw_datasets["train"][0]["ner_tags"]
word_ids = inputs.word_ids()
print(labels)
print(align_labels_with_tokens(labels, word_ids))
[3, 0, 7, 0, 0, 0, 7, 0, 0]
[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100]

如我们所见,我们的函数为两个开头和结尾的特殊标记添加了-100,并在被分割成两个令牌的单词中添加了一个新的0

📝 您的任务! 有些研究人员更喜欢为每个单词分配一个标签,并将单词内的其他子标记的标签设置为-100。这样可以避免长单词被大量子标记强烈影响损失。请修改之前的函数,遵循这个规则来将标签与输入ID对齐。

要预处理我们的整个数据集,我们需要对所有输入进行分词,并对所有标签应用 align_labels_with_tokens()。为了利用我们快速分词器的速度,最好一次处理大量文本,因此我们将编写一个函数,处理一个示例列表,并使用 Dataset.map() 方法,其中 batched=True。与之前的示例唯一的不同是,当输入到分词器是文本列表(在我们的例子中,是单词列表的列表)时,word_ids() 函数需要获取我们想要获取单词 ID 的示例的索引,因此我们也会添加这个:

def tokenize_and_align_labels(examples):
    tokenized_inputs = tokenizer(
        examples["tokens"], truncation=True, is_split_into_words=True
    )
    all_labels = examples["ner_tags"]
    new_labels = []
    for i, labels in enumerate(all_labels):
        word_ids = tokenized_inputs.word_ids(i)
        new_labels.append(align_labels_with_tokens(labels, word_ids))

    tokenized_inputs["labels"] = new_labels
    return tokenized_inputs

注意,我们还没有对输入进行填充;稍后在使用数据聚合器创建批次时会这样做。

现在,我们可以一次性对数据集的其他部分应用所有预处理:

tokenized_datasets = raw_datasets.map(
    tokenize_and_align_labels,
    batched=True,
    remove_columns=raw_datasets["train"].column_names,
)

我们已经完成了最困难的部分!现在数据已经预处理,实际的训练将与我们在第 3 章中所做的非常相似。

使用 Trainer API 进行微调

实际使用 Trainer 的代码将与之前相同;唯一的改变是数据如何聚合成批次和指标计算函数。

数据聚合

我们不能像在第 3 章中那样使用 DataCollatorWithPadding,因为那只能对输入(输入 ID、注意力掩码和分词类型 ID)进行填充。在这里,我们的标签应该与输入以相同的方式填充,以便它们保持相同的大小,使用 -100 作为值,以便在损失计算中忽略相应的预测。

这都是通过 DataCollatorForTokenClassification 完成的。就像 DataCollatorWithPadding 一样,它使用预处理输入的 tokenizer

from transformers import DataCollatorForTokenClassification

data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer)

我们可以用它在我们的分词训练集的一些样本上进行测试:

batch = data_collator([tokenized_datasets["train"][i] for i in range(2)])
batch["labels"]
tensor([[-100,    3,    0,    7,    0,    0,    0,    7,    0,    0,    0, -100],
        [-100,    1,    2, -100, -100, -100, -100, -100, -100, -100, -100, -100]])

让我们比较一下数据集中第一个和第二个元素的标签:

for i in range(2):
    print(tokenized_datasets["train"][i]["labels"])
[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100]
[-100, 1, 2, -100]

如我们所见,第二个标签集已经被填充到了第一个标签集的长度,使用 -100 进行填充。

指标

为了让 Trainer 每个 epoch 计算一个指标,我们需要定义一个 compute_metrics() 函数,它接受预测和标签的数组,并返回一个包含指标名称和值的字典。

传统的序列标注评估框架是seqeval。要使用这个指标,我们首先需要安装 seqeval 库:

!pip install seqeval

然后,我们可以像在第 3 章中那样通过 evaluate.load() 函数加载它:

import evaluate

metric = evaluate.load("seqeval")

这个指标的行为并不像标准的准确率:它会直接处理标签列表作为字符串,而不是整数。在传递给指标之前,我们需要先完全解码预测和标签。让我们看看它是如何工作的。首先,获取我们第一个训练样本的标签:

labels = raw_datasets["train"][0]["ner_tags"]
labels = [label_names[i] for i in labels]
labels
['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O']

然后,我们可以为这些标签创建一些假预测,只需将索引2的值改为"O":

predictions = labels.copy()
predictions[2] = "O"
metric.compute(predictions=[predictions], references=[labels])

请注意,指标需要一个预测列表(而不仅仅是单个预测)和一个标签列表。这是输出:

{
    'MISC': {'precision': 1.0, 'recall': 0.5, 'f1': 0.67, 'number': 2},
    'ORG': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1},
    'overall_precision': 1.0,
    'overall_recall': 0.67,
    'overall_f1': 0.8,
    'overall_accuracy': 0.89
}

这提供了很多信息!我们得到了每个单独实体的精确度、召回率和F1分数,以及整体的。在计算指标时,我们将只保留整体分数,但你可以根据需要调整compute_metrics()函数,使其返回你想要报告的所有指标。

这个compute_metrics()函数首先对logits取argmax,将其转换为预测(如往常一样,logits和概率的顺序相同,所以我们不需要应用softmax)。然后,我们需要将标签和预测从整数转换为字符串。我们移除所有标签为-100的值,然后将结果传递给metric.compute()方法:

import numpy as np

def compute_metrics(eval_preds):
    logits, labels = eval_preds
    predictions = np.argmax(logits, axis=-1)

    # Remove ignored index (special tokens) and convert to labels
    true_labels = [[label_names[l] for l in label if l != -100] for label in labels]
    true_predictions = [
        [label_names[p] for (p, l) in zip(prediction, label) if l != -100]
        for prediction, label in zip(predictions, labels)
    ]
    all_metrics = metric.compute(predictions=true_predictions, references=true_labels)
    return {
        "precision": all_metrics["overall_precision"],
        "recall": all_metrics["overall_recall"],
        "f1": all_metrics["overall_f1"],
        "accuracy": all_metrics["overall_accuracy"],
    }

现在,完成这些步骤后,我们几乎准备好定义Trainer了。我们只需要一个model来微调!

定义模型

由于我们处理的是序列标注问题,我们将使用AutoModelForTokenClassification类。在定义这个模型时,主要要记住的是传递有关我们有多少标签的信息。最简单的方法是通过num_labels参数传递,但如果想要像本节开头所示的那样,有一个漂亮的推理小部件,最好设置正确的标签对应关系。

它们应该通过两个字典设置,id2labellabel2id,分别包含ID到标签和反之的映射:

id2label = {i: label for i, label in enumerate(label_names)}
label2id = {v: k for k, v in id2label.items()}

现在,我们可以将它们传递给AutoModelForTokenClassification.from_pretrained()方法,它们将被设置在模型的配置中,并正确保存和上传到Hub:

from transformers import AutoModelForTokenClassification

model = AutoModelForTokenClassification.from_pretrained(
    model_checkpoint,
    id2label=id2label,
    label2id=label2id,
)

就像我们在第3章中定义AutoModelForSequenceClassification时一样,创建模型时会发出警告,说明有些权重未使用(来自预训练头的权重),有些权重被随机初始化(来自新的序列标注头的权重),并且建议对模型进行训练。我们稍后会进行训练,但首先让我们确认模型的标签数量正确:

model.config.num_labels
9

⚠️ 如果你的模型标签数量不正确,后续调用Trainer.train()方法时会收到一个晦涩的错误(比如“CUDA错误:设备侧断言触发”)。这是用户报告的此类错误的首要原因,所以确保进行此检查以确认你有预期的标签数量。

调整模型

现在我们准备好训练模型了!在定义Trainer之前,我们只需要做两件事:登录Hugging Face并定义训练参数。如果你在笔记本中工作,有一个便利函数可以帮助你:

from huggingface_hub import notebook_login

notebook_login()

这将显示一个对话框,让你输入Hugging Face的登录凭据。

如果你不在笔记本中,只需在终端中输入以下行:

huggingface-cli login

完成这些后,我们可以定义TrainingArguments

from transformers import TrainingArguments

args = TrainingArguments(
    "bert-finetuned-ner",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    num_train_epochs=3,
    weight_decay=0.01,
    push_to_hub=True,
)

你之前已经见过大部分参数:我们设置了超参数(如学习率、训练轮数和权重衰减),并指定push_to_hub=True,表示我们希望在每个epoch结束时保存模型并评估它,同时将结果上传到模型库。你可以通过hub_model_id参数指定要推送的仓库名称(例如,当我们将模型推送到huggingface-course组织时,我们在TrainingArguments中添加了hub_model_id="huggingface-course/bert-finetuned-ner")。默认情况下,使用的仓库将是你自己的命名空间,并且以你设置的输出目录命名,所以在我们的例子中,它将是"sgugger/bert-finetuned-ner"

💡 如果你使用的输出目录已经存在,它需要是你想要推送的仓库的本地克隆。如果不是,当你定义Trainer时会收到错误,需要设置一个新的名称。

最后,我们将所有内容传递给Trainer并启动训练:

from transformers import Trainer

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized_datasets["train"],
    eval_dataset=tokenized_datasets["validation"],
    data_collator=data_collator,
    compute_metrics=compute_metrics,
    tokenizer=tokenizer,
)
trainer.train()

在训练过程中,每次模型保存(这里每轮一次)都会在后台上传到Hub。这样,如果有必要,你可以在另一台机器上恢复训练。

训练完成后,我们使用push_to_hub()方法确保上传最新的模型版本:

trainer.push_to_hub(commit_message="Training complete")

此命令返回刚刚完成的提交的URL,如果你想查看:

'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed'

Trainer还会草拟一个包含所有评估结果的模型卡,并将其上传。此时,你可以使用模型库的推理小部件测试你的模型并与朋友分享。你已经成功地在一个序列标注任务上微调了一个模型——恭喜!

如果你想深入了解训练循环,我们将展示如何使用🤗 Accelerate来实现相同的事情。

现在让我们来看看完整的训练循环,以便您可以轻松定制所需的部分。它将类似于我们在第3章中所做的,但会有一些针对评估的更改。

自定义训练循环

训练准备

首先,我们需要根据我们的数据集构建DataLoader。我们将重用data_collator作为collate_fn,并随机打乱训练集,但不打乱验证集:

from torch.utils.data import DataLoader

train_dataloader = DataLoader(
    tokenized_datasets["train"],
    shuffle=True,
    collate_fn=data_collator,
    batch_size=8,
)
eval_dataloader = DataLoader(
    tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8
)

接下来,我们需要重新实例化模型,确保我们从预训练的BERT模型开始,而不是继续之前的微调:

model = AutoModelForTokenClassification.from_pretrained(
    model_checkpoint,
    id2label=id2label,
    label2id=label2id,
)

然后,我们需要一个优化器。我们将使用经典的AdamW,它类似于Adam,但对权重衰减的处理方式有所不同:

from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-5)

有了这些对象后,我们可以将它们发送到accelerator.prepare()方法:

from accelerate import Accelerator

accelerator = Accelerator()
model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
    model, optimizer, train_dataloader, eval_dataloader
)

🚨 如果您在TPU上训练,您需要将从上述单元格开始的所有代码放入一个专用的训练函数中。有关更多详细信息,请参阅第3章

现在,我们将train_dataloader发送到accelerator.prepare()后,我们可以使用其长度来计算训练步数。记住,我们应该始终在准备数据加载器后这样做,因为该方法会改变其长度。我们使用经典的线性学习率调度器,从学习率逐渐减小到0:

from transformers import get_scheduler

num_train_epochs = 3
num_update_steps_per_epoch = len(train_dataloader)
num_training_steps = num_train_epochs * num_update_steps_per_epoch

lr_scheduler = get_scheduler(
    "linear",
    optimizer=optimizer,
    num_warmup_steps=0,
    num_training_steps=num_training_steps,
)

最后,为了将模型推送到Hub,我们需要在工作目录中创建一个Repository对象。首先,如果您还没有登录Hugging Face,可以登录。我们将从我们想要给模型的模型ID中确定仓库名称(您可以根据自己的选择替换repo_name,只需要包含您的用户名,get_full_repo_name()函数会完成这个任务):

from huggingface_hub import Repository, get_full_repo_name

model_name = "bert-finetuned-ner-accelerate"
repo_name = get_full_repo_name(model_name)
repo_name
'sgugger/bert-finetuned-ner-accelerate'

然后,我们可以在本地目录中克隆该仓库。如果它已经存在,那么这个本地目录应该是我们正在工作的仓库的现有克隆:

output_dir = "bert-finetuned-ner-accelerate"
repo = Repository(output_dir, clone_from=repo_name)

现在,我们可以使用repo.push_to_hub()方法上传我们在output_dir中保存的任何内容。这将帮助我们在每个epoch结束时上传中间模型。

训练循环

现在我们已经准备好编写完整的训练循环。为了简化评估部分,我们定义一个postprocess()函数,它接受预测和标签,并将它们转换为metric对象期望的字符串列表形式:

def postprocess(predictions, labels):
    predictions = predictions.detach().cpu().clone().numpy()
    labels = labels.detach().cpu().clone().numpy()

    # Remove ignored index (special tokens) and convert to labels
    true_labels = [[label_names[l] for l in label if l != -100] for label in labels]
    true_predictions = [
        [label_names[p] for (p, l) in zip(prediction, label) if l != -100]
        for prediction, label in zip(predictions, labels)
    ]
    return true_labels, true_predictions

接下来,我们可以编写训练循环。首先定义一个进度条来跟踪训练进度,循环分为三个部分:

  • 本身的训练,包括遍历train_dataloader,通过模型前向传播,然后反向传播和优化器步长。
  • 评估,在模型对一批数据进行预测后,会有一个新特性:由于可能对输入和标签进行了不同的填充,我们需要使用accelerator.pad_across_processes()来确保预测和标签具有相同的形状,否则评估会出错或永远挂起。然后我们将结果发送到metric.add_batch(),在评估循环结束后调用metric.compute()
  • 保存和上传,首先保存模型和分词器,然后调用repo.push_to_hub()。注意我们使用blocking=False参数告诉🤗 Hub库异步推送。这样,训练可以正常进行,而这个(较长)指令在后台执行。

完整的训练循环代码如下:

from tqdm.auto import tqdm
import torch

progress_bar = tqdm(range(num_training_steps))

for epoch in range(num_train_epochs):
    # 训练
    model.train()
    for batch in train_dataloader:
        outputs = model(**batch)
        loss = outputs.loss
        accelerator.backward(loss)

        optimizer.step()
        lr_scheduler.step()
        optimizer.zero_grad()
        进度条.update(1)

    # 评估
    model.eval()
    for batch in eval_dataloader:
        with torch.no_grad():
            outputs = model(**batch)

        predictions = outputs.logits.argmax(dim=-1)
        labels = batch["labels"]

        # 必要的对预测和标签进行填充,以便进行聚集
        predictions = accelerator.pad_across_processes(predictions, dim=1, pad_index=-100)
        labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100)

        predictions_gathered = accelerator.gather(predictions)
        labels_gathered = accelerator.gather(labels)

        true_predictions, true_labels = postprocess(predictions_gathered, labels_gathered)
        metric.add_batch(predictions=true_predictions, references=true_labels)

    results = metric.compute()
    print(
        f"epoch {epoch}:",
        {
            key: results[f"overall_{key}"]
            for key in ["precision", "recall", "f1", "accuracy"]
        },
    )

    # 保存和上传
    accelerator.wait_for_everyone()
    unwrapped_model = accelerator.unwrap_model(model)
    unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
    if accelerator.is_main_process:
        tokenizer.save_pretrained(output_dir)
        repo.push_to_hub(
            commit_message=f"训练中 epoch {epoch}", blocking=False
        )

如果你是第一次看到使用🤗 Accelerate保存模型,让我们花点时间检查与之相关的三行代码:

accelerator.wait_for_everyone()
unwrapped_model = accelerator.unwrap_model(model)
unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)

第一行的含义显而易见:它告诉所有进程在继续之前等待所有人到达这个阶段,以确保在保存模型之前,所有进程都有相同的模型。然后我们获取unwrapped_model,这是我们定义的基本模型。accelerator.prepare()方法将模型转换为分布式训练模式,因此它不再具有save_pretrained()方法;accelerator.unwrap_model()方法则撤销了这个步骤。最后,我们调用save_pretrained(),但告诉该方法使用accelerator.save()而不是torch.save()

完成这些步骤后,你应该会有一个与Trainer训练得到的模型相似的结果。你可以查看在huggingface-course/bert-finetuned-ner-accelerate中训练的模型。如果你想尝试修改训练循环,可以直接编辑上面显示的代码!

使用微调后的模型

我们已经展示了如何在模型库中使用我们微调的模型。要在本地使用pipeline,只需指定适当的模型标识符:

from transformers import pipeline

# 将此替换为自己的检查点
model_checkpoint = "huggingface-course/bert-finetuned-ner"
token_classifier = pipeline(
    "token-classification", model=model_checkpoint, aggregation_strategy="simple"
)
token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.")
[{'entity_group': 'PER', 'score': 0.9988506, 'word': 'Sylvain', 'start': 11, 'end': 18},
 {'entity_group': 'ORG', 'score': 0.9647625, 'word': 'Hugging Face', 'start': 33, 'end': 45},
 {'entity_group': 'LOC', 'score': 0.9986118, 'word': 'Brooklyn', 'start': 49, 'end': 57}]

太好了!我们的模型与这个管道的默认模型表现得一样好!

  • 13
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HITzwx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值