基于transformers框架实践Bert系列2--命名实体识别

本系列用于Bert模型实践实际场景,分别包括分类器、命名实体识别、选择题、文本摘要等等。(关于Bert的结构和详细这里就不做讲解,但了解Bert的基本结构是做实践的基础,因此看本系列之前,最好了解一下transformers和Bert等)
本篇主要讲解命名实体识别应用场景。本系列代码和数据集都上传到GitHub上:https://github.com/forever1986/bert_task

目录

  • 1 环境说明
  • 2 前期准备
    • 2.1 了解Bert的输入输出
    • 2.2 数据集与模型
    • 2.3 任务说明
    • 2.4 实现关键
  • 3 关键代码
    • 3.1 数据集处理
    • 3.2 模型加载
    • 3.3 评估函数
    • 3.4 设置TrainingArguments
    • 3.5 设置DataCollatorForTokenClassification
  • 4 整体代码
  • 5 运行效果

1 环境说明

1)本次实践的框架采用torch-2.1+transformer-4.37
2)另外还采用或依赖其它一些库,如:evaluate、pandas、datasets、accelerate、seqeval等

2 前期准备

Bert模型是一个只包含transformer的encoder部分,并采用双向上下文和预测下一句训练而成的预训练模型。可以基于该模型做很多下游任务。

2.1 了解Bert的输入输出

Bert的输入:input_ids(使用tokenizer将句子向量化),attention_mask,token_type_ids(句子序号)、labels(结果)
Bert的输出:
last_hidden_state:最后一层encoder的输出;大小是(batch_size, sequence_length, hidden_size)(注意:这是关键输出,本次任务就需要获取该值,并进行一次线性层处理
pooler_output:这是序列的第一个token(classification token)的最后一层的隐藏状态,输出的大小是(batch_size, hidden_size),它是由线性层和Tanh激活函数进一步处理的。(通常用于句子分类,至于是使用这个表示,还是使用整个输入序列的隐藏状态序列的平均化或池化,视情况而定)。
hidden_states: 这是输出的一个可选项,如果输出,需要指定config.output_hidden_states=True,它也是一个元组,它的第一个元素是embedding,其余元素是各层的输出,每个元素的形状是(batch_size, sequence_length, hidden_size)
attentions:这是输出的一个可选项,如果输出,需要指定config.output_attentions=True,它也是一个元组,它的元素是每一层的注意力权重,用于计算self-attention heads的加权平均值。

2.2 数据集与模型

1)数据集:weibo_senti_100k(微博公开数据集),这里只是演示,使用其中2400条数据
2)模型:bert-base-chinese
注意:本次练习都是采用本地数据集和本地权重模型,不直接从hf下载,因为速度过慢

2.3 任务说明

1)什么是命名实体识别
命名实体识别其实就是对输出结果的每个token做出标签(标注属于哪个实体),而实体类型包括地缘政治实体(GPE.NAM)、地名(LOC.NAM)、机构名(ORG.NAM)、人名(PER.NAM)及其对应的代指(以NOM为结尾)
2)NLP的序列标注
NLP的序列标注有很多种方式:IOB、IBO2、BIOES、IOE1等等,有兴趣可以自行了解。这里简单说一下IBO2,这个案例使用到的。就是通过3类标签(B-begin(实体开始),I-inside(实体中间),O-outside(实体之外)),下表就是本次weibo_ner数据集的基本标签

标签含义标签含义
B-PER.NAM名字(张三)开始标签I-PER.NAM名字(张三)中间标签
B-PER.NOM代称、类别名(穷人)开始标签I-PER.NOM代称、类别名(穷人)中间标签
B-LOC.NAM特指名称(紫玉山庄)开始标签I-LOC.NAM特指名称(紫玉山庄)中间标签
B-LOC.NOM泛称(大峡谷、宾馆)开始标签I-LOC.NOM泛称(大峡谷、宾馆)中间标签
B-GPE.NAM行政区的名称(北京)开始标签I-GPE.NAM行政区的名称(北京)中间标签
B-GPE.NOM泛指行政区的名称(国家)开始标签I-GPE.NOM泛指行政区的名称(国家)中间标签
B-ORG.NAM特定机构名称(通惠医院)开始标签I-ORG.NAM特定机构名称(通惠医院)中间标签
B-ORG.NOM泛指名称、统称(文艺公司)开始标签I-ORG.NOM泛指名称、统称(文艺公司)中间标签
O非实体标签

3)命名实体识别任务就是给每个token都标注最终属于哪个标签含义

2.4 实现关键

其实就是将bert输出的每个token预测为对应到上表中每个label的值,所以是每个token,因此需要使用last_hidden_state输出结果,将输出结果在接入到线性层(输入是hidden_size, 输出是num_labels的线性层),最终将输出做argmax可以得出每个token的标签。loss计算使用交叉熵。

3 关键代码

3.1 数据集处理

weibo_ner原始数据是如下
人 O
生 O
如 O
戏 O
, O
导 B-PER.NOM
演 I-PER.NOM
是 O
自 O
己 O
蜡 O
烛 O
经过我们处理,将其保存为如下:

{'id': '0', 'tokens': ['科', '技', '全', '方', '位', '资', '讯', '智', '能', ',', '快', '捷', '的', '汽', '车', '生', '活', '需', '要', '有', '三', '屏', '一', '云', '爱', '你'], 'ner_tags': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]}

其中tokens是每个中文字,ner_tags为标签label。这是为了方便tokenizer进行转换。数据tokenizer后转换的函数如下:

# 关键点2处
# 1.需要增加is_split_into_words=True,因为我们的数据tokens是每个字分开的,需要将其连成一个list,比如tokenizer之后都是分开的数组
# 2.labels和input_ids匹配,因为每个中文字tokenizer之后可能对应成多个token,我们需要知道每个token对于哪个字,这时候就需要使用word_ids属性来判断,这样就能对齐label_ids与input_ids
# 3.使用word_ids就必须使用BertTokenizerFast,原先BertTokenizer返回值是没有word_ids的
def process_function(datas):
    tokenized_datas = tokenizer(datas["tokens"], max_length=256, truncation=True, is_split_into_words=True)
    labels = []
    for i, label in enumerate(datas["ner_tags"]):
        word_ids = tokenized_datas.word_ids(batch_index=i)
        label_ids = []
        for word_id in word_ids:
            if word_id is None:
                label_ids.append(-100)
            else:
                label_ids.append(label[word_id])
        labels.append(label_ids)
    tokenized_datas["labels"] = labels
    return tokenized_datas

3.2 模型加载

model = BertForTokenClassification.from_pretrained(model_path, num_labels=len(label_list))

注意:这里使用的是BertForTokenClassification,因为我们在2.4实现关键中说了,我们可以看看BertForTokenClassification实现关键代码是否满足我们的要求:

# 在__init__方法中增加dropout和分类线性层
self.bert = BertModel(config, add_pooling_layer=False)
classifier_dropout = (
    config.classifier_dropout if config.classifier_dropout is not None else config.hidden_dropout_prob
)
self.dropout = nn.Dropout(classifier_dropout)
self.classifier = nn.Linear(config.hidden_size, config.num_labels)
# 在forward方法中,将bert输出outputs中的第一个值(也就是前面讲到的last_hidden_state),进行dropout处理并关联线性层
outputs = self.bert(
            input_ids,
            attention_mask=attention_mask,
            token_type_ids=token_type_ids,
            position_ids=position_ids,
            head_mask=head_mask,
            inputs_embeds=inputs_embeds,
            output_attentions=output_attentions,
            output_hidden_states=output_hidden_states,
            return_dict=return_dict,
        )

        sequence_output = outputs[0]

        sequence_output = self.dropout(sequence_output)
        logits = self.classifier(sequence_output)

3.3 评估函数

本次评估函数使用f1,主要考虑到命名实体识别其实就是判断其边界是否正确;实体的类型是否标注正确。对于f1能综合考虑它们的加权调和平均值。使用seqeval库来计算f1。

# 评估函数:此处的评估函数可以从https://github.com/huggingface/evaluate下载到本地
seqeval = evaluate.load("./evaluate/seqeval_metric.py")
def evaluate_function(preprediction):
    predictions, labels = preprediction
    predictions = numpy.argmax(predictions, axis=-1)

    # 将id转换为原始的字符串类型的标签
    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)
    ]
    result = seqeval.compute(predictions=true_predictions, references=true_labels, mode="strict", scheme="IOB2")
    return {
        "f1": result["overall_f1"]
    }

3.4 设置TrainingArguments

这里相对于情感分类的应用场景中没有设置学习率和权重衰减,好像效果更好,大家也可以自己尝试一下,可能是数据集太少的原因,训练准确率不高。

# step 5 创建TrainingArguments
# train是1350条数据,batch_size=32,因此每个epoch的step=43,总step=129,
train_args = TrainingArguments(output_dir="./checkpoints",      # 输出文件夹
                               per_device_train_batch_size=32,  # 训练时的batch_size
                               per_device_eval_batch_size=32,    # 验证时的batch_size
                               num_train_epochs=3,              # 训练轮数
                               logging_steps=20,                # log 打印的频率
                               evaluation_strategy="epoch",     # 评估策略
                               save_strategy="epoch",           # 保存策略
                               save_total_limit=3,              # 最大保存数
                               metric_for_best_model="f1",      # 设定评估指标
                               load_best_model_at_end=True      # 训练完成后加载最优模型
                               )

3.5 设置DataCollatorForTokenClassification

注意本次实验没有使用DataCollatorWithPadding,而是使用DataCollatorForTokenClassification。因为ner任务需要对标签label进行补齐,而DataCollatorWithPadding不对标签label补齐。而在情感分类中,我们的label其实只取第一个token(pooler_output)都是一个值0或1,所以不需要补齐。

4 整体代码

"""
基于BERT做命名实体识别
1)数据集来自:weibo_ner
2)模型权重使用:bert-base-chinese
"""
# step 1 引入数据库
import numpy
import evaluate
from datasets import DatasetDict
from transformers import BertForTokenClassification, TrainingArguments, Trainer, DataCollatorForTokenClassification, \
     pipeline, BertTokenizerFast


model_path = "./model/tiansz/bert-base-chinese"
data_path = "data/weibo_ner"


# step 2 数据集处理
datasets = DatasetDict.load_from_disk(data_path)
# 保存label真实描述,用于显示正确结果和传入模型初始化告诉模型分类数量
label_list = ['B-GPE.NAM', 'B-GPE.NOM', 'B-LOC.NAM', 'B-LOC.NOM', 'B-ORG.NAM', 'B-ORG.NOM', 'B-PER.NAM', 'B-PER.NOM',
              'I-GPE.NAM', 'I-GPE.NOM', 'I-LOC.NAM', 'I-LOC.NOM', 'I-ORG.NAM', 'I-ORG.NOM', 'I-PER.NAM', 'I-PER.NOM',
              'O']
tokenizer = BertTokenizerFast.from_pretrained(model_path)


# 借助word_ids 实现标签映射
def process_function(datas):
    tokenized_datas = tokenizer(datas["tokens"], max_length=256, truncation=True, is_split_into_words=True)
    labels = []
    for i, label in enumerate(datas["ner_tags"]):
        word_ids = tokenized_datas.word_ids(batch_index=i)
        label_ids = []
        for word_id in word_ids:
            if word_id is None:
                label_ids.append(-100)
            else:
                label_ids.append(label[word_id])
        labels.append(label_ids)
    tokenized_datas["labels"] = labels
    return tokenized_datas


new_datasets = datasets.map(process_function, batched=True)

# step 3 加载模型
model = BertForTokenClassification.from_pretrained(model_path, num_labels=len(label_list))

# step 4 评估函数:此处的评估函数可以从https://github.com/huggingface/evaluate下载到本地
seqeval = evaluate.load("./evaluate/seqeval_metric.py")


def evaluate_function(prepredictions):
    predictions, labels = prepredictions
    predictions = numpy.argmax(predictions, axis=-1)
    # 将id转换为原始的字符串类型的标签
    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)
    ]
    result = seqeval.compute(predictions=true_predictions, references=true_labels, mode="strict", scheme="IOB2")
    return {
        "f1": result["overall_f1"]
    }


# step 5 创建TrainingArguments
# train是1350条数据,batch_size=32,因此每个epoch的step=43,总step=129
train_args = TrainingArguments(output_dir="./checkpoints",      # 输出文件夹
                               per_device_train_batch_size=32,  # 训练时的batch_size
                               # gradient_checkpointing=True,     # *** 梯度检查点 ***
                               per_device_eval_batch_size=32,    # 验证时的batch_size
                               num_train_epochs=3,              # 训练轮数
                               logging_steps=20,                # log 打印的频率
                               evaluation_strategy="epoch",     # 评估策略
                               save_strategy="epoch",           # 保存策略
                               save_total_limit=3,              # 最大保存数
                               metric_for_best_model="f1",      # 设定评估指标
                               load_best_model_at_end=True      # 训练完成后加载最优模型
                               )

# step 6 创建Trainer
trainer = Trainer(model=model,
                  args=train_args,
                  train_dataset=new_datasets["train"],
                  eval_dataset=new_datasets["validation"],
                  data_collator=DataCollatorForTokenClassification(tokenizer=tokenizer),
                  compute_metrics=evaluate_function,
                  )

# step 7 训练
trainer.train()

# step 8 模型评估
evaluate_result = trainer.evaluate(new_datasets["test"])
print(evaluate_result)

# step 9:模型预测
ner_pipe = pipeline("token-classification", model=model, tokenizer=tokenizer, device=0, aggregation_strategy="simple")
res = ner_pipe("对,输给一个女人,的成绩。失望")
print(res)

5 运行效果

在这里插入图片描述

注:本文参考来自大神:https://github.com/zyds/transformers-code

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/638854.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

webSocket+Node+Js实现在线聊天(包含所有代码)

这篇文章主要介绍了如何使用 webSocket、Node 和 Js 实现在线聊天功能。 重要亮点 💻 技术选型:使用 Node.js 搭建服务器,利用 Express 框架和 Socket.io 库实现 WebSocket 通信。 📄 实现思路:通过建立数组存储聊天…

中国上市公司融资约束指数数据上市公司SA指数与WW指数(2000-2023年)

上市公司融资约束指数,是用来评估公司面临的融资限制程度的工具。SA指数由Hadlock和Pierce开发,基于公司规模和年龄计算,其中较小且较年轻的公司通常会有更高的指数值,表明其融资约束较大。另一方面,WW指数由Whited和W…

Linux .eh_frame section以及libunwind

文章目录 前言一、LSB二、The .eh_frame section2.1 简介2.2 The Common Information Entry Format2.1.1 Augmentation String Format 2.3 The Frame Description Entry Format 三、The .eh_frame_hdr section四、libunwind五、基于Frame Pointer和基于unwind 形式的栈回溯比较…

【计算机网络】初识Tcp协议

💻文章目录 📄前言Tcp基础概念Tcp 的报文格式三次握手四次挥手 Tcp的滑动窗口机制概念超时重传机制高速重传 TCP传输控制机制流量控制拥堵控制慢启动 Tcp的性能优化机制延迟应答捎带应答 📓总结 📄前言 TCP三次握手、四次挥手&…

【qt】QListWidget 组件

QListWidget 组件 一.QListWidget的用途二.界面设计三.QListWidget的添加1.界面添加2.代码添加 四.列表项的设置1.文本2.图标3.复选框4.列表大小 五.字体和图标的设置1.字体:2.图标: 六.设置显示模式1.图标2.列表 七.其他功能实现1.删除2.全选3.反选4.ad…

IO端口编址

统一编址 特点 独立编址 特点 内存地址分配 区别 应用 IO端口地址译码 硬件上的实现 示例1: 示例2: IO指令 软件上的实现 示例

Vue - JavaScript基础学习

一、语言概述 JavaScript 中的类型应该包括这些: 1.数字(默认双精度区别于Java) console.log(3 / 2); // 1.5,not 1 console.log(Math.floor(3 / 2)); // 10.1 0.2 0.30000000000000004NaN(Not a Number&#x…

为什么 buffer 越大传输效率越低

先看 从边际效益递减看 buffer 中挤占带宽 中的两个模型: E1 inflight_prop - inflight_buff: y 2 t x − b x a − x y2tx-\dfrac{bx}{a-x} y2tx−a−xbx​E2 bw / delay: y a x − x 2 b t a − t x y\dfrac{ax-x^2}{bta-tx} ybta−…

OpenMV学习笔记1——IDE安装与起步

目录 一、OpenMV IDE下载 二、OpenMV界面 三、Hello World! 四、将代码烧录到OpenMV实现脱机运行 五、插SD卡(为什么买的时候没送?) 一、OpenMV IDE下载 浏览器搜索OpenMV官网,进入后点击“立即下载”&#xff0…

深度学习基于Tensorflow卷积神经网络VGG16的CT影像识别分类

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 随着医疗技术的快速发展,CT(Computed Tomography)影像已成为医生…

面试准备【面试准备】

面试准备【面试准备】 前言面试准备自我介绍:项目介绍: 论坛项目功能总结数据库表设计注册功能登录功能显示登录信息功能发布帖子评论私信点赞功能关注功能通知搜索网站数据统计热帖排行缓存 论坛项目技术总结Http的无状态cookie和session的区别为什么要…

Linux-应用编程学习笔记(二、文件I/O、标准I/O)

一、文件I/O基础 文件 I/O 指的是对文件的输入/输出操作,就是对文件的读写操作。Linux 下一切皆文件。 1.1 文件描述符 在 open函数执行成功的情况下, 会返回一个非负整数, 该返回值就是一个文件描述符(file descriptor&#x…

Python3 笔记:sort() 和 sorted() 的区别

1、sort() 可以对列表中的元素进行排序,会改变原列表,之前的顺序不复存在。 list.sort(key, reverse None) key:默认值是None,可指定项目进行排序,此参数可省略。 reverse&#…

零基础PHP入门(一)选择IDE和配置环境

配置环境 官网下载安装包,windows https://windows.php.net/download#php-8.3 我是下载的最新版,也可以切换其他版本 https://windows.php.net/downloads/releases/archives/ 下载好压缩文件后,双击解压到一个目录 D:\soft\php 复制ph…

Vue 3 的 setup语法糖工作原理

前言 我们每天写vue3项目的时候都会使用setup语法糖,但是你有没有思考过下面几个问题。setup语法糖经过编译后是什么样子的?为什么在setup顶层定义的变量可以在template中可以直接使用?为什么import一个组件后就可以直接使用,无需…

匝间冲击耐压试验仪产品介绍及工作原理

产品简介 武汉凯迪正大KD2684S匝间冲击耐压试验仪适用于电机、变压器、电器线圈等这些由漆包线绕制的产品。因漆包线的绝缘涂敷层本身存在着质量问题,以及在绕线、嵌线、刮线、接头端部整形、绝缘浸漆、装配等工序工艺中不慎而引起绝缘层的损伤等,都会造…

零基础代码随想录【Day42】|| 1049. 最后一块石头的重量 II,494. 目标和,474.一和零

目录 DAY42 1049.最后一块石头的重量II 解题思路&代码 494.目标和 解题思路&代码 474.一和零 解题思路&代码 DAY42 1049.最后一块石头的重量II 力扣题目链接(opens new window) 题目难度:中等 有一堆石头,每块石头的重量都是正整…

Axure软件安装教程

链接:https://pan.baidu.com/s/1fHrSrZ7PIeDZZpn6QyJ6jQ?pwdb4mv 提取码:b4mv 安装完后点击Finish 名字随便起 关闭Axure 复制到安装目录下 最后成果

ASP+ACCESS基于WEB社区论坛设计与实现

摘要:系统主要实现BBS网站全部功能。采用目前应用最为广泛的ASP作为开发工具来开发此系统、以保证系统的稳定性。采用目前最为流行的网页制作工具Dreamweaver和目前最为流行的动画制作工具Flash MX。整个系统从符合操作简便、界面友好、灵活、实用、安全的要求出发&…

第七步 实现打印函数

文章目录 前言一、如何设计我们的打印函数?二、实践检验! 查看系列文章点这里: 操作系统真象还原 前言 现在接力棒意见交到内核手中啦,只不过我们的内核现在可谓是一穷二白啥都没有,为了让我们设计的内核能被看见被使用…