开发环境
Windows:Win7 64 bit
Java:java version “1.8.0_45”;Java HotSpot(TM) 64-Bit Server VM
Lucene:5.5
Jcseg:1.9.7
Ansj_seg:3.7.1
Ansj_lucene5_plug:3.0
Maven:3.3.3
Lucene集成Jcseg
Jcseg简介
Jcseg是使用Java开发的一个开源中文分词器,使用流行的mmseg算法实现,有兴趣的可以参考算法原文,并且提供了最高版本的lucene、solr、elasticsearch的分词接口。
Google Code最新版V1.9.6:https://code.google.com/archive/p/jcseg/
Git OSChina最新版V1.9.7:http://git.oschina.net/lionsoul/jcseg
GitHub地址:https://github.com/lionsoul2014/jcseg
Maven编译Jcseg项目源码
从OSChina上下载ZIP压缩包之后,解压即可,文件夹重命名为jcseg,进入jcseg根目录,使用mvn clean package命令打包,前提是已经配置过maven环境变量,否则mvn无法识别。成功打包之后如下所示
[INFO] Reactor Summary:
[INFO]
[INFO] jcseg-core ………………………………….. SUCCESS [ 13.469 s]
[INFO] jcseg-analyzer ………………………………. SUCCESS [ 4.601 s]
[INFO] jcseg-elasticsearch ………………………….. SUCCESS [ 4.355 s]
[INFO] jcseg-server ………………………………… SUCCESS [ 7.346 s]
[INFO] jcseg ………………………………………. SUCCESS [ 0.113 s]
[INFO] ————————————————————————
[INFO] BUILD SUCCESS
[INFO] ————————————————————————
[INFO] Total time: 30.134 s
[INFO] Finished at: 2016-03-22T16:47:59+08:00
[INFO] Final Memory: 31M/282M
[INFO] ————————————————————————
Maven将Jar包安装到本地仓库
打开maven的settings.xml文件,配置本地仓库路径
|
|
使用maven命令mvn clean install将之前打包的jar文件安装到本地maven仓库中,成功安装如下所示
[INFO] — maven-install-plugin:2.4:install (default-install) @ jcseg-server —
[INFO] Installing D:\jcseg\jcseg-server\target\jcseg-server-1.9.7.jar to D:\apache-maven-3.3.3\repo\org\lionsoul\jcseg\jcseg-server\1.9.7\jcseg-server-1.9.7.jar
[INFO] Installing D:\jcseg\jcseg-server\dependency-reduced-pom.xml to D:\apache-maven-3.3.3\repo\org\lionsoul\jcseg\jcseg-server\1.9.7\jcseg-server-1.9.7.pom
[INFO] Installing D:\jcseg\jcseg-server\target\jcseg-server-1.9.7-sources.jar to D:\apache-maven-3.3.3\repo\org\lionsoul\jcseg\jcseg-server\1.9.7\jcseg-server-1.9.7-sources.jar
[INFO]
[INFO] ————————————————————————
[INFO] Building jcseg 1.9.7
[INFO] ————————————————————————
[INFO]
[INFO] — maven-clean-plugin:2.5:clean (default-clean) @ jcseg —
[INFO]
[INFO] — maven-enforcer-plugin:1.0:enforce (enforce-maven) @ jcseg —
[INFO]
[INFO] — maven-install-plugin:2.4:install (default-install) @ jcseg —
[INFO] Installing D:\jcseg\pom.xml to D:\apache-maven-3.3.3\repo\org\lionsoul\jcseg\jcseg\1.9.7\jcseg-1.9.7.pom
[INFO] ————————————————————————
[INFO] Reactor Summary:
[INFO]
[INFO] jcseg-core ………………………………….. SUCCESS [ 10.195 s]
[INFO] jcseg-analyzer ………………………………. SUCCESS [ 6.793 s]
[INFO] jcseg-elasticsearch ………………………….. SUCCESS [ 3.815 s]
[INFO] jcseg-server ………………………………… SUCCESS [ 5.745 s]
[INFO] jcseg ………………………………………. SUCCESS [ 0.016 s]
[INFO] ————————————————————————
[INFO] BUILD SUCCESS
[INFO] ————————————————————————
[INFO] Total time: 26.687 s
[INFO] Finished at: 2016-03-22T16:53:28+08:00
[INFO] Final Memory: 31M/253M
[INFO] ————————————————————————
Maven项目配置Jcseg
如果按照默认添加依赖方式如下所示
|
|
是无法成功引用到本地仓库中的jcseg的,这是因为,如果依赖的版本是RELEASE或者LATEST,则Maven基于更新策略读取所有远程仓库的元数据groupId/artifactId/maven-metadata.xml,将其与本地仓库的对应元数据合并,如果不指定依赖版本,也就是说当依赖版本不明晰的时候,如RELEASE、LATEST、SNAPSHOT,Maven就需要基于更新远程仓库的更新策略来检查更新。而我们知道jcseg是我们自己安装到本地仓库中的,远程仓库必然不存在对应的元数据文件,所以这时maven会报错,那么如何解决呢?
这时候就需要指定scope属性,scope用来指定依赖的范围,当scope指定的值是system的时候,Maven直接从本地文件系统解析构建,而不会去远程仓库查询。
scope还有其它几种取值,说明如下
- compile:编译依赖范围,如果没有指定,就会默认使用该依赖范围
- test:测试依赖范围,使用此依赖范围的Maven依赖,只对于测试classpath有效,典型的例子是JUnit,它只在编译测试代码及运行测试的时候才需要
- provided:已提供依赖范围,使用此依赖范围的Maven依赖,对于编译和测试classpath有效,但在运行时无效,典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复地引入一遍
- runtime:运行时依赖范围,使用此依赖范围的Maven依赖,对于测试和运行classpath有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动
- system:系统依赖范围,该依赖与三种classpath的关系,和provided依赖范围完全一致。但是,在使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件路径。通常此依赖与本机系统绑定,造成构建的不可移植,应该谨慎使用。
- import:导入依赖范围,该依赖范围不会对三种classpath产生实际的影响,三种classpath指的是,编译classpath、测试classpath、运行时classpath
所以解决方案已经有了,如下所示,其它依赖本地仓库中的jar包配置类似:
|
|
Lucene集成Jcseg的测试代码
将jcseg源码包中的lexicon和jcseg.properties两个文件复制到src/main/resources下,并修改jcseg.properties中的lexicon.path = src/main/resources/lexicon
|
|
结果输出
+text:中文 +text:zhong wen +text:国语 +text:分词器 +text:fen ci qi
命中数:1
得分:0.080312796
jcseg是使用Java开发的一款开源的中文分词器, 基于流行的mmseg算法实现,分词准确率高达98.4%, 支持中文人名识别, 同义词匹配, 停止词过滤等。并且提供了最新版本的lucene,solr,elasticsearch分词接口。
Lucene集成Ansj
Ansj简介
Ansj是一个ICTCLAS的Java实现。基本上重写了所有的数据结构和算法。词典是用的开源版的ICTCLAS所提供的。并且进行了部分的人工优化。
还是一个基于n-Gram+条件随机场模型的中文分词的Java实现。分词速度达到每秒钟大约200万字左右(Mac Air下测试),准确率能达到96%以上。目前实现了中文分词、中文姓名识别、用户自定义词典。可以应用到自然语言处理等方面,适用于对分词效果要求高的各种项目。
GitHub项目地址:https://github.com/NLPchina/ansj_seg
Ansj的仓库地址,包括针对Lucene的插件:http://maven.nlpcn.org/org/ansj/
Maven项目配置Ansj
根据官方手册,在pom.xml文件中加入依赖,如下所示
|
|
但是你应该能想到,中央仓库或者镜像仓库中并没有Ansj啊,那上述依赖必然报错。对的,所以还需要在settings.xml加入Ansj的仓库地址
|
|
如果你使用了中央仓库的镜像请注意如下内容,如果你没使用镜像请忽略之。
一般在天朝,访问maven中央仓库速度是很慢的,所以国内一般会用某个中央仓库的镜像,而我用的是OSChina的镜像,在镜像的配置中,如果你使用了通配符,那么需要注意该通配符同样会屏蔽掉Ansj仓库的地址,所以需要在通配符之后排除掉Ansj的仓库地址
|
|
当然如果你没使用通配符,而是指定对Maven中央仓库做镜像的话,就无需使用!mvn-repo
进行排除了,其中mvn-repo是Ansj仓库的ID。指定对Maven中央仓库做镜像配置如下:
|
|
Maven之镜像
如果仓库X可以提供仓库Y存储的所有内容,那么就可以认为X是Y的一个镜像。换句话说,任何一个可以从仓库Y获得的构建,都能够从它的镜像中获取。
关于镜像的一个更为常见的用法是结合私服。由于私服可以代理任何外部的公共仓库,因此,对于组织内部的Maven用户来说,使用一个私服地址就等于使用了所有需要的外部仓库,这可以将配置集中到私服,从而简化Maven本身的配置。
有关镜像的一些配置说明如下:
|
|
需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候,Maven仍将无法访问被镜像仓库,因而将无法下载构建。
Maven中自定义变量
通常在依赖一个项目多个组件的时候,为每一个组件单独指定版本号是可以的,但是当升级版本号的时候,就需要对每个组件都做升级,很麻烦,这时就需要自定义变量了。在pom.xml中定义如下
|
|
使用时,直接在version中引用标签名即可,例如
|
|
Maven内置变量
Maven本身就内置了很多预定义变量,可以直接引用,选取一些举例如下
- 内置属性
- ${basedir} represents the directory containing pom.xml
- ${version} equivalent to ${project.version} or ${pom.version}
- ${project.basedir} 同 ${basedir}
- ${project.baseUri} 表示项目文件地址
- ${maven.build.timestamp} 表示项目构件开始时间;
- ${maven.build.timestamp.format} 表示属性 ${maven.build.timestamp} 的展示格式,默认值为yyyyMMdd-HHmm
- POM属性
- ${project.build.directory} results in the path to your “target” dir, this is the same as ${pom.project.build.directory}
- ${project.build.outputDirectory} results in the path to your “target/classes” dir
- ${project.name} refers to the name of the project
- ${project.version} refers to the version of the project
- ${project.build.finalName} refers to the final name of the file created when the built project is packaged
- Settings文件属性
- ${settings.localRepository} refers to the path of the user’s local repository
- Java系统属性
- 使用
mvn help:system
命令可以查看所有的Java系统属性 System.getProperties()
可以得到所有的Java属性- ${user.home} 表示用户目录
- ${java.home} specifies the path to the current JRE_HOME environment use with relative paths to get for example:
<jvm>${java.home}../bin/java.exe</jvm>
- 使用
- 环境变量属性
- ${env.M2_HOME} returns the Maven2 installation path.
- ${env.JAVA_HOME} 表示JAVA_HOME环境变量的值;
- 自定义属性
<properties><my.version>hello</my.version></properties>
则引用 ${my.version}就会得到值hello
Lucene集成Ansj的测试代码
Ansj In Lucene的官方参考文档:http://nlpchina.github.io/ansj_seg/
到https://github.com/NLPchina/ansj_seg 下载ZIP压缩文件,解压,将其中的library文件夹和library.properties
文件拷贝到maven项目下的src/main/resources
中,修改library.properties
内容如下
|
|
Lucene集成Ansj测试代码如下
|
|
本以为大功告成,怀着激动的心情运行Junit单元测试,但是天雷滚滚啊,报错啊,想想这可是官方给的测试Demo啊,居然报错!!!
废话不说,上结果:
java.lang.AssertionError: TokenStream implementation classes or at least their incrementToken() implementation must be final
Notes 2016/3/27:经反馈,作者已fix此Bug,但本文不再做更新。Issue编号:#249,反馈地址:https://github.com/NLPchina/ansj_seg/issues/249#event-604309598
说的很清楚啊,Ansj作者自己测试通过否?要么将类修饰为final,要么将incrementToken()方法修饰为final,这是为啥哩?
查源码,从AnsjAnalyzer追踪到AnsjTokenizer,再追踪到Tokenizer,别停,继续追踪TokenStream,该类位于org.apache.lucene.analysis
包下,在该类的Doc注释中,终于发现如下说明
The {@code TokenStream}-API in Lucene is based on the decorator pattern. Therefore all non-abstract subclasses must be final or have at least a final implementation of {@link #incrementToken}! This is checked when Java assertions are enabled.
意思是说所有的非抽象子类必须是final的或者至少有一个final修饰的incrementToken()覆写方法。但是Ansj针对Lucene的插件中,这两者都没有做!!!
Lucene集成Ansj报错解决方案
- 既然Ansj两者都没做,那么一种方法就是修改Ansj的源码,但是我们使用的是Ansj仓库中提供的Jar包,修改源码之后,只能本地引用修改后的Jar包,不方便项目的迁移,所以不采用
- 提供一个final修饰的覆写方法incrementToken(),通过实现两个内部类,分别继承自AnsjAnalyzer和AnsjTokenizer,在使用的时候调用自己实现的内部类
修复BUG后的代码如下
|
|
运行部分结果如下:
信息: init user userLibrary ok path is : D:\Multi-module-project\Lucene\src\main\resources\library\default.dic
信息: init ambiguityLibrary ok!
信息: init core library ok use time :281
信息: init ngram ok use time :668
索引建立完毕
index ok to search!
text:”季德胜 蛇药片”
“季德胜蛇药片”:共找到1条记录!季德胜蛇药片 10片*6板
Ansj设置词典路径
正规方式
创建library.properties中增加123#path of userLibrary 自定义词典路径userLibrary=library/userLibrary/userLibrary.dicambiguityLibrary=library/ambiguity.dic在用词典未加载前可以通过,代码方式方式来加载
1MyStaticValue.userLibrary=[你的路径]调用api加载。在程序运行的任何时间都可以。动态加载。
1loadLibrary.loadLibrary(String path)方式加载
路径可以是具体文件也可以是一个目录,如果是一个目录,那么会扫描目录下的dic文件自动加入词典。
Lucene集成Ansj添加自定义词典
如果需要添加自己的自定义词典,参考default.dic格式即可。用户自定义词典的格式是word[tab]nature[tab]freq
,example: 小李子 nr 100
分布式分词组件Word
Notes:另外发现了一个分布式中文分词组件,希望以后有机会可以深入研究,地址:https://github.com/ysc/word
Word分词是一个Java实现的分布式的中文分词组件,提供了多种基于词典的分词算法,并利用ngram模型来消除歧义。能准确识别英文、数字,以及日期、时间等数量词,能识别人名、地名、组织机构名等未登录词。能通过自定义配置文件来改变组件行为,能自定义用户词库、自动检测词库变化、支持大规模分布式环境,能灵活指定多种分词算法,能使用refine功能灵活控制分词结果,还能使用词频统计、词性标注、同义标注、反义标注、拼音标注等功能。提供了10种分词算法,还提供了10种文本相似度算法,同时还无缝和Lucene、Solr、ElasticSearch、Luke集成。注意:word1.3需要JDK1.8。