命名实体识别(NER)全解析

一 、什么是命名实体识别?

命名实体识别(Named Entity Recognition,简称NER),又称作“专名识别”,是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。通常包括两部分:(1)实体边界识别;(2) 确定实体类别(人名、地名、机构名或其他)。

NER是NLP中一项基础性关键任务。从自然语言处理的流程来看,NER可以看作词法分析中未登录词识别的一种,是未登录词中数量最多、识别难度最大、对分词效果影响最大问题。同时NER也是关系抽取、事件抽取、知识图谱、机器翻译、问答系统等诸多NLP任务的基础。

NER当前并不算是一个大热的研究课题,因为学术界部分学者认为这是一个已经解决的问题。当然也有学者认为这个问题还没有得到很好地解决,原因主要有:命名实体识别只是在有限的文本类型(主要是新闻语料中)和实体类别(主要是人名、地名、组织机构名)中取得了不错的效果;与其他信息检索领域相比,实体命名评测预料较小,容易产生过拟合;命名实体识别更侧重高召回率,但在信息检索领域,高准确率更重要;通用的识别多种类型的命名实体的系统性能很差。

命名实体识别的研究主体一般包括3大类(实体类、时间类和数字类)和7小类(人名、地名、机构名、时间、日期、货币和百分比)命名实体。评判一个命名实体是否被正确识别包括两个方面:实体的边界是否正确;实体的类型是否标注正确。

从语言分析的全过程来看, 命名实体识别属于词法分析中未登录词识别的范畴。命名实体识别是未登录词中数量最多、识别难度最大、对分词效果影响最大的问题,同时它也是信息抽取、信息检索、机器翻译、问答系统等多种自然语言处理技术必不可少的组成部分。

事件检测:地点、时间、人物是时间的几个基本构成部分,在构建事件的摘要时,可以突出相关人物、地点、单位等。在事件搜索系统中,相关的人物、时间、地点可以作为索引关键词。事件的几个构成部分之间的关系,从语义层面更详细的描述了事件。

信息检索:命名实体可以用来提高和改进检索系统的效果,当用户输入“重大”时,可以发现用户更想检索的是“重庆大学”,而不是其对应的形容词含义。此外,在建立倒排索引的时候,如果把命名实体切成多个单词,将会导致查询效率降低。此外,搜索引擎正在向语义理解、计算答案的方向发展。

语义网络:语义网络中一般包括概念和实例及其对应的关系,例如“国家”是一个概念,中国是一个实例,“中国”是一个“国家”表达实体与概念之间的关系。语义网络中的实例有很大一部分是命名实体。

机器翻译:命名实体的翻译常会有一些特殊翻译规则,例如中国人民翻译成英文时要使用名字的拼音来表示,有名在前姓在后的规则,而普通的词语要翻译成对应的英文单词。准确识别出文本中的命名实体,对提高机器翻译的效果有重要的意义。

问答系统:准确的识别出问题的各个组成部分特别重要,问题的相关领域,相关概念。目前,大部分问答系统都只能搜索答案,而不能计算答案。搜索答案进行关键词的匹配,用户根据搜索结果人工提取答案,而更加友好的方式是把答案计算好呈现给用户。问答系统中有一部分问题需要考虑到实体之间的关系,例如“美国第四十五届总统”,目前的搜索引擎会以特殊的格式返回答案“特朗普”。

命名实体识别当前并不是一个大热的研究课题,因为学术界部分认为这是一个已经解决了的问题,但是也有学者认为这个问题还没有得到很好地解决,原因主要有:命名实体识别只是在有限的文本类型(主要是新闻语料中)和实体类别(主要是人名、地名)中取得了效果;与其他信息检索领域相比,实体命名评测预料较小,容易产生过拟合;命名实体识别更侧重高召回率,但在信息检索领域,高准确率更重要;通用的识别多种类型的命名实体的系统性很差。

同时,中文的命名实体识别与英文的相比,挑战更大,目前未解决的难题更多。英语中的命名实体具有比较明显的形式标志,即实体中的每个词的第一个字母要大写,所以实体边界识别相对容易,任务的重点是确定实体的类别。和英语相比,汉语命名实体识别任务更加复杂,而且相对于实体类别标注子任务,实体边界的识别更加困难。

汉语命名实体识别的难点主要存在于:(1)汉语文本没有类似英文文本中空格之类的显式标示词的边界标示符,命名实体识别的第一步就是确定词的边界,即分词;(2)汉语分词和命名实体识别互相影响;(3)除了英语中定义的实体,外国人名译名和地名译名是存在于汉语中的两类特殊实体类型;(4)现代汉语文本,尤其是网络汉语文本,常出现中英文交替使用,这时汉语命名实体识别的任务还包括识别其中的英文命名实体;(5)不同的命名实体具有不同的内部特征,不可能用一个统一的模型来刻画所有的实体内部特征。

最后,现代汉语日新月异的发展给命名实体识别也带来了新的困难。

其一,标注语料老旧,覆盖不全。譬如说,近年来起名字的习惯用字与以往相比有很大的变化,以及各种复姓识别、国外译名、网络红人、虚拟人物和昵称的涌现。

其二,命名实体歧义严重,消歧困难。譬如下列句子:

余则成潜伏在敌后 VS 余则成潜伏在线

我和你一起唱《我和你》吧。

看完吓死你:惊悚视频,胆小勿入。

当前命名实体识别的主要技术方法分为:基于规则和词典的方法、基于统计的方法、二者混合的方法等。

1基于规则和词典的方法

基于规则的方法多采用语言学专家手工构造规则模板,选用特征包括统计信息、标点符号、关键字、指示词和方向词、位置词(如尾字)、中心词等方法,以模式和字符串相匹配为主要手段,这类系统大多依赖于知识库和词典的建立。

基于规则和词典的方法是命名实体识别中最早使用的方法,它们依赖于手工规则的系统, 都使用命名实体库, 而且对每一个规则都赋予权值。当遇到规则冲突的时候, 选择权值最高的规则来判别命名实体的类型。一般而言,当提取的规则能比较精确地反映语言现象时,基于规则的方法性能要优于基于统计的方法。但是这些规则往往依赖于具体语言、领域和文本风格,编制过程耗时且难以涵盖所有的语言现象,特别容易产生错误,系统可移植性不好,对于不同的系统需要语言学专家重新书写规则。

基于规则的方法的另外一个缺点是代价太大,存在系统建设周期长、移植性差而且需要建立不同领域知识库作为辅助以提高系统识别能力等问题。

2基于统计的方法

基于统计机器学习的方法主要包括:隐马尔可夫模型(HiddenMarkovMode,HMM)、最大熵(MaxmiumEntropy,ME)、支持向量机(Support VectorMachine,SVM)、条件随机场(ConditionalRandom Fields,CRF)等。

在这4种学习方法中,最大熵模型结构紧凑,具有较好的通用性,主要缺点是训练时间复杂性非常高,有时甚至导致训练代价难以承受,另外由于需要明确的归一化计算,导致开销比较大。而条件随机场为命名实体识别提供了一个特征灵活、全局最优的标注框架,但同时存在收敛速度慢、训练时间长的问题。一般说来,最大熵和支持向量机在正确率上要比隐马尔可夫模型高一些,但是隐马尔可夫模型在训练和识别时的速度要快一些,主要是由于在利用Viterbi算法求解命名实体类别序列的效率较高。隐马尔可夫模型更适用于一些对实时性有要求以及像信息检索这样需要处理大量文本的应用,如短文本命名实体识别。

基于统计的方法对特征选取的要求较高,需要从文本中选择对该项任务有影响的各种特征,并将这些特征加入到特征向量中。依据特定命名实体识别所面临的主要困难和所表现出的特性,考虑选择能有效反映该类实体特性的特征集合。主要做法是通过对训练语料所包含的语言信息进行统计和分析,从训练语料中挖掘出特征。有关特征可以分为具体的单词特征、上下文特征、词典及词性特征、停用词特征、核心词特征以及语义特征等。

基于统计的方法对语料库的依赖也比较大,而可以用来建设和评估命名实体识别系统的大规模通用语料库又比较少,这是此种方法的又一大制约。

3混合方法

自然语言处理并不完全是一个随机过程,单独使用基于统计的方法使状态搜索空间非常庞大,必须借助规则知识提前进行过滤修剪处理。目前几乎没有单纯使用统计模型而不使用规则知识的命名实体识别系统,在很多情况下是使用混合方法,主要包括:

a.统计学习方法之间或内部层叠融合。

b. 规则、词典和机器学习方法之间的融合,其核心是融合方法技术。在基于统计的学习方法中引入部分规则,将机器学习和人工知识结合起来。

c. 将各类模型、算法结合起来,将前一级模型的结果作为下一级的训练数据,并用这些训练数据对模型进行训练,得到下一级模型。

这种方法在具体实现过程中需要考虑怎样高效地将两种方法结合起来,采用什么样的融合技术。由于命名实体识别在很大程度上依赖于分类技术,在分类方面可以采用的融合技术主要包括如Voting,XVoting,GradingVa,l Grading等。

目前解决命名实体识别问题的主导技术就是监督式学习,这项技术包括Hidden Markov

Models,Decision Trees, Maximun Entropy Models, Support Vector Machines 和Conditional Random Fields.他们都需要一个庞大的注释语料库,储存大量实体列表并根据那些具有区分能力的特征实际各种用于消除歧义的规则。

半监督式学习是最近兴起的一项技术,主要技术成为“bootstrapping",它也包括了一些监督式学习的方法,例如,都需要从一系列种子来开始学习的过程,比如一个主要是别疾病名称的系统运行之前就需要用户提供几个疾病实体的名称,然后系统就开始搜索包含这些名称的文本,并根据上下文的线索和一些其他的规则来找出相同文本中的其他疾病实例的名称。之后系统再用新找到的实体作为新的种子,重读的在文本中进行搜索的过程并寻找新的实例。通过多次的重复,可以从大量的文本中找出大量的疾病名称实体。近期进行的半监督的命名实体识别实验的结果显示,其性能和基线监督方法的性能相比具有很大竞争力。

命名实体识别近年来在多媒体索引、半监督和无监督的学习、复杂语言环境和机器翻译等方面取得大量新的研究成果。随着半监督的学习和无监督的学习方法不断被引入到这个领域, 采用未标注语料集等方法将逐步解决语料库不足的问题。在复杂语言现象(如借喻等)研究以及命名实体识别系统与机器翻译的互提高方面, 也有广阔的发展空间。命名实体识别将在更加开放的领域中, 综合各方面的发展成果, 为自然语言处理的深层次发展奠定更坚实的基础。

 汉语作为象形文字,相比于英文等拼音文字来说,针对中文的NER任务来说往往要更有挑战性,下面列举几点:

      (1) 中文文本里不像英文那样有空格作为词语的界限标志,而且“词”在中文里本来就是一个很模糊的概念,中文也不具备英文中的字母大小写等形态指示

      (2) 中文的用字灵活多变,有些词语在脱离上下文语境的情况下无法判断是否是命名实体,而且就算是命名实体,当其处在不同的上下文语境下也可能是不同的实体类型

      (3) 命名实体存在嵌套现象,如“北京大学第三医院”这一组织机构名中还嵌套着同样可以作为组织机构名的“北京大学”,而且这种现象在组织机构名中尤其严重

      (4) 中文里广泛存在简化表达现象,如“北医三院”、“国科大”,乃至简化表达构成的命名实体,如“国科大桥”。

      NER 的各种方法(由于出版年限较早,未涵盖神经网络方法),这里笼统地摘取三类方法:

      1. 基于规则的方法:利用手工编写的规则,将文本与规则进行匹配来识别出命名实体。例如,对于中文来说,“说”、“老师”等词语可作为人名的下文,“大学”、“医院”等词语可作为组织机构名的结尾,还可以利用到词性、句法信息。在构建规则的过程中往往需要大量的语言学知识,不同语言的识别规则不尽相同,而且需要谨慎处理规则之间的冲突问题;此外,构建规则的过程费时费力、可移植性不好。

用规则做命名实体识别——NER系列(一)

兑现自己上一篇立下的flag,从头开始写这几个月对命名实体识别这个任务的探索历程。这是这个系列的第一篇——用规则来做命名实体识别。

1.什么是命名实体识别

命名实体识别(Named Entity Recognition,简称NER),是一个基本的NLP任务,按照传统,下面是百度百科对它的解释:

 一般来说,命名实体识别的任务就是识别出待处理文本中三大类(实体类、时间类和数字类)、七小类(人名、机构名、地名、时间、日期、货币和百分比)命名实体。

当然了,你可以自己定义这个任务,比如识别商品名,也是这个任务范畴。

说得更泛一点,命名实体识别本质上是序列标注问题。

2.为什么要做命名实体识别

第一次接触这个任务的时候,我也问过这个问题,甚至还觉得我直接搞一个数据库,把所有实体都存进去,然后直接匹配不就好了。

事实证明我太天真了。因为,命名实体识别存在以下几个问题:

  1. 实体数量巨大,而且在不断增长。举个例子,每年那么多新的机构、公司,要收集起来也是一个头条的问题。
  2. 实体组合多。很多实体都是组合词,包括中英文组合也很多。
  3. 歧义多。比如拿我的名字举个例,“李鹏飞起来了”,是不是可以识别出两个人名。等等等等,反正,这个任务是有意义的。

3.用规则来做命名实体识别

先说一句,命名实体识别有非常多的方法,其中基于规则是最古老、最笨的方法,但是为了熟悉这个任务,以及认识到这个方法有多麻烦,我还是进行了探索。

2017CCF BDCI(CCF 大数据与计算智能大赛)有个赛题:《基于机构实体的智能摘要和风险等级识别》,里面有个子任务就是识别机构实体。

我先用规则来做,因为语料集里面大多数的实体,都是有规律的,比如:命名实体数据

上面命名实体都用颜色标注出来了,可以看到,基本上的组成机构是这样的:

若干地名+若干其他成分+若干特征词

其中,特征词指的是诸如公司有限公司管理局这些词语。

那么这个基于规则的实体识别算法就呼之欲出了。步骤如下:

1.分词

我用的是jieba分词:https://github.com/fxsjy/jieba

将原始文本进行分词,同时使用jieba的词性标注功能。(这个步骤也可以去停用词,依你的语料集而定)

2.收集特征词词典

将训练文本中的实体取出来,对于每一个实体,进行分词,取最后一个词,存入词典,最后做去重。

如果想把词典做的更细致,可以借助外部词典,比如hanlp的词典,具体他的词典构造,我将在之后写hmm模型的时候作介绍。

3.标注序列

对文本分词后的词语,进行标注。

①如果这个词语的词性为ns(地名),则将这个词标记为S。(如果不想用jieba的词性标注,这里也可以用地名词典来识别,需自己整理)

②如果这个词语在【步骤2】的特征词词典里,则将这个词标记为E。

③其它情况,标记为O。

这样,我们就把输入的句子转化为了标注序列。比如:

我 来到 北京 天安门

就会被标记为:

OOSE

4.正则匹配,识别实体

我们上面说到,对于这个语料集,我们发现实体的组成规律是这样的:

若干地名+若干其他成分+若干特征词

那么,我们只需要对标注后的序列做一个正则表达式的匹配即可。模式为:

S+O*E+

上面表达式的意思是,必须以1个以上地名开头,以1个以上特征词结尾,中间成分,数量就无所谓了。

匹配出符合要求的字符,将其背后的中文组合起来,就是我们要的实体。

你可以任意定义标注和模式,来适应你的规则。


方法很笨很暴力。

但是你不得不说,如果有的任务很简单,很有规律,用规则来提取实体是个不错的选择,因为它不费时,不费力,能解决问题。


      2. 基于特征模板的方法: 

      统计机器学习方法将 NER 视作序列标注任务,利用大规模语料来学习出标注模型,从而对句子的各个位置进行标注。常用的应用到 NER 任务中的模型包括生成式模型HMM、判别式模型CRF等。比较流行的方法是特征模板 + CRF的方案:特征模板通常是人工定义的一些二值特征函数,试图挖掘命名实体内部以及上下文的构成特点。对于句子中的给定位置来说,提特征的位置是一个窗口,即上下文位置。而且,不同的特征模板之间可以进行组合来形成一个新的特征模板。CRF的优点在于其为一个位置进行标注的过程中可以利用到此前已经标注的信息,利用Viterbi解码来得到最优序列。对句子中的各个位置提取特征时,满足条件的特征取值为1,不满足条件的特征取值为0;然后把特征喂给CRF,training阶段建模标签的转移,进而在inference阶段为测试句子的各个位置做标注。

      3. 基于神经网络的方法

      近年来,随着硬件能力的发展以及词的分布式表示(word embedding)的出现,神经网络成为可以有效处理许多NLP任务的模型。这类方法对于序列标注任务(如CWS、POS、NER)的处理方式是类似的,将token从离散one-hot表示映射到低维空间中成为稠密的embedding,随后将句子的embedding序列输入到RNN中,用神经网络自动提取特征,Softmax来预测每个token的标签。这种方法使得模型的训练成为一个端到端的整体过程,而非传统的pipeline,不依赖特征工程,是一种数据驱动的方法;但网络变种多、对参数设置依赖大,模型可解释性差。此外,这种方法的一个缺点是对每个token打标签的过程中是独立的分类,不能直接利用上文已经预测的标签(只能靠隐状态传递上文信息),进而导致预测出的标签序列可能是非法的,例如标签B-PER后面是不可能紧跟着I-LOC的,但Softmax不会利用到这个信息。

      学界提出了 LSTM-CRF 模型做序列标注。文献[4][5]在LSTM层后接入CRF层来做句子级别的标签预测,使得标注过程不再是对各个token独立分类。引入CRF这个idea最早其实可以追溯到文献[6]中。文献[5]还提出在英文NER任务中先使用LSTM来为每个单词由字母构造词并拼接到词向量后再输入到LSTM中,以捕捉单词的前后缀等字母形态特征。文献[8]将这个套路用在了中文NER任务中,用偏旁部首来构造汉字。

先谈谈几种标记传统的方法

NER一直是NLP领域中的研究热点,从早期基于词典和规则的方法,到传统机器学习的方法,到近年来基于深度学习的方法,NER研究进展的大概趋势大致如下图所示。

    在基于机器学习的方法中,NER被当作是序列标注问题。与分类问题相比,序列标注问题中当前的预测标签不仅与当前的输入特征相关,还与之前的预测标签相关,即预测标签序列之间是有强相互依赖关系的。例如,使用BIO标签策略进行NER时,正确的标签序列中标签O后面是不会接标签I的。

    在传统机器学习中,条件随机场(Conditional Random Field,CRF)是NER目前的主流模型。它的目标函数不仅考虑输入的状态特征函数,而且还包含了标签转移特征函数。在训练时可以使用SGD学习模型参数。在已知模型时,给输入序列求预测输出序列即求使目标函数最大化的最优序列,是一个动态规划问题,可以使用维特比算法进行解码。

在传统机器学习方法中,常用的特征如下:

接下里我们重点看看如何使用神经网络结构来进行NER 。

NER、分词都可以看作是序列的标注问题,而这一类问题比较传统的方法是以以马尔科夫模型(HMM)、条件随机场(CRF)为代表的概率图模型,还有最大熵隐马尔可夫模型(MEMM),这三者在之前都取得了非常不错的结果,近几年随着深度学习的兴起,深度神经网络也加入到NLP的任务中,跟CV一样,网络层起到的作用依然是特征提取的过程,深度神经网络+概率图模型成为处理NLP任务的一个非常好的解决方案。在这粗略的梳理下HMM, MEMM,CRF三个概率图模型。(MEMM 需要先理解最大熵模型,在李航老师的《统计学习方法》中,最大熵模型是跟逻辑斯蒂回归放到了统一章节中,理解该节的时候不妨参照着概率图模型中的条件随机场一块理解)

Naive Bayes 跟 HMM 是generative model, LR 跟 CRF 是discriminative model

  • HMM(generative model)

齐次马尔科夫性:即某时刻的状态只跟前一状态有关;
观测独立性假设:t时刻的观测结果只与该时刻的状态有关;

jieba分词工具,其中对未收录词的处理就使用HMM来实现的,通过viterbi算法,有兴趣的可以看下jieba的源码。

  • MEMM(discriminative model)
    最大熵马尔科夫模型,是对最大熵模型以及隐马尔可夫模型的扩展,最大熵模型是在符合所有约束条件下作出最不偏倚的假设,而这些约束条件就是我们所说的特征函数(特征函数的权重w是在通过拉格朗日乘子法求解过程中引入的权重)

    最大熵模型,与CRF极其相似,前者特征函数中并没有状态之间的相互关系

MEMM 是在每个状态状态节点都做了归一化,这也是导致标注偏移的原因

MEMM 抛弃了HMM中的观测独立性假设,保留了齐次马尔科夫性,是对HMM的一个改进。

  • CRF(discriminative model)


    对比CRF与MEMM两个模型,会发现二者在归一化存在差异,MEMM对每一项进行局部归一化后连乘,而CRF则是对全局归一化,也正因为这一项改进,CRF克服了标注偏移的问题。在这的特征函数f(),包括转移特征以及状态特征,在tensorflow的contrib.crf.crf_log_likelihood中的实现方式跟上边说的这些不太一致,后边会有解释。

NER 标注方法有很多种, 这里主要介绍常用的3种最常见。

IOB 标注法

IOB 标注法, 是 CoNLL 2003 采用的标注法, I 表示 inside, O 表示 Outside, B 表示 Begin。而标注的 label是 I-XXX 的, 表示这个字符, 在 XXX类命名实体的内部(inside)。B用于标记一个命名实体的开始。

比如:

Tom B-PER
hanks I-PER
is O
my O
name O

BIOESbu'ao'zhu

这是在 IOB方法上,扩展出的一个更复杂,但更完备的标注方法。其中 B表示这个词处于一个实体的开始(Begin), I 表示内部(inside), O 表示外部(outside), E 表示这个词处于一个实体的结束为止, S 表示,这个词是自己就可以组成一个实体(Single)

BIOES 是目前最通用的命名实体标注方法。

Markup

Makeup 是 OntoNotes 使用的标注方法, 思路比较简单, XML, 比如:

ENAMEX TYPE=”ORG”>DisneyENAMEX> is a global brand .

它用标签把 命名实体框出来, 然后,在 TYPE 上, 设置相应的类型。

当然, 还有很多其他的标注方法, 比如 IO, BMEWO 等等,感兴趣的读者可以 google一下。

下边再谈谈用语言处理包来进行实体的提取

二 、基于NLTK的命名实体识别:

NLTK:由宾夕法尼亚大学计算机和信息科学使用python语言实现的一种自然语言工具包,其收集的大量公开数据集、模型上提供了全面、易用的口,涵盖了分词、

词性标注(Part-Of-Speech tag, POS-tag)、命名实体识别(Named Entity Recognition, NER)、句法分析(Syntactic Parse)等各项NLP领域的功能。使用前需要先

下载NLTK,下载地址为:http://pypi.python.org/pypi/nltk,安装完成后,在python环境下输入import nltk测试是否安装成功,然后输入nltk.download()下载nltk所

需要的数据包,完成安装。

Python代码实现(注意文件的编码格式为utf-8无BOM格式):

 
  1. # -*- coding: utf-8 -*-

  2. import sys

  3. reload(sys)

  4. sys.setdefaultencoding('utf8') #让cmd识别正确的编码

  5. import nltk

  6. newfile = open('news.txt')

  7. text = newfile.read() #读取文件

  8. tokens = nltk.word_tokenize(text) #分词

  9. tagged = nltk.pos_tag(tokens) #词性标注

  10. entities = nltk.chunk.ne_chunk(tagged) #命名实体识别

  11. a1=str(entities) #将文件转换为字符串

  12. file_object = open('out.txt', 'w')

  13. file_object.write(a1) #写入到文件中

  14. file_object.close( )

  15. print entities

具体的方法可参考NLTK官网介绍:http://www.nltk.org/,输出的结果为:

 
  1. >>> entities = nltk.chunk.ne_chunk(tagged)

  2. >>> entities

  3. Tree('S', [('At', 'IN'), ('eight', 'CD'), ("o'clock", 'JJ'),

  4. ('on', 'IN'), ('Thursday', 'NNP'), ('morning', 'NN'),

  5. Tree('PERSON', [('Arthur', 'NNP')]),

  6. ('did', 'VBD'), ("n't", 'RB'), ('feel', 'VB'),

  7. ('very', 'RB'), ('good', 'JJ'), ('.', '.')])

当然为了方便查看,我们可以以树结构的形式把结果绘制出来:

 
  1. >>> from nltk.corpus import treebank

  2. >>> t = treebank.parsed_sents('wsj_0001.mrg')[0]

  3. >>> t.draw()


此外,关于标注词性的解释,我们可以参考以下文章:http://blog.csdn.net/john159151/article/details/50255101

三 、基于Stanford的NER:

Stanford Named Entity Recognizer (NER)是斯坦福大学自然语言研究小组发布的成果之一,主页是:http://nlp.stanford.edu/software/CRF-NER.shtml。Stanford NER 是一个Java实现的命名实体识别(以下简称NER))程序。NER将文本中的实体按类标记出来,例如人名,公司名,地区,基因和蛋白质的名字等。

NER基于一个训练而得的Model(模型可识别出 Time, Location, Organization, Person, Money, Percent, Date)七类属性,其用于训练的数据即大量人工标记好的文本,理论上用于训练的数据量越大,NER的识别效果就越好。

因为原始的NER是基于java实现的,所以在使用Python编程之前,要确保自己电脑上已经安装了jar1.8的环境(否则会报关于Socket的错误)。

然后我们使用Pyner使用python语言实现命名实体识别。下载地址为:https://github.com/dat/pyner

安装Pyner:解压下载的Pyner,命令行中将工作目录切换到Pyner文件夹下, 输入命令 :python setup.py install 完成安装.

接下来,还需要下载StanfordNER工具包,下载地址为:http://nlp.stanford.edu/software/stanford-ner-2014-01-04.zip,然后在解压后的目录打开cmd命令窗体,执行,java -mx1000m -cp stanford-ner.jar edu.stanford.nlp.ie.NERServer -loadClassifier classifiers/english.muc.7class.distsim.crf.ser.gz -port 8080 -outputFormat inlineXML,直到结果为:Loading classifier from classifiers/english.muc.7class.distsim.crf.ser.gz ... done [1.2 sec].

        以上操作是因为斯坦福的命名实体识别是基于java的socket写的,所以必要保证有一个窗题与我们执行的命令通信。关于java的socket编程,可以参考以下文章:http://www.cnblogs.com/rond/p/3565113.html

最后,我们终于可以使用python编程实现NER了:

 
  1. import ner

  2. import sys

  3. import nltk

  4. reload(sys)

  5. sys.setdefaultencoding('utf8')

  6. newfile = open('news.txt')

  7. text = newfile.read()

  8. tagger = ner.SocketNER(host='localhost', port=8080)#socket编程

  9. result=tagger.get_entities(text) #stanford实现NER

  10. a1=str(result)

  11. file_object = open('outfile.txt', 'w')

  12. file_object.write(a1)

  13. file_object.close( )

  14. print result

以上是我对文本文件进行的测试,官网的案例https://github.com/dat/pyner运行结果为:

  1. >>> import ner

  2. >>> tagger = ner.SocketNER(host='localhost', port=8080)

  3. >>> tagger.get_entities("University of California is located in California, United States")

  4. {'LOCATION': ['California', 'United States'],

  5. 'ORGANIZATION': ['University of California']}

由此可见,stanford的命名实体识别可以更好的把地名,人名,机构名等标注出来。

四 、两种方法的比较:

我拿同一个文本文件用两种方法进行命名实体识别,结果如下:

NLTK:

Stanford:

比较两种方式,我们可以发现,NLTK下的命名实体识别更加倾向于分词和词性标准,虽然它也会将组织名,人名,地名等标注出来,但由于它把文件中的谓语,宾语等成分也标注了出来,造成了输出文本的冗余性,不利于读者很好的识别命名实体,需要我们对文本做进一步处理。NLTK下的命名实体识别的有点时,可以使用NLTK下的treebank包将文本绘制为树形,使结果更加清晰易读。相较而言,我更加倾向于Stanford的命名实体识别,它可以把Time, Location, Organization, Person, Money, Percent, Date七类实体很清晰的标注出来,而没有多余的词性。但由于NER是基于java开发的,所以在用python实现时可能由于jar包或是路径问题出现很多bug。

以上就是关于NLTK和stanford对英文文本的命名实体识别,关于自然语言处理中文文件,我们可以考虑jieba分词:
随着深度学习的兴起,将DNN模型应用到标签标注问题上,取得了不俗的结果。比较各模型的结果,一般来说, DNN之前,CRF的结果最好,应用中也最为广泛, DNN这把神器出来后,state-of-the-art的结果均已被DNN的各种模型占领。DNN重在特征的学习和表示,通过DNN学习特征,取代传统CRF中的特征工程,集合DNN和CRF各自的优点,是这一系列方法的主要思路。
下面介绍几种常见的DNN+CRF模型在命名实体识别问题上的具体应用。


1 文献调查

文献 年份 方法 conll2003 ,English, F1值
[1]Neural Architectures for Named Entity Recognition 2016.4 BiLSTM+CRF+character 90.94
[2]End-to-end Sequence Labeling via Bi-directional LSTM-CNNs-CRF 2016.5 BiLSTM+CNN+CRF+character 91.21
[3]LSTM-Based NeuroCRFs for Named Entity Recognition 2016.9 LSTM/BiLSTM+CRF 89.30/89.23
[4]Attending to Characters in Neural Sequence Labeling Models 2016.11 LSTM/BiLSTM+CRF 84.09
[5]Fast and Accurate Entity Recognition with Iterated Dilated Convolutions 2017.7 IDCNN+CRF 90.54±±0.18
[6]Named Entity Recognition with Gated Convolutional Neural Networks 2017 GCNN+CRF 91.24

以上模型结果存在差异一方面是因为模型结构和输入数据不同,另一方面是模型参数的设置不同,比如embedding的维度,lstm的隐藏状态htht 的维度等。以上数据仅作为参考,不作为绝对标准。

目前将神经网络与CRF模型相结合的NN/CNN/RNN-CRF模型成为了目前NER的主流模型。我认为对于CNN与RNN,并没有谁占据绝对的优势,各自有相应的优点。由于RNN有天然的序列结构,所以RNN-CRF使用更为广泛。基于神经网络结构的NER方法,继承了深度学习方法的优点,无需大量人工特征。只需词向量和字符向量就能达到主流水平,加入高质量的词典特征能够进一步提升效果。对于少量标注训练集问题,迁移学习,半监督学习应该是未来研究的重点。

2 命名实体识别模型

以上各模型可以概括为如下的结构,不同之处主要在于使用的是CNN还是RNN(LSTM,Bi-LSTM),以及输入数据(word-embeddding, character-representation)。

2.1 模型基本结构

给定观测序列

X=(x1,x2,⋯,xn),X=(x1,x2,⋯,xn),


假设矩阵 P∈n×kP∈n×k 是有DNN输出的得分矩阵(matrix of scores output).其中kk是需要标注的标签个数;Pi,jPi,j表示序列中的第ii 个word对应的第jj 标签的分数.假设上述序列预测标签为

y=(y1,y2,⋯,yn)y=(y1,y2,⋯,yn)

,定义该序列的得分为

s(X,y)=∑i=0nAyi,yi+1+∑i=1nPi,yi,s(X,y)=∑i=0nAyi,yi+1+∑i=1nPi,yi,


其中AA为转移矩阵,Ai,jAi,j 为从标签ii 跳转到标签 jj 的概率值.y0y0 和 ynyn 表示添加到输入序列上的startstart和endend对应的标签。因此X∈R(k+2)×(k+2)X∈R(k+2)×(k+2).
通过softmax函数对所有可能的标签序列的得分进行变换,得到各个序列对应的概率值:

p(y|X)=es(X,y)∑y~∈YXes(X,y~)p(y|X)=es(X,y)∑y~∈YXes(X,y~)


概率maxy,Xp(y|X)maxy,Xp(y|X) 对应的标签序列为最终的标签预测结果。在训练的时候则采用最大化对数概率:

log(p(y|X))=s(X,y)−log⎛⎝∑y~∈YXes(X,y~)⎞⎠log⁡(p(y|X))=s(X,y)−log⁡(∑y~∈YXes(X,y~))


其中YXYX 为序列XX 对应的所有可能的标签序列。最后,

y∗=argmaxy~∈YXs(X,y~)y∗=arg⁡maxy~∈YXs(X,y~)

2.2 几种具体的网络

2.2.1 RNN 网络(Bi-LSTM)

  • 文献[1]的输入数据为word-embedding dtdt以及 character-embedding htht。对于wtwt(对应的字符序列为(c1,c2,⋯,cR)(c1,c2,⋯,cR),采用Bi-LSTM生成character-embedding,即

    hi→=LSTM(ct,hi−1−→−),hi←=LSTM(ci,hi+1←−−),h=[hR−→,h1←−]hi→=LSTM(ct,hi−1→),hi←=LSTM(ci,hi+1←),h=[hR→,h1←]


    将生成的character-embedding 级联到 word-embedding, 作为每个word的featrure,送入到 Bi-LSTM, 最后将Bi-LSTM的输出送入到CRF层。其中character-embedding 采用forward-LSTM和 backword-LSTM的输出的最终状态(final state)拼接而成,即

    xt=concat{dt,h}xt=concat{dt,h}


    究其原因,LSTM在生成wtwt representations时,更多的是包含距离 wtwt 最近的信息(they have a representation biased towards their most recent inputs.)。因此,在用Bi-LSTM提取character-level的representation时,forward LSTM的最终状态能够更好的对wtwt 的后缀进行表示,backward LSTM的最终状态能够更好的对 wtwt 的前缀进行表示。
  • 文献[3]的输入数据为word-embedding以及pos-embedding(5-dim), capitalization-embedding(5-dim)(也就是大小写标识)。
  • 文献[4]的输入数据为word-embedding 以及character-embedding,但是word-embedding和character-embedding组合的方式不用于文献[1].首先对hh 做了一层非线性变换,即

    h^=tanh(Wth),h^=tanh⁡(Wth),

    原因是通过非线性变换(相当于增加了一层隐藏层)可以提取更高层的特征(An additional hidden layer allows the model to detect higher-level feature combinations, while constraining it to be small forces it to focus on more generalisable patterns).(不是很理解)其次,将word-embedding dtdt 和 character-embedding h^h^进行加权组合

    z=σ(W(3)ztanh(W(1)zdt+W(2)zh^)),x~=z⋅d+(1−z)⋅h^z=σ(Wz(3)tanh⁡(Wz(1)dt+Wz(2)h^)),x~=z⋅d+(1−z)⋅h^


    这种方式的有点是网络可以动态决定word-embedding 和character-embedding 分别取多少。for example,words with regular suffixes can share some character-level features, whereas irregular words can store exceptions into word embeddings.(有规则后缀的单词可以共享一些字符级特性,而不规则的单词可以将异常存储为单词嵌入。–有道翻译 :) ) .
    为了让dd和 h^h^ 进行对齐,作者又设计了一种目标函数:

    E~=E+∑t=1Tgt(1−cos(h^),dt)gt={01ifwt==OOVotherwiseE~=E+∑t=1Tgt(1−cos⁡(h^),dt)gt={0ifwt==OOV1otherwise

    最后,贴上一段文献中比较有启发意义的表述:

    2.2.2 CNN 网络

    考虑RNN不能并行计算,并且虽然RNN能够解决长时以来的问题,但是当序列较长时,序列尾部对序列头部的依赖依然会损失很多信息。于是有学者考虑用CNN进行建模。

  • 文献[5]采用CNN对序列建模,并且采用的是迭代扩张卷积Iterated Dilated Convoltuons(IDCNNs).输入数据有word-embdding、character-embdding,shape-embdding, 通过concat 串起来。具体来说:1, 采用滑动窗口,在窗口内做卷积,然而卷积层后面并没有接池化层,而是采用扩张卷积的思路,继续卷积,进行了L次迭代扩张卷积,将这一过程称为一个Block。由于采用了扩张的方式,没增加一层网络,就会使得新增层的神经元的”感受区域”以2的指数倍增加(rl−1rl−1,窗口宽度为w=2r+1w=2r+1);2,采用Multi-Scale Context Aggregation 机制,将上一个Block的输出作为下一个Block的输入。这样,由于上一个Block已经涵盖了真个输入的所有信息,具备了全局信息,那么这种方式实际上为当前计算的word添加非局部信息。


3,改进的损失函数。对于DNN+CRF的结构来说,损失函数一般是将网络的输出送入到CRF层计算整个序列的标签的最大概率。由于文章提出的模型具有多个Block,每个Block后都可以直接接入CRF层并计算序列的标签概率。这样,假如使用了LbLb个Block,每个Block的输出都送入到CRF层计算概率,将这LbLb个标签作为整体进行优化。这种除了提高精度外,还能降低梯度弥散问题。
采用扩张卷积而不是池化有两个原因:一,凡是池化就有信息损失,不管是有用的还是无用的;二,扩张卷积可以以网络层数的指数级增加每个节点的覆盖范围(也就是感受野)

文献[2]的输入数据也是word-embedding 和 character-embedding, 与上述文献不同的地方时,本文采用CNN提取 character-embedding, 其网络结构如下图
图1 RNN+CNN+CRF网络结构图
作者认为,CNN是一种提取形态信息的有效方法。比如一个词的前缀或者后缀。

2.3 一些trick

2.3.1 Embedding

  • 对于输入数据的embedding,要么随机初始化,要么采用预训练好的embedding,比如w2v,glove. 由于embedding可以在更大的数据集上训练(无监督的),因此采用pre-train的embedding进行初始化,使得网络能够收敛到更好的结果。试验结果也表明pre-train的embedding相比随机初始化的embedding 有较大的提升[1]。
  • character-embedding 通常是随机初始化,在训练中进行更新优化;当然也可以用w2v or glove 在大语料上预训练,然后在训练中进行微调;

2.3.2 Initialization

  • 随机初始化embedding, 从均分分布[−3dim−−−√,3dim−−−√][−3dim,3dim] 中采样,其中dimdim 为embedding的维度。 实际上就是He-initializer. 这一步的初始化,如果采用CNN提取character-embedding时初始化建议采用He-initializer(激活函数为ReLU),如果才能用LSTM提取embedding是初始化建议采用Xavier-initialize(激活函数为σ,tanh)σ,tanh).
  • Weight Matrix and Bias Vectors. Weight Matrix 从均匀分布[−6r+c−−−√,6r+c−−−√][−6r+c,6r+c] 中采样,其中 rr和cc分布是Weight Matrix的行数和列数。Bias Vectors初始化为零,但LSTM中的遗忘门对应的偏置bfbf 初始化为1.0。
    Most applications of LSTMs simply initialize the LSTMs with small random weights which works well on many problems. But this initialization effectively sets the forget gate to 0.5. This introduces a vanishing gradient with a factor of 0.5 per timestep, which can cause problems whenever the long term dependencies are particularly severe.This problem is addressed by simply initializing the forget gates bfto a large value such as 1 or 2. By doing so, the forget gate will be initialized to a value that is close to 1, enabling gradient flow.

2.3.4 dropout

通常在hidden layers 将dropout 设为0.5, 在 input layers 将 dropout 设为0.8。一般来讲,对于隐藏层的神经元,其 dropout率等于 0.5时效果最好,因为此时通过 dropout 方法,随机生成的网络结构最具多样性。

2.4 总结

  • 加入CRF层是为了利用标签之间的局部依赖关系;
  • 加入 character-level 是为了缓解 OOV问题; unseen words and rare words 的 embedding由于缺少数据,通常不能够有效的表示该word,引入character-level embedding可以有效缓解这个问题,比如对于“cabinets”,虽然没有数据集中没有”cabinets”,但是有”cabinet”以 “-s”,那么就可以在character-level 推导出 “cabinets”.同时频繁词项又可以获得更高质量的word-embedding(The main benefits of character-level modeling are expected to come from improved handling of rare and unseen words, whereas frequent words are likely able to learn high-quality word-level embeddings directly. We would like to take advantage of this, and train the character component to predict these word embeddings.[4])
  • 在文本分类中有学者采用多通道输入,一个静态的word-embedding,一个动态的embedding,静态的embedding在学习中保持不变(维护了全局特征),动态的embedding在学习中进行微调,也就是task-specific。应用到NER中不知效果如何。

3 中文命名实体识别

在模型结构上中文命名实体识别与英文的命名实体识别并没有的区别,主要的一点是,character-embedding的使用,在中文语料下,单个汉字并没有形态上的区别。待我整理完数据后做个试验。

基于字模型命名实体常用模型

NER中主流的神经网络结构

2.1 NN/CNN-CRF模型

    《Natural language processing (almost) from scratch》是较早使用神经网络进行NER的代表工作之一。在这篇论文中,作者提出了窗口方法与句子方法两种网络结构来进行NER。这两种结构的主要区别就在于窗口方法仅使用当前预测词的上下文窗口进行输入,然后使用传统的NN结构;而句子方法是以整个句子作为当前预测词的输入,加入了句子中相对位置特征来区分句子中的每个词,然后使用了一层卷积神经网络CNN结构。

    在训练阶段,作者也给出了两种目标函数:一种是词级别的对数似然,即使用softmax来预测标签概率,当成是一个传统分类问题;另一种是句子级别的对数似然,其实就是考虑到CRF模型在序列标注问题中的优势,将标签转移得分加入到了目标函数中。后来许多相关工作把这个思想称为结合了一层CRF层,所以我这里称为NN/CNN-CRF模型。

在作者的实验中,上述提到的NN和CNN结构效果基本一致,但是句子级别似然函数即加入CRF层在NER的效果上有明显提高。

2.2 RNN-CRF模型

    借鉴上面的CRF思路,在2015年左右出现了一系列使用RNN结构并结合CRF层进行NER的工作。代表工作主要有:

    将这些工作总结起来就是一个RNN-CRF模型,模型结构如下图:

    它主要有Embedding层(主要有词向量,字符向量以及一些额外特征),双向RNN层,tanh隐层以及最后的CRF层构成。它与之前NN/CNN-CRF的主要区别就是他使用的是双向RNN代替了NN/CNN。这里RNN常用LSTM或者GRU。实验结果表明RNN-CRF获得了更好的效果,已经达到或者超过了基于丰富特征的CRF模型,成为目前基于深度学习的NER方法中的最主流模型。在特征方面,该模型继承了深度学习方法的优势,无需特征工程,使用词向量以及字符向量就可以达到很好的效果,如果有高质量的词典特征,能够进一步获得提高。

3 . 少量标注数据

    对于深度学习方法,一般需要大量标注数据,但是在一些领域并没有海量的标注数据。所以在基于神经网络结构方法中如何使用少量标注数据进行NER也是最近研究的重点。其中包括了迁移学习《Transfer Learning for Sequence Tagging with Hierarchical Recurrent Networks》和半监督学习。这里我提一下最近ACL2017刚录用的一篇论文《Semi-supervised sequence tagging with bidirectional language models》。该论文使用海量无标注语料库训练了一个双向神经网络语言模型,然后使用这个训练好的语言模型来获取当前要标注词的语言模型向量(LM embedding),然后将该向量作为特征加入到原始的双向RNN-CRF模型中。实验结果表明,在少量标注数据上,加入这个语言模型向量能够大幅度提高NER效果,即使在大量的标注训练数据上,加入这个语言模型向量仍能提供原始RNN-CRF模型的效果。整体模型结构如下图:

4 总结

    最后进行一

目前业界比较常用的模型,是 LSTM + CRF。 这这类模型中, NCRF++算法, 是目前最好的 NER 算法, 发表在 COLLING 2018上,论文见 https://arxiv.org/abs/1806.04470 。NCRF++在它的文章中报告其在 CoNLL2003 上能达到91.35的 F1。

框架

NCRF++的整体框架如下:

它支持 BIO(注意,BIO和 IOB 有点区别), BIOES 两种标注模式。因为 CoNLL2003 太过久远,一般将其转换到新的标注格式上来,转换方法见:https://github.com/jiesutd/NCRFpp/blob/master/utils/tagSchemeConverter.py

表现

NCRF++是目前 state-of-the-art的命名实体识别方案:

ID Model Nochar CharLSTM CharCNN
1 WordLSTM 88.57 90.84 90.73
2 WordLSTM+CRF 89.45 91.20 91.35
3 WordCNN 88.56 90.46 90.30
4 WordCNN+CRF 88.90 90.70 90.43

速度

NCRF++的速度表现也非常优异, 在使用全批处理的情况下, 在单个1080 显卡上, 训练速度能到到1000句话每秒,解码速度能达到2000句话每秒。

目前 NCRF++ 已经开源(https://github.com/jiesutd/NCRFpp)。

 NeuroNER 网络结构分析

假设大家对上边的内容都已经熟悉了,在这看下NeuroNER的网络结构


 

命名实体识别(NER)是在自然语言处理中的一个经典问题,其应用也极为广泛。比如从一句话中识别出人名、地名,从电商的搜索中识别出产品的名字,识别药物名称等等。传统的公认比较好的处理算法是条件随机场(CRF),它是一种判别式概率模型,是随机场的一种,常用于标注或分析序列资料,如自然语言文字或是生物序列。简单是说在NER中应用是,给定一系列的特征去预测每个词的标签。如下图:

X我们可以看做成一句话的每个单词对应的特征,Y可以看做成单词对应的标签。这里的标签就是对应场景下的人名、地名等等。重点在X的理解上面,什么是特征呢?通常我们都会取的特征是词性,如果名词、动词… 但是有人会反问,知道了词性就能学习出标签吗?显然是不够的,我们可能需要更多的特征来完成我们的学习。但是这些特征需要我们根据不同的场景去人工的抽取,比如抽取人名的特征我们往往可能看看单词的第一个字是不是百家姓等等。所以更多严谨的CRF的图应该如下:

至于y之间的连线请参考CRF算法的详细内容。接下来会更注重用深度学习的方法来解决NER问题。

看了上图有的人则会有疑惑了,这个特征要怎么提取呢?如何知道提取出来的特征是有效的呢?要提多少个特征才好呢?的确,特征工程确实是一个麻烦的问题,关于特征的提取也大都是根据经验和拍脑门想出来的,这整个过程很是麻烦。幸运的是现在深度学习大红大紫,其原因除了它的效果卓越之外还免去了我们手工提取特征的烦恼,以下将会讲解本人Github上的代码思路与实现。

本代码用TensorFlow实现了双向循环神经网络(Bi-RNN)+ 条件随机场(CRF)在NER上的应用。了解深度学习的人都知道,我们把数据放入神经网络中然后输出我们想要的结果,无论想要处理的问题是分类、回归或者是序列的问题,其本质就是通过神经网络学习出特征然后根据特征求得结果。这不就正是我们需要的吗?我们只要让神经网络替我们把特性选好,然后我们只要简单的把特征放到CRF里不就好了吗?这就是整个程序的本质。

知道了本质我们需要克服的困难也很多,接下来我们一一来解决。

  1. 如何把词转换成神经网络能接受的数据?

神经网络只接受数字,不接受字符串,所以我们需要用工具把词转换成为词向量,这个工具可以是gensim word2vec、glove等等。训练的数据最好是要有个庞大的数据集,比如从网上爬取下来的新闻然后用这些数据来训练词向量。如果就用已有的数据训练行不行呢?或者干脆不训练,随机初始化这些词向量行不行呢?答案是:可以。但是解决不了下面这个问题。

2. 如何处理训练数据中没有见过的词?

我们之所以需要用庞大的新闻数据来预训练词向量的原因就是为了克服训练数据量小的问题。因为人力有限,我们不可能有很大的标记过的数据,如果在测试样例中出现了我们没有标记过的词,那么显然会影响NER结果。然后word2vec的用处就是将相似的词的“距离”拉的很近(由于word2vec又是一个很大的话题,这里不拓展讨论啦),这样可以一定程度上减少未出现词的影响。

整个流程图如下:

使用基于字的BiLSTM-CRF,主要参考的是文献[4][5]。使用Bakeoff-3评测中所采用的的BIO标注集,即B-PER、I-PER代表人名首字、人名非首字,B-LOC、I-LOC代表地名首字、地名非首字,B-ORG、I-ORG代表组织机构名首字、组织机构名非首字,O代表该字不属于命名实体的一部分。如:

      

这里当然也可以采用更复杂的BIOSE标注集。

      以句子为单位,将一个含有 nn 个字的句子(字的序列)记作

x=(x1,x2,...,xn)x=(x1,x2,...,xn)

其中 xixi 表示句子的第 ii 个字在字典中的id,进而可以得到每个字的one-hot向量,维数是字典大小。

      模型的第一层是 look-up 层,利用预训练或随机初始化的embedding矩阵将句子中的每个字 xixi 由one-hot向量映射为低维稠密的字向量(character embedding)xi∈Rdxi∈Rd ,dd 是embedding的维度。在输入下一层之前,设置dropout以缓解过拟合。

      模型的第二层是双向LSTM层,自动提取句子特征。将一个句子的各个字的char embedding序列 (x1,x2,...,xn)(x1,x2,...,xn) 作为双向LSTM各个时间步的输入,再将正向LSTM输出的隐状态序列 (h1⟶,h2⟶,...,hn⟶)(h1⟶,h2⟶,...,hn⟶) 与反向LSTM的 (h1⟵,h2⟵,...,hn⟵)(h1⟵,h2⟵,...,hn⟵) 在各个位置输出的隐状态进行按位置拼接 ht=[ht⟶;ht⟵]∈Rmht=[ht⟶;ht⟵]∈Rm ,得到完整的隐状态序列

(h1,h2,...,hn)∈Rn×m(h1,h2,...,hn)∈Rn×m

在设置dropout后,接入一个线性层,将隐状态向量从 mm 维映射到 kk 维,kk 是标注集的标签数,从而得到自动提取的句子特征,记作矩阵 P=(p1,p2,...,pn)∈Rn×kP=(p1,p2,...,pn)∈Rn×k 。可以把 pi∈Rkpi∈Rk的每一维 pijpij 都视作将字 xixi 分类到第 jj 个标签的打分值,如果再对 PP 进行Softmax的话,就相当于对各个位置独立进行 kk 类分类。但是这样对各个位置进行标注时无法利用已经标注过的信息,所以接下来将接入一个CRF层来进行标注。

      模型的第三层是CRF层,进行句子级的序列标注。CRF层的参数是一个 (k+2)×(k+2)(k+2)×(k+2)的矩阵 AA ,AijAij 表示的是从第 ii 个标签到第 jj 个标签的转移得分,进而在为一个位置进行标注的时候可以利用此前已经标注过的标签,之所以要加2是因为要为句子首部添加一个起始状态以及为句子尾部添加一个终止状态。如果记一个长度等于句子长度的标签序列 y=(y1,y2,...,yn)y=(y1,y2,...,yn) ,那么模型对于句子 xx 的标签等于 yy 的打分为

score(x,y)=∑i=1nPi,yi+∑i=1n+1Ayi−1,yiscore(x,y)=∑i=1nPi,yi+∑i=1n+1Ayi−1,yi

可以看出整个序列的打分等于各个位置的打分之和,而每个位置的打分由两部分得到,一部分是由LSTM输出的 pipi 决定,另一部分则由CRF的转移矩阵 AA 决定。进而可以利用Softmax得到归一化后的概率:

P(y|x)=exp(score(x,y))∑y′exp(score(x,y′))P(y|x)=exp⁡(score(x,y))∑y′exp⁡(score(x,y′))

      模型训练时通过最大化对数似然函数,下式给出了对一个训练样本 (x,yx)(x,yx) 的对数似然:

logP(yx|x)=score(x,yx)−log(∑y′exp(score(x,y′)))log⁡P(yx|x)=score(x,yx)−log⁡(∑y′exp⁡(score(x,y′)))

如果这个算法要自己实现的话,需要注意的是指数的和的对数要转换成 log∑iexp(xi)=a+log∑iexp(xi−a)log⁡∑iexp⁡(xi)=a+log⁡∑iexp⁡(xi−a)(TensorFlow有现成的CRF,PyTorch就要自己写了),在CRF中上式的第二项使用前向后向算法来高效计算。

      模型在预测过程(解码)时使用动态规划的Viterbi算法来求解最优路径:

y∗=argmaxy′score(x,y′)y∗=arg⁡maxy′score(x,y′)

      整个模型的结构如下图所示:

实验结果

      关于CRF模型的特征模板就不细讲了,是参考 [3] 来做的。提好特征之后用CRF++工具包即可,这部分是小伙伴做的~

      实验结果如下表:

      下面开始一本正经地胡说八道:      

      1. 总的来说,经过仔细选择特征模板的CRF模型在人名上的识别效果要优于BiLSTM-CRF,但后者在地名、组织机构名上展现了更好的性能。究其原因,可能是因为:
      (1) 人名用字较灵活且长度比较短,用特征模板在窗口内所提取的特征要比神经网络自动学习的特征更有效、干扰更少
      (2) 地名、组织机构名的构成复杂、长度较长,使用双向LSTM能够更好地利用句子级的语义特征,而特征模板只能在窗口内进行提取,无法利用整句话的语义。

      2. 对于CRF模型来说,使用 {字符,词性,词边界,实体列表} 这一组合模板的效果在CRF模型系列中表现最好(各个单一模板以及其他组合模板的结果未列出)。

      3. 对于BiLSTM-CRF模型来说,这里在每一层的处理都是比较简单的,还有可以提高的空间。例如字向量embedding的初始化方式,这里只是用了最简单的随机初始化,然而由于语料规模比较小,所以不太合适。可以考虑对句子做分词,然后将字向量初始化为该字所在词的词向量(可以用在别的大型语料上的预训练值)。此外,还可以尝试文献[5][7][8]的思路,将low-level的特征经过一个RNN或CNN,进而通过“组合”的方式来得到字级别的embedding(英文是用字母构造单词,中文是用偏旁部首构造汉字),将其与随机初始化的字向量拼接在一起。

      另外要提的一点是BiLSTM-CRF在这应该是过拟合了,迭代轮数(120轮)给大了,测试集指标在大约60轮之后已经开始下降。应该划个验证集做early stopping。

      BiLSTM-CRF模型的代码在GitHub上,README.md里介绍了如何训练、测试。我是用笔记本的显卡训练的,batch_size 取64,Adam优化器训练120个epoch,大概用了4个多小时。如果机器条件允许,不妨试试 batch_size 直接取1,优化器用 SGD+Momentum 。

中文分词

说到命名实体抽取,先要了解一下基于字标注的中文分词。
比如一句话

“我爱北京天安门”。

分词的结果可以是

“我/爱/北京/天安门”。

那什么是基于字标注呢?

“我/O 爱/O 北/B 京/E 天/B 安/M 门/E”。

就是这样,给每个字都进行一个标注。我们可以发现这句话中字的标注一共有四种。他们分别代表的意义如下。

B | 词首
M | 词中
E | 词尾
O | 单字

B表示一个词的开始,E表示一个词的结尾,M表示词中间的字。如果这个词只有一个字的话,用O表示。

命名实体识别

数据处理

了解了中文分词,那么实体识别也差不多。就是把不属于实体的字用O标注,把实体用BME规则标注,最后按照BME规则把实体提取出来就ok了。

数据可以自己标注,也可以找个公开的数据集先练练手。我是用的是玻森数据提供的命名实体识别数据,https://bosonnlp.com 这是官网,在数据下载里面有一个命名实体识别数据集,或者在我的github里下载。

这个数据集一个包含了6个实体类别:

time: 时间
location: 地点
person_name: 人名
org_name: 组织名
company_name: 公司名
product_name: 产品名

例:

{{product_name:浙江在线杭州}}{{time:4月25日}}讯(记者{{person_name: 施宇翔}} 通讯员 {{person_name:方英}})毒贩很“时髦”,用{{product_name:微信}}交易毒品。没料想警方也很“潮”,将计就计,一举将其擒获。

每个实体用都用大括号括了起来,并标明实体类别。当然自己标注的时候也不一定要这么标,只要能提取出来就可以。

然后我们要做的就是把原始数据按照BMEO规则变成字标注的形式,以便模型训练。这使用python实现还是比较简单的,嫌麻烦的可以看我github里的代码。按字标注后结果如下。

浙/B_product_name 江/M_product_name 在/M_product_name 线/M_product_name 杭/M_product_name 州/E_product_name 4/B_time 月/M_time 2/M_time 5/M_time 日/E_time 讯/O (/O 记/O 者/O /B_person_name 施/M_person_name 宇/M_person_name 翔/E_person_name /O 通/O 讯/O 员/O /O 方/B_person_name 英/E_person_name )/O 毒/O 贩/O 很/O “/O 时/O 髦/O ”/O ,/O 用/O 微/B_product_name 信/E_product_name 交/O 易/O 毒/O 品/O 。/O 没/O 料/O 想/O 警/O 方/O 也/O 很/O “/O 潮/O ”/O ,/O 将/O 计/O 就/O 计/O ,/O 一/O 举/O 将/O 其/O 擒/O 获/O 。

然后我们习惯按照标点符号把一个长句分成几个短句,反正一般实体里面也没有标点符号。结果如下。

浙/B_product_name 江/M_product_name 在/M_product_name 线/M_product_name 杭/M_product_name 州/E_product_name 4/B_time 月/M_time 2/M_time 5/M_time 日/E_time 讯/O
记/O 者/O /B_person_name 施/M_person_name 宇/M_person_name 翔/E_person_name /O 通/O 讯/O 员/O /O 方/B_person_name 英/E_person_name
毒/O 贩/O 很/O
时/O 髦/O
用/O 微/B_product_name 信/E_product_name 交/O 易/O 毒/O 品/O
没/O 料/O 想/O 警/O 方/O 也/O 很/O
潮/O
将/O 计/O 就/O 计/O
一/O 举/O 将/O 其/O 擒/O 获/O

然后的思路就是建立一个word2id词典,把每个汉字转换成id。这里习惯现按照数据集中每个汉字出现的次数排序,id从1开始。
这里写图片描述
再建立一个tag2id词典,把每一个字标注的类型转换成id。这里的顺序我就随便搞的。
这里写图片描述

之后就把刚按标点分开的数据,按照一一对应的顺序,把汉字和每个字的标签转换成id,分别存到两个数组里面,一起保存到一个pkl文件中,这样模型使用时候就可以直接读取,不用每次都处理数据了。这里习惯把每一句话都转换成一样的长度。这个长度当然是自己设置的,比它长的就把后面舍弃,比它短的就在后面补零。
这里写图片描述
这里第一个数组里是这句话汉字转换成的id,第二个数组里存的是这句话每个字的标注转换成的id。

以上数据处理的代码是我的github里data_util.py实现的功能。

模型

这里模型就直接用的pytorch tutorial里的Bilstm+crf模型,也可以看BiLSTM_CRF.py这个文件。这个模型由于没有使用batch,速度比较慢,但是初学者就先用这个吧,学会了之后再进行修改。

至于想要看懂这部分的代码,就要学一下pytorch了,当然使用tensorflow也是可以的。推荐一下莫烦python的教程,pytorch和tensorflow都有。

pytorch里,lstm是写好的,可以直接使用,crf目前还没有,所以是直接写的。

lstm就是很经典的lstm,网上有很多讲解。crf我想另写一篇,详细讲一下。

训练

训练直接运行train.py就可以了。就是把之前存到pkl中的数据取出来,转换成tensor形式,输入到模型中。crf的损失函数是另外定义的。然后通过pytorch提供的

loss.backward()
optimizer.step()

反向传播和梯度下降,更新参数,让损失函数越来越小即可。看代码吧。

准确度判断

命名实体识别的准确度判断有三个值。准确率、召回率和f值。

这里需要先定义一个交集,是经过模型抽取出来的实体,与数据集中的所有实体,取交集。

准确率=交集/模型抽取出的实体
召回率=交集/数据集中的所有实体
f值=2×(准确率×召回率) / (准确率+召回率)

猜你喜欢

转载自blog.csdn.net/qq_41853758/article/details/82933080