`
lmx800
  • 浏览: 29856 次
  • 来自: ...
文章分类
社区版块
存档分类
最新评论

搜索引擎之中文分词实现(java版)

阅读更多
分词技术在搜索引擎,信息提取,机器翻译等领域的重要地位与应用就不敖述了。步入正题:)
 
<!--[if !supportLists]-->一、 <!--[endif]-->项目概述
 
本切分系统的统计语料是用我们学校自己开放的那部分,大家可以在 这里 下载,中文字符约184万,当然这都是已切分好了的,可以用此建立一个比较小的语料库。本系统我主要分下面四个步骤完成:
<!--[if !supportLists]-->1、 <!--[endif]-->语料预处理
<!--[if !supportLists]-->2、 <!--[endif]-->建立 2-gram(统计二元模型)
<!--[if !supportLists]-->3、 <!--[endif]-->实现全切分
<!--[if !supportLists]-->4、 <!--[endif]-->评估测试
下面我分别对这四个方面一一道来。
 
<!--[if !supportLists]-->1、 <!--[endif]-->语料预处理
 
 下载的已切分的语料都是形如“19980131-04-012-001/m 现实/n 的/u 顿悟/vn 却/d 被/p 描/v 出/v 形/Ng 来/v 。/w ” ,有的前面还保留了日期编号,因为这些切分语料的来源是人民日报。预处理主要是按标点符号分句,句子简单定义为( 。?! : ;)这五种标点符号结尾的词串,句子首尾分别添加<BOS>和<EOS>这两个表示句子开始和结束的标记,这在2-gram建模时要用的,后面会提到。处理过程中,忽略词类信息和前面的日期信息,因为我这个切分系统不考虑词类标注。如前面这句预处理后应该为下面形式 “<BOS>现实 的 顿悟 却 被 描 出 形 来 。<EOS>” ,当然切分词之间你可以用你想用的符号标记,而不必是空格。因为考虑到所有的英文字符和数字的ASCII,我用了下面方法实现之:
       out ; //输出流      
in; //输入流
StringBuffer s1 = new StringBuffer();     //缓冲
char a = in.read();
while (a != -1)             //判断是否已到流的终点
{                 
      if ((a == '。' || a == '?' || a == '!' || a == ':' || a == ';' )) //一句结束
{            
             String s2 = new String(s1);
             out.write("<BOS>");                //在句子前加 <BOS>
             out.write(s2);
             out.write("<EOS>");                    //在句子末尾加 <EOS>
             out.write('/n');        //换行
             s1 = new StringBuffer();
      }
else if ( a == '/')                         
s1 = s1.append((char)32);   //分词位置空格
      else if (a > 256 )                         
             s1 = s1.append((char)a);      
a = in.read();                                                        
}
out.close(); 
in.close();
 
<!--[if !supportLists]-->2、 <!--[endif]-->建立 2-gram模型(统计二元模型)
 
   在这里首先简单介绍一下n-gram模型和2-gram模型。
根据语言样本估计出的概率分布P就称为语言L的语言模型。对给定的句子s = w1w2…wn,(数字,n,i都为下标,wi为句子s的一个词)。由链式规则(Chain rule),P(s) = p(w1)p(w2|w1)p(w3|w1w2)……p(wn|w1w2w3w(n-1)) , 对p(wi|w1w2…w(i-1))而言,(w1w2…w(i-1))即为wi的历史。考虑前面n-1个词构成历史的模型即为n-gram模型。 n越大,提供的语境信息也越多,但代价就越大,且需训练语料多;n较小时,提供的信息比较少,但计算代价小,且无需太多训练语料。
    令c(w1,…,wi)表示词串w1,w2…wi在训练语料中出现的次数,则由最大似然估计, P(wn|w1,…,w(n-1)) = c(w1,…,wn) / c(w1,…,w(n-1)). 同理,则2-gram为 P(wn|w(n-1)) = c(w(n-1),wn) / c(w(n-1)).
    若想了解更多相关知识,大家找相关资料看看,随便把大学时的那本概率与统计课本拿出来翻翻,数学真是一个好东东:)
    回归项目:) 训练语料一共有5万多个不同的词。建立2-gram统计模型时不断要把每个词在训练语料中出现频率统计出来,还要把每个词及其后面的那个词组成的2-gram在训练语料中出现频率统计出来。因为在切分时会频繁的在建立的2-gram模型中查找相关的数据,所有,存储这个2-gram模型数据的数据结构一定要能提供高效的查找。故选择hash表,它能提供常数时间的查找。Java类库里提供了HashMap类,基于数据两还不是非常大,故可直接拿来用。在存储时,每一个key值对应一个在训练语料中出现过的词语,而每一个key值对应的value值又是一个HashMap。暂且称为子hashmap.这个结构有点类似文件结构里的二级索引。 其相关代码如下:
 
    怎么在预处理文件里把词分别读出来就不罗嗦了,方法:每读入一行,按空格分成String数组,用个正则表达式匹配下即能得到。
 
//此方法传入的两个词组成一个2-gramprewd为前一个词,currwd为紧随其后的词
public static void add(String prewd , String currwd){              
                  String key = prewd;
                     String curr = currwd;
                     boolean bb = HMap.containsKey(key);         //Hmap是一个已存在的HashMap,用来存储2-gram统计模型。在这里判断 preword 是否在map
                     if (bb == false) {           //map 中无,则添加
                            HashMap hm = new HashMap();          //首先,新构造一个MAP
                            hm.put(key , new Integer(1));             //存储KEY 的频率                                                   hm.put(curr , new Integer(1));            //存储KEY 后面紧接着的那个词频率
                            HMap.put(key,hm);              //KEY 和对应的MAP 放入MAP
                     }
                     else         //map 中含有该词            
       {
                            HashMap temp = (HashMap)HMap.get(key);              //返回KEY 所对应的MAP ,进行值的修改
                            int count = ((Integer)temp.get(key)).intValue() + 1;           //map 中将key 次数加 1
                            temp.put(key , new Integer(count));                                
 
                            if (temp.containsKey(curr))                 //判断map 中是否含有该词
                            {
                                   int value = ((Integer)temp.get(curr)).intValue() + 1;                                                                   temp.put(curr , new Integer(value));   
                            }           
                            else
                                   temp.put(curr, new Integer(1));                  //若无,则将其存入子map  
                            HMap.put(key , temp);                 //map 修改完毕,将其重新放入map
                     }           
       }
}
因为语言中的大部分词属于低频词,所以稀疏问题肯定存在。而MLE(最大似然估计)给在训练语料中没有出现的2-gram的赋给0概率。所以还得对2-gram模型进行数据平滑,以期得到更好的参数。目前平滑技术比较多,如Add-one,Add-delta,Witten-Bellheld-out留存平滑等。本系统主要采用了Add-deltaheld-out两中平滑方式,下面就Add-delta平滑技术为例,对2-gram进行平滑。对2-gram模型,其平滑公式为:
P(wn|w(n-1)) = [c(w(n-1),wn) + delta ] / ( N + delta * V) ,这里去delta0.5
其中,N:训练语料中所有的2-gram的数量
       V:所有的可能的不同的2-gram的数量
平滑思路1.产生主hashmap的迭代器iterator,依次读key;
              2.对每一个key,又读出其value,即一个子hashmap;
              3.然后根据平滑公式对子map里的值进行计算修改
算法框架:
              Iterator it = hashmap.keySet().iterator();
              While(it.hasNext())
              {
                     key = it.next();
                     hashmap = (HashMap)hashmap.get(key);              
Iterator itr = hashmap.keySet().iterator();
While(itr.hasNext())
{
       根据平滑公式依次计算修改
}
 
}
注意问题:1.因为计算得出的概率值一般都比较小,为了防止出现下溢,可对其取对数,再取反。
              2.每一个主key所对应的所有没有出现过的,即频率为零的2-gram,统一用一个键值对存储在相应的子hashmap里即可。
       完毕,对象序列化。使用该系统时,lazy load将其载入内存,然后可让其一直存活在内存,这会大大加快速度。
       到此,2-gram模型建立完毕。
 
<!--[if !supportLists]-->            3、 <!--[endif]-->全切分实现
 
切词一般有最大匹配法(MMRMM),基于规则的方法,基于统计的方法。关于前两者就不罗嗦了。所谓全切分就是要根据字典得到所以可能的切分形式。歧义识别的方法主要有:基于规则的方法和基于统计的方法。这里当然是采用基于2-gram统计模型的方法了:)为了避免切分后再进行歧义分析的时间浪费。并且这里采用边切分边评价的方法,即在切分进行的同时进行评价的方法。  
对一个句子进行全切分的结果,即所以可能的组合,可以形成一棵解空间树
于是,可用回溯法搜索最优解
若将所有的全切分组合先搜索出来,然后再根据2-gram选择最佳,显然会很浪费时间,因为过程中可能存在很多的重复搜索,而回溯搜索的时间复杂度为指数时间
所以,在搜索过程中要结合剪枝,避免无效搜索,可很大提高效率
采用树的深度优先法则。可找到最优解
具体算法如下:
Stack.push(BOS) //树节点
       while stack不为空
              x=stack.pop()
              pos=xPos w = x.w oldvalue= x.value preword=x.preword
              if m>O then    //m为首词串的个数
                     forj=1 to m do
                        FWjfwc的第j个元素l
                        if length(w+FWj) =length(c)且概率最大 then output w+FWjl且设置最新的句子最大概率值
                        else
                           posl=pos+length(FWj)l
                           if probability(w+FWjposlnewsate)>maxValue(pos1)
                            stack.push(x)
                           endif
                     endfor
              endif
       endwhile
end
在算法实现过程中需要考虑一些诸如树节点保存,首词串处理等问题。
 
4.评估测试
环境:windows XP2, AMD Athlon 1800+, Memory 768mJDK1.5
Delta平滑:随着delta的取值变小,准确率上升,0.50.010.0001
召回率: 0.9756     0.9826         0.9928
准确率: 0.9638     0.9710        0.9883
 
留存平滑
召回率:        0.9946
准确率:        0.9902
一般情况下,留存平滑应该还是比delta平滑更好
 
所有建模过程及平滑过程在1分钟内都可完成。
切分时间与效率:
<!--[if !supportLists]-->n       <!--[endif]-->测试语料,17455字符,(中文17287),平均句长 41个字,时间340ms, 平均切分速度:5.1 /S
<!--[if !supportLists]-->n       <!--[endif]-->20.5万测试语料(取自笑傲江湖),预处理后 17.46,时间 110 MS,句子文本行数目 24945,平均句长 7  切分时间 1300MS 平均13.46 /  
<!--[if !supportLists]-->n       <!--[endif]-->20.5万测试语料(取自笑傲江湖),不预处理,平均句长 239 ,切分时间40S平均 5000/
回溯算法是时间开销为O(N!),所以在切分过程中句子长度直接决定了切分的速度,因为句子越长词越多
经过预处理,句子短,平均句长 7, 回溯短,故速度要快很多。
 
到此,该系统基本完成,告一段落。感觉写的挺乱的呵呵
现在在做另一个作业,做个简单搜索引擎,准备把这个东东结合在搜索引擎里面,实现切分功能:)
分享到:
评论

相关推荐

    JAVA实现的中文分词程序

    Java实现的中文分词程序是一种基于Java编程语言的文本处理工具,主要应用于处理中文文本,将其拆分成有意义的词汇单元,这一过程被称为分词。在自然语言处理(NLP)领域,分词是预处理阶段的关键步骤,为后续的文本...

    java版本结巴分词

    Java版本的结巴分词是基于Java实现的中文分词工具,它在处理中文文本时具有高效、灵活和易用的特点。结巴分词(Jieba)最初是由Python开发的,但为了满足Java开发者的需求,也有了Java版本。本文将深入探讨Java版...

    java实现中文分词simhash算法

    总的来说,Java实现的中文分词SimHash算法结合了Sanford分词库的分词功能和SimHash的相似度检测,为中文文本的相似度分析提供了一种高效且准确的方法。在实际应用中,这种技术广泛应用于搜索引擎的去重、推荐系统、...

    jieba分词java版项目

    jieba分词是中文处理领域的一个著名...综上所述,jieba分词Java版项目提供了一种在Java环境下进行中文分词的解决方案,通过Eclipse导入并运行测试,你可以快速了解和使用这个工具,为你的中文信息处理任务带来便利。

    搜索引擎-中文分词.zip

    综上所述,"搜索引擎-中文分词.zip"可能包含了一个基本的Java实现的中文分词系统,它依赖于词典进行分词,并且可能提供了扩展词典的机制。对于想要深入理解或改进中文分词技术的人来说,这是一个很好的学习和实践...

    中文分词java源代码

    对于搜索引擎、信息检索、情感分析等领域而言,准确高效的分词是提高系统性能的关键。Java作为一种跨平台、面向对象的编程语言,因其强大的库支持和良好的可维护性,常被用于构建这类复杂的NLP系统。 在这个源代码...

    中文分词技术 源代码 对于搜索引擎爱好者相当有用

    中文分词技术是自然语言处理领域的一个重要环节,尤其对于搜索引擎爱好者来说,掌握这项技术能够极大地提升信息检索的准确性和效率。在这个压缩包中,包含了实现中文分词功能的源代码,非常适合对这一领域感兴趣的人...

    搜索引擎中的分词以及查找的编程心得

    ### 搜索引擎中的分词与查找技术 在探讨搜索引擎中的分词技术和查找机制之前,我们首先需要理解几个基本概念:分词(Tokenization)、词干提取(Stemming)和词形还原(Lemmatization)。这些技术是构建高效、准确...

    IKAnalyzer中文分词器 java

    - **搜索引擎**:IKAnalyzer常用于构建基于Java的全文搜索引擎,如Elasticsearch、Solr等,帮助提升搜索结果的相关性。 - **信息抽取**:在数据挖掘和文本分析中,IKAnalyzer可以帮助提取关键信息,如用户评论的...

    java实现的搜索引擎

    在这个基于Java的搜索引擎项目中,我们主要涉及了几个关键的技术点,包括分词算法、倒排文档以及检索技术。接下来,我将详细介绍这些知识点。 首先,**分词算法**是搜索引擎的基础。在处理文本时,我们需要将连续的...

    java 实现的中文分词算法(代码)

    Java实现的中文分词算法是自然语言处理领域中的重要技术,尤其在文本挖掘、搜索引擎、信息检索等场景中发挥着关键作用。FMM(Fast Mapping Model)和BMM(Bigram Mapping Model)是两种常见的中文分词算法,它们都是...

    结巴分词及其Java、Python、C++的使用示例

    除了基本的分词,jieba还支持自定义词典、全模式、精确模式、搜索引擎模式等多种分词策略,以及关键词提取、词性标注等功能,极大地丰富了中文文本处理的能力。在实际应用中,开发者可以根据具体需求选择合适的模式...

    java版jieba分词统计,含1.02版本jar

    总的来说,Java版的jieba分词库为Java开发者提供了方便的中文分词工具,可以实现基本的分词和频率统计功能,同时也鼓励开发者参考其他成熟系统如Elasticsearch的分词器设计,进一步提升分词效果和应用范围。

    分词程序java源码

    在IT领域,分词是文本处理的一个重要环节,特别是在自然语言处理、搜索引擎和信息检索等领域。Java作为一种跨平台、面向对象的编程语言,被广泛应用于各种复杂系统的开发,包括分词系统。本分词程序是用Java编写的,...

    基于JAVA的源代码搜索引擎架构实现.pdf

    ### 基于JAVA的源代码搜索引擎架构实现 #### 概述 随着互联网技术的快速发展,网络信息资源呈现爆炸性增长。人们越来越依赖互联网来满足信息需求,这使得高效地从海量信息中提取有价值的数据变得至关重要。在此...

    搜索引擎的研究与实现(Java)(含源码)

    本项目专注于搜索引擎的研究与实现,采用Java编程语言进行开发,并包含了完整的源代码,这对于学习和理解搜索引擎的工作原理以及实践开发提供了宝贵的资源。 搜索引擎的核心概念包括以下几个部分: 1. **爬虫...

    JAVA技术实现的搜索引擎(含源码)Java实用源码整理learns

    在本资源中,我们主要关注的是使用JAVA技术实现的搜索引擎。搜索引擎是信息技术领域的一个关键组件,它能够帮助用户快速、准确地找到所需的信息。这里提供的源码是一个实用的学习材料,适用于那些想要深入理解搜索...

    搜索引擎返回结果的分词源码

    在这个“搜索引擎返回结果的分词实现源码”中,我们可以深入理解搜索引擎如何处理用户查询并返回相关的搜索结果。 首先,我们要明白分词的基本原理。在中文中,由于没有明显的空格分隔词,因此分词系统通常会采用字...

    搜索引擎 基于java的搜索引擎

    本项目“基于Java的搜索引擎”提供了一个基本的实现,涵盖了搜索引擎的关键组成部分:爬虫、网页处理、索引构建以及检索功能。这里我们将深入探讨这些概念及其在Java编程语言中的应用。 首先,**爬虫(Spider)**是...

Global site tag (gtag.js) - Google Analytics