当前位置: 首页 > news >正文

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 应用于 POSNERQA 任务,并熟悉这些任务的实现细节,包括所用数据集及执行过程。通过本节的学习,将能够使用 transformers 库执行各种词元分类任务。

1. 词元分类

词元分类 (Token Classification) 任务是对词元序列中的每个词元 (token) 进行分类的任务,任务要求模型将每个词元分类到一个特定的类别,典型的词元分类任务包括词性标注 (Part-of-Speech, POS) 和命名实体识别 (Named Entity Recognition, NER),问答 (Question Answering, QA) 也是属于词元分类的重要自然语言处理 (Natural Language Processing, NLP) 任务。需要注意的是,NERPOSQA 等任务可以从两个角度来看待:一是将现有文本的上下文分类为相应类别,二是将其视为文本生成任务。
词元分类方法将文本作为输入,并将每个相应的词元分类为某个类别。例如,以句子 “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]

输出结果如下所示,可以看到 POSNER 的相应标签:

输出结果
(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 Facedatasets 库为许多任务提供了多种评估指标。对于 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) 运行 trainertrain 函数,结果如下所示:

trainer.train()

运行结果
(8) 训练完成后,保存模型和分词器:

model.save_pretrained("ner_model")
tokenizer.save_pretrained("tokenizer")

(8) 如果希望模型与管道 (pipeline) 结合使用,必须读取配置文件,并根据 label_list 对象中使用的标签正确分配 label2idid2label

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语言模型进行回归分析

http://www.dtcms.com/a/392515.html

相关文章:

  • ModelView【QT】
  • ES6 promise-try-catch-模块化开发
  • webrtc弱网-ProbeController类源码分析与算法原理
  • Pycharm远程同步Jetson Orin Super
  • 深入解析Tomcat类加载器:为何及如何打破Java双亲委派模型
  • 基于BP神经网络的PID控制器matlab参数整定和性能仿真
  • RabbitMQ死信队列与幂等性处理的性能优化实践指南
  • 基于python全国热门景点旅游管理系统的设计与实现
  • 鸿蒙Next ArkTS卡片生命周期:深入理解与管理实践
  • 荣耀手机(安卓)快速传数据换机到iPhone17 Pro
  • Linux的线程池
  • [bitcoin白皮书_1] 时间戳服务器 | 简化支付验证
  • OAuth 认证在电商 API 中的实现与安全
  • Linux 是什么?初学者速查表
  • openharmony之AV_CodeC音视频编解码模块驱动实现原理详解(三)
  • Llamaindex-Llama_indexRAG进阶_Embedding_model与ChromaDB-文档切分与重排序
  • 如何使用WordToCard自动拆分文章制作小红书卡片
  • RTX 4090重塑数字内容创作:4K视频剪辑与3D渲染的效率革命
  • Spring AI开发指导-MCP
  • C++/操作系统
  • 动手学深度学习(pytorch版):第八章节—循环神经网络(4)循环神经网络
  • Jenkins与Arbess,CICD工具一文全面对比分析
  • 矩阵、线性代数
  • react常用的hooks
  • 重构的艺术:从‘屎山’恐惧到优雅掌控的理性之旅
  • 在c++中,怎么理解把析构函数设置为virtual呢?
  • CUDA性能优化 ---- 通过矢量化内存访问提高性能
  • 【序列晋升】39 Spring Data REST 的优雅实践,让数据交互更符合 REST 规范
  • 能当关系型数据库还能玩对象特性,能拆复杂查询还能自动管库存,PostgreSQL 凭什么这么香?
  • 【2025PolarCTF秋季个人赛】WEB方向wp