文字特征提取

Lucene 工作原理

Lucene是一个高性能的java全文检索工具包,它使用的是倒排文件索引结构。该结构及相应的生成算法如下:


  0)设有两篇文章12
  文章1的内容为:Tom lives in Guangzhou,I live in Guangzhou too.
  文章2的内容为:He once lived in Shanghai.


  1)由于lucene是基于关键词索引和查询的,首先我们要取得这两篇文章的关键词,通常我们需要如下处理措施
  a.我们现在有的是文章内容,即一个字符串,我们先要找出字符串中的所有单词,即分词。英文单词由于用空格分隔,比较好处理。中文单词间是连在一起的需要特殊的分词处理。
  b.文章中的”in”, “once” “too”等词没有什么实际意义,中文中的”“等字通常也无具体含义,这些不代表概念的词可以过滤掉
  c.用户通常希望查“He”时能把含“he”“HE”的文章也找出来,所以所有单词需要统一大小写
  d.用户通常希望查“live”时能把含“lives”“lived”的文章也找出来,所以需要把“lives”“lived”还原成“live”
  e.文章中的标点符号通常不表示某种概念,也可以过滤掉


  在lucene中以上措施由Analyzer类完成

  经过上面处理后
   文章1的所有关键词为:[tom] [live] [guangzhou] [live] [guangzhou]
   文章2的所有关键词为:[he] [live] [shanghai]
  
  2) 有了关键词后,我们就可以建立倒排索引了。上面的对应关系是:文章号文章中所有关键词。倒排索引把这个关系倒过来,变成:关键词拥有该关键词的所有文章号。文章12经过倒排后变成

  关键词               文章号
  guangzhou         1
  he                     2
  i                        1
  live                    1,2
  shanghai            2
  tom                   1
  
  通常仅知道关键词在哪些文章中出现还不够,我们还需要知道关键词在文章中出现次数和出现的位置,通常有两种位置:a)字符位置,即记录该词是文章中第几个字符(优点是关键词亮显时定位快);b)关键词位置,即记录该词是文章中第几个关键词(优点是节约索引空间、词组(phase)查询快),lucene 中记录的就是这种位置。

  
  加上出现频率出现位置信息后,我们的索引结构变为:

  关键词             文章号[出现频率]                     出现位置
  guangzhou        1[2]                                        36
  he                     2[1]                                       1
  i                        1[1]                                       4
  live                    1[2],2[1]                                252
  shanghai            2[1]                                       3
  tom                   1[1]                                       1
  
  以live 这行为例我们说明一下该结构:live在文章1中出现了2次,文章2中出现了一次,它的出现位置为“2,5,2”这表示什么呢?我们需要结合文章号和出现频率来分析,文章1中出现了2次,那么“2,5”就表示live在文章1中出现的两个位置,文章2中出现了一次,剩下的“2”就表示live是文章2中第2个关键字。
  以上就是lucene索引结构中最核心的部分。我们注意到关键字是按字符顺序排列的(lucene没有使用B树结构),因此lucene可以用二元搜索算法快速定位关键词。
  实现时 lucene将上面三列分别作为词典文件(Term Dictionary)、频率文件(frequencies)、位置文件 (positions)保存。其中词典文件不仅保存有每个关键词,还保留了指向频率文件和位置文件的指针,通过指针可以找到该关键字的频率信息和位置信息。
   Lucene中使用了field的概念,用于表达信息所在位置(如标题中,文章中,url中),在建索引中,该field信息也记录在词典文件中,每个关键词都有一个field信息(因为每个关键字一定属于一个或多个field)
  为了减小索引文件的大小,Lucene对索引还使用了压缩技术。首先,对词典文件中的关键词进行了压缩,关键词压缩为<前缀长度,后缀>,例如:当前词为阿拉伯语,上一个词为阿拉伯,那么阿拉伯语压缩为<3,语>。其次大量用到的是对数字的压缩,数字只保存与上一个值的差值(这样可以减小数字的长度,进而减少保存该数字需要的字节数)。例如当前文章号是16389(不压缩要用3个字节保存),上一文章号是16382,压缩后保存7(只用一个字节)。
 

   下面我们可以通过对该索引的查询来解释一下为什么要建立索引。
  假设要查询单词 “live”lucene先对词典二元查找、找到该词,通过指向频率文件的指针读出所有文章号,然后返回结果。词典通常非常小,因而,整个过程的时间是毫秒级的。
  而用普通的顺序匹配算法,不建索引,而是对所有文章的内容进行字符串匹配,这个过程将会相当缓慢,当文章数目很大时,时间往往是无法忍受的。
本段来自CSDN博客,转载请标明出处:http://blog.csdn.net/geekwang/archive/2008/11/29/3410187.aspx

IK的整个分词处理过程

首先,介绍一下IK的整个分词处理过程:

1. Lucene的分词基类是Analyzer,所以IK提供了Analyzer的一个实现类IKAnalyzer。首先,我们要实例化一个IKAnalyzer,它有一个构造方法接收一个参数isMaxWordLength,这个参数是标识IK是否采用最大词长分词,还是采用最细粒度切分两种分词算法。实际两种算法的实现,最大词长切分是对最细粒度切分的一种后续处理,是对最细粒度切分结果的过滤,选择出最长的分词结果。

2. IKAnalyzer类重写了AnalyzertokenStream方法,这个方法接收两个参数,field name和输入流reader,其中filed nameLucene的属性列,是对文本内容进行过分词处理和创建索引之后,索引对应的一个名称,类似数据库的列名。因为IK仅仅涉及分词处理,所以对field name没有进行任何处理,所以此处不做任何讨论。

3. tokenStream方法在Lucene对文本输入流reader进行分词处理时被调用,在IKAnalyzertokenStream方法里面仅仅实例化了一个IKTokenizer类,该类继承了LuceneTokenizer类。并重写了incrementToken方法,该方法的作用是处理文本输入流生成token也就Lucene的最小词元term,在IK里面叫做Lexeme

4. IKtokenizer的构造方法里面实例化了IK里面最终要的分词类IKSegmentation,也称为主分词器。它的构造方法接收两个参数,readerisMaxWordLength

5. IKsegmentation的构造方法里面,主要做了三个工作,创建上下文对象Context,加载词典,创建子分词器。

6. Contex主要是存储分词结果集和记录分词处理的游标位置。

7. 词典是作为一个单例被创建的,主要有量词词典、主词典和停词词典。词典是被存储在字典片段类DictSegment 这个字典核心类里面的。DictSegment有一个静态的存储结构charMap,是公共词典表,用来存储所有汉字,keyvalue都是一个中文汉字,目前IK里面的charMap大概有7100多的键值对。另外,DictSegment还有两个最重要的数据结构,是用来存储字典树的,一个是DictSegment的数组childrenArray,另一个是key为单个汉字(每个词条的第一个汉字),valueDictSegmentHashMap childrenMap。这两个数据结构二者取其一,用来存储字典树。

8. 子分词器才是真正的分词类,IK里面有三个子分词器,量词分词器,CJK分词器(处理中文),停词分词器。主分词器IKSegmentation遍历这三个分词器对文本输入流进行分词处理。

9. IKTokenizerincrementToken方法调用了IKSegmentationnext方法,next的作用是获得下一个分词结果。next在第一次被调用的时候,需要加载文本输入流,并将其读入buffer,此时便遍历子分词器,对buffer种的文本内容进行分词处理,然后把分词结果添加到contextlexemeSet中。

下面,以CJKSegmenter子分词器为例介绍一下生成分词结果集的流程:

1.  IKSegmentation遍历Segmenter时,调用CJKSegmenternextLexeme方法,该方法接收两个参数,segmentBufcontextsegmentBuf是一个Character数组,文件输入流分解后得到,contextIKContext类实例,用来记录segmentBuf游标以及存储切分后的词元lexeme

2.  进入nextLexeme方法,首先判断是否中文字符,然后判断是否存在词段队列,举例来说中华人民共和国,这个词条,就会存在一个词段队列,分别存储中华中华人民中华人民共和中华人民共和国,前面词段依次是后面词段的前缀。这个词段队列也是在nextLexeme方法中填充的。当一个词条和字典中的词匹配成功,并且也是字典中某个词条的前缀,则被加入队列,当一个词条不再是某个词条的前缀,移除出队列。

3. 如果词段队列里面不存在词段,把当前的Character与字典中的词匹配,创建一个新的hit,如果字典种存在这个Character,把hit添加进词段队列。如果词段队列里面已经存在词段,遍历词段队列,判断当前Character是否可以以词段队列中的词为前缀,组成一个字典中存在的词,如果可以,则加入lexemeSet中,如果不能匹配成词,则将这个前缀hit从队列种移除,因为以后也不会匹配成词了,故删除。

4. 如此循环执行nextLuxeme方法,最终将文本输入流切分成的词元lexeme完全放入contextlexemeSet中。这时,再次调用IKSegmentation类的next方法时,则会直接读取lexemeSet中已经切分好的词元。所以所有的切分工作都在第一次调用IKSegmentationnext的时候完成。

5. IK 分词算法理解

根据作者官方说法 IK 分词器采用正向迭代最细粒度切分算法, 分析它 的源代码, 可以看到分词工具类 IKQueryParser 起至关重要的作用, 它对搜索 关键词采用从最大词到最小词层层迭代检索方式切分,比如搜索词:中华人 民共和国成立了, 首先到词库中检索该搜索词中最大分割词, 即分割为: 中 华人民共和国成立了, 然后对中华人民共和国切分为中华人民人 民共和国,以此类推。最后,中华人民共和国成立了切分为:中华人民 中华 华人 人民 人民共和国 共和国 共和 成立 立了,当然, 该切分方式为默认的细粒度切分,若按最大词长切分,结果为:中华人民共 和国 成立 立了。核心算法代码如下

boolean accept(Lexeme _lexeme){

/* * 检查新的lexeme 对当前的branch 的可接受类型 

* acceptType : REFUSED 不能接受

* acceptType : ACCEPTED 接受 

* acceptType : TONEXT 由相邻分支接受

*/

 int acceptType = checkAccept(_lexeme);

 switch(acceptType){ 

 case REFUSED: 

 // REFUSE 情况 

return false;

case ACCEPTED : 

if(acceptedBranchs == null){ 

//当前branch没有子branch,则添加到当前branch 

acceptedBranchs = new ArrayList<TokenBranch>(2); 

acceptedBranchs.add(new TokenBranch(_lexeme)); 

}else{ 

boolean acceptedByChild = false; 

//当前branch拥有子branch,则优先由子branch接纳 

for(TokenBranch childBranch : acceptedBranchs){ 

acceptedByChild = childBranch.accept(_lexeme) || acceptedByChild; 

}

//如果所有的子branch不能接纳,则由当前branch接纳 

if(!acceptedByChild){ 

acceptedBranchs.add(new TokenBranch(_lexeme)); 

//设置branch的最大右边界 

if(_lexeme.getEndPosition() > this.rightBorder){ 

this.rightBorder = _lexeme.getEndPosition(); 

}

break;

case TONEXT : 

//lexeme放入当前branch的相邻分支 

if(this.nextBranch == null){ 

 //如果还没有相邻分支,则建立一个不交叠的分支 

this.nextBranch = new TokenBranch(null);  

 } 

 this.nextBranch.accept(_lexeme); 

 break; 

} return true; 

}

从代码中可以了解到,作者采用了递归算法(代码中加粗的部分)切分 搜索词。若词存在子词则递归该函数,继续切分。

IK 分词弱点、缺点

总体来说,IK 是一个很不错的中文分词工具,但它自身也存在一些缺点,比如: a. 对歧义分词还需要扩展、改进,如:湖北石首” 和 蒋介石首次访问如果用户搜索石首会把蒋介石首次访问也显示出来。 b. 对英文单词的搜索还需改进,比如:”IKAnalyzer””UU 音乐,如果用户输 入搜索关键词”IKAnaly””U”则无法搜索出结果。 c. IKAnalyzer.cfg.xml 中关于词典的配置,暂不支持通配符方式,这样导致如果有大批词典配置文件时会很麻烦。

本段来自ITEYE中的博文http://yingbin920.iteye.com/blog/1568108

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值