放牧代码和思想
专注自然语言处理、机器学习算法

实战HMM-Viterbi角色标注地名识别

目录

nlp.jpg

命名实体识别(Named Entity Recognition)也是自然语言处理中的一个难关,特别是中文这样没有大小写等固定形态的语言。上次介绍过《实战HMM-Viterbi角色标注中国人名识别》,这次基于类似的原理,为HanLP实现中文地址地名(NS)的自动识别。

开源项目

本文代码已集成到HanLP中开源:http://www.hankcs.com/nlp/hanlp.html

原理

训练

对熟语料自动角色标注,统计单词的角色频次、角色的转移概率等,训练出一个模型,同时总结一些可用的模式串。

识别

根据上述模型,利用HMM-Viterbi算法标注陌生文本的粗分结果,利用Aho-Corasick算法模式匹配,匹配出可能的地址,将其送入第二层隐马尔可夫模型中。

实战

训练

自动角色标注

基于层叠隐马尔可夫模型的中文命名实体识别.pdf》中使用如下地名识别角色:

我在此基础上拓充了CDE分别为三字地名的三个字位,H为中国地名的后缀,G为整个地址,这样一般最多可以识别6字地名(CDE地名+三字后缀),比论文有所改进。

通过少量的代码即可自动对熟语料进行角色标注,比如对于这一句人民日报2014切分语料中的句子:

王先东/nr 来自/v 湖北/ns 荆门/ns ,/w 在/p 佛山市/ns [南海区/ns 大沥镇/ns]/nz 某/rz 物业公司/nis 做/v 保安/b

逐步处理得到

原始语料 [未##人/nr, 来自/v, 湖北/ns, 的/ude1, 荆门/ns, ,/w, 在/p, 乌鲁木齐市/ns, [南海区/ns 大沥镇/ns]/ns, 某/rz, 物业公司/nis, 做/v, 保安/b]
添加首尾 [始##始/S, 未##人/nr, 来自/v, 湖北/ns, 的/ude1, 荆门/ns, ,/w, 在/p, 乌鲁木齐市/ns, [南海区/ns 大沥镇/ns]/ns, 某/rz, 物业公司/nis, 做/v, 保安/b, 末##末/Z]
标注上文 [始##始/S, 未##人/nr, 来自/A, 湖北/ns, 的/A, 荆门/ns, ,/w, 在/A, 乌鲁木齐市/ns, [南海区/ns 大沥镇/ns]/ns, 某/rz, 物业公司/nis, 做/v, 保安/b, 末##末/Z]
标注下文 [始##始/S, 未##人/nr, 来自/A, 湖北/ns, 的/B, 荆门/ns, ,/B, 在/A, 乌鲁木齐市/ns, [南海区/ns 大沥镇/ns]/ns, 某/B, 物业公司/nis, 做/v, 保安/b, 末##末/Z]
标注中间 [始##始/S, 未##人/nr, 来自/A, 湖北/ns, 的/X, 荆门/ns, ,/B, 在/A, 乌鲁木齐市/ns, [南海区/ns 大沥镇/ns]/ns, 某/B, 物业公司/nis, 做/v, 保安/b, 末##末/Z]
拆分地名 [始##始/S, 未##人/nr, 来自/A, 湖北/ns, 的/X, 荆门/ns, ,/B, 在/A, 乌鲁木齐市/ns, 南海区/ns, 大沥镇/ns, 某/B, 物业公司/nis, 做/v, 保安/b, 末##末/Z]
处理整个 [始##始/S, 未##人/Z, 来自/A, 湖北/G, 的/X, 荆/C, 门/H, ,/B, 在/A, 乌鲁木齐/G, 市/H, 南/C, 海/D, 区/H, 大/C, 沥/D, 镇/H, 某/B, 物业公司/Z, 做/Z, 保安/Z, 末##末/Z]

统计词频

在对所有熟语料句子执行自动标注后,即可统计每一个非Z词语的词频,得到一个角色词典:

位于 A 1660 X 93 B 33
位列 B 17 A 13 X 1
位居 B 25 A 14 X 1
位次 B 1
位置 B 5 A 1
低 B 9
低于 A 18 B 2
低产田 B 1
低价 B 1
低估 A 5
低保 B 3
低保户 B 3
低效 B 1
低温 B 3
低热值 B 1
低碳 B 27
低空 B 2
低调 B 5
低速 B 3
低阶煤 B 1
住 A 81 B 53
住友 B 1
住在 A 271 B 1

统计转移矩阵

转移矩阵指的是从一个角色标签转移到另一个角色的频次,利用它和角色词频可以计算出HMM中的初始概率、转移概率、发射概率,进而完成求解。关于维特比算法和实现请参考《通用维特比算法的Java实现》。

这里对人民日报2014切分语料训练出如下转移矩阵:

识别

例子

以“南翔向宁夏固原市彭阳县红河镇黑牛沟村捐赠了挖掘机”为例,不进行地名识别时,会得出下列输出:

[南翔/ns, 向/p, 宁夏/ns, 固原市/ns, 彭/nz, 阳/ag, 县/n, 红/a, 河镇/ns, 黑/a, 牛/n, 沟/n, 村/n, 捐赠/v, 了/ule, 挖掘机/n]

上例中“宁夏”“固原市”等属于常用地名,因此被收录到核心词典中,此处表现出正确的分词结果。但是像“彭阳县”“红河镇”“黑牛沟村”等地名属于非常小的地方,没有被词典收录,自然也没法得出正确的分词结果。

角色标注

地名角色观察:[  Z 41339414 ][南翔 H 1000 ][向 A 1076 B 115 X 70 C 49 D 5 ][宁夏 H 1000 ][固原市 H 1000 ][彭 C 85 ][阳 D 1255 C 81 B 1 ][县 H 6878 B 25 A 23 D 19 X 3 C 2 ][红 C 1000 B 46 A 3 ][河镇 H 1000 ][黑 C 960 B 25 ][牛 D 24 C 8 B 7 ][沟 H 107 D 90 E 36 C 27 B 14 A 3 ][村 H 4467 D 68 B 28 A 8 C 3 ][捐赠 B 10 A 1 ][了 A 4115 B 97 ][挖掘机 B 1 ][  Z 41339414 ]
地名角色标注:[ /Z ,南翔/H ,向/B ,宁夏/H ,固原市/H ,彭/C ,阳/D ,县/H ,红/C ,河镇/H ,黑/C ,牛/D ,沟/E ,村/H ,捐赠/B ,了/A ,挖掘机/B , /Z]

模式匹配

利用Aho-Corasick算法模式匹配如下模式串:

        CH
        CDH
        CDEH
        GH

得到如下地名:

识别出地名:彭阳县 CDH
识别出地名:红河镇 CH
识别出地名:黑牛沟村 CDEH

第二层隐马模型细分

其实这应该算是第三层隐马模型,因为地名识别中也用到了一次HMM,并且那次的输出是这次的输入。细分之后得出最终的结果:

[南翔/ns, 向/p, 宁夏/ns, 固原市/ns, 彭阳县/ns, 红河镇/ns, 黑牛沟村/ns, 捐赠/v, 了/ule, 挖掘机/n]

总结

HMM模型可以解决很多问题,将多个HMM模型层叠起来,可以发挥出更加精准的效果。

不过2元文法依然会有误命中的情况,事实上,一些高频地名已经收录到核心词典和用户自定义词典中。所以HanLP的默认配置关闭了地名识别,仅仅在一些极端情况下(专门提取县级地址)交由用户打开。

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » 实战HMM-Viterbi角色标注地名识别

分享到:更多 ()

评论 16

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #10

    湖南省长沙市湘春路169号。
    被分词为:
    湖南/ns, 省长/nnt, 沙市/ns, 湘春路/ns, 169/m, 号/q, 。/w,
    正确的理解应该是:
    湖南省/ns, 长沙市/ns, 湘春路/ns, 169/m, 号/q, 。/w,
    请问这个怎么处理?

    LMSS_871年前 (2016-05-17)回复
  2. #9

    请问下,为什么两个 ns的地名连在一起就变成nt了,例如 使用newSegment 并设置enableAllNamedEntityRecognize(true), “广州市,天河区看电影”得到为“广州市/ns,天河区/ns,看/v,电影/n”,而 “广州市天河区看电影”得到为“广州市天河区/nt,看/v,电影/n”

    郑仁峰1年前 (2016-01-27)回复
    • 可能是机构识别的时候将其识别为nt了,如果关闭机构识别,就回到“广州市/ns,天河区/ns,看/v,电影/n“,但是不会得到想要的“广州市天河区/ns,看/v,电影/n”,还需要根据自己需求做后续的处理。

      jen1年前 (2016-01-27)回复
  3. #8

    请问一下,ns.txt.trie.dat和ns.txt.value.dat是怎么通过ns.tr和ns产生的,有代码吗

    Terry2年前 (2015-12-03)回复
    • 我找到了,抱歉

      Terry2年前 (2015-12-03)回复
  4. #7

    对,独立的

    hankcs2年前 (2015-11-09)回复
  5. #6

    多谢楼主,我看NS的tag和NT的tag,中间的一些角色有些冲突。比如BCD的定义。你这个系统实际运行的时候是先人名识别->地名识别->机构名识别->重新分词的吧。

    finallyly2年前 (2015-11-09)回复
  6. #5
  7. #4

    楼主E是啥标记啊

    liuyu2年前 (2015-11-09)回复
  8. #3

    刚刚的问题,我发现是使用用户词典后,没有清除原来的vertex.from,我觉得使用用户词典后应该更新vertex新的from
    if (config.useCustomDictionary)
    {
    combineByCustomDictionary(vertexList);
    Vertex vertex = null;
    for(Vertex v : vertexList)
    {
    if(v!=null)
    {
    v.from = vertex;
    vertex = v;
    }
    }
    }

    small bird2年前 (2015-09-20)回复
    • 我昨天说的就是这个意思,我嫌这种方法影响效率,有其他方法可以解决问题。

      hankcs2年前 (2015-09-20)回复
  9. #2

    “中国海军152舰艇编队到访埃及”这句话到
    if (config.organizationRecognize)
    {
    // 层叠隐马模型——生成输出作为下一级隐马输
    vertexList = viterbi(wordNetOptimum);/////单步跟踪试一下
    //for(){}

    wordNetOptimum.clear();
    wordNetOptimum.addAll(vertexList);
    preSize = wordNetOptimum.size();
    OrganizationRecognition.Recognition(vertexList, wordNetOptimum, wordNetAll);
    }
    vertexList = viterbi(wordNetOptimum);这句时,“中国海军”被切分成“中国海”+“军”,这是为什么啊?“中国海军”明明在用户词典添加后就已经被识别了。

    small bird2年前 (2015-09-20)回复
    • Segment seg = HanLP.newSegment().enableAllNamedEntityRecognize(true);
      List<Term> termList = seg.seg("中国海军152舰艇编队到访埃及");
      System.out.println(termList);

      [中国海军152舰艇编队/nt, 到访/v, 埃及/ns]

      没有问题

      hankcs2年前 (2015-09-20)回复
  10. #1

    大神好,做过CRF++的命名实体识别没。求带词性标注好的字级别的语料

    刚子2年前 (2015-04-23)回复

我的开源项目

HanLP自然语言处理包基于DoubleArrayTrie的Aho Corasick自动机