【NLP学习计划】万字吃透NER

这篇具有很好参考价值的文章主要介绍了【NLP学习计划】万字吃透NER。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

👨‍🎓 博主介绍:大家好,我是可可卷,一个NLP领域的小小白~
📕 文章介绍:命名实体识别,即Named Entity Recognition(NER),在比如QA,text summarization,machine translation等多项任务中均有涉及。今天我们将研究顶会文章Chinese NER using Lattice LSTM中的数据集,并尝试自己手写模型,领会stoa方案的魅力!
🎉欢迎关注💗点赞👍收藏⭐️评论📝
🙏作者水平很有限,欢迎各位大佬指点,一起学习进步!

1 数据集介绍

1.1 数据集来源

本次算法实验数据集用的是NLP顶会论文ACL 2018Chinese NER using Lattice LSTM中收集的简历数据。

1.2 数据集格式

目前,常用的序列标注方式有BIO和BIOES,其中BIO方式主要将实体X标注为B-X,I-X,O的格式,B-表示实体的起始位置,I-表示实体的中间或结尾,O-表示不属于实体;而BIOES近似于BIO的改进,主要将多元实体X标注为B-X,I-X,E-X的格式,B-表示实体的起始位置,I-表示实体的中间或结尾;一元实体则标记为S-X,;O-X表示X不属于实体。

本数据集采用BMEOS标注法,其中B-表示实体的起始位置,M-表示实体的中间,E表示实体的结尾;一元实体则标记为S-;O-表示不属于实体。

# 数据集示例如下
高 B-NAME
勇 E-NAME
: O
男 O
, O
中 B-CONT
国 M-CONT
国 M-CONT
籍 E-CONT
, O
无 O
境 O
外 O
居 O
留 O
权 O
, O

1 O
9 O
6 O
6 O
年 O
出 O
生 O
, O
汉 B-RACE
族 E-RACE
, O
中 B-TITLE
共 M-TITLE
党 M-TITLE
员 E-TITLE
, O
本 B-EDU
科 M-EDU
学 M-EDU
历 E-EDU
, O
工 B-TITLE
程 M-TITLE
师 E-TITLE
、 O
美 B-ORG
国 M-ORG
项 M-ORG
目 M-ORG
管 M-ORG
理 M-ORG
协 M-ORG
会 E-ORG
注 B-TITLE
册 M-TITLE
会 M-TITLE
员 E-TITLE
( O
P B-TITLE
M M-TITLE
I M-TITLE
M M-TITLE
e M-TITLE
m M-TITLE
b M-TITLE
e M-TITLE
r E-TITLE
) O
、 O
注 B-TITLE
册 M-TITLE
项 M-TITLE
目 M-TITLE
管 M-TITLE
理 M-TITLE
专 M-TITLE
家 E-TITLE
( O
P B-TITLE
M M-TITLE
P E-TITLE
) O
、 O
项 B-TITLE
目 M-TITLE
经 M-TITLE
理 E-TITLE
。 O

该数据集的所有类别如下:

{'B-CONT', 'E-PRO', 'M-CONT', 'S-NAME', 'E-CONT', 'B-PRO', 'M-LOC', 'E-FOOD', 'E-TITLE', 'M-ORG', 'B-ORG', 'M-TITLE', 'B-TITLE', 'B-RACE', 'B-EDU', 'B-FOOD', 'M-EDU', 'M-NAME', 'S-RACE', 'E-LOC', 'E-RACE', 'S-ORG', 'M-PRO', 'M-FOOD', 'E-NAME', 'E-EDU', 'B-LOC', 'E-ORG', 'B-NAME', 'M-RACE', 'O'}

1.3 数据集分析

1.3.1 文本长度分布

【NLP学习计划】万字吃透NER
由上图可以发现,简历数据集以短句居多,有一定比例中长句,长句较少,因此在后序训练时可以更加注重短句的上下文关系。

1.3.2 数据集划分

【NLP学习计划】万字吃透NER
依据10:1:1的比例将数据集划分成训练集、验证集、测试集。

1.3.3 不同标签样本数

【NLP学习计划】万字吃透NER
分析上图,发现标签分布不平衡,其中S-ORG,B-LOC的比例较高,而其他标签的比例较低,有一定训练难度。

1.3.4 数据集句长

【NLP学习计划】万字吃透NER
【NLP学习计划】万字吃透NER
通过箱线图与小提琴图,我们可以清晰地发现,训练集、验证集、测试集的数据集句长分布保存一致。

2 统计学习算法

2.1 Hidden Markov Model

2.1.1 算法原理

隐马尔可夫模型描述由一个隐藏的马尔科夫链随机生成不可观测的状态随机序列,再由各个状态生成一个观测而产生观测随机序列的过程。隐马尔可夫模型由初始状态分布,状态转移概率矩阵以及观测概率矩阵所确定。
命名实体识别本质上可以看成是一种序列标注问题,在使用HMM解决命名实体识别这种序列标注问题的时候,我们所能观测到的是字组成的序列(观测序列),观测不到的是每个字对应的标注(状态序列)。

2.1.2 程序代码

import torch


class HMM(object):
    def __init__(self, N, M):
        self.N = N  # N: 状态数,这里对应存在的标注的种类
        self.M = M  # M: 观测数,这里对应有多少不同的字

        # 状态转移概率矩阵
        self.A = torch.zeros(N, N)
        # 观测概率矩阵
        self.B = torch.zeros(N, M)
        # 初始状态概率
        self.Pi = torch.zeros(N)

    def train(self, word_lists, tag_lists, word2id, tag2id):    # word2id: 将字映射为ID; tag2id: 将标注映射为ID
        # 使用极大似然估计的方法来估计隐马尔可夫模型的参数

        # 估计转移概率矩阵
        for tag_list in tag_lists:
            seq_len = len(tag_list)
            for i in range(seq_len - 1):
                current_tagid = tag2id[tag_list[i]]
                next_tagid = tag2id[tag_list[i+1]]
                self.A[current_tagid][next_tagid] += 1

        self.A[self.A == 0.] = 1e-10    # 将未出现元素设置一个小数
        self.A = self.A / self.A.sum(dim=1, keepdim=True)

        # 估计观测概率矩阵
        for tag_list, word_list in zip(tag_lists, word_lists):
            assert len(tag_list) == len(word_list)
            for tag, word in zip(tag_list, word_list):
                tag_id = tag2id[tag]
                word_id = word2id[word]
                self.B[tag_id][word_id] += 1

        self.B[self.B == 0.] = 1e-10
        self.B = self.B / self.B.sum(dim=1, keepdim=True)

        # 估计初始状态概率
        for tag_list in tag_lists:
            init_tagid = tag2id[tag_list[0]]
            self.Pi[init_tagid] += 1

        self.Pi[self.Pi == 0.] = 1e-10
        self.Pi = self.Pi / self.Pi.sum()

    def test(self, word_lists, word2id, tag2id):
        pred_tag_lists = []
        for word_list in word_lists:
            pred_tag_list = self.decoding(word_list, word2id, tag2id)
            pred_tag_lists.append(pred_tag_list)
        return pred_tag_lists

    def decoding(self, word_list, word2id, tag2id):
        """
        使用维特比算法,其本质是用动态规划解隐马尔可夫模型预测问题(求概率最大路径)
        """

        # 对数化防止下溢
        A = torch.log(self.A)
        B = torch.log(self.B)
        Pi = torch.log(self.Pi)

        # 初始化维比特矩阵viterbi
        seq_len = len(word_list)
        viterbi = torch.zeros(self.N, seq_len)

        # backpointer[i,j]: 标注序列的第j个标注为i时,第j-1个标注的id
        backpointer = torch.zeros(self.N, seq_len).long()


        start_wordid = word2id.get(word_list[0], None)
        Bt = B.t()
        if start_wordid is None:    
            bt = torch.log(torch.ones(self.N) / self.N)  # 如果字不再字典里,则假设状态的概率分布是均匀的
        else:
            bt = Bt[start_wordid]                        # 否则从观测概率矩阵中取bt
        viterbi[:, 0] = Pi + bt
        backpointer[:, 0] = -1

        # 递推公式:viterbi[tag_id, step] = max(viterbi[:, step-1]* self.A.t()[tag_id] * Bt[word])
        for step in range(1, seq_len):
            wordid = word2id.get(word_list[step], None)
            if wordid is None:
               
                bt = torch.log(torch.ones(self.N) / self.N)
            else:
                bt = Bt[wordid]
            for tag_id in range(len(tag2id)):
                max_prob, max_id = torch.max(
                    viterbi[:, step-1] + A[:, tag_id],
                    dim=0
                )
                viterbi[tag_id, step] = max_prob + bt[tag_id]
                backpointer[tag_id, step] = max_id

        # 最优路径的概率
        best_path_prob, best_path_pointer = torch.max(
            viterbi[:, seq_len-1], dim=0
        )

        # 求最优路径
        best_path_pointer = best_path_pointer.item()
        best_path = [best_path_pointer]
        for back_step in range(seq_len-1, 0, -1):
            best_path_pointer = backpointer[best_path_pointer, back_step]
            best_path_pointer = best_path_pointer.item()
            best_path.append(best_path_pointer)

        # 将序列tag_id转化为tag
        id2tag = dict((id_, tag) for tag, id_ in tag2id.items())
        tag_list = [id2tag[id_] for id_ in reversed(best_path)]

        return tag_list

2.1.3 运行结果

【NLP学习计划】万字吃透NER

2.2 Conditional Random Field

2.2.1 算法原理

条件随机场(CRF)是NER目前的主流模型,它的目标函数不仅考虑输入的状态特征函数,而且还包含了标签转移特征函数。CRF为一个位置进行标注的过程中可以利用丰富的内部及上下文特征信息,有效克服了HMM模型面临的问题。

【NLP学习计划】万字吃透NER

(图2.2 线性链条件随机场)

2.2.2 程序代码

from sklearn_crfsuite import CRF

# 抽取单个字的特征
def word2features(sent, i):
    word = sent[i]
    prev_word = "<s>" if i == 0 else sent[i-1]
    next_word = "</s>" if i == (len(sent)-1) else sent[i+1]
    features = {
        'w': word,                  # 当前词
        'w-1': prev_word,           # 前一个词
        'w+1': next_word,           # 后一个词
        'w-1:w': prev_word+word,    # 前一个词+当前词
        'w:w+1': word+next_word,    # 当前词+后一个词
        'bias': 1
    }
    return features


# 抽取序列特征
def sent2features(sent):
    return [word2features(sent, i) for i in range(len(sent))]


class CRFModel(object):
    def __init__(self,
                 algorithm='lbfgs',
                 c1=0.1,
                 c2=0.1,
                 max_iterations=100,
                 all_possible_transitions=False
                 ):

        self.model = CRF(algorithm=algorithm,
                         c1=c1,
                         c2=c2,
                         max_iterations=max_iterations,
                         all_possible_transitions=all_possible_transitions)

    def train(self, sentences, tag_lists):
        features = [sent2features(s) for s in sentences]
        self.model.fit(features, tag_lists)

    def test(self, sentences):
        features = [sent2features(s) for s in sentences]
        pred_tag_lists = self.model.predict(features)
        return pred_tag_lists

2.2.3 运行结果

【NLP学习计划】万字吃透NER

3 深度学习算法

3.1 Bi-LSTM

3.1.1 算法原理

通过依靠神经网络超强的非线性拟合能力,LSTM在训练时将样本通过高维空间中的复杂非线性变换,学习到从样本到标注的函数,之后使用这个函数为指定的样本预测每个token的标注。
而双向长短期有着比普通LSTM更好的捕捉序列之间的依赖关系的能力,能更好地用于捕捉上下文关系。

【NLP学习计划】万字吃透NER

3.1.2 程序代码

import torch
import torch.nn as nn
from torch.nn.utils.rnn import pad_packed_sequence, pack_padded_sequence
from torchinfo import summary
import pickle


class BiLSTM(nn.Module):
    def __init__(self, vocab_size, emb_size, hidden_size, out_size):
        super(BiLSTM, self).__init__()
        self.embedding = nn.Embedding(vocab_size, emb_size)
        self.bilstm = nn.LSTM(emb_size, hidden_size,
                              batch_first=True,
                              bidirectional=True)

        self.lin = nn.Linear(2*hidden_size, out_size)

    def forward(self, sents_tensor, lengths):
        emb = self.embedding(sents_tensor)
        packed = pack_padded_sequence(emb, lengths, batch_first=True)
        rnn_out, _ = self.bilstm(packed)
        rnn_out, _ = pad_packed_sequence(rnn_out, batch_first=True)

        scores = self.lin(rnn_out) 

        return scores


3.1.3 运行结果

【NLP学习计划】万字吃透NER

3.2 Bi-LSTM+CRF

3.2.1 算法原理

Bi-LSTM:

  • 优点是能够根据目标(比如识别实体)自动提取观测序列的特征
  • 缺点是无法学习到状态序列(输出的标注)之间的关系(比如B类标注后面不会再接一个B类标注,而通常接M类标注或E类标注)

CRF:

  • 优点就是能对隐含状态建模,学习状态序列的特点
  • 缺点是需要手动提取序列特征

Bi-LSTM+CRF:在Bi-LSTM后面再加一层CRF,以获得两者的优点

3.2.2 程序代码

from itertools import zip_longest
from copy import deepcopy

import torch
import torch.nn as nn
import torch.optim as optim


class BiLSTM_CRF(nn.Module):
    def __init__(self, vocab_size, emb_size, hidden_size, out_size):
        super(BiLSTM_CRF, self).__init__()
        self.bilstm = BiLSTM(vocab_size, emb_size, hidden_size, out_size)

        # 转移矩阵,初始化为均匀分布
        self.transition = nn.Parameter(torch.ones(out_size, out_size) * 1/out_size)


    def forward(self, sents_tensor, lengths):
        emission = self.bilstm(sents_tensor, lengths)

        # 计算CRF scores
        batch_size, max_len, out_size = emission.size()
        crf_scores = emission.unsqueeze(
            2).expand(-1, -1, out_size, -1) + self.transition.unsqueeze(0)

        return crf_scores

    
    def decode(self, test_sents_tensor, lengths, tag2id):
        
        start_id = tag2id['<start>']
        end_id = tag2id['<end>']
        pad = tag2id['<pad>']
        tagset_size = len(tag2id)

        crf_scores = self.forward(test_sents_tensor, lengths)
        device = crf_scores.device
        B, L, T, _ = crf_scores.size()
		
		# 使用维特比算法进行解码
		tagids = viterbi(B, L, T, device)

        return tagids

3.2.3 运行结果

【NLP学习计划】万字吃透NER

3.3 Bert+BiLSTM+CRF

3.3.1 算法原理

BERT中蕴含了大量的通用知识,利用预训练好的BERT模型,再用少量的标注数据进行FINETUNE是一种快速的获得效果不错的NER的方法。
因此,通常可以采用Bert+BiLSTM+CRF的模型结构,以期获取更高的预测精度。

【NLP学习计划】万字吃透NER

3.3.2 训练模型

了解到kashgari是一个简单而强大的NLP框架,其内包括cnnmodel、blstm模型、cnnlstm模型、avcnnmodel、KMaxnn模型、RCNN模型等序列(文本)分类模型以及cnnlstm模型、blstm模型、BLSTMCRFModel等序列(文本)标签模型,同时提供GPU支持/多GPU支持,因此选择了kashgari库进行Bert模型的预训练以及微调

from kashgari.tasks.labeling import BiLSTM_CRF_Model
import kashgari
from kashgari.embeddings.bert_embedding import  BertEmbedding
import warnings
warnings.filterwarnings('ignore')


bert_embed = BertEmbedding(r'\\chinese_L-12_H-768_A-12',
                           sequence_length=100)

model = BiLSTM_CRF_Model(bert_embed)
model.fit(train_x,
          train_y,
          x_validate=valid_x,
          y_validate=valid_y,
          epochs=5,
          batch_size=512)
model.save('ner.h5')

model.evaluate(test_x, test_y)

3.3.3 训练结果

由于模型训练时间过长(一个epoch需要400s),难以有效地进行fine tune,因此模型的效果并不十分理想。
【NLP学习计划】万字吃透NER

下面是模型训练5个epoch的结果,可以发现,模型的精度一直在提高,并没有达到收敛,这说明模型的潜力值得进一步挖掘。
【NLP学习计划】万字吃透NER

3.3.4 模型结构

【NLP学习计划】万字吃透NER

4 模型融合

4.1 融合策略

考虑到Bert+BiLSTM+CRF结构由于机器的原因,远未发挥应有的效果,因此只将前4个模型进行融合。
融合策略为:voting

4.2 程序代码

def flatten_lists(lists):
    flatten_list = []
    for l in lists:
        if type(l) == list:
            flatten_list += l
        else:
            flatten_list.append(l)
    return flatten_list

# 模型融合
def ensemble_evaluate(results, targets):
    for i in range(len(results)):
        results[i] = flatten_lists(results[i])

    # voting
    pred_tags = []
    for result in zip(*results):
        ensemble_tag = Counter(result).most_common(1)[0][0]
        pred_tags.append(ensemble_tag)

    targets = flatten_lists(targets)
    return targets

##4.3 融合结果
【NLP学习计划】万字吃透NER

【NLP学习计划】万字吃透NER
由模型精度比较图可以发现,HMM的预测精度最低,BiLSTM次低,CRF表现中等,Essemble表现次好,BiLSTM表现最好。

5 总结

  1. HMM认为每个字是独立的,并且标注 t a g i tag_i tagi只与上一个标注 t a g i − 1 tag_{i-1} tagi1有关,这个假设是非常局限的,因此也导致了HMM 的预测精度不够理想。

  2. CRF克服了HMM的上述缺点,通过手动提取特征的方式,关注了每个字的上下文关系,因此预测精度有了一定的上升。

  3. 通过软投票的方式,我们在Essemble过程中对前4个模型进行了融合,我们发现Essemble的预测精度略低于BiLSTM+CRF,这可以认为是其他模型拖累了整体的精度文章来源地址https://www.toymoban.com/news/detail-403853.html

到了这里,关于【NLP学习计划】万字吃透NER的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • IDCNN(迭代扩张卷积神经网络)在NLP-NER任务中的应用

    IDCNN(迭代扩张卷积神经网络)在NLP-NER任务中的应用 IDCNN(Iterated Dilated Convolutional Neural Network)是一种特别设计的卷积神经网络(CNN),用于处理自然语言处理(NLP)中的序列标注问题,例如命名实体识别(NER)。IDCNN的关键特点是使用了扩张卷积(Dilated Convolution),这是一

    2024年01月23日
    浏览(47)
  • NLP NER 任务中的精确度(Precision)、召回率(Recall)和F1值

    在自然语言处理(NLP)中的命名实体识别(NER)任务中,精确度(Precision)、召回率(Recall)和F1值是评估模型性能的关键指标。这些指标帮助我们了解模型在识别正确实体方面的效率和准确性。 精确度(Precision) : 精确度是指模型正确识别的命名实体数与模型总共识别出

    2024年01月23日
    浏览(54)
  • 中文自然语言处理(NLP)中的命名实体识别(NER)任务中,加入注意力(attention)机制

    在中文自然语言处理(NLP)中的命名实体识别(NER)任务中,加入注意力(attention)机制可以极大地提升模型的性能。注意力机制可以帮助模型更好地捕捉序列中的关键信息和上下文依赖关系,从而提高对命名实体的识别准确度。下面是一些关于注意力机制的具体作用和不同

    2024年01月25日
    浏览(54)
  • 【C++】一文带你吃透string的模拟实现 (万字详解)

    (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是 Scort 🎓 🌍博客主页:张小姐的猫~江湖背景 快上车🚘,握好方向盘跟我有一起打天下嘞! 送给自己的一句鸡汤🤔: 🔥真正的大师永远怀着一颗学徒的心 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏 🎉🎉欢迎持续关注! 🎨传统写

    2024年02月03日
    浏览(203)
  • 万字长文带你吃透SpringCloudGateway工作原理+动态路由+源码解析

    Spring Cloud 2.x 实 现 了 社 区 生 态 下 的 Spring CloudGateway(简称SCG)微服务网关项目。Spring Cloud Gateway基于WebFlux框架开发,目标是替换掉Zuul。 Spring Cloud Gateway主要有两个特性: 非阻塞,默认使用RxNetty作为响应式Web容器,通过非阻塞方式,利用较少的线程和资源来处理高并发请

    2023年04月08日
    浏览(46)
  • 万字长文带你吃透Spring是怎样解决循环依赖的

    在Spring框架中,处理循环依赖一直是一个备受关注的话题。这是因为Spring源代码中为了解决循环依赖问题,进行了大量的处理和优化。同时,循环依赖也是Spring高级面试中的必考问题,回答得好可以成为面试中的必杀技。因此,本文旨在为大家提供深入了解Spring的循环依赖及

    2023年04月18日
    浏览(40)
  • 【AI实战】大语言模型(LLM)有多强?还需要做传统NLP任务吗(分词、词性标注、NER、情感分类、知识图谱、多伦对话管理等)

    大语言模型(LLM)是指使用大量文本数据训练的深度学习模型,可以生成自然语言文本或理解语言文本的含义。大语言模型可以处理多种自然语言任务,如文本分类、问答、对话等,是通向人工智能的一条重要途径。来自百度百科 发展历史 2020年9月,OpenAI授权微软使用GPT-3模

    2024年02月10日
    浏览(40)
  • 【多任务学习】Multi-task Learning 手把手编码带数据集, 一文吃透多任务学习

    我们之前讲过的模型通常聚焦单个任务,比如预测图片的类别等,在训练的时候,我们会关注某一个特定指标的优化. 但是有时候,我们需要知道一个图片,从它身上知道新闻的类型(政治/体育/娱乐)和是男性的新闻还是女性的. 我们关注某一个特定指标的优化,可能忽略了对有关注的

    2024年04月27日
    浏览(42)
  • linux万字图文学习进程信号

    信号是进程之间事件异步通知的一种方式,属于软中断。 1.1 linux中我们常用Ctrl+c来杀死一个前台进程 1. Ctrl-C 产生的信号只能发给前台进程。一个命令后面加个可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。 2. Shell可以同时运行一个前台进程

    2024年02月07日
    浏览(37)
  • 【学习学习】NLP理解层次模型

    NLP(Neuro-Linguistic Programming,神经语言程序学),由两位美国人理查得.班德勒(Richard Bandler)与约翰.葛瑞德(John Grinder)于1976年创办,并在企业培训中广泛使用。美国前总统克林顿、微软领袖比尔盖茨、大导演斯皮尔博格等许多世界名人都接受过NLP培训。 在NLP理解层次模型

    2024年02月10日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包