结合 ictclass4j 和 KTDictSeg 写自己的分词器----断句(1)

由于开源和对.net的喜爱,以及较好的分词效果,喜欢上了KTDicSeg。由于工作的原因,我不得不使用java,我们选择使用ictclass4j。

一开始就觉得中文分词很难,而自己的水平似乎有限,在这种想法的左右下,从来没有想过要真正的去认认真真的看一个中文分词的源代码,同样是工作的原因,我现在不得不认认真真的去研究下中文分词。首先我先说明下,不要期待我写出太高明的文章,园中有非常多的前辈,就当是一起进步一起学习吧。

在查阅了大量的文档后,初步对中文分词有一个初步认识。

1、首先是将文章进行拆分成句子。

2、将句子根据字典拆分成词。

3、根据字与词,词与词成词的几率,以及词语的使用频度等等因素确定一种或多种分词方案。

4、根据指定的规则或者方式选择一种分词方案。

其中将文章拆分成句子,应该是最简单的一部分。

ictclass4j和KTDicSeg的处理方式有一些不同,但是最终要实现的目标是一样的。

首先说说ictclass4j处理的方式,我感觉这样的处理方式算不上高明。

1、将字符串分割成一个个字的数组。

2、对生成的数组遍历,连接成新的字符串,当遇到“!,"。”等标点符号的时候,将连接成的字符串放入中间结果集。

3、当遍历完成后,那个中间结果集就是我们需要的结果。

而KTDicSeg处理方式来说就显得干脆简介,直接用了正则,非常方便就将文章分割成句。这里我决定使用正则来处理。

下面是我的程序,我使用的是java来写的,说实话java让我很郁闷,java中没有ref的关键词,搞的我很多时候有点束手无策,由于我是从C#转到java来的吧。

首先就是定义正则了 ,这里我直接套用的是KTDicSeg中的正则

private static final String PATTERNS ="[0-9\\d]+\\%|[0-9\\d]{1,2}月|[0-9\\d]{1,2}日|[0-9\\d]{1,4}年|[0-9\\d]{1,4}-[0-9\\d]{1,2}-[0-9\\d]{1,2}|[0-9\\d]+|[^a-zA-Za-zA-Z0-90-9\u4e00-\u9fa5]|[a-zA-Za-zA-Z]+|[\u4e00-\u9fa5]+";

还有点很郁闷就是java中没有@关键字,反斜杠搞的我晕晕乎乎啊

下面的代码来应用这个正则:

 public ArrayList<String> SenSplit()


     {

         
if(Content.length()==0)

         {

             
return null;

         }

         
else

         {

             ArrayList
<String> ArraySen = new ArrayList<String>();

             Pattern p 
= Pattern.compile(PATTERNS,Pattern.MULTILINE);    

             Matcher m 
= p.matcher(Content);

             
while(m.find())

             {        

                
int start = m.start();

                
int end = m.end();

                ArraySen.add(Content.substring(start,end));

             }

             
return ArraySen;

         }

     }

 

而仅仅使用这个函数是不行的,因为会出现类似下面的情况:25.6 会被分成 25/./6 或者 suyuan19@gmail.com 会被分成 suyuan/19/@/gmail/./com 而我们实际想要结果可不是这样的,这就需要我们进行二次处理了。

首先是合并浮点数,合并email,合并英文关键词 比如 C# 等等,本篇文章中先不讨论英文关键词的问题。呵呵我感觉我是在讲解KTDicSeg了,不错,我们就是在模仿他,我甚至会做一些比较。

比如合并浮点数的问题:


                    if (((word[0>= '0' && word[0<= '9'||(word[0>= '' && word[0<= '')) &&
                        ((word[word.Length 
- 1>= '0' && word[word.Length - 1<= '9'||
                         (word[word.Length 
- 1>= '' && word[word.Length - 1<= '')) 
                        )

                    {
                        
//合并浮点数
                        word = MergeFloat(initSeg, i, ref i);
                        mergeOk 
= true;

 

这是KTDicSeg除非此类问题的条件。这里我提出不同的意见 比如文章中出现了 02343.25 之类的数字,那么最终合并的结果是 就是02343.25 而我个人认为这样是不正确的,我对这样的数字采取不处理的方式。同时我使用正则来解决这类问题。


 //浮点数第一个数字不能为0
             Pattern p = Pattern.compile("[^0][0-9]+");
             
if(p.matcher(reSen.get(index)).matches()&&(index+2)<reSen.size())
             {
                 index
= MergeFloat(reSen,index);

                 if (index>=reSen.size())break;


 同样合并的函数也是这样处理的:

 /**
      * 合并浮点数
      * 
@param arr 
      * 
@param index
      
*/
     
public int MergeFloat(ArrayList<String> arr,int index)
     {
         
//浮点数小数点后面允许以0开头
         Pattern p = Pattern.compile("[0-9]+");
         
if(arr.get(index+1).equals(".")&&p.matcher(arr.get(index+2)).matches())
         {
            
result.add(new Sentence(arr.get(index)+arr.get(index+1)+arr.get(index+2),false));
             
return index+3;
         }
         
else
         {
             
return index;
         }

     }

下面来讨论合并email的函数,中间也大量的用到了正则:

由于email 必须是以字面开头 不能以数字或者_开通,但是email可以有数字或者是下划线 同时@符合后面可以是两段 如:126.com 也可以是三段,比如:126.com.cn 应该也是合法的.甚至是多段:

先看看我们的调用方法:

                 p=Pattern.compile("[a-z]+");
             
//Email被初步分词后至少是4段长度
             if(p.matcher(reSen.get(index)).matches()&&(index+4)<reSen.size())
             {
                 index
=MergeEmail(reSen,index);
                 
if (index>=reSen.size())break;
             }

调用的时候,触发email合并的条件必须是英文字母开头.

下面是具体的Email合并函数:

/**
      * 合并Email
      * 
@param arr 
      * 
@param index
      
*/
     
public int MergeEmail(ArrayList<String> arr,int index)
     {
         
int c_index = index;
         Pattern p 
= Pattern.compile("[0-9_]+");
         String Email
= arr.get(index); 
         
while(!arr.get(index+1).equals("@")&&p.matcher(arr.get(index+1)).matches()&&(index+1)<arr.size())
         {
             Email
+=arr.get(index+1);
             index
++;
         }
         
if(arr.get(index+1).equals("@"))
         {
             Email
+=arr.get(index+1);
             index
++;
         }
         
while(!arr.get(index+1).equals(".")&&p.matcher(arr.get(index+1)).matches()&&(index+1)<arr.size())
         {
             Email
+=arr.get(index+1);
             index
++;
         }
         
if(arr.get(index+1).equals("."))
         {
             Email
+=arr.get(index+1);
             index
++;
         }
         p 
= Pattern.compile("[a-z]+");
         
while(!arr.get(index+1).equals(".")&&p.matcher(arr.get(index+1)).matches()&&(index+1)<arr.size())
         {
             Email
+=arr.get(index+1);
             index
++;
         }
         
if(arr.get(index+1).equals(".")&&p.matcher(arr.get(index+2)).matches())
         {
             Email
+=arr.get(index+1);
             index
++;
             
while(!arr.get(index+1).equals(".")&&p.matcher(arr.get(index+1)).matches()&&(index+1)<arr.size())
             {
                 Email
+=arr.get(index+1);
                 index
++;
             }
         }
         p
= Pattern.compile("^([a-z0-9A-Z]+[-|\\_|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$");
         
if(p.matcher(Email).matches())
         {
             result.add(
new Sentence(Email,false));
             
return index+1;
         }
         
else
         {
             
return c_index;
         }
     }
     

这个函数主要用正则对各个段进行判断,同时对生成的email的合法性进行判断,当合并的email是合法的才返回,否则回溯到原来的状态.用这样的判断方式就不会出现类型 suyuan/19/@/126/./com的情况了.

来看看我们写的这个分句函数的分句结果:

SentenceSeg SenSeg = new SentenceSeg(text);
            ArrayList
<Sentence> SenArray = SenSeg.getSenSeg();
            
for(Sentence Sen : SenArray)
            {
                System.out.println(Sen.getContent()
+Sen.isSeg());

            }

上面是test的调用方式:

下面是分词结果:

Text: 一开始就觉得中文分词很难,而自己的水平似乎有限,25.632 suyuan@126.com.在这种想法的左右下,从来没有想过要真正的去认认真真的看一个中文分词的源代码,同样是工作的原因,我现在不得不认认真真的去研究下中文分词。首先我先说明下,不要期待我写出太高明的文章,园中有非常多的前辈,就当是一起进步一起学习吧。

一开始就觉得中文分词很难true

,false

而自己的水平似乎有限true

,false

25.632false

 false

suyuan@126.comfalse

.false

.false

.false

.false

在这种想法的左右下true

,false

从来没有想过要真正的去认认真真的看一个中文分词的源代码true

,false

同样是工作的原因true

,false

我现在不得不认认真真的去研究下中文分词true

。false

首先我先说明下true

,false

不要期待我写出太高明的文章true

,false

园中有非常多的前辈true

,false

就当是一起进步一起学习吧true

。false


 基本符合了我预期的效果.那我们先讨论到这里,我会在后续时间里继续学习和改编中文分词.随着讨论的深入会越来越麻烦,希望自己可以最终进行下去.以上方法,未进行非常严格的测试,逻辑上面或许有不完善甚至错误的地方,希望大牛们能够给予指正

posted @ 2008-09-08 11:55  雨中漫步的太阳  阅读(2182)  评论(6编辑  收藏  举报