自然语言处理:基于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 BeautifulSoup

user = '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 pd

df = 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 BertTokenizerFast

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)
        # 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 = None
        label_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 tokens
                label_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 token
                label_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 == False
                label_ids.append(label[word_idx] if label_all_tokens else -100)
                # mask the subword representations after the first subword

            previous_word_idx = word_idx
        labels.append(label_ids)
    tokenized_inputs["labels"] = labels
    return tokenized_inputs

tokenizer = 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 DataCollatorForTokenClassification

def 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 += 1
            if true == pred:
                correct_tokens += 1
            
            # 如果是实体标签,更新统计
            if true != "O":  # 真实标签为实体
                total_true += 1
                if true == pred:  # 预测正确的实体
                    total_correct += 1
            
            if pred != "O":  # 预测标签为实体
                total_pred += 1
    
    # 计算指标
    accuracy = correct_tokens / total_tokens if total_tokens > 0 else 0.0
    precision = total_correct / total_pred if total_pred > 0 else 0.0
    recall = total_correct / total_true if total_true > 0 else 0.0
    f1 = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0.0
    
    metrics = {
        "accuracy": accuracy,
        "precision": precision,
        "recall": recall,
        "f1_score": f1
    }
    return metrics

def compute_metrics(pred):
    pred_logits, labels = pred
    pred_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

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

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

相关文章

面试题-RocketMQ的基本架构、支持的消息模式、如何保证消息的可靠传输

相关问题 1、RocketMQ的基本架构是怎样的&#xff1f;请简述各组件的作用。 2、RocketMQ支持哪几种消息模式&#xff08;如点对点、发布/订阅&#xff09;&#xff1f;请简要说明它们的区别。 3、如何使用Java客户端实现一个简单的消息生产者和消费者&#xff1f; 4、RocketMQ…

【力扣】3274. 检查棋盘方格颜色是否相同

一、题目 给你两个字符串 coordinate1 和 coordinate2&#xff0c;代表 8 x 8 国际象棋棋盘上的两个方格的坐标。以下是棋盘格的参考图&#xff1a; 如果这两个方格颜色相同&#xff0c;返回 true&#xff0c;否则返回 false。坐标总是表示有效的棋盘方格。坐标的格式总是先字…

mysql 5.7安装及安装后无法启动问题处理

下载安装包&#xff0c;直接解压 配置环境变量 创建my.ini文件 [mysqld] #端口号 port 3306 #mysql-5.7.27-winx64的路径 basedirD:/soft/mysql57 #mysql-5.7.27-winx64的路径\data datadirD:/soft/mysql57/data #最大连接数 max_connections200 #编码 character-set-server…

2023年第十四届蓝桥杯Scratch国赛真题—推箱子

推箱子 程序演示及其源码解析&#xff0c;可前往&#xff1a; https://www.hixinao.com/scratch/creation/show-188.html 若需在线编程&#xff0c;在线测评模考&#xff0c;助力赛事可自行前往题库中心&#xff0c;按需查找&#xff1a; https://www.hixinao.com/ 题库涵盖…

级联树结构TreeSelect和上级反查

接口返回结构 前端展示格式 前端组件 <template><div ><el-scrollbar height"70vh"><el-tree :data"deptOptions" :props"{ label: label, children: children }" :expand-on-click-node"false":filter-node-me…

28.100ASK_T113-PRO Linux+QT 显示一张照片

1.添加资源文件 2. 主要代码 #include "mainwindow.h" #include "ui_mainwindow.h" #include <QImage> #include <QPixmap>MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow) {ui->setupUi(this);QIm…

基于PySpark 使用线性回归、随机森林以及模型融合实现天气预测

基于PySpark 实现天气预测与模型集成 在大数据分析与机器学习领域&#xff0c;Spark 提供了强大的计算能力和灵活的扩展性。本文将介绍如何利用 PySpark 完成以下任务&#xff1a; 1、数据预处理&#xff1a;清洗和编码天气数据。 2、特征工程&#xff1a;合并数值和分类特征…

【MySQL — 数据库基础】深入理解数据库服务与数据库关系、MySQL连接创建、客户端工具及架构解析

目录 1. 数据库服务&#xff06;数据库&#xff06;表之间的关系 1.1 复习 my.ini 1.2 MYSQL服务基于mysqld启动而启动 1.3 数据库服务的具体含义 1.4 数据库服务&数据库&表之间的关系 2. 客户端工具 2.1 客户端连接MySQL服务器 2.2 客…

各种类型无人机性能及优缺点技术详解

无人机系统种类繁多、用途广泛&#xff0c;且特点鲜明&#xff0c;致使其在尺寸、质量、航程、航时、飞行高度、飞行速度以及任务载荷等多方面都有较大差异。以下是对几种常见类型无人机的性能、优缺点技术的详细解析&#xff1a; 一、固定翼无人机 1.性能&#xff1a; 固定翼…

yolo辅助我们健身锻炼

使用软件辅助健身能够大大提升运动效果并帮助你更轻松地达成健身目标。确保每次锻炼都更加高效且针对性强,精确记录你的训练进度,帮助你更清晰地看到自己的进步,避免无效训练。 借助YOLO11的尖端计算机视觉技术,跟踪和分析锻炼变得异常简单。它可以无缝检测和监控多种锻炼…

Linux修改系统及终端命令行中的用户名

0、前言 最近捣鼓了一下一个很久之前的用过的ubuntu系统&#xff0c;但是之前随意设置了一个用户名&#xff0c;突发奇想地去修改了一下这个ubuntu系统的系统用户名&#xff0c;发现修改起来还是有些麻烦&#xff0c;并没有那种一键修改的选项&#xff0c;所以在这篇博客下面记…

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

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

Linux条件变量线程池详解

一、条件变量 【互斥量】解决了线程间同步的问题&#xff0c;避免了多线程对同一块临界资源访问产生的冲突&#xff0c;但同一时刻对临界资源的访问&#xff0c;不论是生产者还是消费者&#xff0c;都需要竞争互斥锁&#xff0c;由此也带来了竞争的问题。即生产者和消费者、消费…

「Mac畅玩鸿蒙与硬件38」UI互动应用篇15 - 猜数字增强版

本篇将带你实现一个升级版的数字猜谜游戏。相比基础版&#xff0c;新增了计分和历史记录功能&#xff0c;用户可以在每次猜测后查看自己的得分和猜测历史。此功能展示了状态管理的进阶用法以及如何保存和显示历史数据。 关键词 UI互动应用数字猜谜状态管理历史记录用户交互 一…

【IMF靶场渗透】

文章目录 一、基础信息 二、信息收集 三、flag1 四、flag2 五、flag3 六、flag4 七、flag5 八、flag6 一、基础信息 Kali IP&#xff1a;192.168.20.146 靶机IP&#xff1a;192.168.20.147 二、信息收集 Nmap -sP 192.168.20.0/24 Arp-scan -l nmap -sS -sV -p- -…

【C#】书籍信息的添加、修改、查询、删除

文章目录 一、简介二、程序功能2.1 Book类属性&#xff1a;方法&#xff1a; 2.2 Program 类 三、方法&#xff1a;四、用户界面流程&#xff1a;五、程序代码六、运行效果 一、简介 简单的C#控制台应用程序&#xff0c;用于管理书籍信息。这个程序将允许用户添加、编辑、查看…

【Code First】.NET开源 ORM 框架 SqlSugar 系列

.NET开源 ORM 框架 SqlSugar 系列 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列【Code First】.NET开源 ORM 框架 SqlSugar 系列【数据事务…

CLIP模型也能处理点云信息

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

配置宝塔php curl 支持http/2 发送苹果apns消息推送

由于宝塔面板默认的php编译的curl未加入http2的支持&#xff0c;如果服务需要使用apns推送等需要http2.0的访问就会失败&#xff0c;所以重新编译php让其支持http2.0 编译方法&#xff1a; 一、安装nghttp2 git clone https://github.com/tatsuhiro-t/nghttp2.git cd nghttp…

单片机学习笔记 12. 定时/计数器_定时

更多单片机学习笔记&#xff1a;单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…