您的位置:首页 > 编程语言 > Java开发

自己写中文分词器之(一)_逆向最大匹配算法的实现

2013-07-01 00:39 489 查看
一直都想着自己动手写一写中文分词,但是一直都没有动手。今天终于开始了。从最简单的开始,步步深入。希望自己最后能把分词、词性标注、命名实体识别这几块都完成。

好了,话不多述,进入正题。
分词最简单的思路就是查词典,确实,最开始大家都是这么做的。包括现在都有人这样做。所以分词效果的好坏最重要的是要有一部好词典,及一个好的匹配算法。
第一步:找到好词典。
我分词开始用的是在搜狗实验室弄到一份通词典。一共有15万七千多词;后来又找到了一个词典,有28万词。在分词中用的是28万词的词典,我想简单的应用应该够了。
28万词的词典下载地址:http://www.xunsearch.com/scws/download.php,同时这里也实现了一个php的分词。
第二步:确定好算法。
这里的学习主要参考了52nlp里面的一个博客:
http://www.52nlp.cn/maximum-matching-method-of-chinese-word-segmentation
讲得很详细,还有C++版本的分词实现。
基于博客,我简单地用Java实现了一下:
package com.xh.seg;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.xh.dictionary.DictionaryUtil;
public class Segmentation {
private StringBuilder sb=new StringBuilder();
private List<String> ls=new ArrayList<String>();
private int maxwl;//设置词的最大长度
/*
* @param source 待分词的句子
* @return 分词后的结果,用空格隔开。
* */
public String seg_str(String source){
sb.delete(0, sb.length());
int len;
String word;
maxwl=5;
while((len=source.length())>0){
if(maxwl>len){
maxwl=len;
}
word=source.substring(len-maxwl);
int start=len-maxwl;
boolean find=false;
//从左边不断减字,直到匹配成功或者字的长度为一
while(word.length()>1){
if(DictionaryUtil.match(word)){
sb.insert(0, "\t"+word);find=true;
break;
}
++start;
word=source.substring(start);
}
if(!find){
sb.insert(0,"\t"+ word);
}
source=source.substring(0, start);
}
return sb.toString();
}
/*
* @param source 待分词的句子
* @return 分词后的结果,用list存储。
* */
public List<String> seg_list(String source){
ls.clear();
int len;
String word;
maxwl=5;
while((len=source.length())>0){
if(maxwl>len){
maxwl=len;
}
word=source.substring(len-maxwl);
int start=len-maxwl;
boolean find=false;
while(word.length()>1){
if(DictionaryUtil.match(word)){
ls.add(word);find=true;
break;
}
++start;
word=source.substring(start);
}
if(!find){
ls.add(word);
}
source=source.substring(0, start);
}
Collections.reverse(ls);
return ls;
}

}
程序里面的两个方法是一样的,只是返回结果不一样而已。
词典的加载程序如下:

package com.xh.dictionary;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
public class DictionaryUtil {
static Map<String,Integer> dic=new HashMap<String,Integer>(160000);
static{
//loadDic("dictionary/SougouLabDic_utf8.txt");
loadDic("dictionary/dic_utf8.txt");
}
public static void loadDic(String name){
BufferedReader br=null;
try {
br=new BufferedReader(new InputStreamReader(new FileInputStream(DictionaryUtil.class
.getClassLoader()
.getResource("dictionary/SogouLabDic_utf8.dic")
.getFile()),"utf-8"));

String line;
int end;
while((line=br.readLine())!=null){
end=line.indexOf('\t');
if(end!=-1)
dic.put(line.substring(0, end), 1);
}
} catch (Exception e) {
e.printStackTrace();
}finally{try {br.close();} catch (IOException e) {e.printStackTrace();}}
System.out.println("dic load success,have been load "+dic.size()+" words!");
}

public static boolean match(String word){
if(dic.containsKey(word))
return true;
return false;
}
}
当时考虑了下,感觉用set会更直接,但是set是建立在map的基础上的,所以直接用map的性能应该会高一点,当然只是猜想。
第三步:测试
测试代码如下:
package com.xh.seg;
import org.junit.Test;
public class SegmentationTest {
@Test
public void testSeg_str(){

Segmentation seg=new Segmentation();
String str[]={
"按自己喜欢选择的,质量不错",
"结婚的和尚未结婚的都需要登记",
"他说的确实在理",
"阿拉斯加遭强暴风雪袭击致xx人死亡",
"邓颖超生前使用过的物品 ",
"乒乓球拍卖完了",
"吴江西陵印刷厂"
};
for (String string : str) {
System.out.println(seg.seg_list(string));
}
}
}


分词的结果如下:
dic load success,have been load 284646 words!
[按, 自己, 喜欢, 选择, 的, ,, 质量, 不错]
[结婚, 的, 和, 尚未, 结婚, 的, 都, 需要, 登记]
[他, 说, 的, 确实, 在理]
[阿拉斯加, 遭, 强, 暴风雪, 袭击, 致, x, x, 人, 死亡]
[邓颖超, 生前, 使用, 过, 的, 物品, ]
[乒乓球, 拍卖, 完了]
[吴江, 西陵, 印刷厂]

第四步:总结
除了“乒乓球拍卖完了”这句分错外,其它的都没什么问题。可见,逆向最大匹配算法在词典够完善的情况下,分词的效果还是勉强可以接受的。毕竟是这么简单的代码实现。最后还是感谢提供词典和分词思路的作者。接下来将想学习双数组Trie树结构在分词上的实现。

参考文献:
(分词算法) http://www.52nlp.cn/maximum-matching-method-of-chinese-word-segmentation
(词 典)http://www.xunsearch.com/scws/download.php
(测试数据) http://my.oschina.net/jcseg/blog/135746
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息