Transformer实战(19)——微调Transformer语言模型进行词元分类
Transformer实战(19)——微调Transformer语言模型进行词元分类
- 0. 前言
- 1. 词元分类
- 2. 命名实体识别
- 3. 词性标注
- 4. 问答任务
- 5. 微调语言模型进行命名实体识别
- 5.1 数据加载与处理
- 5.2 模型微调
- 小结
- 系列链接
0. 前言
在本节中,我们将学习如何对语言模型进行微调,以适应词元分类任务。我们将探索命名实体识别 (Named Entity Recognition
, NER
)、词性标注 (Part-of-Speech
, POS
) 和问答 (Question Answering
, QA
) 等任务,并了解如何针对这些任务微调特定的语言模型。我们将重点介绍 BERT
模型,学习如何微调 BERT
应用于 POS
、NER
和 QA
任务,并熟悉这些任务的实现细节,包括所用数据集及执行过程。通过本节的学习,将能够使用 transformers
库执行各种词元分类任务。
1. 词元分类
词元分类 (Token Classification
) 任务是对词元序列中的每个词元 (token
) 进行分类的任务,任务要求模型将每个词元分类到一个特定的类别,典型的词元分类任务包括词性标注 (Part-of-Speech
, POS
) 和命名实体识别 (Named Entity Recognition
, NER
),问答 (Question Answering
, QA
) 也是属于词元分类的重要自然语言处理 (Natural Language Processing
, NLP
) 任务。需要注意的是,NER
、POS
和 QA
等任务可以从两个角度来看待:一是将现有文本的上下文分类为相应类别,二是将其视为文本生成任务。
词元分类方法将文本作为输入,并将每个相应的词元分类为某个类别。例如,以句子 “I went to Berlin.
” 为例,使用基于词元分类的 NER
,输出结果如下:
I = O
Went = O
to = O
Berlin = Location
而生成式模型的解决方法则有所不同;它们接受文本输入,并以文本形式生成结果。例如,基于 T5 的 NER
模型使用以下格式:
I went to [ Berlin | location ]
如果需要提取相应的实体,则需要额外的后处理步骤。同样,QA
任务也是如此,在生成式 QA
模型中,答案并不总是完全从上下文中提取的,模型还会加入对问题的狭义理解和重新表述来生成答案。
生成式 QA
在不仅需要从文本中提取答案,还需要额外的知识和背景信息时非常有用。但使用生成式 QA
时,我们无法保证生成的答案始终是正确的,因为生成的词语可能与原始答案有所不同。
接下来,我们将重点介绍基于词元分类的方法,这些方法仅根据任务需求对输入的文本进行标注。例如,问答 (Question Answering, QA) 仅指使用词元分类的开卷问答 (open-book QA
)。同样,命名实体识别 (Named Entity Recognition
, NER
) 和词性标注 (Part-of-Speech
, POS
) 也属于这一范畴。
2. 命名实体识别
命名实体识别 (Named Entity Recognition
, NER
) 是一个典型的词元分类任务,它的目标是识别每个词元是否为实体,并识别每个检测到的实体类型。例如,一段文本中可以同时包含多个实体——人名、地点名、组织名等多种类型的实体。以下文本是一个典型的 NER
示例:
Li Bai is a poet from China.
Li Bai
是一个人名,而 China
是一个地点名。序列标注模型需要为每个词元打上标签,标签中包含实体的相关信息。标准的 NER
任务通常使用 BIO
标签。下表列出了 NER
任务中常用的标签及其描述:
标签 | 描述 |
---|---|
O | 非实体 |
B-PER | 人名实体开始的部分 |
I-PER | 人名实体内部的部分 |
B-LOC | 地名实体开始的部分 |
I-LOC | 地名实体内部的部分 |
B-ORG | 组织机构名称实体开始的部分 |
I-ORG | 组织机构名称实体内部的部分 |
B-MISC | 杂项类实体开始的部分 |
I-MISC | 杂项类实体内部的部分 |
在上表中,B
表示标签的开始,I
表示标签内部,而 O
则表示实体之外的部分。这就是这种标注方式被称为 BIO
标注的原因。例如,句子 ”Li Bai is a poet from China.
” 可以使用 BIO
进行标注如下:
[B-PER|Li] [I-PER|Bai] [O|is] [O|a] [O|poet] [O|from] [B-LOC|China] [O|.]
相应地,序列必须以 BIO
格式进行标注。像 CoNLL-2003 这样的数据集通常采用以下格式:
除了 NER
标签外,这个数据集还提供了词性标注 (Part-of-Speech
, POS
) 标签。
3. 词性标注
词性标注 (Part-of-Speech
, POS
) ,也称语法标注,是根据词语在文本中的语法角色来标注其词性。举个简单的例子,在一个给定的文本中,识别每个单词属于名词、形容词、副词或动词就是词性标注。然而,从语言学的角度来看,除了这四个类外,还有许多其他的语法角色。
在词性标注中,存在不同的标注方法,但 Penn Treebank
词性标注集是最经典的方法之一。下表展示了语法角色的总结和相应的描述:
词性标注任务的数据集通常是如上图中展示的标注方法所示。这些标签的标注在特定的自然语言处理 (Natural Language Processing
, NLP
) 任务应用中非常有用,是许多其他自然语言处理方法的基础之一,Transformers
可以通过其复杂的架构理解单词之间的关系。
4. 问答任务
问答 (Question Answering
, QA
) 或阅读理解任务由一组阅读理解文本及其对应的问题组成。一个典型的数据集是斯坦福问答数据集 (Stanford Question Answering Dataset
, SQuAD
),该数据集包含了维基百科文本以及针对这些文本提出的问题,答案则是原始维基百科文本中的片段。下图展示了该数据集中的一个示例:
图中用红色高亮的部分是答案,每个问题的关键部分则用蓝色高亮显示。对于一个优秀的 NLP
模型来说,需要根据问题对文本进行分段,而这种分段可以通过序列标注的方式来实现。模型标注出答案片段的起始和结束部分。
我们已经了解了 NLP
序列标注任务的基础知识,包括词性标注 (Part-of-Speech
, POS
)、命名实体识别 (Named Entity Recognition
, NER
) 和问答 (Question Answering
, QA
) 。接下来,将学习如何针对这些特定任务微调 BERT
,并使用 datasets
库中的相关数据集。
5. 微调语言模型进行命名实体识别
在本节中,我们将学习如何针对命名实体识别 (Named Entity Recognition
, NER
) 任务微调 BERT
模型。
5.1 数据加载与处理
(1) 首先需要从 datasets
库加载 CoNLL-2003
数据集。访问数据集链接可以获取该数据集的相关信息:
从上图中可以看到关于数据集的描述,例如数据集的大小和特点,接下来,我们深入了解该数据集。
(2) 加载数据集:
from datasets import load_dataset
conll2003 = load_dataset("conll2003")
(3) 检查数据集,查看训练样本:
conll2003["train"][0]
输出结果如下所示,可以看到 POS
和 NER
的相应标签:
(4) 在本节中,我们仅使用 NER
标签。使用以下方法获取数据集中可用的 NER
标签:
conll2003["train"].features["ner_tags"]
输出结果如下示,可以看到所有的 BIO
标签,共有九个标签:
Sequence(feature=ClassLabel(names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'], id=None), length=-1, id=None)
(5) 加载 BERT
分词器:
from transformers import BertTokenizerFast
tokenizer = BertTokenizerFast.from_pretrained("bert-base-uncased")
tokenizer
类也可以处理以空格为分隔符的句子。我们需要让分词器能够处理以空格分词的句子,因为 NER
任务中每个词元 (token
) 都有一个基于词元的标签。在 NER
任务中,词元通常是以空格分词的单词,而不是通过字节对编码 (Byte Pair Encoding, BPE) 或其他分词器生成的词元。将分词器用于以空格分词的句子,只需将 is_split_into_words
设置为 True
:
tokenizer(["Oh","this","sentence","is","tokenized", "and", "splitted","by","spaces"], is_split_into_words=True)
(6) 在将数据集用于训练之前,需要对数据进行预处理。为此,定义函数 tokenize_and_align_labels()
,并将其应用于整个数据集:
def tokenize_and_align_labels(examples, label_all_tokens=True): tokenized_inputs = tokenizer(examples["tokens"], truncation=True, is_split_into_words=True) labels = [] for i, label in enumerate(examples["ner_tags"]): word_ids = tokenized_inputs.word_ids(batch_index=i) previous_word_idx = None label_ids = [] for word_idx in word_ids: if word_idx is None: label_ids.append(-100) elif word_idx != previous_word_idx: label_ids.append(label[word_idx]) else: label_ids.append(label[word_idx] if label_all_tokens else -100) previous_word_idx = word_idx labels.append(label_ids) tokenized_inputs["labels"] = labels return tokenized_inputs
(7) tokenize_and_align_labels()
函数用于确保词元和标签对齐。之所以需要对齐,是因为词元是以片段形式进行切分的,但单词必须是完整的。为了测试函数效果,通过输入单个样本观察其工作原理:
q = tokenize_and_align_labels(conll2003['train'][4:5])
print(q)
结果如下所示:
但是以上结果可读性较差,可以通过以下方式将其转换为人类可读的形式:
for token, label in zip(tokenizer.convert_ids_to_tokens(q["input_ids"][0]),q["labels"][0]): print(f"{token:_<40} {label}")
结果如下所示:
(8) 将 tokenize_and_align_labels
函数应用于数据集,可以通过使用 datasets
库的 map
函数完成:
tokenized_datasets = conll2003.map(tokenize_and_align_labels, batched=True)
5.2 模型微调
(1) 加载具有相应标签数量的 BERT
模型:
from transformers import AutoModelForTokenClassification
model = AutoModelForTokenClassification.from_pretrained("bert-base-uncased", num_labels=9)
(2) 准备 Trainer
和训练参数:
from transformers import TrainingArguments, Trainer
args = TrainingArguments( "test-ner",evaluation_strategy = "epoch", learning_rate=2e-5, per_device_train_batch_size=16, per_device_eval_batch_size=16, num_train_epochs=3, weight_decay=0.01,
)
(3) 定义数据合并器 (collator
),在训练数据集上应用批操作,以节省内存并加速性能:
from transformers import DataCollatorForTokenClassification
data_collator = DataCollatorForTokenClassification(tokenizer)
(4) 为了评估模型的性能,Hugging Face
的 datasets
库为许多任务提供了多种评估指标。对于 NER
任务,我们将使用序列评估指标。seqeval
是一个用于评估序列标注算法和模型的优秀 Python
框架。使用 pip
命令安装 seqeval
库:
$ pip install seqeval
安装完成后,加载评估指标:
import evaluate
metric = evaluate.load("seqeval")
查看指标的用法:
example = conll2003['train'][0]
label_list = conll2003["train"].features["ner_tags"].feature.names
labels = [label_list[i] for i in example["ner_tags"]]
metric.compute(predictions=[labels], references=[labels])
输出如下所示:
(5) 定义函数 compute_metrics()
计算指标,如准确率、F1 分数
、精度和召回率:
import numpy as np
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 = 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"], }
(6) 创建 trainer
并进行训练:
trainer = Trainer( model, args, train_dataset=tokenized_datasets["train"], eval_dataset=tokenized_datasets["validation"], data_collator=data_collator, tokenizer=tokenizer, compute_metrics=compute_metrics
)
(7) 运行 trainer
的 train
函数,结果如下所示:
trainer.train()
(8) 训练完成后,保存模型和分词器:
model.save_pretrained("ner_model")
tokenizer.save_pretrained("tokenizer")
(8) 如果希望模型与管道 (pipeline
) 结合使用,必须读取配置文件,并根据 label_list
对象中使用的标签正确分配 label2id
和 id2label
:
id2label = {str(i): label for i,label in enumerate(label_list)
}
label2id = {label: str(i) for i,label in enumerate(label_list)
}
import json
config = json.load(open("ner_model/config.json"))
config["id2label"] = id2label
config["label2id"] = label2id
json.dump(config, open("ner_model/config.json","w"))
之后,按照以下方式使用模型:
from transformers import pipeline
mmodel = AutoModelForTokenClassification.from_pretrained("ner_model")
nlp = pipeline("ner", model=mmodel, tokenizer=tokenizer)
example = "I live in Beijing"
ner_results = nlp(example)
print(ner_results)
结果如下所示:
[{'entity': 'B-LOC', 'score': 0.9967907, 'index': 4, 'word': 'beijing', 'start': 10, 'end': 17}]
在特定数据集、领域,甚至语言上的微调可以显著提升模型性能。然而,这种微调也有其优缺点;数据集需要足够广泛,以覆盖我们实际使用的领域。例如,用金融领域数据微调的模型,在医学领域的表现可能不佳。
小结
在本节中,我们介绍了如何对预训练模型进行微调,以进行词元分类任务。我们介绍了如何在命名实体识别 (Named Entity Recognition
, NER
) 任务上微调模型,并通过示例详细介绍了如何使用预训练和微调模型通过管道 (pipelines
) 处理特定任务,还学习了 NER
任务的预处理步骤,同时介绍了如何保存针对特定任务微调的预训练模型。
系列链接
Transformer实战(1)——词嵌入技术详解
Transformer实战(2)——循环神经网络详解
Transformer实战(3)——从词袋模型到Transformer:NLP技术演进
Transformer实战(4)——从零开始构建Transformer
Transformer实战(5)——Hugging Face环境配置与应用详解
Transformer实战(6)——Transformer模型性能评估
Transformer实战(7)——datasets库核心功能解析
Transformer实战(8)——BERT模型详解与实现
Transformer实战(9)——Transformer分词算法详解
Transformer实战(10)——生成式语言模型 (Generative Language Model, GLM)
Transformer实战(11)——从零开始构建GPT模型
Transformer实战(12)——基于Transformer的文本到文本模型
Transformer实战(13)——从零开始训练GPT-2语言模型
Transformer实战(14)——微调Transformer语言模型用于文本分类
Transformer实战(15)——使用PyTorch微调Transformer语言模型
Transformer实战(16)——微调Transformer语言模型用于多类别文本分类
Transformer实战(17)——微调Transformer语言模型进行多标签文本分类
Transformer实战(18)——微调Transformer语言模型进行回归分析