• ictclas4j for lucene analyzer


    原文出处: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

  • 相关阅读:
    使用DBUtils获取Blob类型数据
    关于 JupyterLab 与 Pandas 资源整理
    关于 Conda 在 MacOS Catalina 环境变量问题
    推荐一个符合 OIDC 规范的 JAVA 客户端
    关于 Chrome 的 Kiosk 模式
    Kubernetes 中的服务发现与负载均衡
    Prometheus 监控领域最锋利的“瑞士军刀”
    CD 基金会、Jenkins、Jenkins X、Spinnaker 和 Tekton 的常问问题
    Installing on Kubernetes with NATS Operator
    升级 ASP.NET Core 3.0 设置 JSON 返回 PascalCase 格式与 SignalR 问题
  • 原文地址:https://www.cnblogs.com/chenying99/p/2583809.html
Copyright © 2020-2023  润新知