君子博学而日参省乎己 则知明而行无过矣

博客园 首页 新随笔 联系 订阅 管理

原文出处:http://blog.chenlb.com/2009/01/ictclas4j-for-lucene-analyzer.html

在 lucene 的中文分词域里,有好几个分词选择,有:je、paoding、IK。最近想把 ictclas 拿来做 lucene 的中文分词。网上看了下资料,觉得 ictclas4j 是比较好的选择,作者博客相关文章:http://blog.csdn.net/sinboy/category/207165.aspx 。ictclas4j 目前是0.9.1版,项目地址:http://code.google.com/p/ictclas4j/ ,下载地址:http://ictclas4j.googlecode.com/files/ictclas4j_0.9.1.rar 。

下载 ictclas4j 看了下源码,正找示例,org.ictclas4j.run.SegMain 可以运行。分词的核心逻辑在org.ictclas4j.segment.Segment 的 split(String src) 方法中。运行 SegMain 的结果是一串字符串(带有词性标注),细看了 Segment 与 org.ictclas4j.bean.SegResult 没看到一个个分好的词。这样就比较难以扩展成为 lucene 的分词器。555,接下还是 hack 一下。

hack 的突破口的它的最终结果,在 SegResult 类里的 finalResult 字段记录。 在Segment.split(String src) 生成。慢慢看代码找到 outputResult(ArrayList<SegNode> wrList) 方法把一个个分好的词拼凑成 string。我们可以修改这个方法把一个个分好的词收集起来。下面是 hack 的过程。

1、修改 Segment:
1)把原来的outputResult(ArrayList<SegNode> wrList) 复制为 outputResult(ArrayList<SegNode> wrList, ArrayList<String> words) 方法,并添加收集词的内容,最后为:

  1. // 根据分词路径生成分词结果  
  2. private String outputResult(ArrayList<SegNode> wrList, ArrayList<String> words) {  
  3.     String result = null;  
  4.     String temp=null;  
  5.     char[] pos = new char[2];  
  6.     if (wrList != null &amp;amp;&amp;amp; wrList.size() > 0) {  
  7.         result = "";  
  8.         for (int i = 0; i < wrList.size(); i++) {  
  9.             SegNode sn = wrList.get(i);  
  10.             if (sn.getPos() != POSTag.SEN_BEGIN &amp;amp;&amp;amp; sn.getPos() != POSTag.SEN_END) {  
  11.                 int tag = Math.abs(sn.getPos());  
  12.                 pos[0] = (char) (tag / 256);  
  13.                 pos[1] = (char) (tag % 256);  
  14.                 temp=""+pos[0];  
  15.                 if(pos[1]>0)  
  16.                     temp+=""+pos[1];  
  17.                 result += sn.getSrcWord() + "/" + temp + " ";  
  18.                 if(words != null) { //chenlb add  
  19.                     words.add(sn.getSrcWord());  
  20.                 }  
  21.             }  
  22.         }  
  23.     }  
  24.   
  25.     return result;  
  26. }  

2)原来的outputResult(ArrayList<SegNode> wrList) 改为:

  1. //chenlb move to outputResult(ArrayList<SegNode> wrList, ArrayList<String> words)  
  2. private String outputResult(ArrayList<SegNode> wrList) {  
  3.     return outputResult(wrList, null);  
  4. }  

3)修改调用outputResult(ArrayList<SegNode> wrList)的地方(注意不是所有的调用),大概在 Segment 的126行 String optResult = outputResult(optSegPath); 改为 String optResult = outputResult(optSegPath, words); 当然还要定义ArrayList<String> words了,最终 Segment.split(String src) 如下:

  1. public SegResult split(String src) {  
  2.     SegResult sr = new SegResult(src);// 分词结果  
  3.     String finalResult = null;  
  4.   
  5.     if (src != null) {  
  6.         finalResult = "";  
  7.         int index = 0;  
  8.         String midResult = null;  
  9.         sr.setRawContent(src);  
  10.         SentenceSeg ss = new SentenceSeg(src);  
  11.         ArrayList<Sentence> sens = ss.getSens();  
  12.   
  13.         ArrayList<String> words = new ArrayList<String>();  //chenlb add  
  14.   
  15.         for (Sentence sen : sens) {  
  16.             logger.debug(sen);  
  17.             long start=System.currentTimeMillis();  
  18.             MidResult mr = new MidResult();  
  19.             mr.setIndex(index++);  
  20.             mr.setSource(sen.getContent());  
  21.             if (sen.isSeg()) {  
  22.   
  23.                 // 原子分词  
  24.                 AtomSeg as = new AtomSeg(sen.getContent());  
  25.                 ArrayList<Atom> atoms = as.getAtoms();  
  26.                 mr.setAtoms(atoms);  
  27.                 System.err.println("[atom time]:"+(System.currentTimeMillis()-start));  
  28.                 start=System.currentTimeMillis();  
  29.   
  30.                 // 生成分词图表,先进行初步分词,然后进行优化,最后进行词性标记  
  31.                 SegGraph segGraph = GraphGenerate.generate(atoms, coreDict);  
  32.                 mr.setSegGraph(segGraph.getSnList());  
  33.                 // 生成二叉分词图表  
  34.                 SegGraph biSegGraph = GraphGenerate.biGenerate(segGraph, coreDict, bigramDict);  
  35.                 mr.setBiSegGraph(biSegGraph.getSnList());  
  36.                 System.err.println("[graph time]:"+(System.currentTimeMillis()-start));  
  37.                 start=System.currentTimeMillis();  
  38.   
  39.                 // 求N最短路径  
  40.                 NShortPath nsp = new NShortPath(biSegGraph, segPathCount);  
  41.                 ArrayList<ArrayList<Integer>> bipath = nsp.getPaths();  
  42.                 mr.setBipath(bipath);  
  43.                 System.err.println("[NSP time]:"+(System.currentTimeMillis()-start));  
  44.                 start=System.currentTimeMillis();  
  45.   
  46.                 for (ArrayList<Integer> onePath : bipath) {  
  47.                     // 得到初次分词路径  
  48.                     ArrayList<SegNode> segPath = getSegPath(segGraph, onePath);  
  49.                     ArrayList<SegNode> firstPath = AdjustSeg.firstAdjust(segPath);  
  50.                     String firstResult = outputResult(firstPath);  
  51.                     mr.addFirstResult(firstResult);  
  52.                     System.err.println("[first time]:"+(System.currentTimeMillis()-start));  
  53.                     start=System.currentTimeMillis();  
  54.   
  55.                     // 处理未登陆词,进对初次分词结果进行优化  
  56.                     SegGraph optSegGraph = new SegGraph(firstPath);  
  57.                     ArrayList<SegNode> sns = clone(firstPath);  
  58.                     personTagger.recognition(optSegGraph, sns);  
  59.                     transPersonTagger.recognition(optSegGraph, sns);  
  60.                     placeTagger.recognition(optSegGraph, sns);  
  61.                     mr.setOptSegGraph(optSegGraph.getSnList());  
  62.                     System.err.println("[unknown time]:"+(System.currentTimeMillis()-start));  
  63.                     start=System.currentTimeMillis();  
  64.   
  65.                     // 根据优化后的结果,重新进行生成二叉分词图表  
  66.                     SegGraph optBiSegGraph = GraphGenerate.biGenerate(optSegGraph, coreDict, bigramDict);  
  67.                     mr.setOptBiSegGraph(optBiSegGraph.getSnList());  
  68.   
  69.                     // 重新求取N-最短路径  
  70.                     NShortPath optNsp = new NShortPath(optBiSegGraph, segPathCount);  
  71.                     ArrayList<ArrayList<Integer>> optBipath = optNsp.getPaths();  
  72.                     mr.setOptBipath(optBipath);  
  73.   
  74.                     // 生成优化后的分词结果,并对结果进行词性标记和最后的优化调整处理  
  75.                     ArrayList<SegNode> adjResult = null;  
  76.                     for (ArrayList<Integer> optOnePath : optBipath) {  
  77.                         ArrayList<SegNode> optSegPath = getSegPath(optSegGraph, optOnePath);  
  78.                         lexTagger.recognition(optSegPath);  
  79.                         String optResult = outputResult(optSegPath, words); //chenlb changed  
  80.                         mr.addOptResult(optResult);  
  81.                         adjResult = AdjustSeg.finaAdjust(optSegPath, personTagger, placeTagger);  
  82.                         String adjrs = outputResult(adjResult);  
  83.                         System.err.println("[last time]:"+(System.currentTimeMillis()-start));  
  84.                         start=System.currentTimeMillis();  
  85.                         if (midResult == null)  
  86.                             midResult = adjrs;  
  87.                         break;  
  88.                     }  
  89.                 }  
  90.                 sr.addMidResult(mr);  
  91.             } else {  
  92.                 midResult = sen.getContent();  
  93.                 words.add(midResult);   //chenlb add  
  94.             }  
  95.             finalResult += midResult;  
  96.             midResult = null;  
  97.         }  
  98.   
  99.         sr.setWords(words); //chenlb add  
  100.   
  101.         sr.setFinalResult(finalResult);  
  102.         DebugUtil.output2html(sr);  
  103.         logger.info(finalResult);  
  104.     }  
  105.   
  106.     return sr;  
  107. }  

4)Segment中的构造方法,词典路径分隔可以改为"/"

5)同时修改了一个漏词的 bug,请看:ictclas4j的一个bug

2、修改 SegResult:
添加以下内容:

  1. private ArrayList<String> words;  //记录分词后的词结果,chenlb add  
  2.     /** 
  3.      * 添加词条。 
  4.      * @param word null 不添加 
  5.      * @author chenlb 2009-1-21 下午05:01:25 
  6.      */  
  7.     public void addWord(String word) {  
  8.         if(words == null) {  
  9.             words = new ArrayList<String>();  
  10.         }  
  11.         if(word != null) {  
  12.             words.add(word);  
  13.         }  
  14.     }  
  15.   
  16.     public ArrayList<String> getWords() {  
  17.         return words;  
  18.     }  
  19.   
  20.     public void setWords(ArrayList<String> words) {  
  21.         this.words = words;  
  22.     }  

下面是创建 ictclas4j 的 lucene analyzer
1、新建一个ICTCLAS4jTokenizer类:

  1. package com.chenlb.analysis.ictclas4j;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.Reader;  
  5. import java.util.ArrayList;  
  6.   
  7. import org.apache.lucene.analysis.Token;  
  8. import org.apache.lucene.analysis.Tokenizer;  
  9. import org.ictclas4j.bean.SegResult;  
  10. import org.ictclas4j.segment.Segment;  
  11.   
  12. /** 
  13.  * ictclas4j 切词 
  14.  * 
  15.  * @author chenlb 2009-1-23 上午11:39:10 
  16.  */  
  17. public class ICTCLAS4jTokenizer extends Tokenizer {  
  18.   
  19.     private static Segment segment;  
  20.   
  21.     private StringBuilder sb = new StringBuilder();  
  22.   
  23.     private ArrayList<String> words;  
  24.   
  25.     private int startOffest = 0;  
  26.     private int length = 0;  
  27.     private int wordIdx = 0;  
  28.   
  29.     public ICTCLAS4jTokenizer() {  
  30.         words = new ArrayList<String>();  
  31.     }  
  32.   
  33.     public ICTCLAS4jTokenizer(Reader input) {  
  34.         super(input);  
  35.         char[] buf = new char[8192];  
  36.         int d = -1;  
  37.         try {  
  38.             while((d=input.read(buf)) != -1) {  
  39.                 sb.append(buf, 0, d);  
  40.             }  
  41.         } catch (IOException e) {  
  42.             e.printStackTrace();  
  43.         }  
  44.         SegResult sr = seg().split(sb.toString());  //分词  
  45.         words = sr.getWords();  
  46.     }  
  47.   
  48.     public Token next(Token reusableToken) throws IOException {  
  49.         assert reusableToken != null;  
  50.   
  51.         length = 0;  
  52.         Token token = null;  
  53.         if(wordIdx < words.size()) {  
  54.             String word = words.get(wordIdx);  
  55.             length = word.length();  
  56.             token = reusableToken.reinit(word, startOffest, startOffest+length);  
  57.             wordIdx++;  
  58.             startOffest += length;  
  59.   
  60.         }  
  61.   
  62.         return token;  
  63.     }  
  64.   
  65.     private static Segment seg() {  
  66.         if(segment == null) {  
  67.             segment = new Segment(1);  
  68.         }  
  69.         return segment;  
  70.     }  
  71. }  

2、新建一个ICTCLAS4jFilter类:

  1. package com.chenlb.analysis.ictclas4j;  
  2.   
  3. import org.apache.lucene.analysis.Token;  
  4. import org.apache.lucene.analysis.TokenFilter;  
  5. import org.apache.lucene.analysis.TokenStream;  
  6.   
  7. /** 
  8.  * 标点符等, 过虑. 
  9.  * 
  10.  * @author chenlb 2009-1-23 下午03:06:00 
  11.  */  
  12. public class ICTCLAS4jFilter extends TokenFilter {  
  13.   
  14.     protected ICTCLAS4jFilter(TokenStream input) {  
  15.         super(input);  
  16.     }  
  17.   
  18.     public final Token next(final Token reusableToken) throws java.io.IOException {  
  19.         assert reusableToken != null;  
  20.   
  21.         for (Token nextToken = input.next(reusableToken); nextToken != null; nextToken = input.next(reusableToken)) {  
  22.             String text = nextToken.term();  
  23.   
  24.                 switch (Character.getType(text.charAt(0))) {  
  25.   
  26.                 case Character.LOWERCASE_LETTER:  
  27.                 case Character.UPPERCASE_LETTER:  
  28.   
  29.                     // English word/token should larger than 1 character.  
  30.                     if (text.length()>1) {  
  31.                         return nextToken;  
  32.                     }  
  33.                     break;  
  34.                 case Character.DECIMAL_DIGIT_NUMBER:  
  35.                 case Character.OTHER_LETTER:  
  36.   
  37.                     // One Chinese character as one Chinese word.  
  38.                     // Chinese word extraction to be added later here.  
  39.   
  40.                     return nextToken;  
  41.                 }  
  42.   
  43.         }  
  44.         return null;  
  45.     }  
  46. }  

3、新建一个ICTCLAS4jAnalyzer类:

  1. package com.chenlb.analysis.ictclas4j;  
  2.   
  3. import java.io.Reader;  
  4.   
  5. import org.apache.lucene.analysis.Analyzer;  
  6. import org.apache.lucene.analysis.LowerCaseFilter;  
  7. import org.apache.lucene.analysis.StopFilter;  
  8. import org.apache.lucene.analysis.TokenStream;  
  9.   
  10. /** 
  11.  * ictclas4j 的 lucene 分析器 
  12.  * 
  13.  * @author chenlb 2009-1-23 上午11:39:39 
  14.  */  
  15. public class ICTCLAS4jAnalyzer extends Analyzer {  
  16.   
  17.     private static final long serialVersionUID = 1L;  
  18.   
  19.     // 可以自定义添加更多的过虑的词(高频无多太用处的词)  
  20.     private static final String[] STOP_WORDS = {  
  21.         "and", "are", "as", "at", "be", "but", "by",  
  22.         "for", "if", "in", "into", "is", "it",  
  23.         "no", "not", "of", "on", "or", "such",  
  24.         "that", "the", "their", "then", "there", "these",  
  25.         "they", "this", "to", "was", "will", "with",  
  26.         "的"  
  27.     };  
  28.   
  29.     public TokenStream tokenStream(String fieldName, Reader reader) {  
  30.         TokenStream result = new ICTCLAS4jTokenizer(reader);  
  31.         result = new ICTCLAS4jFilter(new StopFilter(new LowerCaseFilter(result), STOP_WORDS));  
  32.         return result;  
  33.     }  
  34.   
  35. }  

下面来测试下分词效果:
文本内容:

京华时报1月23日报道 昨天,受一股来自中西伯利亚的强冷空气影响,本市出现大风降温天气,白天最高气温只有零下7摄氏度,同时伴有6到7级的偏北风。

原分词结果:

京华/nz 时/ng 报/v 1月/t 23日/t 报道/v  昨天/t ,/w 受/v 一/m 股/q 来自/v 中/f 西伯利亚/ns 的/u 强/a 冷空气/n 影响/vn ,/w 本市/r 出现/v 大风/n 降温/vn 天气/n ,/w 白天/t 最高/a 气温/n 只/d 有/v 零下/s 7/m 摄氏度/q ,/w 同时/c 伴/v 有/v 6/m 到/v 7/m 级/q 的/u 偏/a 北风/n 。/w 

analyzer:

[京华] [时] [报] [1月] [23日] [报道] [昨天] [受] [一] [股] [来自] [中] [西伯利亚] [强] [冷空气] [影响] [本市] [出现] [大风] [降温] [天气] [白天] [最高] [气温] [只] [有] [零下] [7] [摄氏度] [同时] [伴] [有] [6] [到] [7] [级] [偏] [北风]

我改过的源码可以下载:ictclas4j-091-for-lucene-src

依赖的jar:commons-lang-2.1.jar,log4j-1.2.12.jar,lucene-core-2.4.jar

posted on 2012-07-09 22:49  刺猬的温驯  阅读(447)  评论(0编辑  收藏  举报