日期:2014-05-16  浏览次数:20512 次

分词算法–正向最大匹配和反向最大匹配
转自:http://www.zetascope.com/url/e152944a5c106b0d1453bda63adec25d/content

最近看了一下分词算法的东西,整理如下:

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

定义比较抽象,举个例子来说明正向最大匹配和反向最大匹配。

例子:’今天来了许多新同事’
1.正向最大匹配方式,最大长度为5
今天来了许
今天来了
今天来
今天 ====》 得到一个词–今天
来了许多新
来了许多
来了许
来了
来 ====》 得到一个词–来
了许多新同
了许多新
了许多
了许
了 ====》 得到一个词–了
许多新同事
许多新同
许多新
许多 ====》得到一个词– 许多
新同事
新同
新 ====》得到一个词– 新
同事 ====》得到一个词– 同事

最后正向最大匹配的结果是:

/今天/来/了/许多/新/同事/

2.反向最大匹配方式,最大长度为5
许多新同事
多新同事
新同事
同事 ====》得到一个词– 同事
来了许多新
了许多新
许多新
多新
新 ====》得到一个词– 新
天来了许多
来了许多
了许多
许多 ====》得到一个词– 许多
今天来了
天来了
来了
了 ====》得到一个词– 了
今天来
天来
来 ====》得到一个词– 来
今天 ====》得到一个词– 今天

最后反向最大匹配的结果是:

/今天/来/了/许多/新/同事/

正向最大匹配和反向最大匹配的结果并不一定相同

例子:’我一个人吃饭’
1.正向最大匹配方式,最大长度为5
我一个人吃
我一个人
我一个
我一
我 ====》得到一个词– 我
一个人吃饭
一个人吃
一个人
一个 ====》得到一个词– 一个
人吃饭
人吃
人 ====》得到一个词– 人
吃饭 ====》得到一个词– 吃饭

最后正向最大匹配的结果是:

/我/一个/人/吃饭/

2.反向最大匹配方式,最大长度为5
一个人吃饭
个人吃饭
人吃饭
吃饭 ====》得到一个词– 吃饭
我一个人
一个人
个人 ====》得到一个词– 个人
我一
一 ====》得到一个词– 一
我 ====》得到一个词– 我

最后反向最大匹配的结果是:

/我/一/个人/吃饭/

这次两种方式的结果就不一致了。

下面贴出ORACLE中实现的脚本:

CREATE OR REPLACE FUNCTION get_keys(in_str VARCHAR2,
in_dir VARCHAR2,
in_max NUMBER) RETURN VARCHAR2 AS
n_length NUMBER;
n_start NUMBER;
n_str VARCHAR2(2000);
n_substr VARCHAR2(2000);
n_output VARCHAR2(2000);
n_count NUMBER;
n_max NUMBER;
n_last NUMBER;
BEGIN

n_length := length(in_str);
n_output := ‘/’;
n_last := 0;

IF n_length = 0 THEN
RETURN ‘0′;
ELSIF n_length < in_max THEN
n_max := n_length;
ELSE
n_max := in_max;
END IF;

IF in_dir = ‘1′ THEN
n_str := in_str;
ELSE
SELECT reverse(in_str) into n_str from dual;
END IF;

n_substr := SUBSTR(n_str, 1, n_max);
n_start := 1;

WHILE (n_substr IS NOT NULL) LOOP

IF LENGTH(n_substr) < n_max THEN
n_max := LENGTH(n_substr);
END IF;

FOR c2 IN 0 .. n_max - 1 LOOP
if in_dir = ‘1′ then
SELECT COUNT(*)
INTO n_count
FROM dic
WHERE KEY = SUBSTR(n_substr, 1, n_max - c2);
else
SELECT COUNT(*)
INTO n_count
FROM dic
WHERE reverse(KEY) = SUBSTR(n_substr, 1, n_max - c2);
end if;
DBMS_OUTPUT.put_line(SUBSTR(n_substr, 1, n_max - c2));

IF n_count > 0 THEN
n_output := n_output || SUBSTR(n_substr, 1, n_max - c2) || ‘/’;
n_start := n_start + n_max - c2;
n_last := 1;
EXIT;
END IF;

IF c2 = n_max - 1 THEN
if n_last = 0 then
n_output := substr(n_output, 1, length(n_output) - 1) ||
SUBSTR(n_substr, 1, n_max - c2) || ‘/’;
else
n_output := n_output || SUBSTR(n_substr, 1, n_max - c2) || ‘/’;
end if;
n_start := n_start + n_max - c2;
n_last := 1;
EN