中文分词工具探析(一):ICTCLAS (NLPIR)

摘要:
系列:开源中文分词工具分析(I):ICTCLAS开源中文分词软件分析(II):杰巴开源中文分词系统分析(III):Ansj开源中文分词程序分析(IV):THULAC开源中文分词分析(V):FNLP开源中文分词器分析(六) :斯坦福CoreNLP开源中文分词工具分析(VII):LTP1。前言ICTCLAS是张华平推出的中文分词系统,2009年更名为NLPIR。有时,对应于最大联合概率的分割结果不一定是最优的。为了解决这个问题,ICTCLAS采用了N最佳策略,即求解N条最短路径,而不是直接求解最短路径。

【开源中文分词工具探析】系列:

  1. 开源中文分词工具探析(一):ICTCLAS (NLPIR)
  2. 开源中文分词工具探析(二):Jieba
  3. 开源中文分词工具探析(三):Ansj
  4. 开源中文分词工具探析(四):THULAC
  5. 开源中文分词工具探析(五):FNLP
  6. 开源中文分词工具探析(六):Stanford CoreNLP
  7. 开源中文分词工具探析(七):LTP

1. 前言

ICTCLAS是张华平老师推出的中文分词系统,于2009年更名为NLPIR。ICTCLAS是中文分词界元老级工具了,作者开放出了free版本的源代码(1.0整理版本在此). 作者在论文[1] 中宣称ICTCLAS是基于HHMM(Hierarchical Hidden Markov Model)实现,后在论文[2]中改成了基于层叠隐马尔可夫模型CHMM(Cascaded Hidden Markov Model)。我把HHMM的原论文[3]读了一遍,对照ICTCLAS源码,发现ICTCLAS本质上就是一个Bigram的Word-Based Generative Model,用HMM来做未登录词识别(修正分词)与词性标注,与HHMM没有半毛钱关系。Biagram语法模型对应于1阶Markov假设,则ICTCLAS分词模型的联合概率为

egin{equation}
P(w_1^m) = prod_i P(w_{i} | w_{i-1})
label{eq:bigram}
end{equation}

2. ICTCLAS分解

ICTCLAS Free版的C++源代码实在是佶屈聱牙,因本人水平有限,故参照的源码为sinboy的Java实现 ictclas4j"com.googlecode.ictclas4j" % "ictclas4j" % "1.0.1")。ICTCLAS分词流程如下:

  1. 按照核心词典进行第一次切词;
  2. 在第一次切词的基础上,求解最大联合概率eqref{eq:bigram},作者称之为“二元切分词图”;
  3. HMM识别未登录词,诸如:人名、翻译人名、地名、机构名等;
  4. 分词结果整理,词性标注。

词典

ICTCLAS的词典包括六种:

import org.ictclas4j.segment._

// 核心词典
val coreDict = new Dictionary("data/coreDict.dct")
// 二元词典
val bigramDict = new Dictionary("data/bigramDict.dct")
// 人名词典
val personTagger = new PosTagger(Utility.TAG_TYPE.TT_PERSON, "data/nr", coreDict)
// 翻译人名词典
val transPersonTagger = new PosTagger(Utility.TAG_TYPE.TT_TRANS_PERSON, "data/tr", coreDict)
// 地名词典
val placeTagger = new PosTagger(Utility.TAG_TYPE.TT_TRANS_PERSON, "data/ns", coreDict)
// 词性标注
val lexTagger = new PosTagger(Utility.TAG_TYPE.TT_NORMAL, "data/lexical", coreDict)

其中,核心词典(coreDict)用于初始切词,二元词典(bigramDict)记录了两个词联合出现的频数。coreDict与bigramDict的数据格式如下

# coreDict
块0 
  count:4 
	wordLen:0	frequency:3	handle:25856	word:(啊) 
	wordLen:0	frequency:69	handle:30976	word:(啊) 
	wordLen:2	frequency:0	handle:25856	word:(啊)呀 
	wordLen:2	frequency:0	handle:25856	word:(啊)哟 
块1 
  count:133 
	wordLen:0	frequency:0	handle:26624	word:(阿) 
	wordLen:0	frequency:94	handle:27136	word:(阿) 
	...
# bigramDict
块0 
  count:9 
	wordLen:3	frequency:3	handle:3	word:(啊)@、 
	wordLen:3	frequency:4	handle:3	word:(啊)@。 
	wordLen:3	frequency:1	handle:3	word:(啊)@” 
	wordLen:3	frequency:39	handle:3	word:(啊)@! 
	wordLen:3	frequency:20	handle:3	word:(啊)@,

其中,frequency表示词频,handle表示词性:

让我们先看一个具体的例子:
wordLen:4 frequency:12 handle:28275 word:(爱)尔兰
handle = 28275, 转成HEX表示,为0x6E73。
把0x6E73解释为字符数组,0x6E -> 'n', 0x73 -> 's', 所以0x6E73 -> "ns"。
查北大的《汉语文本词性标注标记集》, ns ~ 地名 (名词代码n和处所词代码s并在一起)。

计算

为了计算联合概率eqref{eq:bigram}最大值,ICTCLAS做了一个巧妙的转换——求对数并做Linear interpolation平滑:

[egin{aligned} arg max prod_i P(w_{i} | w_{i-1}) & = arg min - sum_i log P(w_{i} | w_{i-1}) \ & approx arg min - sum_i log left[ aP(w_{i-1}) + (1-a) P(w_{i}|w_{i-1}) ight] end{aligned} ]

将所有的可能分词组合构建成一个有向图,边的权重设为(上式中)平滑后的log值。由此,将求解最大联合概率转化成一个图论中最短路径问题。有时最大联合概率对应的分词结果不一定是最优的。为了解决这个问题,ICTCLAS采取了N-best策略,即求解N-最短路径而不是直接求解最短路径。然后在N-最短路径的分词结果上,做HMM修正——识别未登录词与词性标注。为了更清晰地了解分词流程,我用scala把分词函数SegTag::split()重新写了一遍:

import java.util

import org.ictclas4j.bean.{Dictionary, SegNode}
import org.ictclas4j.segment._
import org.ictclas4j.utility.{POSTag, Utility}

import scala.collection.JavaConversions._

val sentence = "深夜的穆赫兰道发生一桩车祸,女子丽塔在车祸中失忆了"
val pathCount = 1
// 存储分词结果
val stringBuilder = StringBuilder.newBuilder
// 按[空格|回车]分隔句子
val sentenceSeg = new SentenceSeg(sentence)
sentenceSeg.getSens.filter(_.isSeg).foreach { sen =>
  // 原子切分
  val as: AtomSeg = new AtomSeg(sen.getContent)
  // 全切分:根据核心词典,生成所有的可能词
  val segGraph: SegGraph = GraphGenerate.generate(as.getAtoms, coreDict)
  // 二元切分词图
  val biSegGraph: SegGraph = GraphGenerate.biGenerate(segGraph, coreDict, bigramDict)
  val nsp: NShortPath = new NShortPath(biSegGraph, pathCount)
  nsp.getPaths.foreach { onePath =>
    // 得到初次分词路径
    val segPath = getSegPath(segGraph, onePath)
    val firstPath = AdjustSeg.firstAdjust(segPath)
    // 处理未登陆词,进对初次分词结果进行优化
    val optSegGraph: SegGraph = new SegGraph(firstPath)
    personTagger.recognition(optSegGraph, firstPath)
    transPersonTagger.recognition(optSegGraph, firstPath)
    placeTagger.recognition(optSegGraph, firstPath)

    // 根据优化后的结果,重新进行生成二叉分词图表
    val optBiSegGraph: SegGraph = GraphGenerate.biGenerate(optSegGraph, coreDict, bigramDict)
    // 重新求取N-最短路径
    val optNsp: NShortPath = new NShortPath(optBiSegGraph, pathCount)
    val optBipath: util.ArrayList[util.ArrayList[Integer]] = optNsp.getPaths

    // 词性标记
    optBipath.foreach { optOnePath =>
      val optSegPath: util.ArrayList[SegNode] = getSegPath(optSegGraph, optOnePath)
      lexTagger.recognition(optSegPath)
      // 对分词结果做最终的调整,主要是人名的拆分或重叠词的合并
      val adjResult = AdjustSeg.finaAdjust(optSegPath, personTagger, placeTagger)
      val adjrs: String = outputResult(adjResult)
      stringBuilder ++= adjrs
    }
  }
}
println(stringBuilder.toString())
// 深夜/t 的/u 穆赫兰/nr 道/q 发生/v 一/m 桩/q 车祸/n ,/w 女子/n 丽塔/nr 在/p 车祸/n 中/f 失/v 忆/vg 了/y

其中,调用了函数:

// 根据二叉分词路径生成分词路径
private def getSegPath(sg: SegGraph, biPath: util.ArrayList[Integer]): util.ArrayList[SegNode] = {
	sg != null && biPath != null match {
	  case true =>
	    val path = biPath.map { t => sg.getSnList.get(t) }
	    new util.ArrayList[SegNode](path)
	  case _ => null
	}
}

// 根据分词路径生成分词结果
private def outputResult(wrList: util.ArrayList[SegNode]): String = {
    ...
}

后记:据闻ICTCLAS的第一版是作者在中科院读硕期间完成的,虽说代码质量惹人吐槽,但是不得不惊叹于作者的代码功底以及在训练模型上所下的功夫。

3. 参考资料

[1] Zhang, Hua-Ping, et al. "HHMM-based Chinese lexical analyzer ICTCLAS." Proceedings of the second SIGHAN workshop on Chinese language processing-Volume 17. Association for Computational Linguistics, 2003.
[2] 刘群, et al. "基于层叠隐马模型的汉语词法分析." 计算机研究与发展 41.8 (2004): 1421-1429.
[3] Fine, Shai, Yoram Singer, and Naftali Tishby. "The hierarchical hidden Markov model: Analysis and applications." Machine learning 32.1 (1998): 41-62.

免责声明:文章转载自《中文分词工具探析(一):ICTCLAS (NLPIR)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇cookie 设置有效期 检测cookie利用正则表达式,分割地址至省市县,更新MySQL数据库数据下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

几种开源分词工具的比較

  搜集了一些资料,与同学一起进行了简单的測试,总结例如以下。 分词工  具       特点    支持语言     原理 词典及扩展性 StandardAnalyzer 中文、英文(unicode) 中文:单字符切分 英文:依据空格切分 ChineseAnalyzer 中文,不支持中文和英文及数字混合的文本分词  按字分词,与S...

开源中文分词工具探析(四):THULAC

THULAC是一款相当不错的中文分词工具,准确率高、分词速度蛮快的;并且在工程上做了很多优化,比如:用DAT存储训练特征(压缩训练模型),加入了标点符号的特征(提高分词准确率)等。 【开源中文分词工具探析】系列: 开源中文分词工具探析(一):ICTCLAS (NLPIR) 开源中文分词工具探析(二):Jieba 开源中文分词工具探析(三):Ansj 开...

中文分词与词云绘制

中文分词与词云绘制 1.数据采集 数据来源,B站视频弹幕。 得到的结果是xml格式;使用正则表达式进行数据解析; # -*- coding: utf-8 -*- ''' @Time : 2021/12/19 12:27 @Author : ziqingbaojian @File : 01.爱意随风起.py ''' import request...

自然语言处理中的分词问题总结

众所周知,英文是以词为单位的,词和词之间是靠空格隔开,而中文是以字为单位,句子中所有的字连起来才能描述一个意思。把中文的汉字序列切分成有意义的词,就是中文分词,有些人也称为切词。本文转载自明略研究院的技术经理牟小峰老师讲授的语言处理中的分词问题。 如何界定分词   中文分词指的是将一个汉字序列切分成一个一个单独的词。分词就是将连续的字序列按照一定的规范重新...

ES笔记一:周边工具

ElasticSearch是一个基于Lucene构建的开源,分布式,RESTful搜索引擎。它用Java 编写的,它的内部使用 Lucene做索引与搜索,目的是使全文检索变得简单,设计用于云计算中,能够达到实时搜索,稳定,可靠,使用简单方便(使用JSON进行数据索引,通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API)。...

lucene.net 详解

转自:http://www.360doc.com/content/09/0216/17/32573_2562131.shtml 1 lucene简介1.1 什么是luceneLucene是一个全文搜索框架,而不是应用产品。因此它并不像www.baidu.com 或者google Desktop那么拿来就能用,它只是提供了一种工具让你能实现这些产品。1.2 ...