jcseg - 中文姓名识别算法

狮子的魂 发布于 2012/11/19 14:46
阅读 4K+
收藏 9

jcseg支持中文姓名的识别。

但是并不是什么很具有新意的算法,或者说需要经过一大版的数学公式计算才能实现的。

jcseg的姓名识别算法很简单,但是从实际效益来看,确实达到了我预期的效果。

首先说明:jcseg使用的不是机械匹配中文姓名(虽然词库里面有一些人名,cc-cedict里面整理出来的),本人觉得这样太机械了,这么的中文姓名不可能都存词库。

如何实现的呢? 

巧妙利用mmseg算法的特性和中文姓名的特性。(那么哥们你就要熟悉mmseg算法了)

每一次调用next方法去获取下一个切分结果,jcseg会首先返回经过四种过滤算法筛选出来的最好的一个chunk。

例如:A_B_C

mmseg每确认一个切分结果都会向后查找三个词,然后吧所有可能的组合情况组合成很多chunk,但是经过四种过滤算法后,最终会返回一个最好的chunk(组合)(也有返回多个的情况,但是这个不是问题)。

在这个过程中,mmseg已经告诉我们了,所有可能情况我都试过了,这个是最好的,也就是能成词的已经成词了,没有成词的单字,通常是“成词自由语素度”很高的词,例如:的,是。。。

(想象以下,如果在返回的chunk中,能够找到中文姓氏,而且大部分的中文姓氏的“成词自由语素度”都很低,这说明什么?说明这个地方隐藏了一个中文姓名)。

姓名识别过程:

1.先确认整个chunk的长度(词语的个数)大于1,并且这个chunk的第一个词长度(字的个数)小于等于2。(大于2那就没必要分析了,说明是从词库里面匹配的结果,而一个中文姓氏最长为2,当然是指汉族)

jcseg中代码:

IChunk chunk = getBestCJKChunk(chars, cjkidx);
w = chunk.getWords()[0];
					
int T = -1;
if ( Config.I_CN_NAME
    && w.getLength() <= 2 && chunk.getWords().length > 1  ) {
    StringBuilder sb = new StringBuilder(); 
    sb.append(w.getValue());
    String str = null;
						
    if ( dic.match(ILexicon.CN_LNAME, w.getValue()) && (str = findCHName(chars, 0, chunk)) != null) {
        T = IWord.T_CN_NAME;
        sb.append(str);
    }
    else if ( dic.match(ILexicon.CN_LNAME_ADORN, w.getValue())
        && chunk.getWords()[1].getLength() <= 2 
        && dic.match(ILexicon.CN_LNAME, 
              chunk.getWords()[1].getValue())) {
        T = IWord.T_CN_NICKNAME;
        sb.append(chunk.getWords()[1].getValue());
    }
    /*
     * the length of the w is 2:
     * the last name and the first char make up a word
     * for the double name. 
     */
    /*else if ( w.getLength() > 1 && findCHName( w, chunk ))  {
        T = IWord.T_CN_NAME;
        sb.append(chunk.getWords()[1].getValue().charAt(0));
    }*/
						
    if ( T != -1 ) w = new Word(sb.toString(), T);

}


第一个if里面的条件就是进行相关的判断。


2.判断是否满足中文姓名查找条件:

(1).中文姓氏确认。

if ( dic.match(ILexicon.CN_LNAME, w.getValue()) 
    && (str = findCHName(chars, 0, chunk)) != null)

获取word的值:w.getValue()返回这词的字符串表示形式,去姓氏词库里面查找(ILexicon.CN_LNAME,表示姓氏词库,词库文件为:lex-lname.lex,包含中文姓名中所有单复姓氏),确认w是否为一个中文姓氏,这是姓名查找最基本的要求吧。(记得有双字姓氏哦,所以“诸葛亮”可以识别出来)


(2).姓氏修饰词确认,这个我们通常老叫,老陈,老张,小陈。。。 对,就是前面的老,小什么的。这种姓氏修饰有一个专门的词库:lex-ln-adorn.lex


//the w is Chinese last name adorn
else if ( dic.match(ILexicon.CN_LNAME_ADORN, w.getValue()) {

}

获取word的值:w.getValue()返回这个词的字符串表示形式,去姓氏修饰词库里面查找(ILexicon.CN_LNAME_ADORN,表示姓氏修饰词库),确认w是否为一个中文姓氏修饰词。这个也可以有吧。


3.姓名查找:

满足姓名查找条件(满足上述中一个就可以了),就可以开始查找姓名了。

由:findCHName( chars, 0, chunk )这个方法来实现。

主要思路为:(需要根据chunk的词数和词长分情况分析,这里笼统的概述下)

(1).查看是否满足双姓名的要求(很简单,加上姓氏至少需要三个字吧),

--》确认是否为双姓名,查看姓氏后的第一个词是否为双姓名首字(词库:lex-dname-1.lex保存了所有可能做双姓名的首字的单字)

d1表示姓氏后的第一个词。
d2表示姓氏后的第二个词。
if ( dic.match(ILexicon.CN_DNAME_1, d1)
    && dic.match(ILexicon.CN_DNAME_2, d2)) {
    //可能为双姓名。
}

(2).如果不是双姓名,则看看是否为单姓名:


-》先确认姓氏后的第一个词是否为单字姓名词库(lex-sname.lex)中的词。

-》如果是,在确认姓氏后的第二个词的“成词自由语素度”是否满足要求(通常情况下,姓名都和哪些“成词自由语素度”很高的词在一起,而且姓名中的词“自由语素度”都偏低,这个假设得到了统计的验证)。

Config.NANE_SINGLE_THRESHOLD这个就是阕值。可以更改jcseg.properties配置文件实现自主更改。


else if ( dic.match(ILexicon.CN_SNAME, d1) 
    && dic.get(ILexicon.CJK_WORDS, d2).getFrequency() >= Config.NAME_SINGLE_THRESHOLD ) {
    //确认为单姓名。
}


上面的情况,我忽略了能够进行这么判断的条件,具体可以查看jcseg源码。

(而且具体还需要更具chunk的词长和词数来分情况判断。)


剩下就是一些歧义情况:(姓名中的词和周围的词成词的情况)

1.双姓名:

-》双字姓名中,首字和尾字成词的情况,例如:“陈美丽是对的”

chunk: 陈_美丽_是(美丽成词)

这个jcseg是基于姓名周围的单字“成词自由语素度”偏高的情况,来去除歧义的。也就是查看最后一个词的最后一个字的“语素自由度“和Config.NANE_SINGLE_THRESHOLD对比结果。

-》双字姓名中,尾字和后面的词成词的情况,例如:“这本书是陈志高的”,“ 陈美丽的人生”

chunk:陈_志_高的

chunk:陈_美丽的_人生

同上一解决办法一样。


2.单姓名:

-》尾字和其后的一个字成词的情况:例如:"这本书是陈高的"

chunk:陈_高的 (single name)

通过查看“自由语素度”可以去除歧义。


3.不知是“单字姓名”还是”双字姓名“的情况:就是chunk的词数为3,但是中间这个词的字数为1的情况。

例如:“ 陈志高兴奋极了”,“陈高兴奋极了”,“陈志高的”

chunk:陈_志_高兴 (兴和后面成词)

chunk:陈_高_兴奋 (single name)

chunk:陈_志_高的

同时这也是这个算法的最麻烦的部分之一,按照上述过程这个姓名到底是”陈高“这个单字姓名还是“陈高兴”这个双子姓名呢?正确的结果我们都知道。

jcseg是通过在此位置再向后取一个chunk来分析去除歧义的。

基于这样的一个情况,如果最后一个词语(高兴,兴奋,高的)中的“兴,奋,的”和后面的词成词,并且这个词的首字,("高", “兴”, “高”)又是lex-double-2.lex(双字姓名的尾字词库)中的词,则认为是双字姓名,如果不成词,并且这些词的“自由语素度”偏高则,认为为单字姓名,如果“自由语素度”偏高本身就可以成词,至少可以达到统计的切分效果。


另外还有一种情况是jcseg目前没有处理的,就是双字姓名中的姓氏和双字姓名的首字成词的情况:

例如:“我很喜欢陈述高的演讲,我很不喜欢陈述高调的样子。”

中的第一个名字“陈述高”中“陈述”成词。但是第二个不成词。

目前我见过的所有支持中文姓名识别的分词系统中,没有一个可以正确识别的,包括“哈尔滨工程学院”的NLP系统。

都识别成:“陈述”。

本文的第一断代码中有一块注释就是用来处理这种情况的,但是反而带来了更多的负面影响,很不成熟,所以我注释掉了。


jcseg姓名识别歧义:

首先声明我不是什么语言处理专家,我的语文一直都是及格的水平。只是顺着逻辑这么分析而已,总有考虑不到的情况,所以这个算法也有不少识别错误的情况。

1.多个姓氏排在一起的情况就会出问题,

例如:“向林俊德同志表示问候”,

中的向和林都是姓氏,jcseg目前处理这种情况会出问题。(目前我吧“”这个姓氏在词库中注释掉了)。

2.姓氏用作非姓氏的情况:

例如:心血和智慧,

中的“”是姓氏词,但是在这个地方不是用作姓氏。

导致分词结果为:心血|和智慧 (目前我吧“”这个姓氏在词库中注释掉了)。


这些问题会可能会在jcseg 1.7.2版本中解决。那么jcseg的姓名识别正确率可以达到94%以上。

目前我通过使用人名日报的新闻进行姓名识别,有90%以上的正确率。

这里和大家分享下这个思路,希望有更好算法的哥们,能够晒出来和大家一起分享分享下



加载中
返回顶部
顶部