第一章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)
# 输出:
# ['研究', '生命', '起源']

 

posted @ 2021-04-26 15:54  洒哥  阅读(463)  评论(0)    收藏  举报