自然语言处理:基于BERT预训练模型的中文命名实体识别(使用PyTorch)

命名实体识别(NER)

命名实体识别(Named Entity Recognition, NER)是自然语言处理(NLP)中的一个关键任务,其目标是从文本中识别出具有特定意义的实体,并将其分类到预定义的类别中。这些实体可以是人名、地名、组织机构名、日期时间、货币金额等。

  • 主要功能:
    • 实体识别:从文本中找出所有可能的命名实体。
    • 实体分类:将识别出来的实体归类到预先定义好的类别中,如人名、地名、组织名等。
    • 边界检测:确定每个实体在文本中的起始和结束位置。
  • 应用场景:
    • 信息检索:帮助搜索引擎理解查询意图,提供更精准的搜索结果。
    • 问答系统:辅助解析用户问题,提高答案的准确性。
    • 机器翻译:保留原文中的专有名词不被翻译,或根据上下文正确翻译。
    • 数据挖掘:从大量文本数据中提取有价值的信息,如市场分析、舆情监控等。
    • 个性化推荐:通过分析用户的兴趣点,提供个性化的服务和内容。

更多细节可以参考:命名实体识别综述。

本文目标

  • 从公开的新闻报道标题中提取地名,这里的地名主要是一些国家名称
  • 使用预训练的中文Bert模型,bert-base-chinese
  • 数据集的标注方式为BIO。

获取数据集

我们直接抓取漂亮国的中文发布网站的数据。
这里,我把数据存在PostgreSQL数据库里面,我建议大家安装一个数据库,非常方便数据抓取。

import time
import requests
import pandas as pd
from sqlalchemy import create_engine
from tqdm import tqdm
from bs4 import BeautifulSoupuser = 'postgres'
password = '你的密码'
db_name = '你的数据库名称'
db_url = f'postgresql://{user}:{password}@localhost:5432/{db_name}'
engine = create_engine(db_url)def get_title(url):res = requests.get(url, headers=headers)try:txt = res.content.decode('gbk')except UnicodeDecodeError as e:txt = res.content.decode('utf-8')soup = BeautifulSoup(txt, 'lxml')   data = []for li in soup.find_all('li', class_='collection-result'):try:href = li.find('a')['href']except:href = '无数据'try:title = li.find('a').text.replace('\n','').replace('\t','')except:title = '无数据'try:date = li.find('div').text.replace('\n','').replace('\t','')except:date = '无数据'data.append([href, title, date])return pd.DataFrame(data, columns=['href','title','date'])def get_news(url):res = requests.get(url, headers=headers)try:txt = res.content.decode('gbk')except UnicodeDecodeError as e:txt = res.content.decode('utf-8')soup = BeautifulSoup(txt, 'lxml')data = []for div in soup.find_all('div', class_='entry-content'):try:text = '\n'.join([p.get_text(strip=True) for p in div.find_all('p')[:-2]])except:text = '无数据'data.append({'href': url, 'text': text})return pd.DataFrame(data)headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0','cookie':'自己去网站找'
}# 这里是抓取对应标题和url
for i in range(53):  # 页数url = f'https://www.state.gov/translations/chinese/page/{i}/'df = get_title(url)print(f'正在抓取: {url}, 数据长度: {len(df)}')df.to_sql('mfa_usa', con=engine, if_exists='append', index=False)time.sleep(30)# 这里是抓取完整的报道
df = pd.read_sql('select * from mfa_usa', con=engine)
pbar = tqdm(list(df.href)[10:])
for url in pbar:pbar.set_description('Processing %s')df0 = get_news(url)df0.to_sql('mfa_usa_news', con=engine, if_exists='append', index=False)time.sleep(4)
  • 标题在这里插入图片描述
  • 全文
    在这里插入图片描述
  • 一共是500+的数据,差不多了,标注也挺麻烦的。

标注数据集

因为我的任务是提取地名,所以使用比较简单的BIO进行:

  • B-NP:开头
  • I-NP:中间
  • O:不是需要识别的词/字

这里推荐一个开源的NLP标注工具:MarkStudio。

第一步,转换数据格式

下载好之后,打开exe就可以导入自己的数据开始标注,但是数据必须以txt的形式导入,如下图所示。下面是简单的处理脚本

import pandas as pddf = pd.read_csv('data/data.csv')# 将每一行数据写入txt文件
txt_file = 'data/ner_label_in.txt'
with open(txt_file, 'w', encoding='utf-8') as f:for index, row in df.iterrows():f.write(row['text'] + '\n')  #
print(f"数据已成功写入 {txt_file} 文件!")

在这里插入图片描述

第二步,定义标签组

待标注数据准备好之后,我们打开标注工具,然后自定义标签(你也可以使用该工具自带的标签),如下图。
在这里插入图片描述

第三步,创建标注工程

回到工程管理,新建工程,然后导入待标注的txt文件,如下图。

  • 建工程
    在这里插入图片描述
  • 导数据
    在这里插入图片描述

第四步,标注实体

切换到工作台,就可以开始标注数据。
鼠标选中需要标的字或词,他会自动弹出我们预先选择的实体类型,如下图。
在这里插入图片描述

第五步,导出标注数据

该工具导出的标注数据为json格式。所以我后面在进行实验时,进行了预处理。
回到工程管理,点击导出数据即可,如下图。
在这里插入图片描述
我们就导出已经标注的数据。
在这里插入图片描述

微调Bert

数据预处理

import json
from sklearn.model_selection import train_test_split
from datasets import Dataset, DatasetDict# 来自标注好的JSON文件
with open(LABEL_DATA_PATH, 'r', encoding='utf-8') as f:data = json.load(f)texts = []
labels = []for entry in data:text = entry['content']label_sequence = ['O'] * len(text)  # 初始化所有字符的标签为 'O'for tag in entry['tags']:if tag['name'] == 'PLACE':start = tag['start']end = tag['end']# 将开始位置标记为 'B-PLACE'label_sequence[start] = 'B-PLACE'# 将后续位置标记为 'I-PLACE'for i in range(start + 1, end):label_sequence[i] = 'I-PLACE'# 将标签转换为标签索引label_indices = [label2id[label] for label in label_sequence]texts.append(text)labels.append(label_indices)# 检查转换后的格式
print("Texts:", texts[-2:])
print("Labels:", labels[-2:])# 划分数据集--训练测试和验证
texts_train, texts_temp, labels_train, labels_temp = train_test_split(texts, labels, test_size=0.2, random_state=42
)
texts_val, texts_test, labels_val, labels_test = train_test_split(texts_temp, labels_temp, test_size=0.5, random_state=42
)# 构造字典形式的数据
def create_dataset(texts, labels):ids = list(range(len(texts)))tokens_list = [list(text) for text in texts]return {'id': ids, 'tokens': tokens_list, 'ner_tags': labels}train_data = create_dataset(texts_train, labels_train)
val_data = create_dataset(texts_val, labels_val)
test_data = create_dataset(texts_test, labels_test)# 创建 Dataset 和 DatasetDict
train_dataset = Dataset.from_dict(train_data)
val_dataset = Dataset.from_dict(val_data)
test_dataset = Dataset.from_dict(test_data)# 最终的数据集
ner_data = DatasetDict({'train': train_dataset,'validation': val_dataset,'test': test_dataset
})

编码文本

from transformers import BertTokenizerFastdef 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)# word_ids() => Return a list mapping the tokens# to their actual word in the initial sentence.# It Returns a list indicating the word corresponding to each token.previous_word_idx = Nonelabel_ids = []# Special tokens like `` and `<\s>` are originally mapped to None# We need to set the label to -100 so they are automatically ignored in the loss function.for word_idx in word_ids:if word_idx is None:# set –100 as the label for these special tokenslabel_ids.append(-100)# For the other tokens in a word, we set the label to either the current label or -100, depending on# the label_all_tokens flag.elif word_idx != previous_word_idx:# if current word_idx is != prev then its the most regular case# and add the corresponding tokenlabel_ids.append(label[word_idx])else:# to take care of sub-words which have the same word_idx# set -100 as well for them, but only if label_all_tokens == Falselabel_ids.append(label[word_idx] if label_all_tokens else -100)# mask the subword representations after the first subwordprevious_word_idx = word_idxlabels.append(label_ids)tokenized_inputs["labels"] = labelsreturn tokenized_inputstokenizer = BertTokenizerFast.from_pretrained(MODEL_PATH+MODEL_NAME)  # 自己下载的中文 BERT 模型
# 应用于整个数据
tokenized_datasets = ner_data.map(tokenize_and_align_labels, batched=True)

定义模型

from torch.optim import AdamW
from transformers import Trainer, TrainingArguments
from transformers import DataCollatorForTokenClassification# 初始化模型
model = AutoModelForTokenClassification.from_pretrained(MODEL_PATH+MODEL_NAME, num_labels=NUM_LABELS)

构建Trainer

from torch.optim import AdamW
from transformers import Trainer, TrainingArguments
from transformers import DataCollatorForTokenClassificationdef calculate_ner_metrics(true_labels, pred_labels):"""自定义评估函数,输入为二维列表,输出为各指标"""assert len(true_labels) == len(pred_labels), "true_labels 和 pred_labels 的长度必须一致"# 初始化统计变量total_true = 0  # 总的真实实体数total_pred = 0  # 总的预测实体数total_correct = 0  # 预测正确的实体数total_tokens = 0  # 总的标注的token数correct_tokens = 0  # 预测正确的token数# 遍历每个序列for true_seq, pred_seq in zip(true_labels, pred_labels):assert len(true_seq) == len(pred_seq), "每个序列的长度必须一致"for true, pred in zip(true_seq, pred_seq):# 统计 token-level 准确性total_tokens += 1if true == pred:correct_tokens += 1# 如果是实体标签,更新统计if true != "O":  # 真实标签为实体total_true += 1if true == pred:  # 预测正确的实体total_correct += 1if pred != "O":  # 预测标签为实体total_pred += 1# 计算指标accuracy = correct_tokens / total_tokens if total_tokens > 0 else 0.0precision = total_correct / total_pred if total_pred > 0 else 0.0recall = total_correct / total_true if total_true > 0 else 0.0f1 = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0.0metrics = {"accuracy": accuracy,"precision": precision,"recall": recall,"f1_score": f1}return metricsdef compute_metrics(pred):pred_logits, labels = predpred_logits = pred_logits.argmax(-1)# 取去除 padding 的部分predictions = [[id2label[eval_preds] for (eval_preds, l) in zip(prediction, label) if l != -100]for prediction, label in zip(pred_logits, labels)]true_labels = [[id2label[l] for (eval_preds, l) in zip(prediction, label) if l != -100]for prediction, label in zip(pred_logits, labels)]result = calculate_ner_metrics(true_labels,predictions)return result# 重写 Trainer 类
class CustomTrainer(Trainer):def create_optimizer(self):if self.optimizer is None:# 获取模型参数decay_parameters = [p for n, p in self.model.named_parameters() if n.endswith("weight")]no_decay_parameters = [p for n, p in self.model.named_parameters() if n.endswith("bias")]# 将参数分组optimizer_grouped_parameters = [{"params": decay_parameters, "weight_decay": self.args.weight_decay},{"params": no_decay_parameters, "weight_decay": 0.0},]# 使用 AdamW 作为优化器self.optimizer = AdamW(optimizer_grouped_parameters, lr=self.args.learning_rate)return self.optimizer# 创建训练参数
training_args = TrainingArguments(output_dir=OUT_DIR,eval_strategy="epoch",save_strategy="epoch",learning_rate=2e-5,per_device_train_batch_size=BATCH_SIZE,per_device_eval_batch_size=BATCH_SIZE,num_train_epochs=3,weight_decay=0.01,load_best_model_at_end=True,logging_dir=LOG_DIR,save_total_limit=1,
)# 数据收集器,用于将数据转换为模型可接受的格式
data_collator = DataCollatorForTokenClassification(tokenizer)  # 定义 Trainer
trainer = CustomTrainer(model=model,  # 替换为你的模型args=training_args,train_dataset=tokenized_datasets['train'],eval_dataset=tokenized_datasets['validation'],data_collator=data_collator,tokenizer=tokenizer,compute_metrics=compute_metrics,
)

训练

# 训练 model
trainer.train()# 保存模型
best_ckpt_path = trainer.state.best_model_checkpoint
best_ckpt_path

评估

trainer.evaluate(eval_dataset=tokenized_datasets['test'])

结果

  • 训练过程
    在这里插入图片描述
  • 测试集
    在这里插入图片描述
  • 预测
    在这里插入图片描述
    完整代码和数据集发布在Github:chinese_ner_place

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】数组

1.概述 所谓数组&#xff0c;就是一个集合&#xff0c;该集合里面存放了相同类型的数据元素。 数组特点&#xff1a; &#xff08;1&#xff09;数组中的每个数据元素都是相同的数据类型。 &#xff08;2&#xff09;数组是有连续的内存空间组成的。 2、一维数组 2.1维数组定…

微软表示不会使用你的 Word、Excel 数据进行 AI 训练

​微软否认使用 Microsoft 365 应用程序&#xff08;包括 Word、Excel 和 PowerPoint&#xff09;收集数据来训练公司人工智能 (AI) 模型的说法。 此前&#xff0c;Tumblr 的一篇博文声称&#xff0c;雷德蒙德使用“互联体验”功能抓取客户的 Word 和 Excel 数据&#xff0c;用…

leetcode--螺旋矩阵

LCR 146.螺旋遍历二维数组 给定一个二维数组 array&#xff0c;请返回「螺旋遍历」该数组的结果。 螺旋遍历&#xff1a;从左上角开始&#xff0c;按照 向右、向下、向左、向上 的顺序 依次 提取元素&#xff0c;然后再进入内部一层重复相同的步骤&#xff0c;直到提取完所有元…

C++ 分治

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 1.分治法 2.二分搜索 函数传参——数组 3.棋盘覆盖 4.合并排序 5.快速排序 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 1.分治法 基…

Spark常问面试题---项目总结

一、数据清洗&#xff0c;你都清洗什么&#xff1f;或者说 ETL 你是怎么做的&#xff1f; 我在这个项目主要清洗的式日志数据&#xff0c;日志数据传过来的json格式 去除掉无用的字段&#xff0c;过滤掉json格式不正确的脏数据 过滤清洗掉日志中缺少关键字段的数据&#xff…

基于智能语音交互的智能呼叫中心工作机制

在智能化和信息化不断进步的现代&#xff0c;智能呼叫中心为客户提供高质量、高效率的服务体验&#xff0c;提升众多品牌用户的满意度和忠诚度。作为实现智能呼叫中心的关键技术之一的智能语音交互技术&#xff0c;它通过集成自然语言处理&#xff08;NLP&#xff09;、语音识别…

Android Studio 右侧工具栏 Gradle 不显示 Task 列表

问题&#xff1a; android studio 4.2.1版本更新以后AS右侧工具栏Gradle Task列表不显示&#xff0c;这里需要手动去设置 解决办法&#xff1a; android studio 2024.2.1 Patch 2版本以前的版本设置&#xff1a;依次打开 File -> Settings -> Experimental 选项&#x…

SpringBoot集成Kafka和avro和Schema注册表

Schema注册表 为了提升kafka的性能&#xff0c;减少网络传输和存储的数据大小&#xff0c;可以把数据的schema部分单独存储到外部的schema注册表中&#xff0c;整体架构如下图所示&#xff1a; 1&#xff09;把所有数据需要用到的 schema 保存在注册表里&#xff0c;然后在记…

http(请求方法,状态码,Cookie与)

目录 1.http中常见的Header(KV结构) 2.http请求方法 2.1 请求方法 2.2 telnet 2.3 网页根目录 2.3.1 概念 2.3.2 构建一个首页 2.4 GET与POST方法 2.4.1 提交参数 2.4.2 GET与POST提交参数对比 2.4.3 GET和POST对比 3.状态码 3.1 状态码分类 3.2 3XXX状态码 3.2 …

十,[极客大挑战 2019]Secret File1

点击进入靶场 查看源代码 有个显眼的紫色文件夹&#xff0c;点击 点击secret看看 既然这样&#xff0c;那就回去查看源代码吧 好像没什么用 抓个包 得到一个文件名 404 如果包含"../"、"tp"、"input"或"data"&#xff0c;则输出"…

pytest自定义命令行参数

实际使用场景&#xff1a;pytest运行用例的时候&#xff0c;启动mitmdump进程试试抓包&#xff0c;pytest命令行启动的时候&#xff0c;传入mitmdump需要的参数&#xff08;1&#xff09;抓包生成的文件地址 &#xff08;2&#xff09;mitm的proxy设置 # 在pytest的固定文件中…

Unity AssetBundles(AB包)

目录 前言 AB包是什么 AB包有什么作用 1.相对Resources下的资源AB包更好管理资源 2.减小包体大小 3.热更新 官方提供的打包工具:Asset Bundle Browser AB包资源加载 AB包资源管理模块代码 前言 在现代游戏开发中&#xff0c;资源管理是一项至关重要的任务。随着游戏内容…

(一)Linux下安装NVIDIA驱动(操作记录)

目录 一、查看CUDA版本 1.输入nvidia-smi&#xff0c;查看驱动支持的最大CUDA版本&#xff0c;这里是11.6 2.输入nvcc --version&#xff0c;查看当前安装的CUDA版本&#xff0c;这里是11.3 二、卸载旧的NVIDIA驱动 1.卸载原有驱动 2.禁用nouveau&#xff08;必须&#x…

用Python做数据分析环境搭建及工具使用(Jupyter)

目录 一、Anaconda下载、安装 二、Jupyter 打开 三、Jupyter 常用快捷键 3.1 创建控制台 3.2 命令行模式下的快捷键 3.3 运行模式下快捷键 3.4 代码模式和笔记模式 3.5 编写Python代码 一、Anaconda下载、安装 【最新最全】Anaconda安装python环境_anaconda配置python…

Ai编程cursor + sealos + devBox实现登录以及用户管理增删改查(十三)

一、什么是 Sealos&#xff1f; Sealos 是一款以 Kubernetes 为内核的云操作系统发行版。它以云原生的方式&#xff0c;抛弃了传统的云计算架构&#xff0c;转向以 Kubernetes 为云内核的新架构&#xff0c;使企业能够像使用个人电脑一样简单地使用云。 二、适用场景 业务运…

重学设计模式-工厂模式(简单工厂模式,工厂方法模式,抽象工厂模式)

在平常的学习和工作中&#xff0c;我们创建对象一般会直接用new&#xff0c;但是很多时候直接new会存在一些问题&#xff0c;而且直接new会让我们的代码变得非常繁杂&#xff0c;这时候就会巧妙的用到设计模式&#xff0c;平常我们通过力扣学习的算法可能并不会在我们工作中用到…

数据结构4——栈和队列

目录 1.栈 1.1.栈的概念及结构 1.2栈的实现 2.队列 2.1队列的概念及结构 2.2队列的实现 1.栈 1.1.栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一段称为栈顶&#xff0c;另一端称为…

家政小程序开发,打造便捷家政生活小程序

目前&#xff0c;随着社会人就老龄化和生活压力的加重&#xff0c;家政服务市场的需求正在不断上升&#xff0c;家政市场的规模也正在逐渐扩大&#xff0c;发展前景可观。 在市场快速发展的影响下&#xff0c;越来越多的企业开始进入到市场中&#xff0c;同时家政市场布局也发…

Spring Cloud Alibaba(六)

目录&#xff1a; 分布式链路追踪-SkyWalking为什么需要链路追踪什么是SkyWalkingSkyWalking核心概念什么是探针Java AgentJava探针日志监控实现之环境搭建Java探针日志监控实现之探针实现编写探针类TestAgent搭建 ElasticsearchSkyWalking服务环境搭建搭建微服务微服务接入Sky…

HTTP 探秘之旅:从入门到未来

文章目录 导言&#xff1a;目录&#xff1a;第一篇&#xff1a;HTTP&#xff0c;互联网的“快递员”第二篇&#xff1a;从点开网页到看到内容&#xff0c;HTTP 究竟做了什么&#xff1f;第三篇&#xff1a;HTTP 的烦恼与进化史第四篇&#xff1a;HTTP 的铠甲——HTTPS 的故事第…