(一)引言

分词是自然语言处理中非常常见的操作,也是必不可少的文本数据预处理步骤。各国语言的表达方式和书写方式截然不同,因此分词的方式和难度也不同。英文分词是最简单的,因为每个单词已经用空格自动分词了,比如”I like Chinese” 这个句子已经被分成了三个单词。当然,英文分词也是有难点的,比如单词大小写所代表的含义不同以及各种符号的用法,这里暂不讨论。中文是汉字为基本书写单位,词语甚至句子之间并没有明显的区分标记,并且不同的词组合容易产生歧义。比如:“结婚的和尚未结婚的”,计算机很难判断是分成“结婚/的/和/尚未/结婚/的”还是“结婚/的/和尚/未/结婚/的”。因此,中文分词是一项非常具有挑战性的工作。
现今,中文分词方法一般被分为三类:
(1)基于字典的分词方法
(2)基于统计的分词方法
(3)基于机器学习的分词方法
本篇文章主要介绍最直接粗暴的方法——基于词典的分词方法。

(二)正向最大匹配算法

顾名思义,正向最大匹配是从左到右扫描字符串,在一个给定的词典中寻找词的最大匹配。先看一个例子:
给定一个句子:“他是研究生物化学的”。
给定一个词典:[“他”,”是”,”研究”,”研究生”,”生物”,”物化”,”化学”,”学”,”的”]。
思路:先获得词典中词的最大长度m,这个例子中m为3;给字符串初始位置一个指针pi,即在“他”的位置;从当前指针起取m个字作为词(也可以直接到字符串末尾作为词,但是效率低),即“他是研”;选出的词如果在词典中,就在词的后面进行划分,然后指针移动到这个词后面的一个字,如果选出的词不在词典中,就把选出的词长度减一(即m-1),“他是研”就变成了“他是”,然后在进行此步骤的操作。
算法流程:
输入:字符串 s
过程:
1. 令指针 pi 指向 s 的初始位置
2. repeat
3. 计算当前指针 pi 到字串末端的字数(即未被切分字串的长度) n
4. 令 m=词典中最长单词的字数,如果 n5. 从当前 pi 起取 m 个汉字作为词 wi
6. if wi 在词典中
7. then 在 wi 后添加一个切分标志,根据 wi 的长度修改指针 pi
8. else
9. 将 wi 从右端去掉一个字
10. until pi 指向字串末端
输出: 添加切分标志后的字符串 s

示例代码:

  1. text = "他是研究生物化学的"
  2. Dict = ["他","是","研究","研究生","生物","物化","化学","学","的"]
  3. def forword_Match(text, Dict):
  4. '''前向最大匹配'''
  5. word_list = []
  6. pi = 0 #初始位置
  7. #找出字典中的最长的词的长度
  8. m = max([len(word) for word in Dict])
  9. while pi != len(text):
  10. n = len(text[pi:]) #当前指针到字符串末尾的长度
  11. if n < m:
  12. m = n
  13. for index in range(m,0,-1): #从当前 pi 起取 m 个汉字作为词
  14. if text[pi:pi+index] in Dict:
  15. word_list.append(text[pi:pi+index])
  16. pi = pi + index # 根据词的长度修改指针pi
  17. break
  18. print('/'.join(word_list))
  19. forword_Match(text, Dict)
  20. ## 输出: 他/是/研究生/物化/学/的

(三)逆向最大匹配算法

可以想到,逆向最大匹配是从右到左扫描字符串,在一个给定的词典中寻找词的最大匹配。先看一个例子:
给定一个句子:“他是研究生物化学的”。
给定一个词典:[“他”,”是”,”研究”,”研究生”,”生物”,”物化”,”化学”,”学”,”的”]。
思路:先获得词典中词的最大长度m,这个例子中m为3;给字符串末尾位置一个指针pi,即在“的”的位置;从当前指针向左取m个字作为词(也可以直接到字符串开头作为词,但是效率低),即“化学的”;选出的词如果在词典中,就在词的前面进行划分,然后指针移动到这个词前面的一个字,如果选出的词不在词典中,就把选出的词长度减一(即m-1),“化学的”就变成了“学的”,然后在进行此步骤的操作。
算法流程:
输入:字符串 s
过程:
1. 令指针 pi 指向 s 的末尾位置
2. repeat
3. 计算当前指针 pi 到字串开头的字数(即未被切分字串的长度) n
4. 令 m=词典中最长单词的字数,如果 n5. 从当前 pi 起取往左取m个汉字作为词 wi
6. if wi 在词典中
7. then 在 wi 前面添加一个切分标志,根据 wi 的长度修改指针 pi
8. else
9. 将 wi 从左端去掉一个字
10. until pi 指向字串开头
输出: 添加切分标志后的字符串 s

示例代码:

  1. text = "他是研究生物化学的"
  2. Dict = ["他","是","研究","研究生","生物","物化","化学","学","的"]
  3. def back_Match(text, Dict):
  4. '''逆向最大匹配'''
  5. word_list = []
  6. pi = len(text) - 1
  7. m = max(len(word) for word in Dict)
  8. while pi >= 0:
  9. n = len(text[0:pi+1])
  10. if n < m:
  11. m = n
  12. for index in range(m-1,-1,-1):
  13. if text[pi-index:pi+1] in Dict:
  14. word_list.append(text[pi-index:pi+1])
  15. pi = pi - index -1
  16. break
  17. print('/'.join(word_list[::-1]))
  18. back_Match(text, Dict)
  19. ## 输出: 他/是/研究/生物/化学/的

至此就完成了基于词典的中文分词方法,但是这种方法过度依赖于词典。如果词典质量不高(比如容量小、记录不全等)会影响分类效果,还有一些会产生歧义的句子也不适合用这种方法。接下来我们会一起学习基于统计的分词方法……