中文分词:最大匹配算法
(一)引言
分词是自然语言处理中非常常见的操作,也是必不可少的文本数据预处理步骤。各国语言的表达方式和书写方式截然不同,因此分词的方式和难度也不同。英文分词是最简单的,因为每个单词已经用空格自动分词了,比如"I like Chinese" 这个句子已经被分成了三个单词。当然,英文分词也是有难点的,比如单词大小写所代表的含义不同以及各种符号的用法,这里暂不讨论。中文是汉字为基本书写单位,词语甚至句子之间并没有明显的区分标记,并且不同的词组合容易产生歧义。比如:“结婚的和尚未结婚的”,计算机很难判断是分成“结婚/的/和/尚未/结婚/的”还是“结婚/的/和尚/未/结婚/的”。因此,中文分词是一项非常具有挑战性的工作。
现今,中文分词方法一般被分为三类:
(1)基于字典的分词方法
(2)基于统计的分词方法
(3)基于机器学习的分词方法
本篇文章主要介绍最直接粗暴的方法----基于词典的分词方法。
(二)正向最大匹配算法
顾名思义,正向最大匹配是从左到右扫描字符串,在一个给定的词典中寻找词的最大匹配。先看一个例子:
给定一个句子:“他是研究生物化学的”。
给定一个词典:["他","是","研究","研究生","生物","物化","化学","学","的"]。
思路:先获得词典中词的最大长度m,这个例子中m为3;给字符串初始位置一个指针pi,即在“他”的位置;从当前指针起取m个字作为词(也可以直接到字符串末尾作为词,但是效率低),即“他是研”;选出的词如果在词典中,就在词的后面进行划分,然后指针移动到这个词后面的一个字,如果选出的词不在词典中,就把选出的词长度减一(即m-1),“他是研”就变成了“他是”,然后在进行此步骤的操作。
算法流程:
输入:字符串 s
过程:
- 令指针 pi 指向 s 的初始位置
- repeat
- 计算当前指针 pi 到字串末端的字数(即未被切分字串的长度) n
- 令 m=词典中最长单词的字数,如果 n<m, 令 m=n
- 从当前 pi 起取 m 个汉字作为词 wi
- if wi 在词典中
- then 在 wi 后添加一个切分标志,根据 wi 的长度修改指针 pi
- else
- 将 wi 从右端去掉一个字
- until pi 指向字串末端
输出: 添加切分标志后的字符串 s
示例代码:
text = "他是研究生物化学的" Dict = ["他","是","研究","研究生","生物","物化","化学","学","的"] def forword_Match(text, Dict): '''前向最大匹配''' word_list = [] pi = 0 #初始位置 #找出字典中的最长的词的长度 m = max([len(word) for word in Dict]) while pi != len(text): n = len(text[pi:]) #当前指针到字符串末尾的长度 if n < m: m = n for index in range(m,0,-1): #从当前 pi 起取 m 个汉字作为词 if text[pi:pi+index] in Dict: word_list.append(text[pi:pi+index]) pi = pi + index # 根据词的长度修改指针pi break print('/'.join(word_list)) forword_Match(text, Dict) ## 输出: 他/是/研究生/物化/学/的
(三)逆向最大匹配算法
可以想到,逆向最大匹配是从右到左扫描字符串,在一个给定的词典中寻找词的最大匹配。先看一个例子:
给定一个句子:“他是研究生物化学的”。
给定一个词典:["他","是","研究","研究生","生物","物化","化学","学","的"]。
思路:先获得词典中词的最大长度m,这个例子中m为3;给字符串末尾位置一个指针pi,即在“的”的位置;从当前指针向左取m个字作为词(也可以直接到字符串开头作为词,但是效率低),即“化学的”;选出的词如果在词典中,就在词的前面进行划分,然后指针移动到这个词前面的一个字,如果选出的词不在词典中,就把选出的词长度减一(即m-1),“化学的”就变成了“学的”,然后在进行此步骤的操作。
算法流程:
输入:字符串 s
过程:
- 令指针 pi 指向 s 的末尾位置
- repeat
- 计算当前指针 pi 到字串开头的字数(即未被切分字串的长度) n
- 令 m=词典中最长单词的字数,如果 n<m, 令 m=n
- 从当前 pi 起取往左取m个汉字作为词 wi
- if wi 在词典中
- then 在 wi 前面添加一个切分标志,根据 wi 的长度修改指针 pi
- else
- 将 wi 从左端去掉一个字
- until pi 指向字串开头
输出: 添加切分标志后的字符串 s
示例代码:
text = "他是研究生物化学的" Dict = ["他","是","研究","研究生","生物","物化","化学","学","的"] def back_Match(text, Dict): '''逆向最大匹配''' word_list = [] pi = len(text) - 1 m = max(len(word) for word in Dict) while pi >= 0: n = len(text[0:pi+1]) if n < m: m = n for index in range(m-1,-1,-1): if text[pi-index:pi+1] in Dict: word_list.append(text[pi-index:pi+1]) pi = pi - index -1 break print('/'.join(word_list[::-1])) back_Match(text, Dict) ## 输出: 他/是/研究/生物/化学/的
至此就完成了基于词典的中文分词方法,但是这种方法过度依赖于词典。如果词典质量不高(比如容量小、记录不全等)会影响分类效果,还有一些会产生歧义的句子也不适合用这种方法。接下来我们会一起学习基于统计的分词方法......