个人认为分词最根本的有三个步骤:字典加载,根据一定策略分词,消歧。稍微介绍过分词程序的基本词典数据结构,接着看看如何进行消歧。分词不用多说,比较简单。
拿IKAnalyzer分词器为例,IKAnalyzer的切分方式是细粒度切分,当不需要智能处理时,其就把切出的所有词输出,但若启动了智能处理,那么接下来就是要进行消歧工作。
细粒度切出的词比较杂,但是经过智能处理后,其看上去就像是采用MM算法或者RMM算法切分出。
要想知道IKAnalyzer是如何进行消歧的,有必要先查清其实如何进行切分的,切分的词有何特点。下面这段代码是IKAnalyzer汉字切分的主要核心代码:
public void analyze(AnalyzeContext context) {
if(CharacterUtil.CHAR_USELESS != context.getCurrentCharType()){
//优先处理tmpHits中的hit
if(!this.tmpHits.isEmpty()){
//处理词段队列
Hit[] tmpArray = this.tmpHits.toArray(new Hit[this.tmpHits.size()]);
for(Hit hit : tmpArray){
hit = Dictionary.matchWithHit(context.getSegmentBuff(), context.getCursor() , hit);
if(hit.isMatch()){
//输出当前的词
Lexeme newLexeme = new Lexeme(context.getBufferOffset() , hit.getBegin() , context.getCursor() - hit.getBegin() + 1 , Lexeme.TYPE_CNWORD);
context.addLexeme(newLexeme);
if(!hit.isPrefix()){//不是词前缀,hit不需要继续匹配,移除
this.tmpHits.remove(hit);
}
}else if(hit.isUnmatch()){
//hit不是词,移除
this.tmpHits.remove(hit);
}
}
}
//*********************************
//再对当前指针位置的字符进行单字匹配
Hit singleCharHit = Dictionary.matchInMainDict(context.getSegmentBuff(), context.getCursor(), 1);
if(singleCharHit.isMatch()){//首字成词
//输出当前的词
Lexeme newLexeme = new Lexeme(context.getBufferOffset() , context.getCursor() , 1 , Lexeme.TYPE_CNWORD);
context.addLexeme(newLexeme);
//同时也是词前缀
if(singleCharHit.isPrefix()){
//前缀匹配则放入hit列表
this.tmpHits.add(singleCharHit);
}
}else if(singleCharHit.isPrefix()){//首字为词前缀
//前缀匹配则放入hit列表
this.tmpHits.add(singleCharHit);
}
}else{
//遇到CHAR_USELESS字符
//清空队列
this.tmpHits.clear();
}
//判断缓冲区是否已经读完
if(context.isBufferConsumed()){
//清空队列
this.tmpHits.clear();
}
//判断是否锁定缓冲区
if(this.tmpHits.size() == 0){
context.unlockBuffer(SEGMENTER_NAME);
}else{
context.lockBuffer(SEGMENTER_NAME);
}
}
外层有一个游标在控制扫描的字符串位移:
do{
//遍历子分词器
for(ISegmenter segmenter : segmenters){
segmenter.analyze(context);
}
//字符缓冲区接近读完,需要读入新的字符
if(context.needRefillBuffer()){
break;
}
//向前移动指针
}while(context.moveCursor());
这样的切分效果类似于paoding的切分效果,由于IKAnalyzer和paoding的字典数据结构不同,采用的字典匹配方式看上去不一样,但剖析原理,还是一个毪子中出来的,paoding是通过双层循环来尽可能的切出所有的词,而IKAnalyzer是采用
for(Hit hit : tmpArray){
hit = Dictionary.matchWithHit(context.getSegmentBuff(), context.getCursor() , hit);
if(hit.isMatch()){
//输出当前的词
Lexeme newLexeme = new Lexeme(context.getBufferOffset() , hit.getBegin() , context.getCursor() - hit.getBegin() + 1 , Lexeme.TYPE_CNWORD);
context.addLexeme(newLexeme);
if(!hit.isPrefix()){//不是词前缀,hit不需要继续匹配,移除
this.tmpHits.remove(hit);
}
}else if(hit.isUnmatch()){
//hit不是词,移除
this.tmpHits.remove(hit);
}
}
进行尽可能多的切词。这里不细说,读读两者的源码就会明白到底是怎么一回事。IKAnalyzer的词典的数据结构致使其采用这样复杂的方式切词。
明白其切出的词是怎么样子后,下面进入主题,其实如何消歧的?
在解说其如何消歧之前,得先说一个数据结构:TreeSet,因为IKAnalyzer正式借助这样的一个数据结构完成分词的消歧工作。
TreeSet的javaDoc的解释如下
A NavigableSet implementation based on a TreeMap. The elements are ordered using their natural ordering, or by a Comparator provided at set creation time, depending on which constructor is used.
This implementation provides guaranteed log(n) time cost for the basic operations (add, remove and contains).
Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface. (See Comparable or Comparator for a precise definition of consistent with equals.) This is so because the Set interface is defined in terms of the equals operation, but a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two elements that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.
TreeSet是一个有序的集合序列,其必须实现
Comparable
接口,在IKAnalyzer的代码中,LexemePath类实现了Comparable接口,这个类恰好是分词消歧的核心,看看其实如何实现compareTO接口的:
public int compareTo(LexemePath o) {
//比较有效文本长度
if(this.payloadLength > o.payloadLength){
return -1;
}else if(this.payloadLength < o.payloadLength){
return 1;
}else{
//比较词元个数,越少越好
if(this.size() < o.size()){
return -1;
}else if (this.size() > o.size()){
return 1;
}else{
//路径跨度越大越好
if(this.getPathLength() > o.getPathLength()){
return -1;
}else if(this.getPathLength() < o.getPathLength()){
return 1;
}else {
//根据统计学结论,逆向切分概率高于正向切分,因此位置越靠后的优先
if(this.pathEnd > o.pathEnd){
return -1;
}else if(pathEnd < o.pathEnd){
return 1;
}else{
//词长越平均越好
if(this.getXWeight() > o.getXWeight()){
return -1;
}else if(this.getXWeight() < o.getXWeight()){
return 1;
}else {
//词元位置权重比较
if(this.getPWeight() > o.getPWeight()){
return -1;
}else if(this.getPWeight() < o.getPWeight()){
return 1;
}
}
}
}
}
}
return 0;
}
这个就是IKAnalyzer分词消歧的核心,按照如下方式消歧:
1)比较有效文本长度
2)比较词元个数,越少越好
3)路径跨度越大越好
4)根据统计学结论,逆向切分概率高于正向切分,因此位置越靠后的优先
5)词长越平均越好
。。。
这些都是人为定的规则,按照统计方式进行消歧。
看看外围的逻辑代码:
private LexemePath judge(QuickSortSet.Cell lexemeCell , int fullTextLength){
//候选路径集合
TreeSet<LexemePath> pathOptions = new TreeSet<LexemePath>();
//候选结果路径
LexemePath option = new LexemePath();
//对crossPath进行一次遍历,同时返回本次遍历中有冲突的Lexeme栈
Stack<QuickSortSet.Cell> lexemeStack = this.forwardPath(lexemeCell , option);
//当前词元链并非最理想的,加入候选路径集合
pathOptions.add(option.copy());
//存在歧义词,处理
QuickSortSet.Cell c = null;
while(!lexemeStack.isEmpty()){
c = lexemeStack.pop();
//回滚词元链
this.backPath(c.getLexeme() , option);
//从歧义词位置开始,递归,生成可选方案
this.forwardPath(c , option);
pathOptions.add(option.copy());
}
//返回集合中的最优方案
return pathOptions.first();
}
这部分足以说明是采用TreeSet自动为其排好序列,把最优的结果放在最下面,返回最优的结果。
IKAnalyzer是在细粒度切分后的结果中进行消歧,首先是扫描未经过歧义消除的词源,遇到有歧义的词就放到LexemePath数据结构中,当遇到没有歧义的词后就出来LexemePath这个数据结构,对其进行消歧,消歧后的LexemePath放到了 Map<Integer , LexemePath>数据结构中,最后对这个数据结构进行合并把结果放到LinkedList<Lexeme>数据结构中,至此完成分词工作。
代码如下:
void process(AnalyzeContext context , boolean useSmart){
QuickSortSet orgLexemes = context.getOrgLexemes();
Lexeme orgLexeme = orgLexemes.pollFirst();
LexemePath crossPath = new LexemePath();
while(orgLexeme != null){
if(!crossPath.addCrossLexeme(orgLexeme)){
//找到与crossPath不相交的下一个crossPath
if(crossPath.size() == 1 || !useSmart){
//crossPath没有歧义 或者 不做歧义处理
//直接输出当前crossPath
context.addLexemePath(crossPath);
}else{
//对当前的crossPath进行歧义处理
QuickSortSet.Cell headCell = crossPath.getHead();
LexemePath judgeResult = this.judge(headCell, crossPath.getPathLength());
//输出歧义处理结果judgeResult
context.addLexemePath(judgeResult);
}
//把orgLexeme加入新的crossPath中
crossPath = new LexemePath();
crossPath.addCrossLexeme(orgLexeme);
}
orgLexeme = orgLexemes.pollFirst();
}
//处理最后的path
if(crossPath.size() == 1 || !useSmart){
//crossPath没有歧义 或者 不做歧义处理
//直接输出当前crossPath
context.addLexemePath(crossPath);
}else{
//对当前的crossPath进行歧义处理
QuickSortSet.Cell headCell = crossPath.getHead();
LexemePath judgeResult = this.judge(headCell, crossPath.getPathLength());
//输出歧义处理结果judgeResult
context.addLexemePath(judgeResult);
}
}
最后向IKAnalyzer的作者致敬,开源自己的源代码,给后者打开方便之门,给新手提供这么好的学习材料。
分享到:
相关推荐
ikanalyzer分词器是一款在Java环境下广泛使用的中文分词工具,尤其在搜索引擎和文本分析领域中扮演着重要角色。它的核心是ikanalyzer.jar类库,这个库包含了分词算法和其他必要的支持类,使得开发者能够轻松地集成到...
**IKanalyzer分词器详解** IKAnalyzer是一款开源的、基于Java实现的中文分词工具,主要用于对中文文本进行分词处理。它以其高效、灵活的特性,在许多Java开发的搜索引擎和自然语言处理项目中得到广泛应用。这个"IK...
标题中的"IKAnalyzer分词器"指的是IKAnalyzer这个软件工具,它是一个基于Java的全文检索分析引擎。它的主要任务是对中文文本进行分词,即将连续的汉字序列切分成一个个有意义的词汇,这是中文信息处理中的关键步骤。...
IKAnalyzer 分词器所需要的停用词词典 ext_stopword.dic 下载 Solr中使用IK-Analyzer实现中文分词器的配置详情 : http://blog.csdn.net/hello_world_qwp/article/details/78890904
在这个说明中,我们将详细讲解如何在Solr 6.0中配置ikanalyzer分词文件,以实现对中文文本的有效处理。 1. **ikanalyzer简介** ikanalyzer是一款专门为Java语言设计的开源中文分词库,它基于Apache Lucene项目,...
IkAnalyzer3.2的jar包 IK Analyzer 是一个开源的,基于java 语言开发的轻量级的中文分词工具包。从2006 年12 月推出1.0 版开始, IKAnalyzer 已经推出了3 个大版本。最初,它是以开源项目 Luence 为应用主体的,结合...
利用IKAnalyzer分词器来做文章的匹配算法。主要思想是先用IKAnalyzer分词器分析2篇文章,然后把2篇文章的关键字进行比较,如果相同的个数在所有关键字的总数大于某个预设的值,就认为2篇文章是相同的。
**IKAnalyzer分词器**是Java开发的一款高性能的中文分词工具,主要应用于搜索引擎和文本分析领域。它的设计目标是提供一个轻量级、高效能的解决方案,支持自定义词典,可以方便地集成到各种系统中。在本资源中,我们...
IKAnalyzer非常易用的java分词工具。可以自定义扩展词汇。 这个是一个完整的java项目demo。直接可以用,不用再去google下载了。添加了几个自定义词汇,测试好用。 运行ika.java里的main方法即可
**IKAnalyzer中文分词** IKAnalyzer是一款开源的、基于Java实现的中文分词工具,它在中文信息处理领域有着广泛的应用。该工具最初由尹军平(IkGuo)开发,设计目标是提供一个轻量级、高效能的中文分词引擎,用于...
IKAnalyzer是一款广泛应用于Java平台的开源分词工具,专门针对中文文本进行高效的分词处理。它的全称为"Intelligent Chinese Analyzer for Lucene",旨在提高Lucene等搜索引擎在中文环境下的搜索性能。Lucene是...
IKAnalyzer分词,IKAnalyzer分词
IKAnalyzer分词器版本 2012 兼容Lucene3.3以上版本 对solr1.4提供接口实现 使用IK分词器,应为该集群使用到的solr版本为4.10.3-cdh5.7.5,所以使用的 IK 包为IKAnalyzer2012FF_u1.jar,如果是3x的solr,使用IK...
IKAnalyzer是一款专为中文处理设计的开源分词器,它主要应用于搜索引擎、信息检索系统、文本挖掘等领域。这款工具能够高效地对中文文本进行分词,使得计算机可以更好地理解和处理中文信息。IKAnalyzer的名字来源于...
在启动时,IK Analyzer 会加载词典文件,通常是 `dict` 目录下的 `ikanalyzer.dict` 文件,这个文件包含了各种常用词汇及其属性信息。 2. **分词算法**:在处理输入文本时,IK Analyzer 会使用正向和逆向最大匹配...
从 2006年 12 月推出 1.0 版开始, IKAnalyzer 已经推出了 4 个大版本。最初,它是以开源项目Luence 为应用主体的,结合词典分词和文法分析算法的中文分词组件。 从 3.0 版本开始,IK 发展为面向 Java 的公用分词...
**IK Analyzer中文分词器详解** IK Analyzer是一个在IT领域广泛应用的开源项目,它专注于Java平台上的中文分词处理。中文分词是自然语言处理(NLP)中的基础任务,对于信息检索、文本挖掘、机器翻译等领域至关重要...
标题 "IKAnalyzer中文分词demo" 指的是一个基于IKAnalyzer的中文分词演示项目。IKAnalyzer是一款开源的、适用于Java平台的全文检索引擎工具,主要功能是对中文文本进行有效的分词处理,广泛应用于搜索引擎、信息检索...
IKAnalyzer是java语言中一个流行的分词器工具,能够对中文文本进行分词操作。分词是自然语言处理(NLP)中的一项基本操作,用于将文本分割成单个词语,以便进行进一步的处理和分析。IKAnalyzer提供了动态自定义词库...
**IKAnalyzer分词源码详解** IKAnalyzer是一款广泛使用的开源中文分词器,它主要针对Java平台设计,具有良好的性能和扩展性。该分词器适用于各种文本处理场景,如搜索引擎构建、信息检索、文本挖掘等。其核心在于...