分词:一种分词词库设计

原文地址:http://blog.donews.com/dgsheng/archive/2006/03/02/749512.aspx

最近折腾毕业论文,搞得人没心情写blog了。于是觉得不如把毕业论文里的东西贴出来当blog算了。这里主要介绍了我自己的中文分词算法,我觉得它比现在开源代码比较多的中文匹配法要好多了。这里的内容没有任何背景知识啥的,毕竟论文里的背景知道我也是从网上粘贴的,呵呵!因此这篇文章的内容可能适合做搜索引擎的人。如果要了解中文分词算法在搜索引擎中的重要性,或者最大匹配法的思想与过程,请去网上搜吧,资料还是蛮多的。

 

1.1.1 最大匹配法分词的缺陷

尽管最大匹配法分词是常用的解决的方案,但是无疑它存在很多明显的缺陷,这些缺陷也限制了最大匹配法在大型搜索系统中的使用频率。最大匹配法的问题有以下几点:

一、长度限制

由于最大匹配法必须首先设定一个匹配词长的初始值,这个长度限制是最大匹配法在效率与词长之间的一种妥协。我们来看一下以下两种情况:

1)词长过短,长词就会被切错。例如当词长被设成5时,也就意味着它只能分出长度为5以下词,例如当这个词为“中华人民共和国”长度为7的词时,我们只能取出其中的5个字去词库里匹配,例如“中华人民共”,显然词库里是不可能有这样的词存在的。因此我们无法下确的划分出“中华人民共和国”这样的词长大于5的词。

2)词长过长,效率就比较低。也许有人会认为既然5个字无法满足我们的分词要求,何不将词长加大,例如加到10或者100,毕竟这个世界超过100个字长的词还是很少见的,我们的词长问题不就解决了?然而当词长过长时,我们却要付出另一方面的代价:效率。效率是分词算法、甚至是整个算法理论体系的关键,毕竟算法书里所有的高深的查询或排序算法都是从效率出发的,否则任何笨办法都可以解决分词效率低的问题。设想到我们把字长设成100个词时,我们必须将词从100开始一直往下匹配直到找到要查的字为止,而我们大多数词的字长却只有两三个字,这意味着前97次的匹配算法是徒劳的。

因此我们必须要在词长与效率之间进行妥协,既要求分词尽量准确,又要求我们的词长不能太长。尽管我们可能找到这样一个比较优化的字长值使两者都达到比较满足的状态,但是毕竟不管我们怎么设定,总会有些太长词分出来,或者带来效率问题。

 

二、效率低

效率低是最大匹配法分词必然会来的问题。即使我们可以将字长设成相当短,例如5(注意,我们不能再缩短字长了,毕竟字长为5以上的词太多了,我们不能牺牲分词的准确),然而当我们的大数词长为2时,至少有3次的匹配算法是浪费掉的。回想一下算法书里提到的最简单的字符匹配与KMP算法之间天差地别的效率,我们知道通过某种方法,这些浪费的掉的匹配时间是可以补回来的。

 

三、掩盖分词歧义

中文是如此复杂的语言,它的表达方式如此之多,语法文法如此精妙,机械的电脑是很难理解这么复杂的语言,因此它必然会带来歧意性,以下是两个简单的例子:

A.“有意见分歧(正向最大匹配和逆向最大匹配结果不同)
有意/ / 分歧/

/ 意见/ 分歧/
B.“
结合成分子时(正向最大匹配和逆向最大匹配结果相同)
结合/ 成分/ 子时/由于词的歧义性使我们在使用最大匹配法分词会产生错误的结果,而且使用正向分词与逆向分词往往会产生截然不同的结果。尽管使用回溯法或计算计算词的使用频率,可以使出现歧义的可能性减少,但是我们知道,这样的结果是不可避免的,因为中文的变化实在太多了。

 

四、最大匹配的并不一定是想要的分词方式

最大匹配法基于的理念是找到最大的匹配词,但有的时候除了最大匹配词外,我们也可能只需要这个词的一部分。例如“感冒解毒胶囊”是一个完整的词,按照最大匹配法我们无法对它进行拆分了,这样我们输入“感冒”的时候就根本搜不到我们需要的词。这是我们需要的吗?做为生产这种药的厂商,它肯定希望用户输入“感冒”甚至“解毒”,我们都能查到对应的内容。

 

1.2 设计自己的中文分词算法

1.2.1 设计目标

基于对分词算法的理解和对最大匹配法分词的分析,我们知道我们必须提出不同的解决方案,使分词算法的效率、分词的长度限制甚至歧义处理上得到提高。因此我们提出了如下的设计目标:

一、 高效

中文分词算法必须要高效,毕竟效率对于搜索引擎的重要性是不言而喻的。而且我们面对的是海量的数据,而不是一篇几百字或几千字的文章,效率的差别的影响可能会使最后运行效率差几个小时甚至几天。因此我希望我们设计的算法一定要比最大匹配法高,毕竟我们已经常看到最大匹配法的很多次匹配都是浪费在无用功上了,肯定有办法把这些浪费的时间节省回来。

二、无长度限制

最大匹配法的长度限制真是很讨厌的事,我们很难找到词长与效率的之间的平衡。为什么我们需要长度的限制?为什么我们不能设计出任何词长的词(只要词库中存在)都可以分出来?

三、歧义包容

我们相信长度限制的问题总是可以解决的,因为虽然长度限制这个问题很难,但是它是有规律可循的,它是严谨的科学。但是当我们碰到中文歧义时,我知道不管我们怎么努力,它仍然是不可能彻底解决的。因为中文实在太博大精深了,即使有极强的人工智能和机器学习功能,这样的错误仍然是难以避免。既然无法避免?我们为什么不换一个角度去考虑?我们为什么不可以将出现歧义的各种可能性都包含进去,作为分词的参考。例如上述的“有意见分歧”的两种分词方法:

有意/ / 分歧/

/ 意见/ 分歧/

为什么我们不能把这样两种结果都拿来分词呢?毕竟分词的目的是为了搜索,而不是为了教小孩读出。如果把两种分词的可能性都告诉搜索引擎,搜索引擎会很高兴的,因为这下不管是“有意”还是“意见”,它都可以搜到了。这就是我提出来另一个目标:歧义包容。

 

1.2.2 算法的突破口词库

虽然我们的目标已经确定下来了,但是要想出一个更好的算法却是非常难的事。毕竟算法需要的是灵感与突发奇想,这与系统的架构设计和面向对象的设计与编者编码刚好是相反的,象设计模式或重构这样的东西我们需要的实践、总结、再实践。而算法需要的却是当我们在山重水复疑无路的时候会换个角度思考。

但是分词算法的突破口在哪里呢?我们必须要有一个词库,我们必须将全文中的词与词库去匹配,这一切都是不可避免的。

真正要改善是就是我们的匹配过程,我们要减少匹配过程中的浪费,我们要解决匹配中的词长限制。但是我们有什么办法呢?每次的匹配我们必须要去词库中查找一次。怎么改善这样的做法?

我们总是把优化的思路定格在更好的匹配算法,更好地处理词条和全文。但是真正束缚我们的却是词库!是基于关系数据库的词库,我们需要的对词库的改造,我们要让我们的词库更适合用于匹配与分词!

这是几十年来关系数据库带给我们的思维:我们查找的词是数据库的某条记录,通过表格与关系代数,我们总能找到这个词。但是正是关系数据库的这种思维束缚着我们,关系数据库让我们的数据结构及关联表达得清楚又简单,并使某些查询的效率变得很高。但是这不适用于中文分词,有的时候退到几十年前流行的数据库模型也许更适合。这就是层次数据库。

我们要做的是将关系数据库的词按字打散,并存放到层次数据库中。以下就是一个示例:

 

红色的字表示树上面的字串是可以单独组成一个词的,例如“感冒”它本身是词库里可以找到的词,所有红色的表示的是终止符。而黄色则表示树上面的字串是无法单独成词的,例如“感冒解”是不存在的词。

真的很奇妙,词库经过这样的改装后,所有的匹配的思维都变掉了。任何一个句子都会打散成单字去与树状结构的单字去匹配,词的长度变成了树的高度,每一次的匹配变成了树的遍历,而这种遍历的效率竟然都是线性的!

 

1.2.3 中文分词算法设计

有了以上的中文词库后,我们分词算法设计就水到渠成的。首先我们来看一下分词的步骤:

1)首先将要分的全文按标点符号打散成一个一个的句子。这算是预处理的一个步骤,目的是让我们处理的句子短,效率更高。毕竟中间有标点符号的词是不存在的。(注:真正实现时我们是基于luceneSimpleAnalyzer来做的,因为SimpleAnalyzer本身就是为了将全文打散成句子,因此我们没必要耗费体力去实现这一步)。

2)我们开始将要处理的句子在树状结构中遍历,如果找到匹配的就继续,如果遇到红色的终止符,我们就发现这个词是一个完整的词了,这样我们就可以把这个词作为一个一个分词了。

3)从分词后的下一字开始继续做步2这样的遍历,如此循环往复就将词分完了。

可以看到,我们字符匹配效率几乎是线性的!我们所要做的只是取出每一个字去树上找到相应的匹配,每次的匹配代价都是O(1)(如果词库用Hash表的话),这样匹配下来的时间复杂度就是字符串本身的长度!对于一个长度为n的字符串来说,它的分词复杂度是O(n)。而最大匹配的平均复杂度是O(n2)

当然我们这里没有考虑歧义包容与分支处理等情况,但即使加上这些我们复杂度仍然是有限的。

 

1.2.4 中文分词算法的实现细节

一、建立词库

有了改装词库的基本思想后,建立词库的步骤变得很简单,但是仍然会有好多的细节需要注意。

首先是词库的保存格式。现在最常用的保存数据的方式当然是关系数据库,其次是文件系统中的二进制文件。显然关系数据库对于我们并不适用,而自定义的二进制文件则实现起来比较困难,而且读写的效率也会有问题。因为我们想到了最简单的方法是利用javaserialization的功能,把整个内存中的树状结构直接序列化成磁盘的文本文件是最方便的!而且读写的效率也会相当的高。

第二个问题是树的父子节点的导航。我们的树并不是一颗二叉树,父亲的子节点会有好多!尤其是第一层,我们会把词库中所有的首字都取出来作为根节点的子节点,这意味着如果首字有4000个的话,根节点就有4000个儿子。当然随着树层数的增多,节点的儿子数也会减少,毕竟以“感冒”开头的词在整个词库也只有四十多个,而以“感冒清”开头的词则只有两三个了。这意味着如果设计得不合理,我们树的匹配遍历过程并不完全是线性的。最坏的查找算法是O(N)N代表儿子数)。当然如果我们建词库时将儿子有序排列,再按照二分查找的方法,则我们的复杂度会减O(lgN),这样的复杂度已经可以接受了。但是还有更简单又更快的存储方式,为什么不使用呢?那就是HashMap,毕竟在HashMap里查找东西时它的效率几乎是线性的,而且实现起来要比二分查询简单得多。当然用HashMap要付出存储空间变大的代价,但这样的代价来换取速度与简单性也是的。

第三个问题是找到有终结符的字后,我们必须要将它建成一个完整的词。这时我们必须能从字个往上回溯,直到找到根结点。因此我们在每个节点里都保存了父节点的指针

 

有了以上的设计思想,我们就动手建立了我们的词库,词库的来源是中医药数据库的词汇表,因为我们应用一直是围绕中医药的。我们找到了两个最重要的表,这两个表几乎包含了中医药的全部词库:

一体化语言系统词库 92112个词

疾病大全、症状、证候 20879个词

最后生成的词库是java serialization的一个文件,文件的大小是16M 。当然这跟我们采用HashMap存放父子关联有关,也跟java的对象所占空间有关,虽然将词库按这种方式存放实际上也对词库进行了压缩(以“感”开头的字有数十个,关系数据库里就要保存数十个,但我们在词库只保存了一个“感”)。但文件仍然偏大,因此用oracle将这两个表导出后生成的文件大小4M。不过这个大小仍然是可以接受的,毕竟效率才是关键。

 

二、 分词查询

虽然刚才对分词算法进行了描述,但实际上实现的时候我们还会碰到很多问题。

1、分支处理。

这是分词算法时歧义包容所必然碰到的问题。为了歧义包容,我们采用了与最大分词法完全不同的理念,我们的理念是将词库中存在词全部收入囊中!而且会发生重叠。例如“感冒解毒胶囊”,由于词库里存在“感冒”、“解毒”和“感冒解毒胶囊”这三个词,因此在分词的时候,我们会分别分出这三个词,这样用户无论输入“感冒”、“解毒”或“感冒解毒胶囊”搜索引擎都会找到相应的结果。

因此当遇到分支时,我们会分解成两条路线!例如当我们匹配到“感冒”的“冒”时,我们会发现一个终止符,代表“感冒”是一个完整的字,将它收录到分词中。接下来我们会分成两支,一支是继续往下走,匹配树的下一层,因为“冒”不是树的叶子,往下走可能会碰到更大的匹配词,例如“感冒解毒胶囊”。而另一支则从根开始,直接用“解”去匹配树的第一层节点,最后发现了“解毒”也是其中的一个词。

2、动态规划法

分支虽然使我们可以消除很多的歧义,但是显然它会带来副作用:导致分词的复杂度变大。如果一个句子很长时,分词的变化也许会呈指数级的增长,从一开始的两个分支变成四个、八个甚至更多。我们会发现很多句子虽然会有很多分支,但是这些分支又经常会汇聚到一个点,变成一个分支。例如:“感冒解毒胶囊可以治感冒”,我们在分词的时候可能会出现“感冒”,“解毒”,“感冒解毒”,“感冒解毒胶囊”等多个分支,但是当我们到达“囊”这个点的时候,所有的分支又会汇集到一起,因为大家接下来要处理的都是“可以治感冒”这个字符串。如果有办法让我们在汇聚以后只处理一个分支,那么算法的时间复杂度就不会象原来想象的那么坏。

而这刚好是动态规划法发挥威力的时候,动态规划要解决的问题是Overlapping sub-problem。它的处理方法就是将所有的子问题记录在公有的变量里(这里指的是类变量,它相对于某个method来说是公有变量,而不是真的全局变量)。当我遇到的子问题已经被处理过一次了,就直接跳过。这样节约的结果可以使算法复杂度得到质的改变,当然由于中文的变化多端,我们无法精确估计使用动态规划法后算法复杂度得到了多大的提高。

实际上的动态规划法的实现起来比说起来反而简单,我们只是简单地放了一个HashSet来存放已经分词过的位置:

 

然后判断的函数也相当的简单:

 

最后在分词的递归函数中加入这一句判断:

 

当这个位置已经被处理过了就直接返回了。

 

3、词库预load

在使用基于词库的方法时,我们必须要面临的一个问题是:必要将词库读到内存中,而这通常会耗费很长的时间,幸运的是这样的工作我们只需要做一次,当我们将词库load进来以后,所有的工作都会在内存中进行,分词的速度会得到极速提升。我们选择的词库预load时机是我们第一次进行分词时,这相当于lazy load,只有用到的时候我们才去初始化

 

讲完算法,我们来看看分词部分的实现代码,实际上这部分的内容实现起来远比想中简单。在处理的过程中,我们对给每个句子(实际是lucene里用SimpleAnalyzer分词后的一个个Token)都新建一个ChineseSplitter,这是更加面向对象的做法,使我们处理起来更加方便简洁,因为我们会发现如果用一个SingletonChineseSplitter时,它的变量无法共享会导致整个Splitter里的递归方法跟上一堆的参数,容易出错,而且无法调试。代码如下

 

代码简单明了,只是做了词库预load的工作后就将实际的分词工作交给ChineseSplitter

ChineseSplitter的核心功能实际上将句子中词典中能找到的词放到一个队列中,这中队列里提供了分词以后的所有词的信息:

 

下面是分词的核心算法:

 

它是一个递归的过程,初始时我们调用的参数里pos0,这样它就会一级一级递归下去并将所有可能的分词放入到tokenQueue里。

 

1.2.5 中文分词的实验结果

1、实验1——短文分词

在第一个实验中我们选了一篇2000字的文章(是关于中医药的专业论文)。然后用三种Analyzer对它进行处理,以下是实验结果:

Analyzer

分词算法

耗时

SimpleAnalyzer

将文章按标点符号隔开成句子

47ms

StandardAnalyzer

将文章的中文字分成一个一个的单

250ms

ChineseAnalyzer

我们的分词算法

词库没preload: 13359ms , 词库preload: 63ms

我们没有找到最大匹配法分词可用的开源代码,因此只能用SimpleAnalyzerStandardAnalyzer与之比较。这两种算法事实上是根本没有去查词库的,因此也不会按任何语义去分词,SimpleAnalyzer只是简单地将文章按标点符号隔开成句子,而StandardAnalyzer则只是简单地将文章的中文字分成一个一个的单字。结果确实让人惊讶,当词库preload以后,我们的分词速度竟然远超不需要查任何词库的StandardAnalyzer算法!

 

2、实验2——建索引

这是将分词算法应用到我们的索引系统后的效果比较,我们的数据源是来自中医药数据库的几十张表,一共有九十万条记录:

Analyzer

分词算法

耗时

StandardAnalyzer

将文章的中文字分成一个一个的单

35分钟

ChineseAnalyzer

我们的分词算法

31分钟

由于建索引时数据库的查询操作会耗费很多的时间,因此两者的差别不是太明显,但结果至少说明了我们的分词效率确实是很高。

 

文章分类: java
前篇(06-01-13): 评Java Development with Ant
后篇(06-02-20): 发布我的中文分词程序源代码--DartSplitter


最新回复(16件)

主题/内容作者/日时
Re: 我自己设计的中文分词算法

我经常访问你的blog,每次都是有新的收获,非常感谢!

小天蝎
06-02-12 11:24
Re: 我自己设计的中文分词算法

你好,有没有时间聊聊技术和将来的工作.
有兴趣的话,欢迎联系我 wuym@yahoo.com

关注
06-02-13 21:25
Re: 我自己设计的中文分词算法

非常不错,很受启发,我们是做比较购物搜索的,http://www.sobigo.com 希望有机会交流 yuanchun88#Gmail.com

Alex
06-02-14 09:41
Re: 我自己设计的中文分词算法

我论文要研究的是基于主题的搜索引擎研究,你的想法对我有很大的帮助,Lucene是一个很好的开源项目,不知道你对他的了解有多深。
有时间向你多探讨吧
msn:fnyyw@msn.com
mail:fnyyw@mail.ccut.edu.cn

小薇
06-02-14 12:38
Re: 我自己设计的中文分词算法

我的回复.http://info.blog.ccidnet.com/blog/ccid/do_showone/tid_20857.html

宁檬
06-02-14 13:51
Re: 我自己设计的中文分词算法

这篇文章里主要给出了设计的思想,具体实现的代码因为现在和项目绑在一起,过几天有空的我会把它整理出来作为一个开源项目的一部分。
确实我现在设计的东西只用在中医药搜索,现在的词库仍然是专业词库,因此在使用通用词库对通用领域的效果的确需要验证。
但设计的思想是通用的,我相信这种思想对任何领域的分词都是适用的。只是具体的细节上会有差别(例如词库太大时可能要lazy load,要不然所有词都load到内存中电脑就直接暴掉了)。我相信不管在哪个领域它都会比最大匹配法好。
另外,宁蒙说得没错,词库质量确实对搜索效果影响很大。
其实我对搜索领域的研究只是业余玩一下,我更喜欢做面向对象设计与J2EE架构设计,所以对这方面还是蛮孤陋寡闻的,也许别人早已有了更好的算法了,欢迎大家批评指正。

xiecc
06-02-14 23:03
Re: 我自己设计的中文分词算法

我以前的分词程序是通过语言模型,这样的话可以减少歧义。只要语言模型够大,再加上名称识别,基本上可以满足要求。但是体积比较大,我的3-gram语言模型有100M。

一凡
06-02-15 03:47
Re: 我自己设计的中文分词算法

非常不错啊,大受启发。
上面字库是用HashMap保存的,请教一下:HashMap的key和value中存放的数据结构是什么样的?
同时期待着你的代码啊,呵呵,其实关键部分可以先贴上去,这样读起来会比较好理解。

jeek
06-02-15 13:21
Re: 我自己设计的中文分词算法

把分词的核心代码补进去了,其实思想远比代码重要,有了想法后代码真的没几行,任何一个人都可以自己写个实现。
HashMap的key就是那个字的String,value里放到的是一个对象,除了字的String之外还有指向的儿子、父亲和一些与这个对象密切相关方法(如取到这个字向上组成的整个词)。这是面向对象的基本思想:要将数据与相关的操作绑定在一起。

xiecc
06-02-15 14:01
Re: 我自己设计的中文分词算法

对,代码只是补充说明思想
已经基本上清楚了,谢谢

jeek
06-02-15 14:55
Re: 我自己设计的中文分词算法

不错,分析很在点,你可以参考一下下面的论文《汉语自动分词典机制的实验研究》:
www.nlp.org.cn/docs/download.php?doc_id=1117

你的思想基本上和文中所说的逐字二分法类似,只是索引机制不一样。如果词库较大,直接使用HashMap在内存占用是个问题,Lazy Load实现是个问题。所以应该还是如论文中所说,先按字排序,然后进行二分查找。

希望有机会交流:
adamXiong@gmail.com

AdamBear
06-02-15 23:58
Re: 我自己设计的中文分词算法

另外结果的确让我惊讶:

当词库preload以后,我们的分词速度竟然远超不需要查任何词库的StandardAnalyzer算法!

怎么可能呢?有没有性能上的分析

AdamBear
06-02-16 00:28
Re: 我自己设计的中文分词算法

嗯,谢谢,不错的论文,好象跟我的思想很象。
性能分析的结果确实让我很惊讶,我还以为是我的程序在第一次分词后的结果没清掉了,后来就改成词库preload时的分词文章与真正分词的文章不一样了,结果还是一样好。

xiecc
06-02-16 09:43
Re: 我自己设计的中文分词算法

另外,一般的几十万条词库的load到内存里还是可以接受的,毕竟都是在服务器上用的(当然要改JVM的配置啦,否则会OutofMemoryException的),所以一般情况下可能也够用了.
词库的lazy load是可以做的,我当时这样实现过,就是按首字来把词库分类,每次只load一部分,只要根指向的儿子们用WeakHashMap(或其它可垃圾收集的数据库结构)存,如果出现词库占的空间太大时,JVM就会收集掉暂时不要的那部分词库

xiecc
06-02-16 09:51
Re: 我自己设计的中文分词算法

Hoho,写得这么详细。。。赶紧把你的搜索包路径给我改好!!!搜索这部分还没人接呢。。。

BlueGuitar
06-02-20 20:28
Re: 我自己设计的中文分词算法

写得不错, 有钻研精神
恩, 从学术角度上提几点意见.
1. 这篇文章始于对提高搜索引擎准确率的追求, 却终于对数据结构的改进以提高处理速度-跑题啦.

2. 文中所指最大匹配法的问题点, 应参考相关研究以增加说服力

3. 应引用层次数据库的参考文献, 以说明使用层次型数据库要比关系型数据库更加优越. 最好有相关研究作为佐证. 否则, 便无法排除由于研究者对关系型数据库的掌握程度和使用技巧而引起的效率低下的问题.

4. 上面第3点引出的问题, 恰恰说明了本文跑题的原因. 作者不清楚使用层次数据库的理由.

不好意思, 泼了些冷水.
偶对作者的研究精神是非常赞赏的, 只要略做改进, 本文将是一篇十分优秀的论文.

肥猫猫
06-02-27 11:27

 

前几天因为好久没发blog了,就拿我毕设中的一段算法凑数,没想到引起很多人的兴趣。因此就把我的分词算法单独拎出来做了一个项目叫作DartSplitter。暂时把分词算法的名称叫做树状词库分词法。


刚刚统计了一下源代码,一共也就950多行代码,加上测试用例共1200行代码。看来确实还是想法比实现重要。说明如下:

1、由于不能用原来的专业词库,因此我特地去网上找了个Access的词库,一共有一万条记录左右,还有很多单字,因此分词的效果不会太理想。不过我这里只是为了演示一下功能,幸好从数据库转成我现的词库并不复杂,我的演示程序里提供了例子,后面还会有说明。而且,真正好的词库可能还要加入机器学习等功能,真正全面的分词可能还需要将基于词库的分词与无意义的分词结合,不过这些功能都不是那么简单的啦。

2、由于测试对词库的依赖性太强了,因此我的测试用例里没有用太多assert,只是简单地log一下结果。而且考虑到大家用TestNG的还比较少,因此我把测试用例都改成JUnit了。测试用例与外部资源的依赖一直是困扰着我的问题,不知大家有何良策?

3、由于我现在写程序已经对Spring产生依赖症了,因此虽然我希望我程序依赖的包越少越好,但还是用Spring,这样的好处是所有接口与关联都是可配置的。因此如果要替换掉某一部分实现也会比较简单,例如从关系数据库的词库取词的接口肯定是要重写,只要配置文件里修改一行就可以了,这个在后面说明。

4、为了方便大家使用我特意写了示splitterTest,里面提供了两个main,一个是建词库(DictSerializationMain),另一个是对一篇文章的analysis(AnalysisTest),用了SimpleAnalyzerStandardAnalyzer和我的TreeDictAnalyzer进行对比。

5、系统在设计的时候就是与lucene紧耦合,分词的单位也是lucene中token。这是通用性与效率平衡的结果,最后我选择了效率,而且毕竟lucene是大家用得最多的全文检索引擎包。

 

下面讲一下使用说明:

1、如果不需要修改源代码的话,只要下载dartsplitter-0.3.jar就可以了。

2、需要在新建项目的sourceetc下放入以下配置文件(示例项目里都有,只要copy就行了):dartSplitter.properties, dictJdbc.properties, dartSplitterContext.xml

dartSplitter.properties的大概内容如下:

splitter.dictDir=f:/WebDict (指定了词典的路径,主要用于lazy load,目前还没用到)

splitter.dictFile=f:/WebDict/common.dict (词典的文件名,只要将词典文件与配置对就行了)

splitter.maxWordLength=20 (放入词库的最大词长,load之后相当于树的高度)

演示的字典文件名位于dict文件夹下:common.dict commonDict.mdb则是当时找来的access文件。

 

dictJdbc.properties的内容如下:

dict.jdbc.driverClassName=sun.jdbc.odbc.JdbcOdbcDriver

dict.jdbc.url=jdbc:odbc:commonDict

dict.jdbc.username=

dict.jdbc.password=

其实就是词库文件对应的Jdbc链接啦。

 

dartSplitterContext.xmlSpring的配置文件,除了建词库时访问关系数据库的DAO配置要改动外,其它都不要去动。

 

3、建自己的词库

A、自己implements一下DictDAO接口,提供自己的实现,DictDAO的接口定义很简单,只要实现两个方法就行了,可参考CommonDictDAO的实现:

public interface DictDAO {

/**

* @param strPrefix 词的首个字

* @return 以这个字为首字的词对象(@see cn.edu.zju.dartsplitter.data.DictValue)的列表

*/

public List<DictValue> getDictValues(String strPrefix);

/**

* @return 词库中所有词的首字列表

*/

public List<String> getAllPrefixes();

}

B、修改dartSplitterContext.xml的配置:

<bean id="dictTree" class="cn.edu.zju.dartsplitter.impl.DictTreeImpl">

<property name="rebuild"><value>false</value></property>

<property name="maxWordLength"><value>${splitter.maxWordLength}</value></property>

<property name="fileName"><value>${splitter.dictFile}</value></property>

<property name="dictDAOList">

<list>

<ref local="commonDictDAO "/>

</list>

</property>

</bean>

只要在以下这段里将替换commonDictDAO为自己的DAO就行了,也可以加入新的DAO,因为我们考虑到有多个数据来源的情况,因此可以把多个DAO实现一起放入List里。

C、执行一下包里或者示例程序里的DictSerializationMainOK

 

最后感谢要一下blueGuitar,如果没有当时与他讨论时的灵感,就不会有现在的算法。还要谢谢车东,是他引导我使我对中文分词感兴趣的。

 

以下是项目的地址: http://ccnt.zju.edu.cn/projects/DartSplitter

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值