标记分类(Token Classification)¶
标记分类(Token Classification)是指为句子中的每个标记(token)分配一个标签。最常见的标记分类任务之一是命名实体识别(Named Entity Recognition, NER)。NER的目标是为句子中的每个实体(例如人名、地名或组织名)找到相应的标签。
本指南将向你展示如何:
- 微调DistilBERT模型:使用WNUT 17数据集对DistilBERT模型进行微调,以便检测新的实体。
- 使用微调模型进行推理:利用微调后的模型进行实际应用中的推理操作。
要查看与此任务兼容的所有架构和检查点,我们建议检查任务页面。
在开始之前,请确保安装了所有必要的库:
pip install transformers datasets evaluate seqeval
登录你的Hugging Face帐户,以便你可以上传并与社区分享你的模型。出现提示时,输入你的令牌来登录:
from huggingface_hub import notebook_login
notebook_login()
加载 WNUT 17 数据集¶
首先从 WNUTDatasets 库中加载 WNUT 17 数据集:
from datasets import load_dataset
wnut = load_dataset("wnut_17")
我们来看一个示例数据:
wnut["train"][0]
ner_tags中的每个数字代表一个实体。为了找出这些数字对应的实体标签,我们可以将数字转换为它们的标签名称:
label_list = wnut["train"].features[f"ner_tags"].feature.names
label_list
每个ner_tag前面的字母表示实体的标记位置:
B-表示实体的开始。I-表示令牌包含在同一实体中(例如,State令牌是Empire State Building实体的一部分)。0表示令牌不对应于任何实体。
预处理数据¶
下一步是加载DistilBERT tokenizer来预处理tokens字段:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("distilbert/distilbert-base-uncased")
尽管示例中的tokens字段看起来像是已经经过标记化的,但实际上并非如此。
为了正确处理这些单词,你需要使用is_split_into_words=True参数来告诉tokenizer将单词拆分为子单词。例如:
example = wnut["train"][0]
tokenized_input = tokenizer(example["tokens"], is_split_into_words=True)
tokens = tokenizer.convert_ids_to_tokens(tokenized_input["input_ids"])
tokens
当你使用tokenizer处理文本时,会引入特殊的标记[CLS]和[SEP],并且单词可能会被拆分为多个子词标记。这会导致标记和标签之间的不匹配,因为一个标签可能需要对应多个子词标记。为了解决这个问题,你需要重新对齐标记和标签:
- 使用word_ids方法将所有标记映射到它们对应的单词。
- 将标签
-100添加到特殊标记[CLS]和[SEP],以便PyTorch损失函数忽略它们(参见CrossEntropyLoss)。 - 仅标记给定单词的第一个标记。将
-100分配给同一单词的其他子标记。
通过这些步骤,你可以确保每个子词标记都有一个正确的标签,同时特殊标记不会影响模型的训练。
def tokenize_and_align_labels(examples):
tokenized_inputs = tokenizer(examples["tokens"], truncation=True, is_split_into_words=True)
labels = []
for i, label in enumerate(examples[f"ner_tags"]):
word_ids = tokenized_inputs.word_ids(batch_index=i) # Map tokens to their respective word.
previous_word_idx = None
label_ids = []
for word_idx in word_ids: # Set the special tokens to -100.
if word_idx is None:
label_ids.append(-100)
elif word_idx != previous_word_idx: # Only label the first token of a given word.
label_ids.append(label[word_idx])
else:
label_ids.append(-100)
previous_word_idx = word_idx
labels.append(label_ids)
tokenized_inputs["labels"] = labels
return tokenized_inputs
为了在整个数据集上应用预处理函数,你需要使用datasets库中的map函数。这个函数允许你对数据集中的每个元素应用一个函数。为了提高效率,你可以通过设置batched=True来让map函数一次处理多个元素:
tokenized_wnut = wnut.map(tokenize_and_align_labels, batched=True)
接下来,使用DataCollatorWithPadding来创建一批示例。这个数据整理器会在处理批次时动态地将句子填充到该批次中最长句子的长度。这种方法比将整个数据集填充到最大长度更有效率,因为它减少了不必要的填充,从而节省了计算资源:
# PyTorch
from transformers import DataCollatorForTokenClassification
data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer)
# TensorFlow
from transformers import DataCollatorForTokenClassification
data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer, return_tensors="tf")
评估模型¶
在训练模型时,引入指标是非常有帮助的,因为它能让我们更好地了解模型的性能。你可以使用Evaluate库来快速加载和使用评估方法。
具体步骤如下:
- 加载评估方法:
- 对于这个任务,我们推荐使用
seqeval框架。 - 你可以在Evaluate快速浏览部分找到如何加载和计算这些指标的详细说明。
- Seqeval 提供的指标:
- 精确度:衡量模型预测正确的比例。
- 召回率:衡量模型正确识别的比例。
- F1 分数:精确度和召回率的调和平均值,综合评估模型性能。
- 准确度:衡量模型整体预测正确的比例。
通过这些指标,你可以全面了解模型的性能,从而进行更有针对性的优化。
import evaluate
seqeval = evaluate.load("seqeval")
首先,我们需要获取命名实体识别(NER)的标签。接下来,我们可以创建一个函数,将模型的预测结果和真实的标签传递给computing,以评估模型的性能。
import numpy as np
labels = [label_list[i] for i in example[f"ner_tags"]]
def compute_metrics(p):
predictions, labels = p
predictions = np.argmax(predictions, axis=2)
true_predictions = [
[label_list[p] for (p, l) in zip(prediction, label) if l != -100]
for prediction, label in zip(predictions, labels)
]
true_labels = [
[label_list[l] for (p, l) in zip(prediction, label) if l != -100]
for prediction, label in zip(predictions, labels)
]
results = seqeval.compute(predictions=true_predictions, references=true_labels)
return {
"precision": results["overall_precision"],
"recall": results["overall_recall"],
"f1": results["overall_f1"],
"accuracy": results["overall_accuracy"],
}
现在,compute_metrics 函数已经准备好了。当你在设置训练过程时,可以随时调用这个函数来评估模型的性能。
训练模型¶
在开始训练模型之前,使用id 2 label和label 2 id创建一个期望id到标签的映射:
id2label = {
0: "O",
1: "B-corporation",
2: "I-corporation",
3: "B-creative-work",
4: "I-creative-work",
5: "B-group",
6: "I-group",
7: "B-location",
8: "I-location",
9: "B-person",
10: "I-person",
11: "B-product",
12: "I-product",
}
label2id = {
"O": 0,
"B-corporation": 1,
"I-corporation": 2,
"B-creative-work": 3,
"I-creative-work": 4,
"B-group": 5,
"I-group": 6,
"B-location": 7,
"I-location": 8,
"B-person": 9,
"I-person": 10,
"B-product": 11,
"I-product": 12,
}
PyTorch¶
如果你不熟悉使用Trainer对模型进行微调,请查看此处的基本教程!
现在,你可以开始训练你的模型了!我们将使用 AutoModelForTokenClassification 来加载 DistilBERT,并设置预期标签的数量和标签映射。
from transformers import AutoModelForTokenClassification, TrainingArguments, Trainer
model = AutoModelForTokenClassification.from_pretrained(
"distilbert/distilbert-base-uncased", num_labels=13, id2label=id2label, label2id=label2id
)
此时,只剩下三个步骤:
- 定义训练超参数:
- 使用
[TrainingArguments](https://huggingface.co/docs/transformers/main/en/main_classes/trainer#transformers.TrainingArguments)来定义训练的超参数。 - 关键参数:
output_dir:指定保存模型的位置。push_to_hub=True:将模型推送到 Hugging Face Hub(需要先登录 Hugging Face 账号)。
- 在每个 epoch 结束时,Trainer 会自动评估
seqeval分数并保存训练检查点。
- 初始化 Trainer:
- 将训练参数、模型、数据集、标记器、数据整理器以及
compute_metrics函数一起传递给 Trainer。
- 开始训练:
- 调用 train() 方法来微调你的模型。
training_args = TrainingArguments(
output_dir="my_awesome_wnut_model",
learning_rate=2e-5,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
num_train_epochs=2,
weight_decay=0.01,
eval_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
push_to_hub=True,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_wnut["train"],
eval_dataset=tokenized_wnut["test"],
processing_class=tokenizer,
data_collator=data_collator,
compute_metrics=compute_metrics,
)
trainer.train()
完成训练后,你可以通过使用push_to_hub()方法,把你的模型上传到 Hub。这样一来,任何人都能轻松地访问和使用你的模型。
trainer.push_to_hub()
from transformers import create_optimizer
batch_size = 16
num_train_epochs = 3
num_train_steps = (len(tokenized_wnut["train"]) // batch_size) * num_train_epochs
optimizer, lr_schedule = create_optimizer(
init_lr=2e-5,
num_train_steps=num_train_steps,
weight_decay_rate=0.01,
num_warmup_steps=0,
)
接下来,你可以利用TFAutoModelForTokenClassification来加载DistilBERT,同时指定预期标签的数量和标签映射:
from transformers import TFAutoModelForTokenClassification
model = TFAutoModelForTokenClassification.from_pretrained(
"distilbert/distilbert-base-uncased", num_labels=13, id2label=id2label, label2id=label2id
)
使用prepare_tf_dataset()将数据集转换为tf.data.Dataset格式:
tf_train_set = model.prepare_tf_dataset(
tokenized_wnut["train"],
shuffle=True,
batch_size=16,
collate_fn=data_collator,
)
tf_validation_set = model.prepare_tf_dataset(
tokenized_wnut["validation"],
shuffle=False,
batch_size=16,
collate_fn=data_collator,
)
通过compile方法来配置模型,准备进行训练。需要注意的是,Transformers模型已经内置了与任务相关的默认损失函数,所以通常情况下你不需要手动指定损失函数,除非你有特别的需要:
import tensorflow as tf
model.compile(optimizer=optimizer) # No loss argument!
在开始训练之前要做的最后两件事是从预测中计算seqeval分数,并提供一种将模型推送到Hub的方法。两者都是通过使用Keras回调来完成的。
将compute_metrics函数传递给KerasMetricCallback:
from transformers.keras_callbacks import KerasMetricCallback
metric_callback = KerasMetricCallback(metric_fn=compute_metrics, eval_dataset=tf_validation_set)
在PushToHubCallback中,指定你想要将模型和标记器上传到的位置:
from transformers.keras_callbacks import PushToHubCallback
push_to_hub_callback = PushToHubCallback(
output_dir="my_awesome_wnut_model",
tokenizer=tokenizer,
)
接下来,把你的回调函数组合在一起:
callbacks = [metric_callback, push_to_hub_callback]
最后一步,你已经准备好开始训练模型了!使用fit方法,传入你的训练和验证数据集、训练轮次(epoch)的数量以及之前设置的回调函数,来对模型进行微调:
model.fit(x=tf_train_set, validation_data=tf_validation_set, epochs=3, callbacks=callbacks)
训练完成后,你的模型会自动上传到Hub,这样大家都能方便地使用它了!
text = "The Golden State Warriors are an American professional basketball team based in San Francisco."
最简单的方法是使用pipeline()来进行推理。你可以用模型实例化一个NER(命名实体识别)的管道,然后把文本传给它:
from transformers import pipeline
classifier = pipeline("ner", model="stevhliu/my_awesome_wnut_model")
classifier(text)
如果你需要的话,还可以手动获取pipeline的输出结果:
PyTorch¶
对文本进行标记,并返回一个PyTorch张量:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("stevhliu/my_awesome_wnut_model")
inputs = tokenizer(text, return_tensors="pt")
将输入数据传递给模型,并获取输出的logits:
from transformers import AutoModelForTokenClassification
model = AutoModelForTokenClassification.from_pretrained("stevhliu/my_awesome_wnut_model")
with torch.no_grad():
logits = model(**inputs).logits
找出概率最高的类别,然后利用模型的id2label映射将其转换成对应的文本标签:
predictions = torch.argmax(logits, dim=2)
predicted_token_class = [model.config.id2label[t.item()] for t in predictions[0]]
predicted_token_class
TensorFlow¶
对文本进行标记化处理,并返回一个TensorFlow张量:
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("stevhliu/my_awesome_wnut_model")
inputs = tokenizer(text, return_tensors="tf")
将输入数据传递给模型,并获取输出的logits:
from transformers import TFAutoModelForTokenClassification
model = TFAutoModelForTokenClassification.from_pretrained("stevhliu/my_awesome_wnut_model")
logits = model(**inputs).logits
找出概率最高的类别,然后利用模型的id2label映射将其转换成对应的文本标签:
predicted_token_class_ids = tf.math.argmax(logits, axis=-1)
predicted_token_class = [model.config.id2label[t] for t in predicted_token_class_ids[0].numpy().tolist()]
predicted_token_class