自然语言处理:基于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.mzph.cn/pingmian/62914.shtml

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

相关文章

Pytorch实现心跳信号分类识别(支持LSTM,GRU,TCN模型)

Pytorch实现心跳信号分类识别(支持LSTM,GRU,TCN模型&#xff09; 目录 Pytorch实现心跳信号分类识别(支持LSTM,GRU,TCN模型&#xff09; 1. 项目说明 2. 数据说明 &#xff08;1&#xff09;心跳信号分类预测数据集 3. 模型训练 &#xff08;1&#xff09;项目安装 &…

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

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

Windows远程桌面连接到Linux

我的电脑是一台瘦客户端&#xff0c;公司设置的不能安装其他软件&#xff0c;里面只有几个软件&#xff0c;还好有一个远程桌面&#xff08;Remote Desktop Connection&#xff09;&#xff0c;我想连接到另一台Linux的电脑上。 在Linux上安装xrdp&#xff1a; sudo apt insta…

视觉处理基础1

目录 一、CNN 1. 概述 1.1 与传统网络的区别 1.2 全连接的局限性 1.3 卷积思想 1.4 卷积的概念 1.4.1 概念 1.4.2 局部连接 1.4.3 权重共享 2. 卷积层 2.1 卷积核 2.2 卷积计算 2.3 边缘填充 2.4 步长Stride 2.5 多通道卷积计算 2.7 特征图大小计算方法 2…

泛化调用 :在没有接口的情况下进行RPC调用

什么是泛化调用&#xff1f; 在RPC调用的过程中&#xff0c;调用端向服务端发起请求&#xff0c;首先要通过动态代理&#xff0c;动态代理可以屏蔽RPC处理流程&#xff0c;使得发起远程调用就像调用本地一样。 RPC调用本质&#xff1a;调用端向服务端发送一条请求消息&#x…

C++ 之弦上舞:string 类与多样字符串操作的优雅旋律

string 类的重要性及与 C 语言字符串对比 在 C 语言中&#xff0c;字符串是以 \0 结尾的字符集合&#xff0c;操作字符串需借助 C 标准库的 str 系列函数&#xff0c;但这些函数与字符串分离&#xff0c;不符合 OOP 思想&#xff0c;且底层空间管理易出错。而在 C 中&#xff0…

【大数据学习 | Spark调优篇】Spark之内存调优

1. 内存的花费 1&#xff09;每个Java对象&#xff0c;都有一个对象头&#xff0c;会占用16个字节&#xff0c;主要是包括了一些对象的元信息&#xff0c;比如指向它的类的指针。如果一个对象本身很小&#xff0c;比如就包括了一个int类型的field&#xff0c;那么它的对象头实…

使用OpenCV和卡尔曼滤波器进行实时活体检测

引言 在现代计算机视觉应用中&#xff0c;实时检测和跟踪物体是一项重要的任务。本文将详细介绍如何使用OpenCV库和卡尔曼滤波器来实现一个实时的活体检测系统。该系统能够通过摄像头捕捉视频流&#xff0c;并使用YOLOv3模型来检测目标对象&#xff08;例如人&#xff09;&…

【closerAI ComfyUI】物体转移术之图案转移,Flux三重控制万物一致性生图,实现LOGO和图案的精准迁移

更多AI前沿科技资讯,请关注我们:closerAI-一个深入探索前沿人工智能与AIGC领域的资讯平台 closerAIGCcloserAI,一个深入探索前沿人工智能与AIGC领域的资讯平台,我们旨在让AIGC渗入我们的工作与生活中,让我们一起探索AIGC的无限可能性! 【closerAI ComfyUI】物体转移术之图…

2025软考高级《系统架构设计师》案例模拟题合集

首先分享一下系统架构设计师资料合集&#xff0c;有历年真题、自学打卡表、精华知识点等&#xff0c;需要的留邮&#xff0c;打包分享&#xff01; 1、在设计基于混合云的安全生产管理系统中&#xff0c;需要重点考虑5个方面的安全问题。设备安全、网络安全、控制安全、应用安全…

rpm包转deb包或deb包转rpm包

Debian系&#xff08;Ubuntu、Deepin、麒麟Destop等&#xff09;用的安装包是deb的&#xff0c;Red Hat系&#xff08;CentOS、欧拉、麒麟Server等&#xff09;用的安装包是rpm的。 如果需要在Ubuntu上安装rpm&#xff0c;或需要在CentOS上安装deb&#xff0c;需要安装alien s…

【C语言】递归的内存占用过程

递归 递归是函数调用自身的一种编程技术。在C语言中&#xff0c;递归的实现会占用内存栈&#xff08;Call Stack&#xff09;&#xff0c;每次递归调用都会在栈上分配一个新的 “栈帧&#xff08;Stack Frame&#xff09;”&#xff0c;用于存储本次调用的函数局部变量、返回地…

数据仓库的概念

先用大白话讲一下&#xff0c;数据仓库的主要目的就是存储和分析大量结构化数据的。 > 那么它的核心目的是&#xff1a;支持商业智能&#xff08;BI&#xff09;和决策支持系统&#xff0c;也就是说&#xff0c;它不仅仅是为了存储&#xff0c;更重要的是为了分析提供便利。…

LeetCode 438.找到字符串中所有字母异位词

LeetCode 438.找到字符串中所有字母异位词 思路&#x1f9d0;&#xff1a; 需要找到子串异位词&#xff0c;也就是只看该子串是否有相同字母而不管位置是否相同。分析题目发现只需要单调向前找异位词&#xff0c;则可以使用滑动窗口求解&#xff0c;注意这里每当左右边框长度大…

算法刷题Day8:BM30 二叉搜索树与双向链表

题目 牛客网题目传送门 思路 对二叉搜索树进行中序遍历&#xff0c;结果就是按序数组。因此想办法把前面遍历过的节点给记下来&#xff0c;记作pre。当遍历到某个节点node的时候&#xff0c;令前驱指向pre&#xff0c;然后让pre的后驱指向node。 代码 class TreeNode:def…

1.Git安装与常用命令

前言 Git中会用到的一些基本的Linux命令 ls/ll 查看文件目录 (ll可以看隐藏文件)cat 查看文件内容touch 创建文件vi vi编辑器 1.下载与安装 安装成功后鼠标右键会出现Git Bash和Git GUI Git GUI&#xff1a;GUI图形化界面 Git Bash&#xff1a;Git提供的命令行工具 当安装…

ultralytics-YOLOv11的目标检测解析

1. Python的调用 from ultralytics import YOLO import os def detect_predict():model YOLO(../weights/yolo11n.pt)print(model)results model(../ultralytics/assets/bus.jpg)if not os.path.exists(results[0].save_dir):os.makedirs(results[0].save_dir)for result in…

【docker】docker compose多容器部署

Docker Compose 的详细讲解与实际应用 什么是 Docker Compose&#xff1f; Docker Compose 是一个工具&#xff0c;用于定义和运行多容器 Docker 应用。 通过一个 docker-compose.yml 文件&#xff0c;可以同时启动多个服务&#xff0c;简化多容器管理。 Docker Compose 的核心…

【AI系统】CANN 算子类型

CANN 算子类型 算子是编程和数学中的重要概念&#xff0c;它们是用于执行特定操作的符号或函数&#xff0c;以便处理输入值并生成输出值。本文将会介绍 CANN 算子类型及其在 AI 编程和神经网络中的应用&#xff0c;以及华为 CANN 算子在 AI CPU 的详细架构和开发要求。 算子基…

C++:特殊类设计及类型转换

目录 一.常见特殊类的设计方式 1.请设计一个类&#xff0c;不能被拷贝 2.请设计一个类&#xff0c;只能在堆上创建对象 3.请设计一个类&#xff0c;只能在栈上创建对象 4.请设计一个类&#xff0c;不能被继承 5.请设计一个类&#xff0c;只能创建一个对象(单例模式) 二.C…