分词概述
目前中文的分词可分为三大类:基于词典的方法、基于统计的方法和混合方法。基于词典的方法需要分词的源字符串,如果能够找到对应的字符串将成功匹配。这是一种很原始且效率相对低效的分词策略。举个简单案例,在“我要认真看论文”句子中查找关键词“论文”,无论采用何种匹配方式,它都需要从左往右或者从右往左一个字或一个词的查找(长度取决于对分词的粒度控制),直到经过几个轮回之后找到“论文”这个词组,这样才算成功。对于这类分词,根据解析方向的不同,可以分为正向匹配和逆向匹配;按照不同长度优先原则,可以分为最长匹配和最短匹配。
- 正向最大匹配法( Maximum Match Method)。该方法就是从左往右通过词典进行匹配时,尽最大可能去匹配一个语义完整的词汇。例如有这样一个句子:“现在开始进行毕业论文的查重检测了”。对这个句子进行匹配时,方向为从左到右,匹配长度循环递减。假设最大匹配长度为5,那么首先匹配结果是“现在开始进”,不符合要求。接下来是“现在开始”,然后是“现在开”,再然后是“现在”,这是一个符合要求的词,提取出来。同理接下来5个字,“开始进行毕”,提取出“开始”,按此进行不断匹配,最后通过正向最大匹配法得到我们想要的分词结果:“现在/开始/进行/毕业/论文/的/查重/检测/了”。
- 逆向最大匹配法( Reverse Maximum Match Method )。顾名思义,它的匹配方向与正向匹配法相反,从右边取N个字符进行匹配。若失败,则去掉匹配字段最前面的一个字,继续匹配。在实际应用中,通常通过正向匹配法来实现逆向匹配,先将源文件进行倒排,得到逆序文件,然后根据逆序词典(即按照相反的顺序存储词条),对逆序文件使用正向最大匹配法进行处理,这样就得到理论上逆向匹配法得到的结果文件。
- 双向最大匹配法。双向最大匹配法( Bi-directction Matching method) 是将正向最大匹配法得到的分词结果和逆向最大匹配法得到的结果进行比较,然后按照最大匹配原则,选取词数切分最少的作为结果。 据 SunM.S. 和 Benjamin K.T. ( 1995 )的研究表明 ,中文中 90.0% 左右的句子,正向最大匹配法和逆向最大匹配法完全重合且正确,只有大概 9.0% 的句子两种切分方法得到的结果不一样,但其中必有一个是正确的 (歧义检测成功),只有不到 1.0%的句子,使用正向最大匹配法和逆向最大匹配法的切分虽重合却是错的,或者正向最大匹配法和逆向最大匹配法切分不同但两个都不对 (歧义检测失败) 。 这正是双向最大匹配
法在实用中文信息处理系统中得以广泛使用的原因。
python代码实现
import os
import sys
import time
class IMM(object):
def __init__(self, *dic_path):
self.dictionary = set()
self.maximum = 0
# 加载词典
for path in dic_path:
self.load_dic(path)
# 加载字典
def load_dic(self, dic_path):
with open(dic_path, 'r', encoding='utf-8') as fp:
for line in fp:
line = line.strip().split()[0]
if not line:
continue
self.dictionary.add(line)
self.maximum = max(self.maximum, len(line))
# 正向最大匹配
def FMM_cut(self, text):
result = []
index = 0
while index < len(text): # 小标未超过句子长度
match = False
for size in range(self.maximum, 0, -1):
if index + size > len(text):
continue
piece = text[index:(index + size)]
if piece in self.dictionary:
match = True
result.append(piece)
index += size
break
if not match:
result.append(text[index])
index += 1
return result
# 逆向最大匹配
def RMM_cut(self, text):
result = []
index = len(text)
while index > 0:
match = False
for size in range(self.maximum, 0, -1):
if index - size < 0:
continue
piece = text[(index - size):index] # 切分单词
# 匹配成功,index向前移动word长度
if piece in self.dictionary:
match = True
result.append(piece)
index -= size
break
if not match:
result.append(text[index - 1])
index -= 1
return result[::-1]
# 双向最大匹配
def BMM_cut(self, text):
words_FMM = self.FMM_cut(text)
words_RMM = self.RMM_cut(text)
print("FMM:", words_FMM)
print("RMM:", words_RMM)
# 如果正向和反向结果一样,返回任意一个
if words_FMM == words_RMM:
return words_FMM
# 单字词个数
f_single_word = 0
r_single_word = 0
# 总次数
fmm_count = len(words_FMM)
rmm_count = len(words_RMM)
# 非字典数
fmm_oov = 0
rmm_oov = 0
# 罚分都为1分,分值越低越好
fmm_score = 0
rmm_score = 0
# 分词结果不同,返回单字数、非字典词、总词数少的那一个
for each_word in words_FMM:
if len(each_word) == 1:
f_single_word += 1
if each_word not in self.dictionary:
fmm_oov += 1
for each_word in words_RMM:
if len(each_word) == 1:
r_single_word += 1
if each_word not in self.dictionary:
rmm_oov += 1
# 非字典词越少越好
fmm_score = fmm_oov + fmm_count + f_single_word
rmm_score = rmm_oov + rmm_count + r_single_word
# 返回罚分少的那个
if fmm_score < rmm_score:
return words_FMM
else:
return words_RMM
def main():
dict1_path = os.path.join(sys.path[0], r'.\data\dict.txt.big')
dict2_path = os.path.join(sys.path[0], r'.\data\THUOCL_animal.txt')
test_path = os.path.join(sys.path[0], r'.\data\CTBtestingset.txt')
output_path = os.path.join(sys.path[0], r'.\data\output.txt')
tokenizer = IMM(dict1_path, dict2_path)
# tokenizer = IMM(r'./data/THUOCL_animal.txt')
try:
with open(test_path, 'r',
encoding='utf-8') as input_text, open(output_path,
'w',
encoding='utf-8',
newline='') as output:
for line in input_text:
line = tokenizer.BMM_cut(line.strip())
print(line)
line = ' '.join(line) + os.linesep
print(line)
output.write(line)
except Exception:
print(sys.stderr, "文件打开错误")
raise Exception
sys.exit(1)
if __name__ == "__main__":
start = time.time()
main()
end = time.time()
print("运行时间:", end - start)