Lucene:Ansj分词器
Ansj分词器
导入jar包
ansj_seg-5.1.6.jar
nlp-lang-1.7.8.jar
maven配置
<dependency>
<groupId>org.ansj</groupId>
<artifactId>ansj_seg</artifactId>
<version>5.1.1</version>
</dependency>
代码演示
import org.ansj.library.DicLibrary; import org.ansj.splitWord.analysis.*; import org.ansj.util.MyStaticValue; /** * AnsjAnalyzerTest * * @author limingcheng * @Date 2019/11/26 */ public class AnsjAnalyzerTest { /** * 基本分词(BaseAnalysis) * 速度快 */ public static void BaseAnalysisTest(){ String words = "让战士们过一个欢乐祥和的新春佳节。"; System.out.println(BaseAnalysis.parse(words)); } /** * 精准分词(ToAnalysis) * 精准分词方式兼顾精度与速度,比较均衡 */ public static void ToAnalysisTest(){ String words = "让战士们过一个欢乐祥和的新春佳节。"; System.out.println(ToAnalysis.parse(words)); } /** * NLP分词(NlpAnalysis) * NLP分词方式可是未登录词,但速度较慢 */ public static void NlpAnalysisTest(){ String words = "洁面仪配合洁面深层清洁毛孔 清洁鼻孔面膜碎觉使劲挤才能出一点点皱纹 " + "脸颊毛孔修复的看不见啦 草莓鼻历史遗留问题没辙 脸和脖子差不多颜色的皮肤才是健康的 " + "长期使用安全健康的比同龄人显小五到十岁 28岁的妹子看看你们的鱼尾纹。"; System.out.println(NlpAnalysis.parse(words)); } /** * 面向索引分词(IndexAnalysis) */ public static void IndexAnalysisTest(){ String words = "洁面仪配合洁面深层清洁毛孔 清洁鼻孔面膜碎觉使劲挤才能出一点点皱纹"; System.out.println(IndexAnalysis.parse(words)); } /** * 自定词典分词(DicLibrary) * 动态添加 */ public static void DicLibraryTest(){ //添加自定义词语 【 英文,按照小写配置。(大写,不识别。拆词的结果,也转为小写了)】 DicLibrary.insert(DicLibrary.DEFAULT, "基于java", "n", 1); String text = "基于Java开发的轻量级的中分分词工具包"; System.out.println(DicAnalysis.parse(text)); } /** * 自定词典分词(DicLibrary) * 路径获取 */ public static void DicLibraryPath(){ // 关闭名字识别 MyStaticValue.isNameRecognition = false; // 配置自定义词典的位置。注意是绝对路径 MyStaticValue.ENV.put(DicLibrary.DEFAULT, "E:\\indexDir\\library\\default.dic"); String text = "基于Java开发的轻量级的中分分词工具包"; System.out.println(DicAnalysis.parse(text)); } /** * 自定词典分词(DicLibrary) * 配置文件 */ public static void DicLibraryProperties(){ String text = "基于Java开发的轻量级的中分分词工具包"; System.out.println(DicAnalysis.parse(text)); } public static void main(String[] args) { // 基本分词 // BaseAnalysisTest(); // // 精准分词 // ToAnalysisTest(); // // NLP分词 // NlpAnalysisTest(); // // 面向索引分词 // IndexAnalysisTest(); // 词典分词(动态添加) // DicLibraryTest(); // 词典分词(路径) // DicLibraryPath(); // 词典分词(配置文件) DicLibraryProperties(); } }
1.1.5. 搭配Lucene
由于Ansj项目并没有提供analyzer,需要自己手动写一个来适配。因此,首先要创建以下几个类:
AnsjAnalyzer
import org.ansj.library.*; import org.ansj.recognition.impl.StopRecognition; import org.ansj.recognition.impl.SynonymsRecgnition; import org.ansj.splitWord.Analysis; import org.ansj.splitWord.analysis.*; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.Tokenizer; import org.nlpcn.commons.lang.tire.domain.Forest; import org.nlpcn.commons.lang.tire.domain.SmartForest; import org.nlpcn.commons.lang.util.StringUtil; import org.nlpcn.commons.lang.util.logging.Log; import org.nlpcn.commons.lang.util.logging.LogFactory; import java.io.BufferedReader; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * AnsjAnalyzer * * @author limingcheng * @Date 2019/11/26 */ public class AnsjAnalyzer extends Analyzer { public static final Log LOG = LogFactory.getLog(); /** * dic equals user , query equals to * * @author ansj * */ public static enum TYPE { // 基本分词(BaseAnalysis) base_ansj, // 索引分词 index_ansj, // 查询分词 query_ansj, // 自定词典分词(DicLibrary) dic_ansj, // NLP分词(NlpAnalysis) nlp_ansj } /** * 分词类型 */ private Map<String, String> args; /** * filter 停用词 */ public AnsjAnalyzer(Map<String, String> args) { this.args = args; } public AnsjAnalyzer(TYPE type, String dics) { this.args = new HashMap<String, String>(); args.put("type", type.name()); args.put(DicLibrary.DEFAULT, dics); } public AnsjAnalyzer(TYPE type) { this.args = new HashMap<String, String>(); args.put("type", type.name()); } @Override protected TokenStreamComponents createComponents(String text) { BufferedReader reader = new BufferedReader(new StringReader(text)); Tokenizer tokenizer = null; tokenizer = getTokenizer(reader, this.args); return new TokenStreamComponents(tokenizer); } /** * 获得一个tokenizer * * @param reader * @param args type * @param args filter * @return */ public static Tokenizer getTokenizer(Reader reader, Map<String, String> args) { if (LOG.isDebugEnabled()) { LOG.debug("to create tokenizer " + args); } Analysis analysis = null; String temp = null; String type = args.get("type"); if (type == null) { type = AnsjAnalyzer.TYPE.base_ansj.name(); } switch (AnsjAnalyzer.TYPE.valueOf(type)) { case base_ansj: analysis = new BaseAnalysis(); break; case index_ansj: analysis = new IndexAnalysis(); break; case dic_ansj: analysis = new DicAnalysis(); break; case query_ansj: analysis = new ToAnalysis(); break; case nlp_ansj: analysis = new NlpAnalysis(); if (StringUtil.isNotBlank(temp = args.get(CrfLibrary.DEFAULT))) { ((NlpAnalysis) analysis).setCrfModel(CrfLibrary.get(temp)); } break; default: analysis = new BaseAnalysis(); } if (reader != null) { analysis.resetContent(reader); } //用户自定义词典 if (StringUtil.isNotBlank(temp = args.get(DicLibrary.DEFAULT))) { String[] split = temp.split(","); Forest[] forests = new Forest[split.length]; for (int i = 0; i < forests.length; i++) { if (StringUtil.isBlank(split[i])) { continue; } forests[i] = DicLibrary.get(split[i]); } analysis.setForests(forests); } List<StopRecognition> filters = null; //用户自定义词典 if (StringUtil.isNotBlank(temp = args.get(StopLibrary.DEFAULT))) { String[] split = temp.split(","); filters = new ArrayList<StopRecognition>(); for (String key : split) { StopRecognition stop = StopLibrary.get(key.trim()); if (stop != null) { filters.add(stop); } } } List<SynonymsRecgnition> synonyms = null; //同义词词典 if (StringUtil.isNotBlank(temp = args.get(SynonymsLibrary.DEFAULT))) { String[] split = temp.split(","); synonyms = new ArrayList<SynonymsRecgnition>(); for (String key : split) { SmartForest<List<String>> sf = SynonymsLibrary.get(key.trim()); if (sf != null) { synonyms.add(new SynonymsRecgnition(sf)); } } } //歧义词典 if (StringUtil.isNotBlank(temp = args.get(AmbiguityLibrary.DEFAULT))) { analysis.setAmbiguityForest(AmbiguityLibrary.get(temp.trim())); } // 是否开启人名识别 if (StringUtil.isNotBlank(temp = args.get("isNameRecognition"))) { analysis.setIsNameRecognition(Boolean.valueOf(temp)); } // 是否开启数字识别 if (StringUtil.isNotBlank(temp = args.get("isNumRecognition"))) { analysis.setIsNumRecognition(Boolean.valueOf(temp)); } //量词识别 if (StringUtil.isNotBlank(temp = args.get("isQuantifierRecognition"))) { analysis.setIsQuantifierRecognition(Boolean.valueOf(temp)); } //是否保留原字符 if (StringUtil.isNotBlank(temp = args.get("isRealName"))) { analysis.setIsRealName(Boolean.parseBoolean(temp)); } return new AnsjTokenizer(analysis, filters, synonyms); } }
AnsjTokenizer
Ansj分词方式
Ansj分词器提供了以下几种分词模式,各种分词模式都有各自的优劣势,适应不同的需求场景。参考:https://blog.csdn.net/lb521200200/article/details/53696387
ToAnalysis 精准分词
精准分词在易用性,稳定性.准确性.以及分词效率上.都取得了一个不错的平衡。万金油的存在,适合测试。
DicAnalysis 用户自定义词典优先策略的分词
用户自定义词典优先策略的分词。当分词效果不能满足要求,或者待分词的词语实在太过罕见的情况下,使用用户自定义词典可以有效解决该问题。
NlpAnalysis 带有新词发现功能的分词
NLP分词是一种分词效果比较好的分词方式,能识别出未登录词。同时效果极好的后果是消耗性能较大,导致速度比较慢、稳定性差(分词速度约为40w字每秒)。
NLP的适用场景:语法实体名抽取;未登录词整理;只要是对文本进行发现分析等工作。
1.2.4. IndexAnalysis 面向索引的分词
面向索引的分词。顾名思义就是适合在lucene等文本检索中用到的分词。主要考虑以下两点
召回率
召回率是对分词结果尽可能的涵盖。比如对“上海虹桥机场南路” 召回结果是[上海/ns, 上海虹桥机场/nt, 虹桥/ns, 虹桥机场/nz, 机场/n, 南路/nr]
准确率
其实这和召回本身是具有一定矛盾性的Ansj的强大之处是很巧妙的避开了这两个的冲突 。比如我们常见的歧义句“旅游和服务”->对于一般保证召回 。大家会给出的结果是“旅游 和服 服务” 对于ansj不存在跨term的分词。意思就是。召回的词只是针对精准分词之后的结果的一个细分。比较好的解决了这个问题
1.2.5. BaseAnalysis 最小颗粒度的分词
基本就是保证了最基本的分词,词语颗粒度最非常小的。所涉及到的词大约是10万左右。基本分词速度非常快.在macAir上,能到每秒300w字每秒。同时准确率也很高,但是对于新词他的功能十分有限。
总结:
功能统计:
名称 | 用户自定义词典 | 数字识别 | 人名识别 | 机构名识别 | 新词发现 |
BaseAnalysis | 否 | 否 | 否 | 否 | 否 |
ToAnalysis | 是 | 是 | 是 | 否 | 否 |
DicAnalysis | 是 | 是 | 是 | 否 | 否 |
IndexAnalysis | 是 | 是 | 是 | 否 | 否 |
NlpAnalysis | 是 | 是 | 是 | 是 | 是 |
代码演示:
package main.java.cn.lmc.collection.retrieval.web.analyzer; import org.ansj.domain.Result; import org.ansj.library.DicLibrary; import org.ansj.splitWord.analysis.*; import org.ansj.util.MyStaticValue; /** * AnsjAnalyzerTest * * @author limingcheng * @Date 2019/11/26 */ public class AnsjAnalyzerTest { /** * 基本分词(BaseAnalysis) * 速度快 */ public static void BaseAnalysisTest(){ String words = "五月天创建的人生有限公司举报了一场演唱会,陈信宏唱了一首do you ever shine"; System.out.println(BaseAnalysis.parse(words)); } /** * 精准分词(ToAnalysis) * 精准分词方式兼顾精度与速度,比较均衡 */ public static void ToAnalysisTest(){ String words = "五月天创建的人生有限公司举报了一场演唱会,陈信宏唱了一首do you ever shine。"; System.out.println(ToAnalysis.parse(words)); } /** * NLP分词(NlpAnalysis) * NLP分词方式可是未登录词,但速度较慢 */ public static void NlpAnalysisTest(){ String words = "对对对对对对多多小学生 101304471127J"; System.out.println(NlpAnalysis.parse(words)); Result result = NlpAnalysis.parse(words); System.out.println(result.toString()); System.out.println(result.getTerms().toString()); } /** * 面向索引分词(IndexAnalysis) */ public static void IndexAnalysisTest(){ String words = "五月天创建的人生有限公司举报了一场演唱会,陈信宏唱了一首do you ever shine,周杰伦,杰伦"; System.out.println(IndexAnalysis.parse(words)); System.out.println(IndexAnalysis.parse("杰伦")); } /** * 自定词典分词(DicLibrary) * 动态添加 */ public static void DicLibraryTest(){ //添加自定义词语 【 英文,按照小写配置。(大写,不识别。拆词的结果,也转为小写了)】 DicLibrary.insert(DicLibrary.DEFAULT, "基于java", "n", 1); String text = "基于Java开发的轻量级的中分分词工具包"; System.out.println(DicAnalysis.parse(text)); } /** * 自定词典分词(DicLibrary) * 路径获取 */ public static void DicLibraryPath(){ // 关闭名字识别 MyStaticValue.isNameRecognition = false; // 配置自定义词典的位置。注意是绝对路径 MyStaticValue.ENV.put(DicLibrary.DEFAULT, "E:\\indexDir\\library\\default.dic"); String text = "基于Java开发的轻量级的中分分词工具包"; System.out.println(DicAnalysis.parse(text)); } /** * 自定词典分词(DicLibrary) * 配置文件 */ public static void DicLibraryProperties(){ String text = "基于Java开发的轻量级的中分分词工具包"; System.out.println(DicAnalysis.parse(text)); } public static void main(String[] args) { // 基本分词 // BaseAnalysisTest(); // // 精准分词 // ToAnalysisTest(); // // NLP分词 // NlpAnalysisTest(); // // 面向索引分词 IndexAnalysisTest(); // 词典分词(动态添加) // DicLibraryTest(); // 词典分词(路径) // DicLibraryPath(); // 词典分词(配置文件) // DicLibraryProperties(); } }