实体识别是自然语言处理(NLP)领域中的一项重要任务,它用于检测文本中的实体,以便在后续任务中使用。由于某些文本/词汇对于特定上下文比其他文本/词汇更具信息量和重要性,实体识别有时也被称为信息检索。通过实体识别,可以提取一般文本中的人员、地点、组织等信息,也可以针对特定领域提取临床术语、药物、疾病等信息,以提高医疗记录的诊断准确性。
要进行实体识别,需要具备以下先决条件:熟练掌握Python语言和使用PyTorch训练神经网络的知识;了解Transformers和BERT架构;熟悉Transformers中的双向编码器表示(BERT)。BERT是一个通用的语言预训练模型,它在大型数据集上进行预训练,可以微调后用于情感分析、问答系统、命名实体识别等不同任务。BERT是NLP领域中迁移学习的最先进方法。
为了详细了解BERT架构,可以参考以下链接:。此外,如果想要了解如何使用Transformers进行情感分析,可以查看前一篇博客。
在实现实体识别任务时,首先需要在Google Colab中启用GPU支持,然后安装必要的库以使用HuggingFace的transformer。这些库包括datasets用于获取数据,tokenizers用于预处理数据,transformers用于微调模型,seqeval用于计算模型指标。将使用HuggingFace datasets模块中的英语NER数据集,该数据集遵循BIO(开始、内部、外部)格式,用于标记句子中的命名实体识别任务。数据集包含训练集、验证集和测试集,包含tokens、ner_tags、langs和spans。ner_tags有与BIO格式对应的ID,I-TYPE表示单词是TYPE类型的短语内部的一部分。如果两个相同类型的短语紧挨着,第二个短语的第一个单词将有B-TYPE标签,表示它开始了一个新的短语。带有O标签的单词不属于任何短语。总共有四个类别:Person(PER)、Organization(ORG)、Location(LOC)和其他(O)。训练集有20000个样本,验证集和测试集各有10000个样本。
数据需要被处理成transformers模型所需的格式。BERT期望输入格式为input_ids、token_type_ids和attention_mask。标签也需要根据BERT使用的子词分词进行调整。将使用distilbert-base-uncased预训练模型的tokenizer来处理tokens。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased")
为什么需要根据分词输出调整标签?以下是一个示例,其中id 2和5由于子词分词而重复了两次,因此需要为子词重复标签。将为特殊标记设置标签为-100,因为它们的word id为None,这将在损失函数中自动忽略。
def tokenize_adjust_labels(samples):
tokenized_samples = tokenizer.batch_encode_plus(samples["tokens"], is_split_into_words=True, truncation=True)
total_adjusted_labels = []
for k in range(0, len(tokenized_samples["input_ids"])):
prev_wid = -1
word_ids_list = tokenized_samples.word_ids(batch_index=k)
existing_label_ids = samples["ner_tags"][k]
i = -1
adjusted_label_ids = []
for word_idx in word_ids_list:
if(word_idx is None):
adjusted_label_ids.append(-100)
elif(word_idx != prev_wid):
i = i + 1
adjusted_label_ids.append(existing_label_ids[i])
prev_wid = word_idx
else:
label_name = label_names[existing_label_ids[i]]
adjusted_label_ids.append(existing_label_ids[i])
total_adjusted_labels.append(adjusted_label_ids)
tokenized_samples["labels"] = total_adjusted_labels
return tokenized_samples
将使用map函数将tokenize_adjust_labels应用于整个数据集,并使用remove_columns参数移除不需要的列。现在,让看看tokenized_dataset包含的内容。由于不同的样本长度不同,因此需要对tokens进行填充以使它们具有相同的长度。为此,将使用DataCollatorForTokenClassification,它将对输入和标签进行填充。
from transformers import DataCollatorForTokenClassification
data_collator = DataCollatorForTokenClassification(tokenizer)
将使用Distillbert-base-uncased模型进行微调,并指定数据集中存在的标签数量。
model = AutoModelForTokenClassification.from_pretrained("distilbert-base-uncased", num_labels=len(label_names))
需要定义一个函数来处理模型预测并计算所需的指标。将使用seqeval指标,这是用于标记分类的常用指标。
import numpy as np
from datasets import load_metric
metric = load_metric("seqeval")
def compute_metrics(p):
predictions, labels = p
predictions = np.argmax(predictions, axis=2)
true_predictions = [[label_names[p] for (p, l) in zip(prediction, label) if l != -100] for prediction, label in zip(predictions, labels)]
true_labels = [[label_names[l] for (p, l) in zip(prediction, label) if l != -100] for prediction, label in zip(predictions, labels)]
results = metric.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函数的输入是包含模型预测和相应标签的命名元组。将为每个标记选择最大logit,并忽略特殊标记(-100)作为在预处理部分设置的标签。最后,将返回包含精确度、召回率、F1分数和准确度指标的字典。
现在,数据处理和指标函数已经准备好了,可以使用Trainer API对模型进行微调。
from transformers import TrainingArguments, Trainer
batch_size = 16
logging_steps = len(tokenized_dataset['train']) // batch_size
epochs = 2
training_args = TrainingArguments(
output_dir="results",
num_train_epochs=epochs,
per_device_train_batch_size=batch_size,
per_device_eval_batch_size=batch_size,
evaluation_strategy="epoch",
disable_tqdm=False,
logging_steps=logging_steps
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset["train"],
eval_dataset=tokenized_dataset["validation"],
data_collator=data_collator,
tokenizer=tokenizer,
compute_metrics=compute_metrics
)
trainer.train()
predictions, labels, _ = trainer.predict(tokenized_dataset["test"])
predictions = np.argmax(predictions, axis=2)
true_predictions = [[label_names[p] for (p, l) in zip(prediction, label) if l != -100] for prediction, label in zip(predictions, labels)]
true_labels = [[label_names[l] for (p, l) in zip(prediction, label) if l != -100] for prediction, label in zip(predictions, labels)]
results = metric.compute(predictions=true_predictions, references=true_labels)
results