中文分词:最大匹配算法
(一)引言
分词是自然语言处理中非常常见的操作,也是必不可少的文本数据预处理步骤。各国语言的表达方式和书写方式截然不同,因此分词的方式和难度也不同。英文分词是最简单的,因为每个单词已经用空格自动分词了,比如"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)
## 输出: 他/是/研究/生物/化学/的
至此就完成了基于词典的中文分词方法,但是这种方法过度依赖于词典。如果词典质量不高(比如容量小、记录不全等)会影响分类效果,还有一些会产生歧义的句子也不适合用这种方法。接下来我们会一起学习基于统计的分词方法......