Lucene5学习之使用MMSeg4j分词器

     MMSeg4j是一款中文分词器,详细介绍如下:

       1、mmseg4j 用 Chih-Hao Tsai 的 MMSeg 算法(http://technology.chtsai.org/mmseg/ )实现的中文分词器,并实现 lucene 的 analyzer 和 solr 的TokenizerFactory 以方便在Lucene和Solr中使用。

 

       2、MMSeg 算法有两种分词方法:Simple和Complex,都是基于正向最大匹配。Complex 加了四个规则过虑。官方说:词语的正确识别率达到了 98.41%。mmseg4j 已经实现了这两种分词算法。

 

1.5版的分词速度simple算法是 1100kb/s左右、complex算法是 700kb/s左右,(测试机:AMD athlon 64 2800+ 1G内存 xp)。

1.6版在complex基础上实现了最多分词(max-word)。“很好听” -> "很好|好听"; “中华人民共和国” -> "中华|华人|共和|国"; “中国人民银行” -> "中国|人民|银行"。

1.7-beta 版, 目前 complex 1200kb/s左右, simple 1900kb/s左右, 但内存开销了50M左右. 上几个版都是在10M左右

       可惜的是,MMSeg4j最新版1.9.1不支持Lucene5.0,于是我就修改了它的源码将它升级咯,使其支持Lucene5.x,至于我是怎样修改,这里就不一一说明的,我把我修改过的MMSeg4j最新源码上传到了我的百度网盘,现分享给你们咯:

       mmseg4j-1.9.1源码

       mmseg4j-1.9.2源码(支持Lucene5.x)

      

       下面是一个MMSeg4j分词器简单使用示例:

Java代码  

  1. package com.chenlb.mmseg4j.analysis;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import org.apache.lucene.analysis.Analyzer;  
  6. import org.apache.lucene.analysis.TokenStream;  
  7. import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;  
  8. import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;  
  9. import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;  
  10. import org.apache.lucene.analysis.tokenattributes.TypeAttribute;  
  11. import org.junit.Assert;  
  12. import org.junit.Before;  
  13. import org.junit.Ignore;  
  14. import org.junit.Test;  
  15. /** 
  16.  * MMSegAnalyzer分词器测试 
  17.  * @author Lanxiaowei 
  18.  * 
  19.  */  
  20. public class MMSegAnalyzerTest {  
  21.   
  22.     String txt = "";  
  23.   
  24.     @Before  
  25.     public void before() throws Exception {  
  26.         txt = "京华时报2009年1月23日报道 昨天,受一股来自中西伯利亚的强冷空气影响,本市出现大风降温天气,白天最高气温只有零下7摄氏度,同时伴有6到7级的偏北风。";  
  27.         txt = "2009年ゥスぁま是中 ABcc国абвгαβγδ首次,我的ⅠⅡⅢ在chenёlbēū全国ㄦ范围ㄚㄞㄢ内①ē②㈠㈩⒈⒑发行地方政府债券,";  
  28.         txt = "大S小3U盘浙BU盘T恤T台A股牛B";  
  29.     }  
  30.   
  31.     @Test  
  32.     //@Ignore  
  33.     public void testSimple() throws IOException {  
  34.         Analyzer analyzer = new SimpleAnalyzer();  
  35.         displayTokens(analyzer,txt);  
  36.     }  
  37.   
  38.     @Test  
  39.     @Ignore  
  40.     public void testComplex() throws IOException {  
  41.         //txt = "1999年12345日报道了一条新闻,2000年中法国足球比赛";  
  42.         /*txt = "第一卷 云天落日圆 第一节 偷欢不成倒大霉"; 
  43.         txt = "中国人民银行"; 
  44.         txt = "我们"; 
  45.         txt = "工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作";*/  
  46.         //ComplexSeg.setShowChunk(true);  
  47.         Analyzer analyzer = new ComplexAnalyzer();  
  48.         displayTokens(analyzer,txt);  
  49.     }  
  50.   
  51.     @Test  
  52.     @Ignore  
  53.     public void testMaxWord() throws IOException {  
  54.         //txt = "1999年12345日报道了一条新闻,2000年中法国足球比赛";  
  55.         //txt = "第一卷 云天落日圆 第一节 偷欢不成倒大霉";  
  56.         //txt = "中国人民银行";  
  57.         //txt = "下一个 为什么";  
  58.         //txt = "我们家门前的大水沟很难过";  
  59.         //ComplexSeg.setShowChunk(true);  
  60.         Analyzer analyzer = new MaxWordAnalyzer();  
  61.         displayTokens(analyzer,txt);  
  62.     }  
  63.   
  64.     /*@Test 
  65.     public void testCutLeeterDigitFilter() { 
  66.         String myTxt = "mb991ch cq40-519tx mmseg4j "; 
  67.         List<String> words = toWords(myTxt, new MMSegAnalyzer("") { 
  68.  
  69.             @Override 
  70.             protected TokenStreamComponents createComponents(String text) { 
  71.                 Reader reader = new BufferedReader(new StringReader(text)); 
  72.                 Tokenizer t = new MMSegTokenizer(newSeg(), reader); 
  73.                 return new TokenStreamComponents(t, new CutLetterDigitFilter(t)); 
  74.             } 
  75.  
  76.              
  77.         }); 
  78.  
  79.         //Assert.assertArrayEquals("CutLeeterDigitFilter fail", words.toArray(new String[words.size()]), "mb 991 ch cq 40 519 tx mmseg 4 j".split(" ")); 
  80.         for(String word : words) { 
  81.             System.out.println(word); 
  82.         } 
  83.     }*/  
  84.       
  85.     public static void displayTokens(Analyzer analyzer,String text) throws IOException {  
  86.         TokenStream tokenStream = analyzer.tokenStream("text", text);  
  87.         displayTokens(tokenStream);  
  88.     }  
  89.       
  90.     public static void displayTokens(TokenStream tokenStream) throws IOException {  
  91.         OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);  
  92.         PositionIncrementAttribute positionIncrementAttribute = tokenStream.addAttribute(PositionIncrementAttribute.class);  
  93.         CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);  
  94.         TypeAttribute typeAttribute = tokenStream.addAttribute(TypeAttribute.class);  
  95.           
  96.         tokenStream.reset();  
  97.         int position = 0;  
  98.         while (tokenStream.incrementToken()) {  
  99.             int increment = positionIncrementAttribute.getPositionIncrement();  
  100.             if(increment > 0) {  
  101.                 position = position + increment;  
  102.                 System.out.print(position + ":");  
  103.             }  
  104.             int startOffset = offsetAttribute.startOffset();  
  105.             int endOffset = offsetAttribute.endOffset();  
  106.             String term = charTermAttribute.toString();  
  107.             System.out.println("[" + term + "]" + ":(" + startOffset + "-->" + endOffset + "):" + typeAttribute.type());  
  108.         }  
  109.     }  
  110.       
  111.     /** 
  112.      * 断言分词结果 
  113.      * @param analyzer 
  114.      * @param text        源字符串 
  115.      * @param expecteds   期望分词后结果 
  116.      * @throws IOException  
  117.      */  
  118.     public static void assertAnalyzerTo(Analyzer analyzer,String text,String[] expecteds) throws IOException {  
  119.         TokenStream tokenStream = analyzer.tokenStream("text", text);  
  120.         CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);  
  121.         for(String expected : expecteds) {  
  122.             Assert.assertTrue(tokenStream.incrementToken());  
  123.             Assert.assertEquals(expected, charTermAttribute.toString());  
  124.         }  
  125.         Assert.assertFalse(tokenStream.incrementToken());  
  126.         tokenStream.close();  
  127.     }  
  128. }  

 

    mmseg4j分词器有3个字典文件,如图:

       chars.dic是汉字字典文件,里面有12638个汉字

       units.dic里是中文单位词语,如小时,分钟,米,厘米等等,具体自己打开看看就明白了

       words.dic就是用户自定义字典文件,比如:么么哒,T恤,牛B等这些词,放在这个字典文件里,分词器就能把它当作一个词

      我们在使用mmseg4j分词器时,是这样用的:

Java代码  

  1. Analyzer analyzer = new SimpleAnalyzer();  

      查看SimpleAnalyzer的构造函数,

Java代码  

  1. public SimpleAnalyzer() {  
  2.     super();  
  3. }  

   调用的是父类MMSegAnalyzer的无参构造函数,接着查看MMSegAnalyzer类的无参构造函数:

Java代码  

  1. public MMSegAnalyzer() {  
  2.     dic = Dictionary.getInstance();  
  3. }  

    你会发现是通过Dictionary.getInstance()单实例模式去加载字典文件的,接着查看getInstance方法,

 这里的代码注释写的很清楚,告诉了我们字典文件的加载逻辑。

File path = getDefalutPath();用来获取默认的字典文件路径,

然后根据字典文件路径调用getInstance(path)方法去加载字典文件,接着查看该方法,


 先从缓存dics里去字典文件,如果缓存里没有找到,则才会根据字典文件路径去加载,然后把加载到的字典文件放入缓存dics即dics.put(),

      接着看看Dictionary字典是如何初始化的,查看Dictionary的构造函数源码:

      你会发现内部实际是通过调用init(path);方法进行字典初始化的,继续查阅init方法,

      内部又是调用的reload方法加载的字典,继续跟踪至reload方法,

      内部通过loadDic去加载words和chars两个字典文件,通过loadUnit方法去加载units字典文件,wordsLastTime是用来存放每个字典文件的最后一次修改时间,引入这个map的目的是为了实现字典文件重新加载,通过字典文件的最后一次修改时间来判定文件是否修改过,如果这个map里不存在某字典文件的最后一次修改时间,则表明该字典文件是新加入的,需要重新加载至内存,这是loadDic方法的源码:

     

Java代码  

  1. private Map<Character, CharNode> loadDic(File wordsPath) throws IOException {  
  2.         InputStream charsIn = null;  
  3.         File charsFile = new File(wordsPath, "chars.dic");  
  4.         if(charsFile.exists()) {  
  5.             charsIn = new FileInputStream(charsFile);  
  6.             addLastTime(charsFile); //chars.dic 也检测是否变更  
  7.         } else {    //从 jar 里加载  
  8.             charsIn = this.getClass().getResourceAsStream("/data/chars.dic");  
  9.             charsFile = new File(this.getClass().getResource("/data/chars.dic").getFile()); //only for log  
  10.         }  
  11.         final Map<Character, CharNode> dic = new HashMap<Character, CharNode>();  
  12.         int lineNum = 0;  
  13.         long s = now();  
  14.         long ss = s;  
  15.         lineNum = load(charsIn, new FileLoading() { //单个字的  
  16.   
  17.             public void row(String line, int n) {  
  18.                 if(line.length() < 1) {  
  19.                     return;  
  20.                 }  
  21.                 String[] w = line.split(" ");  
  22.                 CharNode cn = new CharNode();  
  23.                 switch(w.length) {  
  24.                 case 2:  
  25.                     try {  
  26.                         cn.setFreq((int)(Math.log(Integer.parseInt(w[1]))*100));//字频计算出自由度  
  27.                     } catch(NumberFormatException e) {  
  28.                         //eat...  
  29.                     }  
  30.                 case 1:  
  31.   
  32.                     dic.put(w[0].charAt(0), cn);  
  33.                 }  
  34.             }  
  35.         });  
  36.         log.info("chars loaded time="+(now()-s)+"ms, line="+lineNum+", on file="+charsFile);  
  37.   
  38.         //try load words.dic in jar  
  39.         InputStream wordsDicIn = this.getClass().getResourceAsStream("/data/words.dic");  
  40.         if(wordsDicIn != null) {  
  41.             File wordsDic = new File(this.getClass().getResource("/data/words.dic").getFile());  
  42.             loadWord(wordsDicIn, dic, wordsDic);  
  43.         }  
  44.   
  45.         File[] words = listWordsFiles();    //只要 wordsXXX.dic的文件  
  46.         if(words != null) { //扩展词库目录  
  47.             for(File wordsFile : words) {  
  48.                 loadWord(new FileInputStream(wordsFile), dic, wordsFile);  
  49.   
  50.                 addLastTime(wordsFile); //用于检测是否修改  
  51.             }  
  52.         }  
  53.   
  54.         log.info("load all dic use time="+(now()-ss)+"ms");  
  55.         return dic;  
  56.     }  

    大致逻辑就是先加载chars.dic再加载words.dic,最后加载用户自定义字典文件,注意用户自定义字典文件命名需要以words开头且文件名后缀必须为.dic,查找所有用户自定义字典文件是这句代码:

Java代码  

  1. File[] words = listWordsFiles();  

    

    注意:dicPath.listFiles表示查找dicPath目录下所有文件,dicPath即我们的words.dic字典文件的所在路径,而重载的accept的意思我想大家都懂的,关键点我用红色方框标注出来了,这句代码意思就是查找words.dic字典文件所在文件夹下的以words开头的dic字典文件,包含子文件夹里的字典文件(即递归查找,你懂的)。看到这里,我想至于如何自定义用户自定义字典文件,大家都不言自明了。为了照顾小白,我还是说清楚点吧,自定义用户字典文件方法步骤如下:

     如果你想把屌丝,高富帅 当作一个词,那你首先需要新建一个.dic文件,注意dic文件必须是无BOM的UTF-8编码的文件(切记!!!!!!),且自定义字典文件命名需要符合上面说过的那种固定格式,不知道的请看上面那张图,看仔细点,然后一行一个词,你懂的,然后把你自定义的字典文件复制到classPath下的data文件夹下,如果你是简单的Java project,那么就在src下新建一个data包,然后 把你自定义字典文件copy到data包下,如果你是Maven Project,那就在src/main/sources包下新建一个package  名字叫data,同理把你自定义字典文件复制到data包下即可。这样你的自定义词就能被分词器正确切分啦!

       mmseg4j就说这么多了吧,mmseg4j我修改过的最新源码上面有贴出百度网盘下载地址,自己去下载,jar包在target目录下,如图:



 

     从我提供的下载地址下载的最新源码包里有打包好的jar包,如图去找就行了,当然为了方便你们,我待会儿也会在底下的附件里将打包的jar包上传上去。

 

     OK,打完收工!!!!如果你还有什么问题,请QQ上联系我(QQ:7-3-6-0-3-1-3-0-5),或者加我的Java技术群跟我们一起交流学习,我会非常的欢迎的。群号:

 

转载:http://iamyida.iteye.com/blog/2207633

时间: 2024-10-07 09:03:48

Lucene5学习之使用MMSeg4j分词器的相关文章

Lucene5学习之使用IKAnalyzer分词器

   之前的示例中,使用的是默认的StandardAnalyzer分词器,不能有效的进行中文分词,下面演示下如何在Lucene5.0中使用IKAnalyzer分词器.     首先下载IKAnalyzer分词器源码,IKAnalyzer分词器源码托管在OSChina的git上.下载地址: http://git.oschina.net/wltea/IK-Analyzer-2012FF 请如图下载IK的源代码:    然后打开Eclipse新建一个Java Project:      然后解压下载下

Lucene5学习之自定义同义词分词器简单示例

  同义词功能在全文搜索时的意义,大家应该都懂的.今天中文我就试着写了一个同义词分词的示例demo,其实主要代码还是参考Lucene in Action 这本英文版书籍的随书代码,只不过Lucenen in Action书里的示例代码目前最新版只支持到Lucene4.x,对于Lucene5.x,代码需要稍作修改,下面是基于Lucene5.x的自定义同义词分词器demo:   Java代码   package com.yida.framework.lucene5.analyzer.synonym;

Lucene5学习之使用Ansj-seg分词器

    这回我们来玩玩ansj分词器,由于Lucene5.0在API上有点小变化,需要修改ansj分词器源码,重新打包jar包,然后mvn install到本地仓库才能引用到项目中.至于怎么修改以及怎么打包jar,我就不过多说明了,有点麻烦,我想偷个懒,哈哈.这里我就直接把打包后的jar分享给你们,jar包注意在底下的附件里下载.     我就说说,怎么在项目中使用ansj分词器,首先pom.xml引入ansj分词器的依赖.   Xml代码   <!-- ansj-seg -->   <

跟益达学Solr5之使用MMSeg4J分词器

   要想在Sor中使用MMSeg4J分词器,首先你需要自定义一个TokenizerFactory实现类,虽然直接配置Analyzer类也可以,但那样无法配置Analyzer构造函数的参数,不够灵活,存在弊端,所以我一直都是以扩展TokenizerFactory的方式来讲解类似MMSeg4J这样的中文分词器在Solr中的使用.       MMSegTokenizerFactory类我花了3个多小时修改了源码并经过N多测试,表示已经可以使用,我主要的是针对Lucene5 API对MMSegTok

11大Java开源中文分词器的使用方法和分词效果对比

本文的目标有两个: 1.学会使用11大Java开源中文分词器 2.对比分析11大Java开源中文分词器的分词效果 本文给出了11大Java开源中文分词的使用方法以及分词结果对比代码,至于效果哪个好,那要用的人结合自己的应用场景自己来判断. 11大Java开源中文分词器,不同的分词器有不同的用法,定义的接口也不一样,我们先定义一个统一的接口: 从上面的定义我们知道,在Java中,同样的方法名称和参数,但是返回值不同,这种情况不可以使用重载. 这两个方法的区别在于返回值,每一个分词器都可能有多种分词

Lucene5学习之QueryParser-Query解析器

  Lucene已经给我们提供了很多Query查询器,如PhraseQuery,SpanQuery,那为什么还要提供QueryParser呢?或者说设计QueryParser的目的是什么?QueryParser的目的就是让你从众多的Query实现类中脱离出来,因为Query实现类太多了,你有时候会茫然了,我到底该使用哪个Query实现类来完成我的查询需求呢,所以Lucene制定了一套Query语法,根据你传入的Query语法字符串帮你把它转换成Query对象,你不用关心底层是使用什么Query实

solr分词都用那个分词器啊 ak还是mmseg4j?

问题描述 solr分词都用那个分词器啊ak还是mmseg4j,这两个区别在什么地方啊?那个比较好点啊,对中文的

Lucene5学习之Suggest关键字提示

     首先需要搞清楚Suggest模块是用来解决什么问题的?Google我想大家都用过,当我们在搜索输入框里输入搜索关键字的时候,紧贴着输入框下方会弹出一个提示框,提示框里会列出Top N个包含当前用户输入的搜索关键字的搜索热词,如图:       这里说的不是前端的这种JS效果,而说的是输入一个关键字如何获取相关的搜索热词,至于js效果,自己Google jQuery自动补全插件,我以前玩过,这里关注的是提示数据如何获取,当然你也可以使用数据库SQL like "%xxxx%"来

Lucene5学习之拼音搜索

     今天来说说拼音检索,这个功能其实还是用来提升用户体验的,别的不说,最起码避免了用户切换输入法,如果能支持中文汉语拼音简拼,那用户搜索时输入的字符更简便了,用户输入次数少了就是为了给用户使用时带来便利.来看看一些拼音搜索的经典案例:             看了上面几张图的功能演示,我想大家也应该知道了拼音检索的作用以及为什么要使用拼音检索了.那接下来就来说说如何实现:      首先我们我们需要把分词器分出来的中文词语转换为汉语拼音,Java中汉字转拼音可以使用pinyin4j这个类库