返回列表 发帖

[转载] 中文分词PHP实现

一、理论基础:
  中文分词在理论上可以分为基于词典的分词、基于语义理解的分词方法。在基于词典的分词方法
中有可以分为最大正向匹配算法和最大逆向匹配方法。
  我个人认为虽然现在研究中文分词的组织有很多,但是技术上的更新是很慢的,特别是很成熟的
商业应用技术更新就更慢了。大多数的商业应用还是停留在很古老的技术上。现在最成熟的的商业
应用还是基于词典的最大正象匹配算法和最大逆向匹配。我想用这种方法的原因主要有两个:
  第一、它的算法很简单,容易实现在计算上耗费的cpu比较小。
  第二、它在分词的准确率上还是可以的,最关键的是它耗费的时间比较少。
二、最大正向匹配算法
  下面就以最大逆向匹配算法为例来说明和实现一个简单的分词程序。考虑到我们的分词程序是用
java实现的一个正规的分词程序。所以为了保密(有朋友想探讨java的实现,请联系:rain200485@163.com)在这里选用php来实现一个简单版本的中文分词算法。
  (1)最大逆向匹配算法简介
  最大正逆向配算法就是从一个句子的最后一个汉字开始,取一定长度的字符串到词典中取匹配,如果
找到则继续向下找,如找不到就从取到的字符串中去掉最前面一个字在从里面找,知道剩下两个字时,
如果还找不到,则从这个句子的第倒数第二个字在找。就这样重复下去直到字符串的结尾。
  例子:以下列句子为例:假定一次取四个汉字
  例句:则从这个句子的第
  1.$s = 句子的第--------->从词典中去查找,找不到,从首部去掉一个字“句”
  2.$s = 子的第 --------->从词典中去查找,找不到,从首部去掉一个字“子”
  3.$s = 的第 --------->从词典中去查找,找不到,由于汉语中词至少是两个字,所以只有从第二个字来找
  4.$s = 个句子的--------->从词典中去查找,找不到,从首部去掉一个字“个”
  5.$s = 句子的 --------->从词典中去查找,找不到,从首部去掉一个字“句”
  6.$s = 子的 --------->从词典中去查找,找不到,跳到一个字去
  7.$s = 这个句子--------->从词典中去查找,找不到,从首部去掉一个字“这“
  8.$s = 个句子 --------->从词典中去查找,找不到,从首部去掉一个字“个”
  9.$s = 句子 --------->从词典中去查找,找到,输出,直接跳到下一个字开始查找即”个“
  10.$s = 则从这个-------->从词典中去查找,找不到,从首部去掉一个字“则”
  11.$s = 从这个 -------->从词典中去查找,找不到,从首部去掉一个字“从”
  12.$s = 这个 -------->从词典中去查找,找到,输出,直接跳到下一个字开始查找即”从“
  13.$s = 则从 -------->从词典中去查找,找不到,到了句子开始。退出

(2)程序实现(php)
  需要一个词典,可以到网上下一个词典文件
  1. <?php
  2. class WordSeg {
  3.   private $wordArr = array();//存储词典中的词
  4.   private $words = array();//存储词分出来的词
  5.   private $splitLen;//设置的词长,如上个例子中的4,即一次取出4个汉字
  6.    
  7.   /**
  8.    * 构造函数主要是从词典中读出词,并且存入$this-&gt;wordArr数组中
  9.    * 存入后要排序,为了以后的二分查找
  10.    * @param $path变量就是字典存储的路径
  11.    */
  12.   public function WordSeg($path){
  13.     $handle = fopen($path,r);
  14.     if($handle){
  15.       $i = 0;
  16.       while(!feof($handle)){
  17.         $buffer = fgets($handle, 12);
  18.         //echo trim($buffer).&quot;&lt;br&gt;&quot;;   
  19.         $this-&gt;wordArr[$i] = trim($buffer);
  20.         $i++;
  21.       }
  22.       //排序
  23.       sort ($this-&gt;wordArr);
  24.       //重新设置数组的下标
  25.       reset ($this-&gt;wordArr);
  26.     }else{
  27.        echo(&quot;the keywords file path is not right&quot;);   
  28.     }
  29.   }
  30.    
  31.   /**
  32.    * 主要设置词长,即一次取出几个汉字,默认为2。在php中一个汉字是占两个字节的,所以默认值为4
  33.    * $len : 词的长度
  34.    */
  35.   public function setLen($len = 4){
  36.     $this-&gt;splitLen = $len;
  37.   }
  38.    
  39.   /**
  40.    * 得到这个词长
  41.    */
  42.   public function getLen(){
  43.     return $this-&gt;splitLen;   
  44.   }
  45.    
  46.   /**
  47.    * 为了提高查询的速度在词典中进行匹配的时候是采用二分查找的方法
  48.    * 这种查找方法在词典文件很大的时候可以提高效率
  49.    * 为了使用这种查找方法必须的在把词典文件读入数组中时必须要排序的
  50.    * 二分查找要求数组中的是按照顺序排列的
  51.    * @param $word : 要匹配的词
  52.    */
  53.   public function findString($word){
  54.     $max = count($this-&gt;wordArr);
  55.     $min = 1;
  56.     while($min &lt;= $max){
  57.       $mid = (int)(($max + $min)/2);
  58.       //echo 'id : '.$mid.&quot; : &quot;.$this-&gt;wordArr[$mid - 1];
  59.       if($this-&gt;wordArr[$mid-1] &gt; $word)
  60.         $max = $mid - 1;
  61.       else if($this-&gt;wordArr[$mid-1] == $word){
  62.         //echo &quot;find word :&quot;.$word;
  63.         $this-&gt;words[] = $word;
  64.         return true;
  65.       }   
  66.       else
  67.         $min = $mid + 1;
  68.     }
  69.     return false;
  70.     /**/
  71.   }
  72.    
  73.   /**
  74.    * 这是最主要的方法,分词的最大逆向匹配方法
  75.    * 此程序的方法还是按照上面的例子来做的
  76.    * @param $wordString : 要分词的字符串
  77.    */
  78.   public function wordSplit($wordString){
  79.     $len = strlen($wordString);
  80.     //echo $len;
  81.     //echo $this-&gt;splitLen;
  82.     for($i = $len; $i &gt; 2; $i = $i - 2){
  83.       $subWord = $this-&gt;getWords($wordString,$i,$this-&gt;splitLen * 2);
  84.       //echo &quot; fu :&quot;.$subWord.&quot;&lt;Br&gt;&quot;;
  85.       if($this-&gt;findString($subWord)){
  86.       //find
  87.         //echo &quot;find : &quot;.$subWord;   
  88.         //如果找到就从从下一个开始找
  89.         $i = $i - $this-&gt;splitLen * 2 + 2;
  90.       }else{
  91.        //not find
  92.        //如果找不到,则去掉取得字符串的最前面的一个字,直到找到或剩下两个汉字为止
  93.         $wordLen = ($i &gt; $this-&gt;splitLen * 2) ? $this-&gt;splitLen*2 : $i;   
  94.         //echo $wordLen.&quot;&lt;Br&gt;&quot;;
  95.         for($j = $wordLen - 2; $j &gt; 2; $j = $j -2){
  96.           $subWord = $this-&gt;getWords($wordString,$i,$j);
  97.           //echo &quot; su :&quot;.$subWord.&quot;&lt;Br&gt;&quot;;
  98.           if($this-&gt;findString($subWord)){
  99.           //find
  100.             //echo &quot;find : &quot;.$subWord;
  101.             $i = $i - $j + 2;
  102.             break;   
  103.           }        
  104.         }//end for
  105.       }
  106.     }//end for
  107.   }
  108.    
  109.   /**
  110.    * 截取字符串的函数,分词的时候要不断的截取字符串
  111.    * @param $wordString : 要截取字符串的母串,所截取的字符串都是从$wordString中截取的
  112.    * @param $start : 截取的开始字符
  113.    * @param $len : 要截取的长度
  114.    */
  115.   private function getWords($wordString,$end,$len){
  116.     if(($end - $len) &lt;= 0){
  117.     //如果截取的长度大于$wordString的长度时,取从0,到要开始截取的字符就可以了
  118.     //由于时逆向截取,所以真正的开始字符是$end-$len
  119.       return substr($wordString,0,$end);
  120.     }
  121.     else{
  122.        //否则就从$start = $end-$len开始截取,截取$len个字符
  123.        return substr($wordString,($end - $len),$len);
  124.     }
  125.   }
  126.    
  127.  /**
  128.    * 取出得到的分词结果
  129.    */
  130.   public function getSplitWords(){
  131.     return $this-&gt;words;   
  132.   }
  133. }
  134. //我的词典位置是在d:word下,文件名是words.dict
  135. $word = new WordSeg('d:wordwords.dict');
  136. $word-&gt;setLen();
  137. $word-&gt;wordSplit('则从这个句子的第');
  138. $wordArr = $word-&gt;getSplitWords();
  139. foreach($wordArr as $value){
  140.   echo 'word is : '.$value.&quot;&lt;br&gt;&quot;;
  141. }

  142. ?>
复制代码
三、总结
  对于中文分词程序来说,这个简单的程序就基本上可以模拟整个的结果和算法,但是这只是个简单的
分词程序,还不能应用。对于简单的文本来说基本上是可以了,但是对于复杂的文本处理,这个程序在效
率上是不能满足要求的。它的效率太低,这个程序的瓶颈是在词典中遍历查询中查询的次数。对于字典的
查询算法有很多,读者可以根据自己的需求来扩充此程序,我们用的是哈希表算法。基本上效率可以提高
一倍。
  对于在搜索引擎中取应用中文分词还有其他的问题,就是如何区分中英文,和中英文混杂的情况,如
何识别新词,如何识别姓名、如何自动的调整(如出现-的时候就自动向前或向后去找月或年等)词的组合。
这些都是搜索引擎中需要考虑的问题。
  据我个人分析百度应该是有两个词典来解决专有名词和普通名词的问题。还有它的自动识别也应该是
对用户输入的词进行统计,选出点击次数最多的来显示相关搜索等。读者不妨个人就研究一下。
  随着笔者对这个问题认识的提高,也会继续深入的去探讨这些问题。

10/05/2007 - 12:16 — rain200485
天行健,君子以自强不息
地势坤,君子以厚德载物
黑色海岸线欢迎您

QQ群:7212260
致力于探索WEB技术精髓:http://www.bitechcn.com
点这里加我!

支持下~
然后再研究...

TOP

返回列表 回复 发帖