我现在愈发觉得,互联网应该有一个垃圾回收机制——技术性的博客因为软件版本的更新早已不再适用,但却仍然占据着搜索的结果。也许以后会有一种新的计算机职业——网络清洁工。他们可以熟练地运用各种技术手段保持网上的内容总是对当下渴望某一方面知识的人是有用的。
——F.W.H
现在在哪里
中文分词是构建一个诗词相关软件的第一步。在本步中,程序应该能完成将一句古诗中的各个语法单元抽取出来的功能。这是一个很难实现的功能,所幸许多前辈们已经开发出来很棒的成品可以解决这一问题。
1.Lucene——经典与专业
Lucene可以说是最经典和专业的分词软件。最重要的是——它是完全开源的。Lucene现在已经更新到了8.0版本,并且仍然保持着极高的更新速度。Lucene使用java语言编写,可以在官网直接下载jar包和相关文档。Lucene除了分词功能外,还具有强大的归类索引,自定义字典等功能,是进行自然文本处理的强大工具。美中不足的是,其对中文的支持性很差,而且其极高的更新速度使得很多中文分词软件在与其配合上出现了问题,比如IKAnalyzer。
Lucene官方网站:http://lucene.apache.org/
蚩尤后裔博主写的Lucene入门博客:
Lucene 实战前 核心理论简述
Lucene 实战之入门案例
个人感觉写的很不错,尤其是代码对着注释敲下来可以对Lucene的机制有比较清楚的了解。可惜的是博主这系列博客的后一篇文章Lucene 中文分词器 Ik-Analyzer 使用教程在我的电脑上无法正常运行。
Lucene有时会依赖apache的common.io包,可以去apache官网下载:http://commons.apache.org/proper/commons-io/download_io.cgi
Lucene对中文的分词效果实在是太差了(一般是每个汉字都会被分成一个词),我们不妨测试一下,就用杜甫的《白帝城楼》
public class TestLucene04 {
private void print(Analyzer analyzer) throws Exception{
String text = "江度寒山阁,城高绝塞楼。\r\n" +
"翠屏宜晚对,白谷会深游。\r\n" +
"急急能鸣雁,轻轻不下鸥。\r\n" +
"彝陵春色起,渐拟放扁舟。";
TokenStream tokenStream = analyzer.tokenStream("content", text);
CharTermAttribute attribute = tokenStream.addAttribute(CharTermAttribute.class);
tokenStream.reset();
while (tokenStream.incrementToken()) {
System.out.print(new String(attribute.toString())+"/");
}
}
public void testStandardAnalyzer() throws Exception{
StandardAnalyzer standardAnalyzer = new StandardAnalyzer();
print(standardAnalyzer);
}
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
TestLucene04 a = new TestLucene04();
a.testStandardAnalyzer();
//a.testSmartChineseAnalyzer();
//a.testMySmartChineseAnalyzer();
}
}
结果如下:
看来我们为了中文分词的需要,还要寻找其他的软件。
2.Lucene中文分词器smartcn——一次失败的尝试
Lucene发展到4.6版本之后,其包内带入了smartcn——智能中文分词器。使用时只需简单的把之前使用标准分词器中的代码中的StandardAnalyzer
替换成SmartChineseAnalyzer
即可,两个类实现的接口即相关成员几乎完全一样,也就是说——之前的代码除了在new成员时换一个类来new之外几乎可以全无改动,这实在是很方便。smartcn因为本身即属于Lucene,所以Lucene所有功能也全部可以通过smartcn来实现。听起来似乎十分完美,但问题是——smartcn不仅英文分词能力丢失了,中文分词能力仍然很差。
将之前代码中的testStandardAnalyzer
成员函数删去,修改为
public void testSmartChineseAnalyzer() throws Exception{
SmartChineseAnalyzer smartChineseAnalyzer = new SmartChineseAnalyzer();
print(smartChineseAnalyzer);
}
再将main函数修改一下
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
TestLucene04 a = new TestLucene04();
//a.testStandardAnalyzer();
a.testSmartChineseAnalyzer();
//a.testMySmartChineseAnalyzer();
}
观察结果
您这也敢叫SmartChineseAnalyzer…
而且smartcn对英文的支持效果也不好,经常会出现漏掉字母的情况。
如果对smartcn的用法感兴趣,可以看xxpsw博主的文章Lucene使用(四)中文分词器smartcn
3.IKAnalyzer——回到七年之前
提到中文分词,就不得不说一下IKAnalyzer。在很长的一段时间内,IKAnalyzer都是中文分词的首选。IKAnalyzer是一个基于java的开源的轻量级的中文分词工具包。它一开始是基于Lucene以类似于插件的方式开发的,但从3.0版本开始,它提供了独立于Lucene之外的公用分词组件,同时提供了对Lucene的默认优化实现。IKAnalyzer的最新版本是IKAnalyzer 2012 FF,也就是说,截至现在该程序已经7年没有官方更新过了,因此现在大家往往更倾向于使用更新的中文分词器。但是很多论文中仍然使用IKAnalyzer作为大型项目中的分词工具,所以对于我这样需要照着论文做大创项目的人来说还是希望这个分词器仍然能够使用。
IKAnayzer可以在Google Code中下载(需要翻墙):http://code.google.com/p/ik-analyzer/
关于IKAnalyzer的Lucene插件相关功能的使用其实很简单,它就和smartcn一样实现了Lucene中的StandAnalyzer的所有接口和成员,一般只需要把代码中的Analyzer analyzer = new StandardAnalyzer();
更改为Analyzer analyzer = new IKAnalyzer();
代码就完全可以正常工作。
关于IKAnalyzer和Lucene的配合使用,可以参考IKAnalyzer中自带的文档,也可以继续看蚩尤后裔博主的博客:
Lucene 中文分词器 Ik-Analyzer 使用教程
但是我在电脑上照着蚩尤后裔博主的文章打上代码运行时,程序出现了各种各样的报错——创建对象错误啦,jdk和jre版本不匹配啦。。。。。。百度得知,其实这些报错的原因都是Lucene和IKAnalyzer的版本不匹配。
于是我就又下载了比较常用的Lucene7.4.0版本进行尝试,仍然报错。难道要下载Lucene4.10.3进行尝试吗?个人感觉在一个软件已经发展到8.0版本之后仍然去使用4.10的版本并不是一个很好的选择,所以准备换个方向。
这里附上两个据说可以和新版本Lucene兼容的IKAnalyzer的下载链接,如果以后有时间且有必要的话可以尝试一下。
IKAnalyzer中文分词支持lucene6.5.0版本
IKAnalyzer 支持高版本Lucene 6.x及以上
4.IKAnalyzerSegement——第一个备选方案
前面已经提到过,IKAnalyzer在3.0版本就提供了不再依赖于Lucene的分词方式。既然现在IKAnalyzer和Lucene无法兼容了,我们是时候尝试一下仅仅使用IKAnalyzer进行分词。这里仍然用上一个方案中的包就可以,不需要下载任何新的东西。IKAnalyzer使用两个类来完成基本的独立于Lucene的分词操作,它们分别是IKSegmentier
和Lexeme
。
IKSegmenter
是IK分词器的核心类,起着类似于之前提到的IKAnalyzer
类的作用,常用的构造函数为public IKSegmenter(Reader input , boolean useSmart)
。其中第一个参数是字符输入读取,第二个参数代表是否采用智能切分策略。true使用智能切分,false使用最细粒度切分。使用IKSegmenter
进行分词十分简单,只需要调用它的next()
成员函数即可返回Lexeme
类型的下一个词元,如果返回null,则表示已完成了对输入对象的分词操作。
除此之外,IKSegmenter
还具有另一个构造函数public IKSegmentation(Reader input , Configuration cfg)
允许用户对分词器进行自定义的配置。
Lexeme
是IK分词器的语义单元对象,也就是我们分词得到的词元结果。它的常用成员函数如下。
public int getBeginPosition()
说明:获取语义单元的起始字符在文本中的位置
public int getEndPosition()
说明:获取语义单元的结束字符的下一个位置
public int getLength()
说明:获取语义单元包含字符串的长度
public String getLexemeText()
说明:获取语义单元包含字符串内容
参考独自等待_T博主的文章使用IK Analyzer实现中文分词(JAVA),对IKSegement相关功能进行一下测试。这次使用我很喜欢的苏轼的《念奴娇·赤壁怀古》进行实验。
public class TestSegmenter {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
String content ="大江东去,浪淘尽,千古风流人物。\r\n" +
"故垒西边,人道是,三国周郎赤壁。\r\n" +
"乱石穿空,惊涛拍岸,卷起千堆雪。\r\n" +
"江山如画,一时多少豪杰。\r\n" +
"遥想公瑾当年,小乔初嫁了,雄姿英发。\r\n" +
"羽扇纶巾,谈笑间,樯橹灰飞烟灭。\r\n" +
"故国神游,多情应笑我,早生华发。\r\n" +
"人生如梦,一尊还酹江月。";
Reader input = new StringReader(content);
IKSegmenter ikSegmenter=new IKSegmenter(input,true);
Lexeme lexeme;
while ((lexeme=ikSegmenter.next())!=null) {
System.out.println(lexeme.getLexemeText());
}
}
}
效果如下:
大江东去/浪/淘/尽/千古/风流人物/故垒/西边/人道/是/三国/周郎/赤壁/乱石/穿空/惊涛拍岸/卷起/千堆/雪/江山如画/一时/多少/豪杰/遥想/公/瑾/当年/小/乔/初/嫁了/雄姿英发/羽扇纶巾/谈笑/间/樯/橹/灰飞烟灭/故国/神游/多情/应/笑/我/早生/华发/人生如梦/一尊/还/酹/江月/
诶诶这就分的很不错了对不对,虽然有些地方像公瑾还没有分出来,但是再加上自定义字典的话应该就够用了。之后的自定义词典构造也许需要花更大功夫,不过那已经不是这个阶段的问题了。
5.结巴分词——走向Python时代
结巴分词是一个以中文分词为主要应用目的的Python代码包,它支持三种分词模式:
1.精确模式,试图将句子最精确地切开,适合文本分析;
2.全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
3.搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
同时,结巴分词也提供自定义词典等功能,而且在github上完全开源,现在仍然有不少爱好者对它进行更新和维护,实在是一柄中文分词的利器。
结巴分词github项目网址https://github.com/fxsjy/jieba
有关结巴分词的下载安装和基本使用方式在项目的readme文档中都有详细介绍,这里不再赘述。
仍然使用《念奴娇·赤壁怀古》对结巴分词进行测试。
# encoding=utf-8
import jieba
seg_list = jieba.cut("大江东去,浪淘尽,千古风流人物。\
故垒西边,人道是,三国周郎赤壁。\
乱石穿空,惊涛拍岸,卷起千堆雪。\
江山如画,一时多少豪杰。\
遥想公瑾当年,小乔初嫁了,雄姿英发。\
羽扇纶巾,谈笑间,樯橹灰飞烟灭。\
故国神游,多情应笑我,早生华发。\
人生如梦,一尊还酹江月。", cut_all=True)
print("全模式: " + "/ ".join(seg_list))
seg_list = jieba.cut("大江东去,浪淘尽,千古风流人物。\
故垒西边,人道是,三国周郎赤壁。\
乱石穿空,惊涛拍岸,卷起千堆雪。\
江山如画,一时多少豪杰。\
遥想公瑾当年,小乔初嫁了,雄姿英发。\
羽扇纶巾,谈笑间,樯橹灰飞烟灭。\
故国神游,多情应笑我,早生华发。\
人生如梦,一尊还酹江月。", cut_all=False)
print("精确模式: " + "/ ".join(seg_list))
seg_list = jieba.cut_for_search("大江东去,浪淘尽,千古风流人物。\
故垒西边,人道是,三国周郎赤壁。\
乱石穿空,惊涛拍岸,卷起千堆雪。\
江山如画,一时多少豪杰。\
遥想公瑾当年,小乔初嫁了,雄姿英发。\
羽扇纶巾,谈笑间,樯橹灰飞烟灭。\
故国神游,多情应笑我,早生华发。\
人生如梦,一尊还酹江月。")
print("搜索引擎模式: " + "/ ".join(seg_list))
结果如下:
我们来只看精确模式的分词结果:
精确模式: 大江东去/ ,/ 浪淘尽/ ,/ 千古/ 风流人物/ 。/ 故垒/ 西边/ ,/ 人道/ 是/ ,/ 三/ 国周郎/ 赤壁/ 。/ 乱石/ 穿空/ ,/ 惊涛拍岸/ ,/ 卷起/ 千堆/ 雪/ 。/ 江山如画/ ,/ 一时/ 多少/ 豪杰/ 。/ 遥想/ 公瑾/ 当年/ ,/ 小乔初/ 嫁/ 了/ ,/ 雄姿英发/ 。/ 羽扇纶巾/ ,/ 谈笑/ 间/ ,/ 樯橹/ 灰飞烟灭/ 。/ 故国/ 神游/ ,/ 多情/ 应笑/ 我/ ,/ 早/ 生/ 华发/ 。/ 人生如梦/ ,/ 一尊/ 还/ 酹/ 江月/ 。
分的确实不错,和IKAnalyzerSegement都有一些对方没有分出来的地方,整体来看单从分词的角度来看结巴分词效果更好一些。
6.结巴分词的其他版本——海纳百川,有容乃大
结巴分词虽然起源于python,但在github上各位前辈已经开发出了针对许多种语言的版本。
截止目前,python分词已拥有Python、Java、C++、Node.js、Erlang、R、IOS、PHP、.NET(C#)、Go、Android共计11个版本,这些版本的github网址均可在Python版本的github网址的其他语言实现处找到。
因为之前决定这次的项目主体使用Java进行开发,我下载了Java版本的结巴分词。然后发现作者好像并没有打包,我下载出来的是许许多多的.java源代码。然后我又发现自己好像并不会打包,我想我之后需要找个时间再钻研一下Java的各种常用操作。这里就先不完善结巴分词Java版本的有关内容了。
后记
此阶段还有以下几个方面可能需要日后研究一下:
- IKAnalyzerSegement和结巴分词有没有类似于Lucene那样为多个文件创建索引的功能
- 可以兼容新版Lucene的IKAnalyzer的寻找与尝试或者Lucene4.10版本与当前版本IKAnalyzer的配合尝试
- IKAnalyzerSegement和结巴分词的分词结果存储的相关内容
- IKAnalyzerSegement和结巴分词自定义字典功能的尝试
- 结巴分词Java版本的尝试