目录
1.介绍
近年来,随着社会化媒体(如博客、微博、社交网站等)的兴起和普及,中文文本的分词成为了中文自然语言处理中的重要任务。分词的目标是将连续的汉字序列切分成有意义的词语,为后续的语义分析和信息处理提供基础。分词对中文文本的理解和处理起着关键作用。分词算法的发展可以追溯到上世纪80年代初,最早的分词方法是基于词典的方法。这些方法通过比对文本与词典中的词语,来确定切分位置。
然而随着互联网和社会化媒体的快速发展,传统的基于词典的方法面临着扩展性和准确性的挑战。为了应对上述挑战,正向最大匹配法和逆向最大匹配法被提出并广泛应用于中文分词。正向最大匹配法从左到右进行匹配和切分,选择最长的词进行切分。而逆向最大匹配法则是从右到左进行匹配和切分。这两种方法都通过建立词典和文本的匹配关系来切分文本,能够有效地处理未登录词和歧义问题,在实践中表现出了一定的优势和适用性。需要注意的是,正向和逆向最大匹配法在处理特定文本和语境下可能存在局限性,例如在处理长词和上下文相关的短语时,可能存在分词错误或切分不准确的情况。
为了进一步提高分词的准确性和适应性,研究人员不断探索和提出新的分词算法和技术。总而言之,正向和逆向最大匹配法作为中文分词的经典方法,已经成为中文自然语言处理中不可或缺的工具。它们在社会化媒体等大规模文本数据处理中发挥着重要的作用,并不断被改进和优化,以满足不断变化的语境和需求。
2.方法概述
2.1 正向最大匹配法
正向最大匹配分词(Forward maximum matching segmentation)通常简称为FMM法。其基本思想为:假定分词词典中的最长词有i个汉字字符,则用被处理文档的当前字串中的前i个字作为匹配字段,查找字典。若字典中存在这样的一个i字词,则匹配成功,匹配字段被作为一个词切分出来。如果词典中找不到这样的一个i字词,则匹配失败,将匹配字段中的最后一个字去掉,对剩下的字串重新进行匹配处理…… 如此进行下去,直到匹配成功,即切分出一个词或剩余字串的长度为零为止。这样就完成了一轮匹配,然后取下一个i字字串进行匹配处理,直到文档被扫描完为止。
2.2 逆向最大匹配法
逆向最大匹配法 (Reverse maximum matching method)通常简称为RMM法。RMM法的基本原理与FMM法相同 ,不同的是分词切分的方向与FMM法相反,而且使用的分词辞典也不同。逆向最大匹配法从被处理文档的末端开始匹配扫描,每次取最末端的2i个字符(i字字串)作为匹配字段,若匹配失败,则去掉匹配字段最前面的一个字,继续匹配。相应地,它使用的分词词典是逆序词典,其中的每个词条都将按逆序方式存放。在实际处理时,先将文档进行倒排处理,生成逆序文档。然后,根据逆序词典,对逆序文档用正向最大匹配法处理即可。
由于汉语中偏正结构较多,若从后向前匹配,可以适当提高精确度。所以,逆向最大匹配法比正向最大匹配法的误差要小。统计结果表明 ,单纯使用正向最大匹配的错误率为 1/16 9,单纯使用逆向最大匹配的错误率为 1/245。
3.实现代码
3.1 正向最大匹配法
注:具体文件在评论区可获得
首先需要将任务给出的停用词文件、分词词表文件和需要分词的句子文件读入,需要注意的是,停用词文件和分词词表文件的编码为gbk,需要分词的句子文件编码为utf-8。
# 读取停用词文件
stopwords = []
with open('停用词.txt', 'r', encoding='gbk') as f:
for line in f:
stopwords.append(line.strip())
# 读取分词词表文件
fencidict = []
with open('中文分词词典.txt', 'r', encoding='gbk') as f:
for line in f:
fencidict.append(line.strip())
# 读取需要分词的句子文件
sentences = []
with open('测试样本.txt', 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line != '':
sentences.append(line)
随后,为了确定一个恰当的最大匹配长度,需要首先提取分词词典中所有词的长度,提取结果发现,词语最大长度为15。在后续的测试中,发现如果选取词语最大长度为15,代码运行效率太低,选取5为词语最大长度是比较适合的。
# 查看分词词典中最长词语数
number = []
for ci in fencidict:
if len(ci) not in number:
number.append(len(ci))
print(max(number)) # 输出分词词典中最长词语数
首先,提取单行句子进行分词,并创建存放分词结果的segment_list数组,随后进行遍历循环,提取句子中的前5个词语,判断逻辑为是否存在于分词词典中,如果存在词典中,则需要判断是否存在于停用词词典中,如果存在于停用词词典中,就需要作为停用词被剔除,跳转到下一个词语的循环;如果不存在于停用词词典中,就可以作为分词结果,将其从原句中剔除后,保存到分词结果segment_list数组中。如果前5个词语不在分词词典中,就需要检查前4个词语是否在分词词典中,不断重复过程。
for sentence in sentences: # 提取单行句子进行分词 segment_list = '' # 存放分词结果 while len(sentence) >= 1: # 当未分词完时,继续分词(句子长度大于1表明还有内容) max_match_len = 5 # 设置最大匹配长度,设置为5就够,数越大运行速度越慢 while max_match_len > 1: # 当匹配单词长度大于1时,循环判断分词 if sentence[0:max_match_len] in fencidict: # 判断前5个字符是否存在于分词字典 if sentence[0:max_match_len] not in stopwords: # 判断该词是否在停用词词典中 segment_list = segment_list + '/' + sentence[0:max_match_len] # 追加到分词词组中 sentence = sentence[max_match_len:len(sentence)] # 将符合的词语从原例句中截取 break # 退出循环,重新从最大匹配长度开始匹配截取 elif sentence[0:max_match_len] in stopwords: sentence = sentence[max_match_len:len(sentence)] # 将符合的词语从原例句中截取 break # 退出循环,重新从最大匹配长度开始匹配截取 else: max_match_len -= 1 # max_match_len累减,开始匹配4、3、2、1个字 if max_match_len == 1: # 只剩下一个字时 if sentence[0:max_match_len] not in stopwords: # 如果该词不在停用词词典中 segment_list = segment_list + '/' + sentence[0:1] # 追加单个字作为分词结果 sentence = sentence[1:len(sentence)] # 将该词从原句中剔除 else: sentence = sentence[1:len(sentence)] # 将该词从原句中剔除 print(segment_list) # 输出分词结果
如果最后缩减到只剩1个词语,需要检查是否在停用词词典中,如果在停用词词典中,就需要作为停用词被剔除,如果不在停用词词典中,就可以作为分词结果保存到segment_list中。
最后输出分词结果:
3.2 逆向最大匹配法
除了方法代码外,读取文件等都与正向最大匹配法相同,方法代码如下:
for sentence in sentences: # 提取单行句子进行分词
segment_list = '' # 存放分词结果
while len(sentence) >= 1: # 当未分词完时,继续分词(句子长度大于1表明还有内容)
max_match_len = 5 # 设置最大匹配长度,设置为5就够,数越大运行速度越慢
while max_match_len > 1: # 当匹配单词长度大于1时,循环判断分词
if sentence[len(sentence)-max_match_len:len(sentence)] in fencidict: # 判断前5个字符是否存在于分词字典
if sentence[len(sentence)-max_match_len:len(sentence)] not in stopwords: # 判断该词是否在停用词词典中
segment_list = sentence[len(sentence)-max_match_len:len(sentence)] + '/' + segment_list # 追加到分词词组中
sentence = sentence[0:len(sentence)-max_match_len] # 将符合的词语从原句中剔除
break # 退出循环,重新从最大匹配长度开始匹配截取
elif sentence[len(sentence)-max_match_len:len(sentence)] in stopwords: # 判断该词是否在停用词词典中
sentence = sentence[0:len(sentence)-max_match_len] # 将符合的词语从原句中剔除
break # 退出循环,重新从最大匹配长度开始匹配截取
else:
max_match_len -= 1 # max_match_len累减,开始匹配4、3、2、1个字
if max_match_len == 1: # 只剩下一个字时,说明当前不再存在任何符合的词语,直接截取一个字作为分词结果
if sentence[len(sentence)-1:len(sentence)] not in stopwords: # 如果该词不在停用词词典中
segment_list = sentence[len(sentence)-1:len(sentence)] + '/' + segment_list # 追加单个字作为分词结果
sentence = sentence[0:len(sentence)-1] # 将该词从原句中剔除
else:
sentence = sentence[0:len(sentence)-1] # 将该词从原句中剔除
print(segment_list) # 输出分词结果
逆向最大匹配法与正向最大匹配法的代词没有太大差别,只需要改动一下从原句提取的5个词语是倒着取。例如:正向最大匹配法中的前5个词语是sentence[0:max_match_len],而逆向最大匹配法中的前5个词语是sentence[len(sentence)-max_match_len:len(sentence)]。
首先,提取单行句子进行分词,并创建存放分词结果的segment_list数组,随后进行遍历循环,提取句子中的后5个词语,判断逻辑为是否存在于分词词典中,如果存在词典中,则需要判断是否存在于停用词词典中,如果存在于停用词词典中,就需要作为停用词被剔除,跳转到下一个词语的循环;如果不存在于停用词词典中,就可以作为分词结果,将其从原句中剔除后,保存到分词结果segment_list数组中。如果后5个词语不在分词词典中,就需要检查后4个词语是否在分词词典中,不断重复过程。
如果最后缩减到只剩1个词语,需要检查是否在停用词词典中,如果在停用词词典中,就需要作为停用词被剔除,如果不在停用词词典中,就可以作为分词结果保存到segment_list中。
最后输出分词结果。
4.结果分析
汉语中偏正结构较多,从后向前匹配可以适当提高精确度,从分词结果的最后一句举例,正向最大匹配会把“的说法”中的“的说”提取出来,把“法”视为停用词;而逆向最大匹配会把“的说法”中的“说法”提取出来,把“的”视为停用词,这是更合理语言规范的。当然,正向最大匹配也有所优点,在对“进行了”分词时,会把“进行”提取出来,把“了”视为停用词;而逆向最大匹配会把“进”和“行了”提取出来,不太符合语言规范。
但是总体上,逆向最大匹配的准确率会更高一些。