第一章nlp词典分词(一)
中文分词是将一段文本拆分为一系列单词的过程,这些单词顺序拼接后等于原文本。词典分词是最简单、最为常见的分词算法,仅需一部词典和一套查询词典规则即可。
常用的规则词典有正向最长匹配、逆向最长匹配和双向最长匹配,它们都基于完全切分过程。
1.完全切分
完全切分指的是,找出一段文本中所有单词。这并不是标准意义上的分词,有些人将这个过程称为分词,其实并不准确。
不考虑效率的话,朴素的完全切分算法其实非常简单,只要遍历文本中的连续序列,查查询该序列是否在词典中即可。
加载词典
1 def load_dictionary(): 2 dic = set() 3 4 # 按行读取字典文件,每行第一个空格之前的字符串提取出来 5 for line in open("CoreNatureDictionary.mini.txt", "r", encoding='utf-8'): 6 dic.add(line[:line.find("\t")]) 7 8 return dic
词典下载地址:
链接:https://pan.baidu.com/s/1mjS3r4Orr8mPOjwvTFY7yg
提取码:i9cr
完全切分
1 # 完全切分:指的是找出一段文本的所有单词 2 def full_segment(text, dic): 3 word_list = [] 4 for i in range(len(text)): # i 从0到text的最后一个字的下标遍历 5 for j in range(i+1, len(text)+1): # j 遍历[(i+1, len(text)] 区间 6 word = text[i:j] # 取出连续区间[i, j]对应的字符串 7 if word in dic: 8 word_list.append(word) 9 return word_list
dic = load_dictionary()
print(full_segment("就读北京大学", dic)
# 输出的结果:
# ['就', '就读', '读', '北', '北京', '北京大学', '京', '大', '大学', '学']
2.正向最长匹配
上面的输出并不是中文分词,我们需要那种有意义的词序序列,而不是所有出现在词典中的单词所构成的链表。比如,我们希望”北京大学“成为一整个词,而不是”北京“和”大学“之类的碎片。
考虑到越长的单词表达的意义越丰富,于是我们定义单词越长优先级越高。具体说来,就是在某个下标为起点递增查词的过程中,优先输出更长的单词,这种规则被称为最长匹配算法。
该下标的扫描顺序如果从前往后,则称为正向最长匹配,反之则称为逆向最长匹配。
1 def forward_segment(text, dic): 2 """ 3 正向最长匹配 4 """ 5 word_list = [] 6 i = 0 7 while i < len(text): 8 longest_word = text[i] # 获取当前单个字符 9 for j in range(i+1, len(text)+1): # i 从0到text的最后一个字的下标遍历 10 word = text[i:j] 11 if word in dic: 12 if len(word) > len(longest_word): 13 longest_word = word # 如果匹配到word词长度大于longest_word, 则将word赋值于longest_word 14 word_list.append(longest_word) 15 i += len(longest_word) 16 return word_list
dic = load_dictionary()
print(forward_segment("研究生命起源", dic)
# 输出:
# ['研究生', '命', '起源']
3.逆向最长匹配
上述的输出结果出现误差,原因在于正向最长匹配的话,”研究生“的优先级是大于”研究“的,那么怎么解决这个冲突呢?
于是有些人想到,正向匹配不行,那就逆向匹配。逆向匹配顾名思义就是从后往前扫描的过程中,保留最长单词。
1 def backward_segment(text, dic): 2 """ 3 逆向最长匹配 4 :param text: 5 :param dic: 6 :return: 7 """ 8 word_list = [] 9 i = len(text) - 1 10 while i >= 0: 11 longest_word = text[i] # 获取当前位置下的单个字符 12 for j in range(0, i): # 遍历[0, i]区间作为待查询词语的起点 13 word = text[j:i+1] 14 if word in dic: 15 if len(word) > len(longest_word): # 越长优先级越高 16 longest_word = word 17 break 18 word_list.insert(0, longest_word) # 逆向扫描,所以越先查出的单词的位置越靠后 19 i -= len(longest_word) 20 return word_list
print(backward_segment("研究生命起源", dic)
# 输出:
# ['研究', '生命', '起源']
4.双向最长匹配
这是一种融合两种匹配方法的复杂规则集,流程如下:
(1)同时执行正向和逆向最长匹配,若两者的词数不同,则返回词数更少的那一个。
(2)否则,返回两者中单字更少的那一个。但单字数也相同时,则返回逆向最长匹配的结果
1 def bidirection_segment(text, dic): 2 """ 3 双向最长匹配 4 这是一种融合两种匹配方法的复杂规则集,流程如下: 5 同时执行正向和逆向最长匹配,若两者的词数不同,则返回次数更少的那一个; 6 否则,返回两者中单字更少的那一个,当单字数也相同时,优先返回逆向最长匹配的结果 7 :param text: 8 :param dic: 9 :return: 10 """ 11 def count_single_char(word_list): 12 return sum([1 for word in word_list if len(word) == 1]) 13 14 f = forward_segment(text, dic) 15 b = backward_segment(text, dic) 16 if len(f) < len(b): # 词数更少优先级更高 17 return f 18 elif len(f) > len(b): 19 return b 20 else: 21 if count_single_char(f) < count_single_char(b): 22 return f 23 else: 24 return b
print(bidirection_segment("研究生命起源", dic)
# 输出:
# ['研究', '生命', '起源']

浙公网安备 33010602011771号