IK分词源码讲解(三)-分词歧义处理(智能分词)

22 篇文章 0 订阅

分词的歧义处理是IK分词的一个重要的核心模块,主要使用组合遍历的方式进行处理。从子分词器中取出不相交的分词集合,例如分词结果为abcd(abcd代表词),abcd是按其在文本中出现的位置排序的,从前到后。假如a与b相交,b与c相交,c与d不相交,则将分词结果切成abc和d两个块分别处理

当在分词的时候使用的是智能分词,那么便从相交的块中选出最优的结果,这个由judge方法来进行处理

/**

     * 分词歧义处理

     * @param orgLexemes

     * @param useSmart

     */

    void process(AnalyzeContext context , boolean useSmart){

       QuickSortSet orgLexemes =context.getOrgLexemes();

       Lexeme orgLexeme =orgLexemes.pollFirst();

      

       LexemePath crossPath = new LexemePath();

       while(orgLexeme !=null){

            //jw出现不相交的分词,把之前的所有词进行歧义处理

           if(!crossPath.addCrossLexeme(orgLexeme)){

              //找到与crossPath不相交的下一个crossPath  

               //jw非智能歧义处理,即使相交,也直接输出分词结果

              if(crossPath.size() == 1|| !useSmart){

                  //crossPath没有歧义或者不做歧义处理

                  //直接输出当前crossPath

                  context.addLexemePath(crossPath);

              }else{

                  //jw出现一个不相交的分词,将之前相交的词开始歧义处理

                  //对当前的crossPath进行歧义处理

                  QuickSortSet.Cell headCell =crossPath.getHead();

                  LexemePath judgeResult =this.judge(headCell, crossPath.getPathLength());

                  //输出歧义处理结果judgeResult

                  context.addLexemePath(judgeResult);

              }

              //jw只要出现不相交的词,即进行歧义处理,选出当前最优结果,然后继续处理后面的词

              //orgLexeme加入新的crossPath

              crossPath= newLexemePath();

              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);

       }

    }

下面是judge方法的代码:

/**

     * 歧义识别

     * @param lexemeCell歧义路径链表头

     * @param fullTextLength歧义路径文本长度

     * @param option候选结果路径

     * @return

     */

    private LexemePath judge(QuickSortSet.Cell lexemeCell ,int fullTextLength){

       //候选路径集合

       TreeSet<LexemePath> pathOptions =newTreeSet<LexemePath>();

       //候选结果路径

       LexemePath option =newLexemePath();

      

       //crossPath进行一次遍历,同时返回本次遍历中有冲突的Lexeme

       Stack<QuickSortSet.Cell> lexemeStack =this.forwardPath(lexemeCell, option);

      

       //当前词元链并非最理想的,加入候选路径集合

       pathOptions.add(option.copy());

       //jw这种处理方式应该不是最优的,只是采用贪心的方法获取近似最优方案,并没有遍历所有的可能集合

        //jw每次从一个歧义位置开始,贪心的获取一种分词方案

       //存在歧义词,处理

       QuickSortSet.Cell c =null;

       while(!lexemeStack.isEmpty()){

           c= lexemeStack.pop();

           //回滚词元链

           this.backPath(c.getLexeme(), option);

           //从歧义词位置开始,递归,生成可选方案

           this.forwardPath(c ,option);

           pathOptions.add(option.copy());

       }

       //jw跳转到LexemePath.java中的compareTo()接口查看最优方案选择策略

       //返回集合中的最优方案

       return pathOptions.first();

 

    }

//候选路径集合

       TreeSet<LexemePath>pathOptions =newTreeSet<LexemePath>();

这个是用来保存候选的分词结果集,并对分词的结果集进行排序。

分词结果的排序方法(此方法在lexemePath.java文件中的compareto)是:比较有效文本长度,有效文本长度是指所有分词结果最靠后的一个词距离最靠前的一个词的长度(这里的靠前和靠后是指词在待匹配文本中的位置),词元个数,即分出来的词的个数,越少越好。路径跨度,指所有词的长度的加和。逆向切分、词元长度、位置权重

这个结构在IK分词的消歧中至关重要

 

Forwardpath方法是将相交的词块传入,来进行歧义处理。使用贪心方法选择不相交的分词结果存放到分词结果候选集option中。然后将存在歧义的词放到conflictstack中。

/**

     * 向前遍历,添加词元,构造一个无歧义词元组合

     * @param LexemePath path

     * @return

     */

    private Stack<QuickSortSet.Cell>forwardPath(QuickSortSet.Cell lexemeCell ,LexemePath option){

       //发生冲突的Lexeme

       Stack<QuickSortSet.Cell> conflictStack =newStack<QuickSortSet.Cell>();

       QuickSortSet.Cell c = lexemeCell;

       //迭代遍历Lexeme链表

       while(c !=null &&c.getLexeme() !=null){

           if(!option.addNotCrossLexeme(c.getLexeme())){

              //词元交叉,添加失败则加入lexemeStack

              conflictStack.push(c);

           }

           c= c.getNext();

       }

       return conflictStack;

    }

 

·        当得到conflictstack后,就是对分词结果进行组合遍历处理,首先从conflictstack中选出一个歧义词c,从option结尾回滚option词元链,直到能放下词c,从词c的位置执行forwardPath,生成一个可选分词结果集, 可以看出,该方法没有遍历所有可能的集合,只是从当前替换歧义词的位置贪心的生成其中一种可选方案,只是一种近似最优的选取结果。分词的冲突不会太复杂,这样的选取结果可以接受

·         

·        消除歧义的具体方法就是: IKAnalyzer是在细粒度切分后的结果中进行消歧,首先是扫描未经过歧义消除的词源,遇到有歧义的词就放到LexemePath数据结构中,当遇到没有歧义的词后就出来LexemePath这个数据结构,对其进行消歧,消歧后的LexemePath放到了 Map<Integer ,LexemePath>数据结构中,最后对这个数据结构进行合并把结果放到LinkedList<Lexeme>数据结构中,至此完成分词工作

不过这个歧义词的消除比较简单,还是有些问题,

比如中华人民共和国,切分出来是:中华人民共和国 这种情况下搜索中华、共和国都不会有结果

共和国,切分出来是:共和国

共和国人 切分出来是:共和、国人   在这种情况下搜索共和国就出不来结果了

 

如果我写的不够清楚的话,可以对“中华人民共和国”这个文本进行调试,记住option每次结束的时候都是一个候选的分词集合,里面结果不冲突,还有就是pathOptions中不是一个结果集,是有多个候选结果集,每个候选结果中的词块都是不相交的。通过pathOptions中的排序方法compareTo(LexemePath o),对结果进行排序,排序的原则是:

1、 比较有效文本长度

2、 比较词元个数,越少越好

3、 路径跨度越大越好

4、 根据统计学结论,逆向切分概率高于正向切分,因此位置越靠后的优先

5、 词长越平均越好

6、 词元位置权重比较

最后处理完毕后使用pathOptions.first()方法输出最优的分词结果。

主要使用的就是贪心算法获取局部最优解,然后继续处理来获取最终的结果。

源码地址:http://download.csdn.net/detail/a925907195/8240641

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值