Jun
22
Posted (xurenlu) in 未分类 on 06月-22-2008

经过摸索,我决定给HyperEstraier加上由Hightman写的scws支持,使之支持中文分词。
实地看了代码之后,发现Hyperestraier的结构划分并不好,这可能决定了他只能是某些Hacker的心血之作,而不适用多人协作开发,大规模推广应用。这是旁话。
既然找到分词默认是使用的estraier.c的est_break_text函数,那我的目标就是改造这个函数,现在这个函数被我改成了:

  1. scws_t seg;
  2. /* Break a sentence of text and extract words. */
  3. void est_break_text(const char *text, CBLIST *list, int norm, int tail){
  4. // hack by renlu.xu
  5.   CBLIST *words;
  6.   const unsigned char *word, *next;
  7.   unsigned char *utext;
  8.   char *tmp;
  9.   int i, j, k, size, cc, wsiz, nsiz, tsiz;
  10.   assert(text && list);
  11. if(seg==NULL)
  12. {
  13. seg=scws_new();
  14. scws_set_charset(seg,"UTF-8");
  15. scws_set_rule(seg,"/home/y/etc/rules.utf8.ini");
  16. int xmode=0;
  17. scws_set_dict(seg,"/home/y/etc/dict.xdb",(xmode & XMODE_DICT_MEM) ? SCWS_XDICT_MEM : SCWS_XDICT_XDB);
  18. if(seg->d == NULL && !(xmode & XMODE_NO_TIME))
  19.         fprintf(stderr, "WARNING: input dict file load failed. /home/y/etc/dict.xdb\n");
  20. }
  21. int fsize;
  22.     fsize=strlen(text);
  23.     scws_send_text(seg,text,fsize);
  24. scws_res_t res,cur;
  25. while ((cur = res = scws_get_result(seg))!= NULL)
  26. {                                                           
  27. while (cur != NULL)                                     
  28. {             
  29. cblistpush(list,text+cur->off,cur->len);
  30. cur = cur->next;                                   
  31. }                                                       
  32. scws_free_result(res);                                 
  33. }
  34. int iter_i;
  35. int wsize=0;
  36. fprintf(stderr,"\nsegmented words:\n");
  37. for(iter_i=0;iter_i<cblistnum(list);iter_i++){
  38. word=CB_LISTVAL2(list,iter_i,wsize);
  39. fprintf(stderr,"%s\t",word);
  40. }
  41. return ;
  42.  }

Ok,其中函数中最后一个for语句是为了调试,看看分词结果如何。
这里用到的scws_send_text等函数是hightman的scws中定义的,因此需要在estraier.c的文件头加上:

  1. #include "scws.h"

并修改Hyperestraier的Makefile:

  1. LIBS = -lqdbm -lz -lm -lc  -lscws

然后make && make install
下面作个测试:

  1. #!/bin/sh
  2. #file:test.sh
  3. rm -rf test_db
  4. estcmd create test_db
  5. find ./ -name "1.txt" -type f |estcmd gather -cl -fm -cm test_db -
  6. estcmd search -vx -max 10 test_db '索引'

根据打印的结果,中文分词已经加入到索引过程中去了!

社区全文检索引擎Hyper Estraier 学习笔记[1]

社区全文检索引擎Hyper Estraier 学习笔记[2]

社区全文检索引擎Hyper Estraier 学习笔记[3]


Tag:
相关文章


     
    May
    01
    Posted (xurenlu) in Ruby on 05月-1-2008

    最近为ruby,ruby on rails的灵活和魔幻而着迷。爽。
    上上周一个同事给我们介绍了自然语言处理的一些知识,觉得很不错。事实上虽然是雅虎公司的一名工程师,但是因为我不是搜索/邮箱/平台研发这些部门,基本上没有接触到比较深一点的东西,也许对于这些部门来说很简单的东西,对于我来说还是很难以理解的。
    课后自己去找了些东西看了看,然后有这样一思路,可以用来构造一个垃圾站。
    基本思路是:
    1.首先我需要一个spider.这个spider由两个部分完成,第一部分是由一个feed管理程序来不停地从网上读rss回来,第二部分是常规spider,跟nutch什么的spider无异.
    2.第二部分是预处理。这一部分包括:纯文本化、去垃圾。
    3.第三部分是运算:分词,关键词提取,提取相关文章。
    4.热点呈现,网站部分。
    具体实现:
    rss 的spider要简单一些,但是根据抓回来的内容,也是很多质量不高的种子。比如新浪新闻的rss基本没有正文,而有些feedsky的种子后面跟着一个小尾巴(广告),需要处理。
    对于没有正文的,很简单,将之平均长度算出来,如果某个种子的平均长度都很短,就将这个种子丢弃。
    对于很多内容不咱的,但是广告链接一大堆的,也有办法:先纯文本化,一个文档就细化为一系列的段。html标签有的去掉了,有的换成空格了.这时将段按空格分组,如果没组长度都很短,即文章内容中若是大量充满着html标签,这样的多半是广告或是垃圾链接。
    比如这一段:

    1. 2006-09-11   charon 写道  我现在对这类动态语言的非本质实现抱很大的怀疑态度。 最近除了这个新闻以外,IronPyton(python的.net版本)1.0也发布了。但看了一下,感觉虽然不是特别差,也是差得可以。也许这是给那些熟悉.net同时又想找一个动态语言的人一个选择? 两个语法相同但是标准库有差异(jruby可能语法上也略有差异),支持库有重大差异的语言,还能算是一个语言吗? 当年不论出于什么原因,Sun对于MS污染java的行为举起了大棒,现在这几个开源社区的动态语言,却纷纷搞出这么些方言来,不好说阿。   IronPython和JRuby可能还是不太一样的。dotnet平台实际上提供了自己统一的dotnet fraemwork类库,所谓不同的编程语言支持,更像是一种语法糖衣而已。但是JRuby其实实现了大部分ruby自己的库,用JRuby并非仅仅用一个ruby语法而已,关键是ruby本身的方便的库和rails框架,至于Java库的支持,只是辅助了。  Sun对JRuby的支持表明了一种态度,这种态度是承认ruby在企业快速开发方面的优势,而对ruby提供更好的支持。而Microsoft支持的IronPython更像是用python语法写C#程序那种感觉,换汤不换药。     charon
    2.                  robbin     浏览: 1653543 次  性别:   来自: 上海      详细资料    搜索本博客        博客分类    全部博客 (119)    杂感 (38)    Java (27)    Ruby (31)    System (5)    JavaEye (19)      我的相册       游乌镇  共 33 张     其他分类    我的收藏  (19)   我的论坛帖子  (4506)   我的精华良好贴  (97)     最近加入圈子    JavaEye沙龙    广州JavaEyer饭局群    Ubuntu For Fun    JavaEye水源    英语学习      链接    javaeye      存档    2008-04  (2)   2008-03  (6)   2008-01  (7)   更多存档...      最新评论    Warp framework - 一个相 ... 
    3. 当然新的东西是可以尝试的,我 ... 吧?
    4.                   -- by dhxyu    总结一下大家对JavaEye网 ... 
    5.                   支持`~~
    6.                   -- by hgz123    关于JavaEye网站未来发展 ... 
    7. javaeye前途无量! 今天认真看了这篇文章,
    8.                   -- by ahkai    SAAS(软件即服务) 离我们 ... 
    9.                   呵呵,在中国,我不看好这种模式,对个人也许有用,但在企业领域,恐怕很难推广。如楼 ...
    10.                   -- by zlxym    以无法为有法,以无限为有 ... 
    11.                   呵呵,楼主还是单纯技术人员的创办思路。缺少营销、商业气味!这道也不是什么坏事,但 ...
    12.                   -- by cljhyjs      评论排行榜    Warp framework - 一个相当有前途的Java轻 ...    《太阳照常升起》观后感    关于JavaEye网站未来发展的思考    从分布式系统的角度看REST    Java已经过时了吗?                            [什么是RSS?]           
    13.           声明:JavaEye文章版权属于作者,受法律保护。没有作者书面许可不得转载。若作者同意转载,必须以超链接形式标明文章原始出处和作者。
    14.           &copy; 2003-2008 JavaEye.com.   All rights reserved. 上海炯耐计算机软件有限公司 [ 沪ICP备05023328号 ]

    这上面有的地方有很多空格,这一段就要去掉。当然可以再对段做分析,将一段中空格多的地方去掉。
    好了经过整理,现在得到了质量稍好一点的文本了。我们进行分析分词。分词,我是在一个叫hightman的开发者的作品的基础上包装了一个ruby库,原文地址是http://www.hightman.cn/bbs/viewthread.php?tid=321.Ruby是一门很好的胶水语言,但是性能较差,因此对性能要求高的部分我选择用c封装ruby模块。
    分好词了,我们再抽出一些实词,比如名词(联想集团),动词(上市,IPO,收购),其实主要思路是跟IT、商业有关的(q我们事先整理一张表,将这些词放进来),将无意义的词(很好,缓慢,深深地…)去掉,对频度,权值排个序,取出前20个,
    记为主题词。
    现在这个也做好了以后我们将文本、主题词入库。再对每一篇文本计算相似文章。这个,我开始是打算就将关键词和文章都存入数据库,然后按照文章之间主题词的重合度来算,但是后来发现文章数越多这个计算就越复杂,并且运算量随文章数的增长而增长,而且增长更快(指数的而不是线性的增长).
    后来想了个办法,我正好也需要一个全文索引,于是利用这个全文检索来完成。全文检索当然不是自己做,现成的有不少系统。用一篇文章的关键词去搜索,搜索结果中的文章就是这篇文章的相似文章。
    至于热点事件挖掘,现在还不知道如何去实现一个demo.可能首先要依赖分词引擎的新词发现比较准确吧。
    搞定以后,有一个问题,我的抓取和分析模块都在我的台式机上完成,但是我需要把数据传输到远程主机上。这点到后面也就用了不到100行代码搞定了(我自己手工写的代码,在40行以内!).因为,一个rails2.0的组件叫activerecord,还有一个叫activeResource,而rails的scaffold可以帮我生成rest协议的相关代码。需要我手工写的代码是:

    1. for item in @items
    2.                     post=Post2s.new
    3.                     post.title=item.title
    4.                     post.md5=item.md5
    5.                     post.body=item.description
    6.                     post.rss_pub_date=item.pubDate
    7.                     post.service_id="blog"
    8.                     ....
    9.                     post.save
    10.             end

    简单明了,谁都看得懂.这是一段运行在我的台式机上的代码,但是他保存的时候,却与远程的主机交互将数据保存在了服务器上,走的是HTTP通道,数据交换格式是HTML/XML.
    Ruby 果然很适合用来替代很多难度不大但是需要大段废话的场合。
    下一篇就写如何搞中文分词的ruby包装。


    Tag:
    相关文章


       
      Jun
      16
      Posted (xurenlu) in Ruby, 未分类 on 06月-16-2007

      中文分词一直是一个看起来似乎比较神秘的东西。记得java中的lucene好像自带了两个分词器。一个是按汉字分,就是一个字分成一个词。比如”我要到饭馆吃饭“,就被分成”我/要/到/饭/馆/吃/饭”.别一个是相邻的两个字分成一个词,分出来的结果是”我要/要到/到饭/饭馆/馆吃/吃饭”.然而这两种虽说在做搜索时建索引什么的操作时也是相当有用的,但是毕竟是一种权宜之计,咱不能一直停留在这个水平上。

      下面是来自Rlucene的一段示例代码,154行,利用sogou的词库搞定了中文分词。代码如下:

      #! /usr/bin/ruby
      require “socket”
      #通过网络得到分词结果
      def segChinese_net(line)
      @conn=TCPSocket.open(”localhost”,1099)
      @conn.write(line)
      line=@conn.read
      return line.split(” “)
      end
      @f= open(”dict/sogou.txt”)
      @datas=@f.read.split(”\n”)
      @f.close

      @f2=open(”dict/firstword.txt”)
      @firstwords=@f2.read.split(”\n”)
      @f2.close
      @maintable=Hash.new()
      @firstwords.each{|x|
      @maintable[x]=[x]
      }
      @datas.each{|x|
      @maintable[x[0].chr+x[1].chr+x[2].chr].push(x)
      }
      def segChinese(line)
      temp=0
      max=(line.length/3)
      words=[]
      while(temp
      pos=temp*3
      str1=line[pos].chr+line[pos+1].chr+line[pos+2].chr
      str12=str1
      str1234=str1
      if(temp<(max-1))
      str2=line[pos+3].chr+line[pos+4].chr+line[pos+5].chr
      str12=line[pos].chr+line[pos+1].chr+line[pos+2].chr+line[pos+3].chr+line[pos+4].chr+line[pos+5].chr
      end
      if(temp<(max-3))
      str1234=line[pos].chr+line[pos+1].chr+line[pos+2].chr+line[pos+3].chr+line[pos+4].chr+line[pos+5].chr+line[pos+6].chr+line[pos+7].chr+line[pos+8].chr+line[pos+9].chr+line[pos+10].chr+line[pos+11].chr
      end
      if(!@maintable[str1].nil?)
      if(!@maintable[str1].index(str1234).nil?)
      temp+=4
      words.push(str1234)
      else
      if(!@maintable[str1].index(str12).nil?)
      temp+=2
      words.push(str12)
      else
      words.push(str1)
      temp+=1
      end
      end
      else
      words.push(str1)
      temp+=1
      end
      end
      return words
      end
      #return an array of the segemention.
      #得到当前句子的分词结果.
      def segment(str)

      chars=Hash.new(0)
      chars["char"]=”~!@#$\%^&*()_+}{[]\\|\”‘:;/?><,. \t\n\b\a”
      chars["num"]=”0123456789″
      chars["alpha"]=”abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”
      bytes=Hash.new(0)
      i=0
      buffer=”"
      #oldtype 表示上一个字符的种类:字符还是字母还是数字
      #newtype表示本字符的种类
      #type:1,表示字符,2表示数字,3表示字母,4表示属于汉字(多字节字符)
      #Buffer用来存储当未完成的一个词.
      #只有当当前字符类型与前一个字符类型不一致时才输出buffer,并清空buffer,改为当前字符值.
      #当与前一字符类型相同时,仅是把当前字符推入buffer(加到buffer的后面)
      #oldtype为4时,当输出一个buffer时,还需要对当前buffer进行中文分词
      oldtype=0
      newtype=0
      flag=0
      words=[]
      0.upto(str.length-1){|a|
      if(flag>0)
      flag=flag-1
      else
      x=str[a]
      if(!chars["char"].index(x.chr).nil?)
      newtype=1
      if(newtype==oldtype)
      buffer=buffer+x.chr
      else
      if(oldtype==4)
      words.concat(segChinese(buffer.strip))
      buffer=x.chr
      else
      words.push( buffer) if (buffer.strip!=”")
      buffer=x.chr
      end
      end
      oldtype=1
      end
      if(!chars["num"].index(x.chr).nil?)
      newtype=2
      if(newtype==oldtype)
      buffer=buffer+x.chr
      else
      if(oldtype==4)
      words.concat(segChinese(buffer.strip))
      buffer=x.chr
      else
      words.push(buffer) if (buffer.strip!=”")
      buffer=x.chr
      end
      end
      oldtype=2
      end
      if(!chars["alpha"].index(x.chr).nil?)
      newtype=3
      if(newtype==oldtype)
      buffer=buffer+x.chr
      else
      if(oldtype==4)
      words.concat(segChinese(buffer.strip))
      buffer=x.chr
      else
      words.push(buffer) if (buffer.strip!=”")
      buffer=x.chr
      end
      end
      oldtype=3
      end
      if(x>127)
      flag=+2
      newtype=4
      if(newtype==oldtype)
      buffer=buffer+str[a].chr+str[a+1].chr+str[a+2].chr
      else
      words.push(buffer) if(buffer.strip!=”")
      buffer=str[a].chr+str[a+1].chr+str[a+2].chr
      end
      oldtype=4
      end
      end
      }
      if(oldtype==4)
      words.concat(segChinese(buffer.strip))
      else
      words.push(buffer) if(buffer.strip!=”")
      end
      total=”"
      words.each{|x| total=total+x+” “}
      return total
      #puts total
      end

      ruby果然就是牛B极了。在学着用BCB折腾nsapi的时候 ,发现了asp,于是我觉得asp真是一个比较牛B的东西。接着发现了php,于是我扔掉了asp, 接着发现python比php有前途,现在,python还没摸透,又瞅 上ruby了。世界变化真是太快了,我快跟不上啦。

      不过要实用,可能还得做些调整。 一般来说,分词的词库得自己整,sogou这个示例可以,真起来肯定有问题。而且要实用,在结果准确度上还要下功能。


      Tag:一个 分成 饭馆 吃饭 两个 我要 中文 利用 下面 来自 
      相关文章