背景
双向最大匹配法(Bi-directction Matching method,BM)
双向最大匹配法是将正向最大匹配法得到的分词结果和逆向最大匹配法的到的结果进行比较,从而决定正确的分词方法。据SunM.S. 和 Benjamin K.T.(1995)的研究表明,中文中90.0%左右的句子,正向最大匹配法和逆向最大匹配法完全重合且正确,只有大概9.0%的句子两种切分方法得到的结果不一样,但其中必有一个是正确的(歧义检测成功),只有不到1.0%的句子,或者正向最大匹配法和逆向最大匹配法的切分虽重合却是错的,或者正向最大匹配法和逆向最大匹配法切分不同但两个都不对(歧义检测失败)。这正是双向最大匹配法在实用中文信息处理系统中得以广泛使用的原因所在。
数据准备
准备中文词库,提供一个中文词库:https://github.com/ling0322/webdict
数据预处理
只考虑中文分词,所以将其他的非中文字符都统一替换成空格。
正向最大匹配(FMM)
从左至右,一次匹配,每次尽量匹配较长的词语。设词库中词语长度最长的为max_len,设每次截取的长度为len,初始化截取长度len=max_len(当然len不能超过当前句子长度,即取再取一个min),每次截取后len减一,若发现截取词语与在词库中,则退出循环,说明找到了最大的匹配词语,然后对剩下的句子依次进行。
实例
将“我们在野生动物园”拆分
其中19为词库中词语最大长度
结果解释:
因为词库中词语最长为19,该句子的长度为8,所以从长度8开始截取,从第一个字开始,第一次截取为整个句子,发现词库中没有整个句子,再截取长度为7,长度为6…,最终发现从第一字开始,长度为2的词语存在,即“我们”;再从‘在’开始匹配,过程与前面的一样。
ps:‘在野’的确是个词语
从上面的结果,可以发现正向最大匹配不一定正确
逆向最大匹配(BMM)
过程与正向最大匹配相反,从后往前依次找最大长度的词语。
实例
双向最大匹配(Bi_MM)
1、正向最大匹配,得到分词结果ans1
2、逆向最大匹配,得到分词结果ans2
3、若ans1等于ans2,返回任意一个
4、若ans1不等于ans2
4.1>返回ans1与ans2词语数量较少的
4.2>若ans1与ans2词语数量相等,返回ans1与ans2单字较少的(单字数量还相等,任意返回一个)
代码
import re
import numpy as np
#中文匹配,把非中文字符换成空格
def extractChinese(s):
import re
pattern="[\u4e00-\u9fa5]+"#中文正则表达式
regex = re.compile(pattern) #生成正则对象
results = regex.findall(s) #匹配
return " ".join(results)
#读取词语,以及词频,在下面的双向最大匹配中,词频没有实际意义
max_len=0 #记录最大单词长度
word_dic=dict()
with open("webdict_with_freq.txt", "r",encoding='utf-8') as f:
for line in f.readlines():
str=re.split('[, \n]',line)
word_dic[str[0]]=int(str[1])
max_len=max(max_len,len(str[0]))
print(max_len)
#读取测试集
test=[]
with open("test.txt", "r",encoding='utf-8') as f:
for line in f.readlines():
str = extractChinese(line)
str=re.split('[ ]',str)
for i in str:
test.append(i)
#正向最大匹配
def FMM(text):
ans=[]
id=0
while id<len(text):
LL= min(len(text)-id,max_len)
flag=False #判断是否找到最大匹配
while LL>0:
tt=text[id:id+LL]
#print(tt)
if word_dic.__contains__(tt):
ans.append(tt)
flag=True
break
LL-=1
if flag:
#找到最大匹配
id+=LL
else:
#词库中没有这个字开头的词,直接跳过
id+=1
return ans
#逆向最大匹配
def BMM(text):
ans = []
id = len(text)
while id>0:
LL = min(id, max_len)
flag = False # 判断是否找到最大匹配
while LL > 0:
tt = text[id-LL:id]
#print(tt)
if word_dic.__contains__(tt):
ans.append(tt)
flag = True
break
LL -= 1
if flag:
# 找到最大匹配
id -= LL
else:
# 词库中没有这个字开头的词,直接跳过
id -= 1
return ans[::-1]
def Bi_MM(text):
ans1=FMM(text)
ans2 = BMM(text)
if ans1==ans2:
return ans1
else:
if len(ans1)<len(ans2):
return ans1
elif len(ans2)<len(ans1):
return ans2
else:
cnt1=0
cnt2=0
for i in ans1:
if len(i)==1:
cnt1+=1
for i in ans2:
if len(i)==1 :
cnt2+=1
if cnt1<=cnt2:
return ans1
else:
return ans2
ans=''
for line in test:
cut=Bi_MM(line)
for i in cut:
ans+=i
ans+=" "
ans+='\n'
fo = open('ans.txt', 'w', encoding='utf-8')
fo.write(ans)
fo.close()
结果