0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

什么是嵌套实体识别

深度学习自然语言处理 来源:深度学习自然语言处理 作者:深度学习自然语言 2022-09-30 15:19 次阅读

嵌套命名实体识别是命名实体识别中的一个颇具挑战的子问题。我们在《实体识别LEAR论文阅读笔记》与《实体识别BERT-MRC论文阅读笔记》中已经介绍过针对这个问题的两种方法。今天让我们通过本文来看看在嵌套实体识别上哪一个方法更胜一筹。

1. 嵌套实体识别

1.1 什么是嵌套实体识别

嵌套实体识别是命名实体识别中一个子问题。那么什么才是嵌套实体呢?我们看下面这个例子:

“北京天安门”是地点实体;

“北京天安门”中“北京”也是地点实体;两者存在嵌套关系。

1.2 嵌套实体识别方法

CRF等传统序列标注方法无法应用于嵌套实体识别。现阶段,业界比较流行的是构建实体矩阵,即用一个矩阵 来代表语料中的所有实体及其类型。

其中任一元素 表示类为 ,起点为 ,结尾为 的实体。比如在下图所示实体矩阵中,就有两个Location类的实体:北京、北京天安门。

通过这样的标注方式我们可以对任何嵌套实体进行标注,从而解决训练和解码的问题。

在本文中,我们将对比目前接触到的部分实体矩阵的构建方法在 CMeEE 数据集(医学NER,有一定比例的嵌套实体)上的表现。

2. 实体矩阵构建框架

2.1 变量与符号约定

为了方便后续对比说明,这里我们先定义几个统一的变量与符号。

首先,上文中 表示类为 ,起点为 ,结尾为 的实体。

在本实验中,我们均使用 bert-base-chinese 作为 编码器。假设 表示最后一层隐藏层中第 个 token 的 embedding,那么 和 分别表示经过编码器之后实体 start 和 end token 的embedding。

我们有公式 ,其中 就表示我们所需要对比的实体矩阵构建头(姑且这么称呼)。

2.2 相关配置

在对比实验中,除了不同实体矩阵构建头对应的batch_size,learning_rate不同,所使用的编码器、损失函数、评估方式以及训练轮次均保持一致。

2.3 对比方法

本文选取了四种实体矩阵构建方法进行比较,分别是:

GlobalPointer

TPLinker(Muti-head selection);

Tencent Muti-head

Deep Biaffine(双仿射)。

3. 代码实现

3.1 GlobalPointer

GlobalPointer 出自苏剑林的博客GlobalPointer:用统一的方式处理嵌套和非嵌套NER[1]。

Global Pointer 的核心计算公式为:

其中 ,。

GlobalPointer 的核心思想类似 attention的打分机制,将多个实体类型的识别视为 Muti-head机制,将每一个head视为一种实体类型识别任务,最后利用attention的score(QK)作为最后的打分。

为考虑Start和end之间距离的关键信息,作者在此基础上引入了旋转式位置编码(RoPE),在其文中显示引入位置信息能给结果带来极大提升,符合预期先验。

classGlobalPointer(Module):
"""全局指针模块
将序列的每个(start,end)作为整体来进行判断
"""
def__init__(self,heads,head_size,hidden_size,RoPE=True):
super(GlobalPointer,self).__init__()
self.heads=heads
self.head_size=head_size
self.RoPE=RoPE
self.dense=nn.Linear(hidden_size,self.head_size*self.heads*2)

defforward(self,inputs,mask=None):
inputs=self.dense(inputs)
inputs=torch.split(inputs,self.head_size*2,dim=-1)
inputs=torch.stack(inputs,dim=-2)
qw,kw=inputs[...,:self.head_size],inputs[...,self.head_size:]
#RoPE编码
ifself.RoPE:
pos=SinusoidalPositionEmbedding(self.head_size,'zero')(inputs)
cos_pos=pos[...,None,1::2].repeat(1,1,1,2)
sin_pos=pos[...,None,::2].repeat(1,1,1,2)
qw2=torch.stack([-qw[...,1::2],qw[...,::2]],4)
qw2=torch.reshape(qw2,qw.shape)
qw=qw*cos_pos+qw2*sin_pos
kw2=torch.stack([-kw[...,1::2],kw[...,::2]],4)
kw2=torch.reshape(kw2,kw.shape)
kw=kw*cos_pos+kw2*sin_pos
#计算内积
logits=torch.einsum('bmhd,bnhd->bhmn',qw,kw)
#排除padding,排除下三角
logits=add_mask_tril(logits,mask)
returnlogits/self.head_size**0.5

3.2 TPLinker

TPLinker 来自论文《TPLinker: Single-stage Joint Extraction of Entities and Relations Through Token Pair Linking》[2]。

TPLinker 原本是为解决实体关系抽取设计的方法,原型为《Joint entity recognition and relation extraction as a multi-head selection problem》[3]论文中的 Muti-head selection机制。此处选取其中用于识别实体部分的机制,作为对比方法。

TPLinker中相应的计算公式如下:

其中

与GlobalPointer不同的是,GlobalPointer 是乘性的,而 Muti-head是加性的。这两种机制,谁的效果更好,我们无法仅通过理论进行分析,因此需要做相应的对比实验,从结果进行倒推。但是在实际实现的过程中,笔者发现加性比乘性占用更多的内存,但是与GlobalPointer中不同的是,加性仍然能实现快速并行,需要在计算设计上加入一些技巧。

classMutiHeadSelection(Module):

def__init__(self,hidden_size,c_size,abPosition=False,rePosition=False,maxlen=None,max_relative=None):
super(MutiHeadSelection,self).__init__()
self.hidden_size=hidden_size
self.c_size=c_size
self.abPosition=abPosition
self.rePosition=rePosition
self.Wh=nn.Linear(hidden_size*2,self.hidden_size)
self.Wo=nn.Linear(self.hidden_size,self.c_size)
ifself.rePosition:
self.relative_positions_encoding=relative_position_encoding(max_length=maxlen,
depth=2*hidden_size,max_relative_position=max_relative)

defforward(self,inputs,mask=None):
input_length=inputs.shape[1]
batch_size=inputs.shape[0]
ifself.abPosition:
#由于为加性拼接,我们无法使用RoPE,因此这里直接使用绝对位置编码
inputs=SinusoidalPositionEmbedding(self.hidden_size,'add')(inputs)
x1=torch.unsqueeze(inputs,1)
x2=torch.unsqueeze(inputs,2)
x1=x1.repeat(1,input_length,1,1)
x2=x2.repeat(1,1,input_length,1)
concat_x=torch.cat([x2,x1],dim=-1)
#与TPLinker原论文中不同的是,通过重复+拼接的方法构建的矩阵能满足并行计算的要求。
ifself.rePosition:
#如果使用相对位置编码,我们则直接在矩阵上实现相加
relations_keys=self.relative_positions_encoding[:input_length,:input_length,:].to(inputs.device)
concat_x+=relations_keys
hij=torch.tanh(self.Wh(concat_x))
logits=self.Wo(hij)
logits=logits.permute(0,3,1,2)
logits=add_mask_tril(logits,mask)
returnlogits

3.3 Tencent Muti-head

《EMPIRICAL ANALYSIS OF UNLABELED ENTITY PROBLEM IN NAMED ENTITY RECOGNITION》[4] 提出了一种基于片段标注解决实体数据标注缺失的训练方法,并在部分数据集上达到了SOTA。关注其实体矩阵构建模块,相当于Muti-head的升级版,因此我把它叫做Tencent Muti-head。

Tencent Muti-head的计算公式如下:

其中

与TPLinker相比,Tencent Muti-head在加性的基础上加入了更多信息交互元素,比如 (作差与点乘),但同时也提高了内存的占用量。

classTxMutihead(Module):

def__init__(self,hidden_size,c_size,abPosition=False,rePosition=False,maxlen=None,max_relative=None):
super(TxMutihead,self).__init__()
self.hidden_size=hidden_size
self.c_size=c_size
self.abPosition=abPosition
self.rePosition=rePosition
self.Wh=nn.Linear(hidden_size*4,self.hidden_size)
self.Wo=nn.Linear(self.hidden_size,self.c_size)
ifself.rePosition:
self.relative_positions_encoding=relative_position_encoding(max_length=maxlen,
depth=4*hidden_size,max_relative_position=max_relative)

defforward(self,inputs,mask=None):
input_length=inputs.shape[1]
batch_size=inputs.shape[0]
ifself.abPosition:
#由于为加性拼接,我们无法使用RoPE,因此这里直接使用绝对位置编码
inputs=SinusoidalPositionEmbedding(self.hidden_size,'add')(inputs)
x1=torch.unsqueeze(inputs,1)
x2=torch.unsqueeze(inputs,2)
x1=x1.repeat(1,input_length,1,1)
x2=x2.repeat(1,1,input_length,1)
concat_x=torch.cat([x2,x1,x2-x1,x2.mul(x1)],dim=-1)
ifself.rePosition:
relations_keys=self.relative_positions_encoding[:input_length,:input_length,:].to(inputs.device)
concat_x+=relations_keys
hij=torch.tanh(self.Wh(concat_x))
logits=self.Wo(hij)
logits=logits.permute(0,3,1,2)
logits=add_mask_tril(logits,mask)
returnlogits

3.4 Deep Biaffine

此处使用的双仿射结构出自《Named Entity Recognition as Dependency Parsing》[5]。原文用于识别实体依存关系,因此也可以直接用于实体命名识别。

Deep Biaffine的计算公式如下:

简单来说双仿射分别 为头 为尾的实体类别后验概率建模 + 对 或 为尾的实体类别的后验概率分别建模 + 对实体类别 的先验概率建模。

不难看出Deep Biaffine是加性与乘性的结合在笔者复现的关系抽取任务中,双仿射确实带来的一定提升,但这种建模思路在实体识别中是否有效还有待验证

classBiaffine(Module):

def__init__(self,in_size,out_size,Position=False):
super(Biaffine,self).__init__()
self.out_size=out_size
self.weight1=Parameter(torch.Tensor(in_size,out_size,in_size))
self.weight2=Parameter(torch.Tensor(2*in_size+1,out_size))
self.Position=Position
self.reset_parameters()

defreset_parameters(self):
torch.nn.init.kaiming_uniform_(self.weight1,a=math.sqrt(5))
torch.nn.init.kaiming_uniform_(self.weight2,a=math.sqrt(5))

defforward(self,inputs,mask=None):
input_length=inputs.shape[1]
hidden_size=inputs.shape[-1]
ifself.Position:
#引入绝对位置编码,在矩阵乘法时可以转化为相对位置信息
inputs=SinusoidalPositionEmbedding(hidden_size,'add')(inputs)
x1=torch.unsqueeze(inputs,1)
x2=torch.unsqueeze(inputs,2)
x1=x1.repeat(1,input_length,1,1)
x2=x2.repeat(1,1,input_length,1)
concat_x=torch.cat([x2,x1],dim=-1)
concat_x=torch.cat([concat_x,torch.ones_like(concat_x[...,:1])],dim=-1)
#bxi,oij,byj->boxy
logits_1=torch.einsum('bxi,ioj,byj->bxyo',inputs,self.weight1,inputs)
logits_2=torch.einsum('bijy,yo->bijo',concat_x,self.weight2)
logits=logits_1+logits_2
logits=logits.permute(0,3,1,2)
logits=add_mask_tril(logits,mask)
returnlogits

4. 实验结果

实验所用的GPU为: P40 24G (x1)。为了把各方法的内存占用情况考虑在内,本次对比实验全都在一张P40 24G的GPU上进行,并把Batch_size开到最大

仅GlobalPointer可达到16;

Tencent Muti-head batch_size只能达到4。

Tencent Muti-head因为需要构建超大矩阵,所以占用内存较大,batch_size最大只能到4。从中,我们可以看出GlobalPointer的性能优势。

需要注意的是,我们这里只比较了各方法在训练过程中在验证集上的最好表现

dc545baa-3fef-11ed-b1c7-dac502259ad0.png

总结

GlobalPointer作为乘性方法,在空间内存占用上明显优于其他方法,并且训练速度较快,能达到一个具有竞争力的效果

TPLinker 和 Tencent Muti-head作为加性方法,在优化过程中均表现出 相对位置编码 > 绝对位置编码 > 不加入位置编码 的特征。这意味着在通过构建实体矩阵进行实体命名识别时位置信息具有绝对重要的优势,且直接引入相对位置信息较优。

在绝对位置编码和不加入位置编码的测试中Tencent Muti-head的效果明显优于TPLinker而两者均差于GlobalPointer,但在引入相对位置信息后Tencent Muti-head略微超越了GlobalPointer,而TPLinker提点显著,作为Tencent Muti-head的原型在最高得分上甚至可能有更好的表现。

Biaffine双仿射表现不佳,意味着这种建模思路不适合用于实体命名识别。

计算资源有限的情况下GlobalPointer是最优的baseline选择,如果拥有足够的计算资源且对训练、推理时间的要求较为宽松,尝试使用TPLinker/Tencent Muti-head + 相对位置编码或许能取得更好的效果。

审核编辑:彭静
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 编码器
    +关注

    关注

    41

    文章

    3361

    浏览量

    131555
  • 内存
    +关注

    关注

    8

    文章

    2767

    浏览量

    72772
  • 解码
    +关注

    关注

    0

    文章

    171

    浏览量

    27132

原文标题:总结

文章出处:【微信号:zenRRan,微信公众号:深度学习自然语言处理】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    HanLP分词命名实体提取详解

    可能词) 5.极速词典分词(速度快,精度一般) 6.用户自定义词典 7.标准分词(HMM-Viterbi) 命名实体识别 1.实体机构名识别(层叠HMM-Viterbi) 2.中国人名
    发表于 01-11 14:32

    基于结构化感知机的词性标注与命名实体识别框架

    `上周就关于《结构化感知机标注框架的内容》已经分享了一篇《分词工具Hanlp基于感知机的中文分词框架》,本篇接上一篇内容,继续分享词性标注与命名实体识别框架的内容。词性标注训练词性标注是分词后紧接着
    发表于 04-08 14:57

    HanLP-命名实体识别总结

    的中国人名自动识别研究》,大家可以百度一下看看 地名识别 理论指导文章为:《基于层叠隐马尔可夫模型的中文命名实体识别》 机构名识别 机构名的
    发表于 07-31 13:11

    CCS5.5全套实验程序

    CCS5.5 全套实验程序 百度云下载CCS5.5 全套实验程序 百度云下载
    发表于 01-07 17:32 45次下载

    套实验板原理图及Pcb

    套实验板原理图及Pcb,PCB学习好资料,欢迎下载学习。
    发表于 03-23 09:49 0次下载

    MSP430原理与应用教程课件配套实验例程

    MSP430原理与应用教程课件配套实验例程.rar
    发表于 07-12 11:54 8次下载

    面向实体识别的聚类算法

    实体识别是数据质量的一个重要方面,对于大数据处理不可或缺,已有的实体识别研究工作聚焦于数据对象相似度算法、分块技术和监督的实体
    发表于 01-09 15:52 0次下载

    基于C51C语言的嵌套实

    常量与变量本质是值,不同的变量只是存储结构的不通。表达式最终也是一个值,所以可以通用,可以嵌套
    发表于 06-29 10:17 946次阅读

    如何在OpenMP中使用嵌套

    此网络研讨会讨论了使用热门团队在OpenMP中使用嵌套的成功示例,并解释了利用嵌套并行机会的最佳实践。
    的头像 发表于 11-07 06:52 2391次阅读

    基于深度信念网络的实体识别算法

    的传感器通信能力有限,设计基于深度信念网络的实体识别算法,通过将热门与冷门实体状态信息分别存储于边缘服务器与云端,节省边缘服务器的存储空间与计算开销。仿真结果表明,与云端数据共享搜索方法Sedasc和层次化搜索方
    发表于 03-25 15:35 16次下载
    基于深度信念网络的<b class='flag-5'>实体</b><b class='flag-5'>识别</b>算法

    命名实体识别的迁移学习相关研究分析

    命名实体识别(NER)是自然语言处理的核心应用任务之一。传统和深度命名实体识别方法严重依赖于大量具有相同分布的标注训练数据,模型可移植性差。然而在实际应用中数据往往都是小数据、个性化数
    发表于 04-02 15:15 8次下载
    命名<b class='flag-5'>实体</b><b class='flag-5'>识别</b>的迁移学习相关研究分析

    基于字语言模型的中文命名实体识别系统

    命名实体识别(NER)旨在识别出文本中的专有名词,并对其进行分类。由于用于监督学习的训练数据通常由人工标主,耗时耗力,因此很难得到大规模的标注数据。为解决中文命名实体
    发表于 04-08 14:36 14次下载
    基于字语言模型的中文命名<b class='flag-5'>实体</b><b class='flag-5'>识别</b>系统

    关于边界检测增强的中文命名实体识别

    引言 命名实体识别(Named Entity Recognition,NER)是自然语言处理领域的一个基础任务,是信息抽取等许多任务的子任务,旨在识别非结构化文本中属于预先定义的类别的命名实体
    的头像 发表于 09-22 16:05 2732次阅读

    基于序列标注的实体识别所存在的问题

    实体识别通常被当作序列标注任务来做,序列标注模型需要对实体边界和实体类别进行预测,从而识别和提取出相应的命名
    的头像 发表于 07-28 11:08 1402次阅读

    介绍python列表的边界和嵌套

    本文介绍python列表的边界和嵌套。只能访问python列表范围内的项,python列表可以嵌套python列表。
    的头像 发表于 02-27 14:49 681次阅读