HanLP中人名识别分析

简介:

在看源码之前,先看几遍论文《基于角色标注的中国人名自动识别研究》

关于命名识别的一些问题,可参考下列一些issue:

名字识别的问题 #387
机构名识别错误
关于层叠HMM中文实体识别的过程

词性标注

层叠HMM-Viterbi角色标注模型下的机构名识别

分词
在HMM与分词、词性标注、命名实体识别中说:

分词:给定一个字的序列,找出最可能的标签序列(断句符号:[词尾]或[非词尾]构成的序列)。结巴分词目前就是利用BMES标签来分词的,B(开头),M(中间),E(结尾),S(独立成词)

分词也是采用了维特比算法的动态规划性质求解的,具体可参考:文本挖掘的分词原理

角色观察
以“唱首张学友的歌情已逝”为例,

先将起始顶点 始##始,角色标注为:NR.A 和 NR.K,频次默认为1

iterator.next();
tagList.add(new EnumItem(NR.A, NR.K)); // 始##始 A K

image

对于第一个词“唱首”,它不存在于 nr.txt中,EnumItem nrEnumItem = PersonDictionary.dictionary.get(vertex.realWord);返回null,于是根据它本身的词性猜一个角色标注:

switch (vertex.guessNature()){

    case nr:
    case nnt:
default:{
    nrEnumItem = new EnumItem<NR>(NR.A, PersonDictionary.transformMatrixDictionary.getTotalFrequency(NR.A));
}

}

image

由于"唱首"的Attribute为 nz 16,不是nr 和 nnt,故默认给它指定一个角色NR.A,频率为nr.tr.txt中 NR.A 角色的总频率。

此时,角色列表如下:

image

接下来是顶点“张”,由于“张”在nr.txt中,因此PersonDictionary.dictionary.get(vertex.realWord)返回EnumItem对象,直接将它加入到角色列表中:

image

EnumItem nrEnumItem = PersonDictionary.dictionary.get(vertex.realWord);
tagList.add(nrEnumItem);
加入“张”之后的角色列表如下:

image
“唱首张学友的歌情已逝” 整句的角色列表如下:

image

至此,角色观察 部分 就完成了。

总结一下,对句子进行角色观察,首先是通过分词算法将句子分成若干个词,然后对每个词查询人名词典(PersonDictionary)。

若这个词在人名词典中(nr.txt),则记录该词的角色,所有的角色在com.hankcs.hanlp.corpus.tag.NR.java中定义。
若这个词不在人名词典中,则根据该词的Attribute “猜一个角色”。在猜的过程中,有些词在核心词典中可能已经标注为nr或者nnt了,这时会做分裂处理。其他情况下则是将这个词标上NR.A角色,频率为 NR.A 在转移矩阵中的总词频。
维特比算法(动态规划)求解最优路径
在上图中,给每个词都打上了角色标记,可以看出,一个词可以有多个标记。而我们需要将这些词选择一条路径最短的角色路径。参考隐马尔可夫模型维特比算法详解

List nrList = viterbiComputeSimply(roleTagList);
//some code....
return Viterbi.computeEnumSimply(roleTagList, PersonDictionary.transformMatrixDictionary);
而这个过程,其实就是:维特比算法解码隐藏状态序列。在这里,五元组是:

隐藏状态集合 com.hankcs.hanlp.corpus.tag.NR.java 定义的各个人名标签

观察状态集合 已经分好词的各个tagList中元素(相当于分词结果)
image

转移概率矩阵 由 nr.tr.txt 文件生成得到。具体可参考:

发射概率 某个人名标签(隐藏状态)出现的次数 除以 所有标签出现的总次数

Math.log((item.getFrequency(cur) + 1e-8) / transformMatrixDictionary.getTotalFrequency(cur)

初始状态(始##始) 和 结束状态(末##末)

image

维特比解码隐藏状态的动态规划求解核心代码如下:

        for (E cur : item.labelMap.keySet())
        {
            double now = transformMatrixDictionary.transititon_probability[pre.ordinal()][cur.ordinal()] - Math.log((item.getFrequency(cur) + 1e-8) / transformMatrixDictionary.getTotalFrequency(cur));
            if (perfect_cost > now)
            {
                perfect_cost = now;
                perfect_tag = cur;
            }
        }

transformMatrixDictionary.transititon_probabilitypre.ordinal() 是前一个隐藏状态 pre.ordinal()转换到当前隐藏状态cur.ordinal()的转移概率。Math.log((item.getFrequency(cur) + 1e-8) / transformMatrixDictionary.getTotalFrequency(cur)是当前隐藏状态的发射概率。二者“相减”得到一个概率 保存在double now变量中,然后通过 for 循环找出 当前观察状态 对应的 最可能的(perfect_cost最小) 隐藏状态 perfect_tag。

至于为什么是上面那个公式来计算转移概率和发射概率,可参考论文:《基于角色标注的中国人名自动识别研究》

在上面例子中,得到的最优隐藏状态序列(最优路径)K->A->K->Z->L->E->A->A 如下:

nrList = {LinkedList@1065} size = 8
"K" 始##始
"A" 唱首
"K" 张
"Z" 学友
"L" 的
"E" 歌
"A" 情已逝
"A" 末##末
例如:
​隐藏状态---观察状态
"K"----------始##始

最大匹配
有了最优隐藏序列:KAKZLEAA,接下来就是:后续的“最大匹配处理”了。

    PersonDictionary.parsePattern(nrList, pWordSegResult, wordNetOptimum, wordNetAll);

在最大匹配之前,会进行“模式拆分”。在com.hankcs.hanlp.corpus.tag.NR.java 定义了隐藏状态的具体含义。比如说,若最优隐藏序列中 存在 'U' 或者 'V',

U Ppf 人名的上文和姓成词 这里【有关】天培的壮烈

V Pnw 三字人名的末字和下文成词 龚学平等领导, 邓颖【超生】前

则会做“拆分处理”

switch(nr)
{

case U:
    //拆分成K B
case V:
    //视情况拆分

}
拆分完成之后,重新得到一个新的隐藏序列(模式)

String pattern = sbPattern.toString();
接下来,就用AC自动机进行最大模式匹配了,并将匹配的结果存储到“最优词网”中。当然,在这里就可以自定义一些针对特定应用的 识别处理规则

trie.parseText(pattern, new AhoCorasickDoubleArrayTrie.IHit(){

//.....
wordNetOptimum.insert(offset, new Vertex(Predefine.TAG_PEOPLE, name, ATTRIBUTE, WORD_ID), wordNetAll);

}
将识别出来的人名保存到最优词网后,再基于最优词网调用一次维特比分词算法,得到最终的分词结果---细分结果。

        if (wordNetOptimum.size() != preSize)
        {
            vertexList = viterbi(wordNetOptimum);
            if (HanLP.Config.DEBUG)
            {
                System.out.printf("细分词网:\n%s\n", wordNetOptimum);
            }
        }

总结
源码上的人名识别基本上是按照论文中的内容来实现的。对于一个给定的句子,先进行下面三大步骤处理:

角色观察
维特比算法解码求解隐藏状态(求解各个分词 的 角色标记)
对角色标记进行最大匹配(可做一些后处理操作)
最后,再使用维特比算法进行一次分词,得到细分结果,即为最后的识别结果。

这篇文章里面没有写维特比分词算法的详细过程,以及转移矩阵的生成过程,以后有时间再补上。看源码,对隐马模型的理解又加深了一点,感受到了理论的东西如何用代码一步步来实现。由于我也是初学,对源码的理解不够深入或者存在一些偏差,欢迎批评指正。

文章来源于网络

相关文章
|
自然语言处理 索引 算法
HanLP分词命名实体提取详解
文本挖掘是抽取有效、新颖、有用、可理解的、散布在文本文件中的有价值知识,并且利用这些知识更好地组织信息的过程。对于文本来说,由于语言组织形式各异,表达方式多样,文本里面提到的很多要素,如人名、手机号、组织名、地名等都称之为实体。
7370 0
|
9月前
|
自然语言处理 算法
Jieba进行词频统计与关键词提取
Jieba进行词频统计与关键词提取
|
自然语言处理 Java Python
自然语言处理hanlp------10HanLP的词典分词实现
自然语言处理hanlp------10HanLP的词典分词实现
自然语言处理hanlp------10HanLP的词典分词实现
|
数据采集 自然语言处理 安全
PaddleNLP基于ERNIR3.0文本分类以中医疗搜索检索词意图分类(KUAKE-QIC)为例【多分类(单标签)】
文本分类任务是自然语言处理中最常见的任务,文本分类任务简单来说就是对给定的一个句子或一段文本使用文本分类器进行分类。文本分类任务广泛应用于长短文本分类、情感分析、新闻分类、事件类别分类、政务数据分类、商品信息分类、商品类目预测、文章分类、论文类别分类、专利分类、案件描述分类、罪名分类、意图分类、论文专利分类、邮件自动标签、评论正负识别、药物反应分类、对话分类、税种识别、来电信息自动分类、投诉分类、广告检测、敏感违法内容检测、内容安全检测、舆情分析、话题标记等各类日常或专业领域中。 文本分类任务可以根据标签类型分为**多分类(multi class)、多标签(multi label)、层次分类
|
自然语言处理 机器学习/深度学习 Windows
Hanlp-地名识别调试方法详解
HanLP收词特别是实体比较多,因此特别容易造成误识别。下边举几个地名误识别的例子,需要指出的是,后边的机构名识别也以地名识别为基础,因此,如果地名识别不准确,也会导致机构名识别不准确。
1098 0
|
自然语言处理
自然语言处理工具HanLP-基于层叠HMM地名识别
本篇接上一篇内容《HanLP-基于HMM-Viterbi的人名识别原理介绍》介绍一下层叠隐马的原理。首先说一下上一篇介绍的人名识别效果对比: 只有Jieba识别出的人名准确率极低,基本为地名或复杂地名组成部分或复杂机构名组成部分。
1198 0
|
自然语言处理 机器学习/深度学习 Windows
HanLP-地名识别调试方法
HanLP收词特别是实体比较多,因此特别容易造成误识别。下边举几个地名误识别的例子,需要指出的是,后边的机构名识别也以地名识别为基础,因此,如果地名识别不准确,也会导致机构名识别不准确。 类型1 数字+地名[1] 暗访哈尔滨网约车:下10单来7辆“黑车” 1辆套牌[2] 房天下每日成交5月12日...
1108 0
|
自然语言处理 算法
HanLP-基于HMM-Viterbi的人名识别原理介绍
Hanlp自然语言处理包中的基于HMM-Viterbi处理人名识别的内容大概在年初的有分享过这类的文章,时间稍微久了一点,有点忘记了。看了 baiziyu 分享的这篇比我之前分享的要简单明了的多。下面就把文章分享给大家交流学习之用,部分内容有做修改。
924 0
|
缓存 自然语言处理
Hanlp自然语言处理中的词典格式说明
使用过hanlp的都知道hanlp中有许多词典,它们的格式都是非常相似的,形式都是文本文档,随时可以修改。本篇文章详细介绍了hanlp中的词典格式,以满足用户自定义的需要。
4793 0
|
自然语言处理 算法 程序员
自然语言处理工具hanlp关键词提取图解TextRank算法
TextRank是在Google的PageRank算法启发下,针对文本里的句子设计的权重算法,目标是自动摘要。它利用投票的原理,让每一个单词给它的邻居(术语称窗口)投赞成票,票的权重取决于自己的票数。这是一个“先有鸡还是先有蛋”的悖论,PageRank采用矩阵迭代收敛的方式解决了这个悖论。
1550 0