1、前言

ansj人名识别会用到两个字典,分别是:person/asian_name_freq.data、person/person.dic。

1.1、asian_name_freq.data

这是一个二进制文件,序列化了一个Map对象。该对象的key为词,value是大小为3的数组。例如:
罗=[[644, 40], [2048, 140, 74], [19, 28, 39, 29]]
value数组各元素分别是大小为2、3、4的数组,分别表示在2字姓名、3字姓名、4字姓名中第1-2,1-3,1-4位置出现的频率(为叙述简单,本文不考虑复姓的情况)。
也就是说,“罗”出现在二字姓名首位的频率是644,末位是40;出现在三字姓名首位的频率是2048,中间是140,末位是74;出现在四字姓名首位的频率是19。
事实上,该Map的key可以是单字,也可能是个词,例如:
高中=[[13, 2], [0, 0, 0], [0, 0, 0, 0]]
这也就意味着,“高中”出现在三字姓名前两个位置(比如“高中伟”)的频率是13,出现在三字姓名后两个位置(比如“王高中”)的频率是2。

1.2、person.dic

该字典存储了人名的上下文环境,分为三类:
11-人名的下文、12-两个中国人名之间的成分、13-可拆分为姓名的词(例如“超生”这个词,“超”字可能会和前词组成姓名)。
这三类词,都会对名字的识别带来争议。

1.3、何时触发人名识别

对于Term类的一个实例term,如果term.termNatures().personAttr.flag为true,那就说明该term可能是人名的开头,就会触发人名识别。
其中,personAttr是PersonNatureAttr(人名标注类)的实例。
在PersonAttrLibrary.init2()中加载asian_name_freq.data文件时,会调用PersonNatureAttr.setlocFreq(int[][] ints)对term的姓名词频进行赋值,代码如下:
public void setlocFreq(int[][] ints) {
for (int i = 0; i < ints.length; i++) {
if (ints[i][0] > 0) {
flag = true;
break ;
}
}
locFreq = ints;
}
如果term可以是四字姓名或者三字姓名或者二字姓名的首字(一般情况下,这就是姓氏),personAttr.flag就会被设置为true。
例如,“罗”可以是姓名首字,“高中”也可以是姓名的开头,这两个词都会触发人名识别。再如,
颖=[[0, 351], [0, 712, 1071], [0, 1, 5, 13]],
说明“颖”不能是姓名的开头,不会触发人名识别。
也就是说,遇到可以是姓名开头的词(一般是姓氏),才会触发人名识别。
这会有一个问题,比如“这里有关天培的壮烈”这句话,人名识别之前的分词结果是:
[这里/r, null, 有关/vn, null, 天/q, 培/v, 的/uj, 壮烈/a, null, 末##末],
“有关”不能是姓名开头,导致识别不出“关天培”。

2、具体实现

ansj人名识别是以基于角色标注的中国人名自动识别研究为理论指导的,但与其又有不同。

2.1识别出可能的人名

以“罗毅虎和曹罗伟是高中同学”为例:
Result terms = ToAnalysis.parse("罗毅虎和曹罗伟是高中同学");
System.out.println("分词结果:" + terms);
由于“罗”是姓氏,触发人名识别,首先判断“罗毅虎和”这个四字名有没存在的可能性。
“和”出现在四字姓名第四个位置的频率为0,所以不认可“罗毅虎和”这个四字姓名。
然后判断“罗毅虎”这个三字姓名是否可能存在,当然这是可能的。
接下来,程序并没有去判断“罗毅”这个二字名是否存在,因为“罗毅虎”是无争议的人名。所谓无争议,是指这个人名中的,没有person.dic字典里面的词。“曹罗伟”就是个有争议的人名,因为“伟”字存在于person.dic,“伟”可能是人名的下文环境,因为还要识别出“曹罗”这个二字名,随后会比较“曹罗伟”和“曹罗”哪个在此处更适合做人名。
“曹罗”也是个无争议的人名,识别出来后,会直接跳过“曹罗”,从“伟”开始识别下一个人名。因此不会识别出“罗伟”这个人名。
最终识别出了“罗毅虎”、“曹罗伟”、“曹罗”三个人名,如下图所示:

2.2、计算人名概率的一些理论基础

要想判断“曹罗伟”和“曹罗”哪个在此处更适合做人名,当然要计算二者存在的概率。

在谈到ansj的计算方法之前,我们先共同学习一下基于角色标注的中国人名自动识别研究这篇论文。(强烈建议读了这篇论文再往下看)

该论文将人名识别问题转化为了,对人名构成角色进行标注的问题。(详见论文的2.1、2.2)

我们要求解最终标注结果$T^{\#}=arg_{T}\,max\,P(T|W)$


论文中并不是直接求解$P(T|W)$,而是通过Bayes公式间接求解:

$P(T|W) = P(T)P(W|T)/P(W)$

没有直接求解,无非就是因为$P(T|W)$直接求解比较困难,或者根本无法直接求解。

我们先来看$P(t_{i}|w_{i})$是指:Token序列中的$w_{i}$,角色是$t_{i}$的概率。例如,对于人名识别前的Token序列:

[罗/j, 毅/nr, 虎/n, 和/c, 曹/nr, 罗/j, 伟/nr, 是/v, 高中/n, null, 同学/n, null, 末##末]

$P(t_{i}|w_{i})$可以是指,“曹”这个$w_{i}$,角色是“三字姓名姓氏”的概率。

在大规模语料库训练的前提下,我们可以得到:

$P(t_{i}|w_{i})\approx C(t_{i},w_{i})/C(w_{i})$

其中,$C(t_{i},w_{i})$指角色$t_{i}$中出现$w_{i}$的次数,$C(w_{i})$是$w_{i}$出现的次数。

以ansj为例,直接求解$P(T|W)$会有两个问题:

(1)、$C(w_{i})$是从大规模预料中统计出来,并保存在core词库中的,对该核心词库的整理,与人名识别无关,例如:

32599 罗 170307 -1 2 {j=25, nr=3, q=0, v=0}
而$C(t_{i},w_{i})$是为了人名识别,根据大规模的人名统计出来,并保存在asian_name_freq.data中的,例如
罗:[[644, 40], [2048, 140, 74], [19, 28, 39, 29]]
这两个数据是从两种完全不同的语料的中统计出来的,导致$C(t_{i},w_{i})$远大于$C(w_{i})$,这样算出来的$P(t_{i}|w_{i})$远大于1,显然是不对的。
(2)、假如$P(t_{i}|w_{i})$和$P(t_{i-1}|w_{i-1})$之间是相互独立的事件,那么可以用连乘的方式求得联合概率,即:
$P(T|W)=\prod_{i=0}^{m}P(t_{i}|w_{i})$
但二者显然不是相互独立是事件。比如,对于一个$w_{i}$“明”,如果$w_{i-1}$的角色是姓氏,那么$w_{i}$是人名的概率自然会高一些。

值得一提的是,$P(w_{i}|t_{i})$和$P(w_{i-1}|t_{i-1})$之间是相互独立的事件。
$P(w_{i}|t_{i})$是指角色为$t_{i}$的Token集合中$w_{i}$的概率。
例如:
$P(w_{i-1}|t_{i-1})$可以是:角色为“姓氏”的集合中“罗”的概率;
$P(w_{i}|t_{i})$可以是:角色为“双名的首字”的集合中“毅”的概率。
这二者之间是独立的。

论文中提到,$P(W)$是一个常数。这确实是一个常数。并且,我们已经在现有核心词库(core.dic)的基础上,算得了这个数值。这正是上一节所讨论的内容。

2.3、计算人名概率

以下讨论的是ansj的人名算法。
(1)、首先不考虑上下文,计算人名的概率
在这里ansj并没有使用论文中的方法,而是另辟蹊径。遇到姓氏后就断定这可能是个人名,然后开始计算概率。事先统计好了二字姓名、三字姓名、四字姓名在所有人名中所占的比例,存放在AsianPersonRecognition类中的FACTORY属性。

FACTORY = { 0.16271366224044456, 0.8060521860870434, 0.031234151672511947 }
例如,遇到一个姓氏时,假设这是一个三字姓名,然后计算概率,此时这三个字的角色已经确定(第一个字是姓氏,第二个字是名字首位,第三个字是名字末位)。
P(该三字人名概率)= P(三字人名所占比例) * $\prod_{i=0}^{3}P(w_{i}|t_{i})$
上面已经提到,
$P(t_{i}|w_{i})\approx C(t_{i},w_{i})/C(w_{i})$
从而有:
$-ln\,P(t_{i}|w_{i})\approx ln\,C(t_{i},w_{i}) - ln\,C(w_{i})$
计算$-ln\,P(t_{i}|w_{i})$的代码,位于AsianPersonRecognition.nameFind(int offe, int beginFreq, int size):
allFreq += Math.log(term.termNatures().allFreq + 1);
allFreq += -Math.log((freq));

但问题是,$C(w_{i})$ 和$C(t_{i},w_{i})$的统计来自不同的语料,所以这里的算法是没有理论依据的。

2.4、构造最短路径

计算完概率后,就可以使用Viterbi算法来求解最短路径,这与上一节类似。

以“陈颖超生前很和蔼”为例,将会得到以下结果:

[陈颖/nr, null, 超生/v, null, 前/f, 和蔼可亲/i, null, null, null, 末##末]

2.5、人名消歧

在上述例子中,需要对“超生”进行拆分,并且将“超”与之前的人名合并。
代码位于NameFix类的nameAmbiguity(Term[] terms, Forest... forests)方法。
程序不对单字名进行拆分(例如,“陈超生前和蔼可亲”最终分词结果是:[陈/nr, 超生/v, 前/f, 和蔼可亲/i],这是不对的)。
经过人名消歧,将得到如下结果:
[陈颖超/nr, null, null, 生/v, 前/f, 和蔼可亲/i, null, null, null, 末##末]

2.6、用户自定义字典识别

代码是ToAnalysis类中的:
// 用户自定义词典的识别
userDefineRecognition(graph, forests);
在用户自定义字段library\default.dic中,是存在“生前”这个词的,经过这一步,会将“生前”进行合并,得到最终结果:
[陈颖超/nr, 生前/t, 和蔼可亲/i]
对于用户自定义词典的识别,打算在下一节进行详细说明。

ansj人名识别的更多相关文章

  1. Hanlp实战HMM-Viterbi角色标注中国人名识别

    这几天写完了人名识别模块,与分词放到一起形成了两层隐马模型.虽然在算法或模型上没有什么新意,但是胜在训练语料比较新,对质量把关比较严,实测效果很满意.比如这句真实的新闻“签约仪式前,秦光荣.李纪恒.仇 ...

  2. HanLP中人名识别分析

    HanLP中人名识别分析 在看源码之前,先看几遍论文<基于角色标注的中国人名自动识别研究> 关于命名识别的一些问题,可参考下列一些issue: 名字识别的问题 #387 机构名识别错误 关 ...

  3. HanLP中人名识别分析详解

    HanLP中人名识别分析详解 在看源码之前,先看几遍论文<基于角色标注的中国人名自动识别研究> 关于命名识别的一些问题,可参考下列一些issue: l ·名字识别的问题 #387 l ·机 ...

  4. HanLP中的人名识别分析详解

    在看源码之前,先看几遍论文<基于角色标注的中国人名自动识别研究> 关于命名识别的一些问题,可参考下列一些issue: u u名字识别的问题 #387 u u机构名识别错误 u u关于层叠H ...

  5. ICTCLAS中的HMM人名识别

    http://www.hankcs.com/nlp/segment/ictclas-the-hmm-name-recognition.html 本文主要从代码的角度分析标注过程中的细节,理论谁都能说, ...

  6. 【文智背后的奥秘】系列篇——基于CRF的人名识别

    版权声明:本文由文智原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/133 来源:腾云阁 https://www.qclou ...

  7. 基于分布式的短文本命题实体识别之----人名识别(python实现)

    目前对中文分词精度影响最大的主要是两方面:未登录词的识别和歧义切分. 据统计:未登录词中中文姓人名在文本中一般只占2%左右,但这其中高达50%以上的人名会产生切分错误.在所有的分词错误中,与人名有关的 ...

  8. hanlp自然语言处理包的人名识别代码解析

    HanLP发射矩阵词典nr.txt中收录单字姓氏393个.袁义达在<中国的三大姓氏是如何统计出来的>文献中指出:当代中国100个常见姓氏中,集中了全国人口的87%,根据这一数据我们只保留n ...

  9. HanLP-基于HMM-Viterbi的人名识别原理介绍

    Hanlp自然语言处理包中的基于HMM-Viterbi处理人名识别的内容大概在年初的有分享过这类的文章,时间稍微久了一点,有点忘记了.看了 baiziyu 分享的这篇比我之前分享的要简单明了的多.下面 ...

随机推荐

  1. 一个操作cvs格式的c++类

    经常需要使用excel,或者把有的数据用excel打开,程序可以生成cvs格式的文件,这样就可以excel打开并处理了,于是找了一个处理cvs的c++类跟大家分享 代码出处找不到了: 代码如下: St ...

  2. Linux常用命令(第二版) --Shell应用技巧

    Shell应用技巧 小技巧: 1.命令补全功能: <Tab>键 2.清屏: Ctrl+l 3.删除光标前所有内容: Ctrl+u 4.命令历史记录: history 这时:  !histo ...

  3. Sample Code for Qp_preq_pub.Price_request Api to Simulate an Ask for Promotion Modifier

    DECLARE p_line_tbl QP_PREQ_GRP.LINE_TBL_TYPE; p_qual_tbl QP_PREQ_GRP.QUAL_TBL_TYPE; p_line_attr_tbl ...

  4. iOS监听模式系列之推送消息通知

    推送通知 和本地通知不同,推送通知是由应用服务提供商发起的,通过苹果的APNs(Apple Push Notification Server)发送到应用客户端.下面是苹果官方关于推送通知的过程示意图: ...

  5. SharePoint 2013 新建网站集图解(绝对菜鸟篇)

    前言:接触SharePoint的人可能是越来越多,但是很多人一接触就很迷茫,在技术群里问如何新建网站集,这样一篇图解,帮助新手学习在搭建好SharePoint环境之后,如何创建一个网站集,做一个基本的 ...

  6. Redis 协议为例谈简单的协议分析

    怎样去研究一个协议的过程,协议的格式,好处,怎么样模拟发包等,下面是一个简单的过程记录. 研究的步骤: 协议相关的资料,RFC,官方文档等.弄清楚协议工作在4层还是7层,是二进制还是文本协议等 抓包, ...

  7. PLSQL Developer使用技巧

    本文由liuyk80贡献 ·PL/SQL Developer 使用技巧 1.PL/SQL Developer 记住登陆密码 在使用 PL/SQL Developer 时,为了工作方便希望 PL/SQL ...

  8. jBPM4工作流应用开发指南

    首先十分感谢作者给我这个机会在他的作品即将问世之前做一些感想,也正好让我能在忙碌中抽空回顾一下这么多年在技术平台方面走过的路以及在Workflow方面的点点滴滴.因为本书是介绍jBPM的专业书籍,所以 ...

  9. P3370 【模板】字符串哈希

    题目描述 如题,给定N个字符串(第i个字符串长度为Mi,字符串内包含数字.大小写字母,大小写敏感),请求出N个字符串中共有多少个不同的字符串. 输入输出格式 输入格式: 第一行包含一个整数N,为字符串 ...

  10. No plugin found for prefix 'tomcat' in the current project and in the plugin groups和java.net.BindException: Address already in use: JVM_Bind <null>:8080的错误解决

    错误报告:No plugin found for prefix 'tomcat' in the current project and in the plugin groups [org.apache ...