PHP如何进行中文分词?

什么是中文分词?


QUOTE:
什么是中文分词?

  众所周知,英文是以词为单位的,词和词之间是靠空格隔开,而中文是以字为单位,句子中所有的字连起来才能描述一个意思。例如,英文句子I am a student,用中文则为:“我是一个学生”。计算机可以很简单通过空格知道student是一个单词,但是不能很容易明白“学”、“生”两个字合起来才表示一个词。把中文的汉字序列切分成有意义的词,就是中文分词,有些人也称为切词。我是一个学生,分词的结果是:我是 一个 学生。
  目前主流的中文分词算法有:

   1、 基于字符串匹配的分词方法

  这种方法又叫做机械分词方法,它是按照一定的策略将待分析的汉字串与一个“充分大的”机器词典中的词条进行配,若在词典中找到某个字符串,则匹配成功(识别出一个词)。按照扫描方向的不同,串匹配分词方法可以分为正向匹配和逆向匹配;按照不同长度优先匹配的情况,可以分为最大(最长)匹配和最小(最短)匹配;按照是否与词性标注过程相结合,又可以分为单纯分词方法和分词与标注相结合的一体化方法。常用的几种机械分词方法如下:
  1)正向最大匹配法(由左到右的方向);
  2)逆向最大匹配法(由右到左的方向);
  3)最少切分(使每一句中切出的词数最小)。
  还可以将上述各种方法相互组合,例如,可以将正向最大匹配方法和逆向最大匹配方法结合起来构成双向匹配法。由于汉语单字成词的特点,正向最小匹配和逆向最小匹配一般很少使用。一般说来,逆向匹配的切分精度略高于正向匹配,遇到的歧义现象也较少。统计结果表明,单纯使用正向最大匹配的错误率为1/169,单纯使用逆向最大匹配的错误率为1/245。但这种精度还远远不能满足实际的需要。实际使用的分词系统,都是把机械分词作为一种初分手段,还需通过利用各种其它的语言信息来进一步提高切分的准确率。
  一种方法是改进扫描方式,称为特征扫描或标志切分,优先在待分析字符串中识别和切分出一些带有明显特征的词,以这些词作为断点,可将原字符串分为较小的串再来进机械分词,从而减少匹配的错误率。另一种方法是将分词和词类标注结合起来,利用丰富的词类信息对分词决策提供帮助,并且在标注过程中又反过来对分词结果进行检验、调整,从而极大地提高切分的准确率。
  对于机械分词方法,可以建立一个一般的模型,在这方面有专业的学术论文,这里不做详细论述。
  
2、 基于理解的分词方法

  这种分词方法是通过让计算机模拟人对句子的理解,达到识别词的效果。其基本思想就是在分词的同时进行句法、语义分析,利用句法信息和语义信息来处理歧义现象。它通常包括三个部分:分词子系统、句法语义子系统、总控部分。在总控部分的协调下,分词子系统可以获得有关词、句子等的句法和语义信息来对分词歧义进行判断,即它模拟了人对句子的理解过程。这种分词方法需要使用大量的语言知识和信息。由于汉语语言知识的笼统、复杂性,难以将各种语言信息组织成机器可直接读取的形式,因此目前基于理解的分词系统还处在试验阶段。
  
3、 基于统计的分词方法

  从形式上看,词是稳定的字的组合,因此在上下文中,相邻的字同时出现的次数越多,就越有可能构成一个词。因此字与字相邻共现的频率或概率能够较好的反映成词的可信度。可以对语料中相邻共现的各个字的组合的频度进行统计,计算它们的互现信息。定义两个字的互现信息,计算两个汉字X、Y的相邻共现概率。互现信息体现了汉字之间结合关系的紧密程度。当紧密程度高于某一个阈值时,便可认为此字组可能构成了一个词。这种方法只需对语料中的字组频度进行统计,不需要切分词典,因而又叫做无词典分词法或统计取词方法。但这种方法也有一定的局限性,会经常抽出一些共现频度高、但并不是词的常用字组,例如“这一”、“之一”、“有的”、“我的”、“许多的”等,并且对常用词的识别精度差,时空开销大。实际应用的统计分词系统都要使用一部基本的分词词典(常用词词典)进行串匹配分词,同时使用统计方法识别一些新的词,即将串频统计和串匹配结合起来,既发挥匹配分词切分速度快、效率高的特点,又利用了无词典分词结合上下文识别生词、自动消除歧义的优点。
原帖由 "hightman" 发表:
由此感受到PHP的一大缺点. PHP的数据类型中的 "资源型" 不支持 serialize() 存放后, 让多个PHP过程(表现为单个文件)共享...

或许PHP可以考虑内建一个大容器来管理这类特殊的共享变量, 让PHP代码中可以透过一定的?.........
如果,可能可以采用:
http://w.yi.org/ftp/FAPM/PHP/php_manual_zh/ref.shmop.html


CODE:[Copy to clipboard]<?php
 
// Create 100 byte shared memory block with system id if 0xff3
$shm_id = shmop_open(0xff3, "c", 0644, 100);
if (!$shm_id) {
  echo "Couldn't create shared memory segment/n";
}

// Get shared memory block's size
$shm_size = shmop_size($shm_id);
echo "SHM Block Size: " . $shm_size . " has been created./n";

// Lets write a test string into shared memory
$shm_bytes_written = shmop_write($shm_id, "my shared memory block", 0);
if ($shm_bytes_written != strlen("my shared memory block")) {
  echo "Couldn't write the entire length of data/n";
}

// Now lets read the string back
$my_string = shmop_read($shm_id, 0, $shm_size);
if (!$my_string) {
  echo "Couldn't read from shared memory block/n";
}
echo "The data inside shared memory was: " . $my_string . "/n";

//Now lets delete the block and close the shared memory segment
if (!shmop_delete($shm_id)) {
  echo "Couldn't mark shared memory block for deletion.";
}
shmop_close($shm_id);
 
?>;
附加一个网友发的一个字典档:
下面是我根据网上找的一个字典档, 简易实现的一个分词程序.

(注: 字典档是gdbm格式, key是词 value是词频, 约4万个常用词)

完整的程序演示及下载请参见: http://root.twomice.net/my_php4/dict/chinese_segment.php


CODE:[Copy to clipboard]<?php
//中文分词系统简易实现办法
//切句单位:凡是ascii值<128的字符
//常见双字节符号:《》,。、?“”;:!¥…… %$#@^&*()[]{}|\/"'
//可以考虑加入超常见中文字: 的 和 是 不 了 啊 (不过有特殊字比如 "打的" "郑和" .. :p)

//计算时间
function getmicrotime(){
  list($usec, $sec) = explode(" ",microtime());
  return ((float)$usec + (float)$sec);
}
$time_start = getmicrotime();


//词典类
class ch_dictionary {
    var $_id;

    function ch_dictionary($fname = "") {
        if ($fname != "") {
            $this->;load($fname);
        }
    }

    // 根据文件名载入字典 (gdbm数据档案)
    function load($fname) {
        $this->;_id = dba_popen($fname, "r", "gdbm");
        if (!$this->;_id) {
            echo "failed to open the dictionary.($fname)<br>;/n";
            exit;
        }
    }

    // 根据词语返回频率, 不存在返回-1
    function find($word) {
        $freq = dba_fetch($word, $this->;_id);
        if (is_bool($freq)) $freq = -1;
        return $freq;
    }
}

// 分词类: (逆向)
// 先将输入的字串正向切成句子, 然后一句一句的分词, 返回由词组成的数组.
class ch_word_split {
    var $_mb_mark_list;    // 常见切分句子的全角标点
    var $_word_maxlen;    // 单个词最大可能长度(汉字字数)
    var $_dic;        // 词典...
    var $_ignore_mark;    // true or false
    
    function ch_word_split () {
        $this->;_mb_mark_list = array(","," ","。","!","?",":","……","、","“","”","《","》","(",")");
        $this->;_word_maxlen = 12;    // 12个汉字
        $this->;_dic = NULL;
        $this->;_ignore_mark = true;
    }

    // 设定字典
    function set_dic($fname) {
        $this->;_dic = new ch_dictionary($fname);
    }

    function set_ignore_mark($set) {
        if (is_bool($set)) $this->;_ignore_mark = $set;
    }

    // 将字串切成句子再加以切分成词
    function string_split($str, $func = "") {      
        $ret = array();
        
        if ($func == "" || !function_exists($func)) $func = "";        
        
        $len = strlen($str);
        $qtr = "";

        for ($i = 0; $i < $len; $i++) {
            $char = $str[$i];

            if (ord($char) < 0xa1) {
                // 读取到一个半角字符
                if (!empty($qtr)) {
                    $tmp = $this->;_sen_split($qtr);
                    $qtr = "";

                    if ($func != "") call_user_func($func, $tmp);                    
                    else $ret = array_merge($ret, $tmp);                    
                }

                // 如果是单词或数字. 根据 char 将数据读取到 >;= 0xa1为止
                if ($this->;_is_alnum($char)) {
                    do {
                        if (($i+1) >;= $len) break;
                        $char2 = substr($str, $i + 1, 1);
                        if (!$this->;_is_alnum($char2)) break;

                        $char .= $char2;
                        $i++;
                    } while (1);

                    if ($func != "") call_user_func($func, array($char));
                    else $ret[] = $char;                    
                }
                elseif ($char == ' ' || $char == "/t") {
                    // nothing.
                    continue;
                }
                elseif (!$this->;_ignore_mark) {
                    if ($func != "") call_user_func($func, array($char));
                    else $ret[] = $char;                    
                }
            }
            else {
                // 双字节字符.
                $i++;
                $char .= $str[$i];
                
                if (in_array($char, $this->;_mb_mark_list)) {
                    if (!empty($qtr)) {
                        $tmp = $this->;_sen_split($qtr);
                        $qtr = "";

                        if ($func != "") call_user_func($func, $tmp);
                        else $ret = array_merge($ret, $tmp);
                    }

                    if (!$this->;_ignore_mark) {
                      if ($func != "") call_user_func($func, array($char));
                      else $ret[] = $char;
                    }
                }
                else {
                    $qtr .= $char;
                }
            }
        }
        
        if (strlen($qtr) >; 0) {
            $tmp = $this->;_sen_split($qtr);

            if ($func != "") call_user_func($func, $tmp);            
            else $ret = array_merge($ret, $tmp);            
        }

        // return value
        if ($func == "") {
            return $ret;
        }
        else {
            return true;
        }
    }

    // 将句子切成词, 逆向
    function _sen_split($sen) {
        $len = strlen($sen) / 2;
        $ret = array();

        for ($i = $len - 1; $i >;= 0; $i--) {
            // 如: 这是一个分词程序
            
            // 先取得最后一个字
            $w = substr($sen, $i * 2, 2);

            // 最终的词长
            $wlen = 1;
            
            // 开始逆向匹配到最大长度.
            $lf = 0; // last freq
            for ($j = 1; $j <= $this->;_word_maxlen; $j++) {
                $o = $i - $j;
                if ($o < 0) break;
                $w2 = substr($sen, $o * 2, ($j + 1) * 2);
                
                $tmp_f = $this->;_dic->;find($w2);
                //echo ".: $w2 (f: $tmp_f)/n";
                if ($tmp_f >; $lf) {
                    $lf = $tmp_f;
                    $wlen = $j + 1;
                    $w = $w2;
                }
            }
            // 根据 $wlen 将 $i 偏移了
            $i = $i - $wlen + 1;
            array_push($ret, $w);
        }

        $ret = array_reverse($ret);
        return $ret;
    }

    // 判断字符是不是 字母数字_- [0-9a-z_-]
    function _is_alnum($char) {
        $ord = ord($char);
        if ($ord == 45 || $ord == 95 || ($ord >;= 48 && $ord <= 57))
            return true;
        if (($ord >;= 97 && $ord <= 122) || ($ord >;= 65 && $ord <= 90))
            return true;
        return false;
    }
}


// 分词后的回调函数
function call_back($ar) {    
    foreach ($ar as $tmp) {
        echo $tmp . " ";
        //flush();
    }
}

// 实例(如果没有输入就从 sample.txt中读取):
$wp = new ch_word_split();
$wp->;set_dic("dic.db");

if (!isset($_REQUEST['testdat']) || empty($_REQUEST['testdat'])) {
  $data = file_get_contents("sample.txt");
}
else {
  $data = & $_REQUEST['testdat'];
}

// output
echo "<h3>;简易分词演示</h3>;/n";
echo "<hr>;/n";
echo "分词结果(" . strlen($data) . " chars): <br>;/n<textarea cols=100 rows=10>;/n";

// 设定是否忽略不返回分词符号(标点,常用字)
$wp->;set_ignore_mark(false);

// 执行切分, 如果没有设置 callback 函数, 则返回由词组成的array
$wp->;string_split($data, "call_back");

$time_end = getmicrotime();
$time = $time_end - $time_start;

echo "</textarea>;<br>;/n本次分词耗时: $time seconds <br>;/n";
?>;
<hr>;
<form method=post>;
您也可以在下面文本框中输入文字,提交后试验分词效果:<br>;
<textarea name=testdat cols=100 rows=10>;</textarea>;<br>;
<input type=submit>;
</form>;
<hr>;
附: <br>;
<li>;本程序源码: <a href="chinese_segment.phps">;chinese_segment.php</a>; (简易实现方式)</li>;
<li>;需要的字典: <a href="dic.db">;dic.db</a>; (gdbm格式)</li>; 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值