简介
关于机器看书(中文!)这件事儿,和我们小时候查字典一样,也得把句子拆了一个词儿一个词儿地查。见的词儿越多,句子分得就越正确,意思理解就更到位。那这篇就聊一聊机器根据词典分句子的算法————最大匹配算法。
最大匹配分词算法有三种途径:正向最大匹配法、反向最大匹配法、双向最大匹配法。
这个最大的含义,是指词典中,最长的词的长度。
正向最大匹配法
简介
正向匹配法,和我们自己读句子很类似,从左向右读。
具体实现步骤,如下:
1.根据词典,获取最长词的长度maxlen,记录文本长度txtlen;
2.起始点star记为0,lens取maxlen的值;
3.若star == txtlen(读完全文),则结束循环;若star+lens <= txtlen,则在句子中从star读lens个字[star,lens],得到“词”word;若star+lens> txtlen,则取lens = txtlen - star,读取[star,lens],得到“词”word;
4.扫描词典,查看有无word这个“词”,若有则执行5,若无则执行6;
5.将 word 这个词记录在分词列表result中,star 取star + lens的值,lens 取 maxlen,回到第3步;
6.lens = lens -1(词长变短),回到第三步;
7.返回分词结果 result;
举个例子:
人生比你想象的要短
使用最大分词法,对它分词,假设词典完美契合这个句子,词典内容为:[人、生、人生、比、你、你想、想象、想、象、的、想象的、要、短、要短]
从左往右开始读,首先最长词长度为3;
- 分词:人生比;扫描词典,没有;
- 分词:人生;扫描词典,有,将其加入结果中;
- 分词:比你想;词典中,没有;
- 分词:比你;扫描词典,没有;
- 分词:比;扫描词典,有,加入结果中;
- 分词:你想象;扫描词典,没有;
- 分词:你想;扫描词典,有,加入结果中;
- 分词:象的要;扫描词典,没有;
- 分词:象的;扫描词典,没有;
- 分词:象;扫描词典,有,加入结果中;
- 分词:的要短;扫描词典,没有;
- 分词:的要;扫描词典,没有;
- 分词:的;扫描词典,有,加入结果中;
- 分词:要短;扫描词典,有,加入结果中; 结束循环。 得到结果[人生 | 比 | 你想 | 象 | 的 | 要短]
整体看来分的好像还不错,当然这个在一定程度上取决于词典;
代码实现
import os
def load_worddic(path):#加载词典
with open(path,'r') as f:
word_list=f.read().splitlines()
f.close()
return word_list
# print(load_worddic("中文分词词典(作业一用).TXT"))
# print(type(load_worddic("中文分词词典(作业一用).TXT")))
def load_stopdic(path):#加载停词表
with open(path,'r') as f:
stop_words=f.read().splitlines()
f.close()
return stop_words
def get_maxlen(load_worddice):#获得词典最长词长度
max_len=0
for word in load_worddice:
if len(word)>max_len:
max_len=len(word)
return max_len
def load_txt(path):#获得文本
with open(path,'r',encoding='utf-8') as f:
txt=f.read()
f.close()
return txt
def MM(txt,stopdic,worddic,max_len): #最大正向匹配分词算法
index=0#每次匹配的起点游标
word_list=[]
while index+max_len< len(txt): #起始游标加上最长词长度,不超过文本长度
word=txt[index:index+max_len]#获取此时的“词”
if word in worddic: #词语在词典中
word_list.append(word+'/')
index=index+len(word)
elif word in stopdic:#词语在停词表中
index=index+len(word)
else:#词语既不在停词表也不在词典,逐渐剪短词语长度
for i in range(max_len):
word=txt[index:index+max_len-i]
if word in worddic:
word_list.append(word + '/')
index = index + len(word)
break
elif word in stopdic:
index = index + len(word)
break
if i ==max_len-1:
index+=1
break
while len(txt)-index>0: #最大词长加上游标已经超过文本长度,最后部分的词判定
for i in range(len(txt)-index):
word=txt[index:index+len(txt)-index-i]
if word in worddic:
word_list.append(word + '/')
index = index + len(word)
elif word in stopdic:
index = index + len(word)
else:
if i == len(txt)-index-1:
index+=1
return word_list
def list_to_sentence(list):#分出的列表结果变为句子结构
content=''
for word in list:
content+=word
return content
def creat_txt(str):#创建分词后的文本文件
with open('result.txt','w') as f:
f.write(str)
f.close()
if __name__ == '__main__':
txt=load_txt('测试样本.txt')
worddic=load_worddic('中文分词词典(作业一用).TXT')
stopdic=load_stopdic('stoplis.txt')
maxlen=get_maxlen(worddic)
print(maxlen)
result=MM(txt,stopdic,worddic,maxlen)
content=list_to_sentence(result)
print(content)
#creat_txt(content)
逆向最大匹配法
简介
理解了正向,逆向就很容易明白,就是把这个过程反过来而已。
有人会问这样有什么区别,直接看效果。
还是那个例子:
人生比你想象的要短
使用逆向最大分词法,词典内容为:[人、生、人生、比、你、你想、想象、想、象、的、想象的、要、短、要短]
从右向左开始读,最长词长度为3;
- 分词:的要短;扫描词典,无;
- 分词:要短;扫描词典,有,加入结果;
- 分词:想象的;扫描词典,有,加入结果;
- 分词:生比你;扫描词典,无,加入结果;
- 分词:比你;扫描词典,有,加入结果;
- 分词:人生;扫描词典,有,加入结果; 结束循环; 返回结果 [要短 | 想象的 | 比你 | 人生] 将结果反转: [人生 | 比你 | 想象的 | 要短]
可以发现,分词次数减少的同时,对句子的理解度也提高了。
代码实现
# -*- coding:utf-8 -*-
import xlrd
import os
import math
import operator
def RP(string,maxlen,worddic,stopdic):
arr=[] #最终生成的列表
end=len(string)
begin=end-maxlen #初始化读词标记 end为字符串最末尾位置,begin为字符串最末尾位置(end)前移最大词长个位置
while begin >= 0 :#begin最少为0,即为string开头
temp = string[begin:end]#获取第一个切片词
#调整代码
# print(temp)
# print(begin, end)
# print(len(temp))
if end<=0:
break
if temp in worddic:# 切片词若在词典中 将end前移切片词长度,begin为新的end位置前移最大词长个位置
end = end-len(temp)
begin = end - maxlen
if begin <= 0:#当begin<=0时,将begin设置为0
begin = 0
# 调整代码
# print(end, begin)
arr.append(temp)
elif temp in stopdic:#切片词在停词表中 将end前移切片词长度,begin为新的end位置前移最大词长个位置(不对其进行保存到字典的操作,即丢弃该词)
end=end-len(temp)
begin=end-maxlen
if begin <= 0:#当begin<=0时,将begin设置为0
begin = 0
# 调整代码
# print(end, begin)
else:#切片词既不在停词表也不在词典中时 ,对该切片词进行进一步切片,每次减掉其第一位字符 temp=temp[1:],循环判断,直至其切为词或者停词表中的词
for i in range(1,maxlen+1):
temp=temp[1:]
if temp in worddic:#为词典中的词
end = end - len(temp)
begin = end - maxlen
if begin <= 0:
begin = 0
#调整代码
# print(end, begin)
arr.append(temp)
elif temp in stopdic:#为停词表中的词
end = end - len(temp)
begin = end - maxlen
if begin <= 0:
begin = 0
# 调整代码
# print(end, begin)
break
else:#既不是停词表也不是词典中的词时
if len(temp)==1:#当切至只剩一个字符时,此时也不输入任意词典,则进行特殊处理,将本字符抛弃,即end-len(temp),begin前移最大词长
end = end - len(temp)
begin = end - maxlen
if begin <= 0:
begin = 0
#调整代码
# print(end,begin)
break
else:#不止一个字符,则继续执行for循环
continue
#判断何时跳出while循环,第一种情况,当begin=end=0时,表示string读完,跳出;第二种情况,string的第一个字符不在任何词典中时,直接舍弃该字符
if begin==0 and end==0:
break
elif begin==0 and end==1 and temp not in stopdic and temp not in worddic:
break
return arr#返回该string的分词列表
def get_max(x):
maxlen=0
for i in x:
if len(i)>maxlen:
maxlen=len(i)
return maxlen
def fun(path):
fileArray = []
for root, dirs, files in os.walk(path):
for fn in files:
eachpath = str(root+'/'+fn)
fileArray.append(eachpath)
return fileArray
#加载词典函数
def load_worddic(path):
wb=xlrd.open_workbook(path)
sheet=wb.sheet_by_index(0)
return sheet.col_values(1)##
#加载停词词典函数
def load_stopdic(path):
wb=xlrd.open_workbook(path)
sheet=wb.sheet_by_index(0)
return sheet.col_values(1)
def count_tf_dif(reader,txt_arr,totle_num):
dic={}
for i in reader:
i_tf=reader.count(i)/len(reader)
count_dif = 1
for j in txt_arr:
file=open(j,'r',encoding='utf-8')
word_content=file.read()
word_list=word_content.split('/')
if i in word_list:
count_dif=count_dif+1
i_dif=math.log(totle_num/count_dif)
tf_dif=i_tf*i_dif
dic[i]=tf_dif
dic = sorted(dic.items(), key=operator.itemgetter(1), reverse=True)
return dic
def create_txt(path,dic):
path='tf-dif/'+path
file=open(path,'w',encoding='utf-8')
file.write('tf-dif统计:')
for i in dic:
file.write(str(i))
print('创建成功')
if __name__ == '__main__':
w=load_worddic('words.xlsx')
s=load_stopdic('stopwords.xlsx')
ml=get_max(w)
txt_arr = fun('分词结果')
totle_num = len(txt_arr)
for txt_name in txt_arr:
file=open(txt_name,'r',encoding='utf-8')
content = file.read()
file.close()
reader = RP(content, ml, w, s)
dic=count_tf_dif(reader,txt_arr,totle_num)
create_txt(txt_name,dic)
print('任务完成!')
双向最大匹配算法
双向最大匹配法:即将两种算法都分词一遍,然后根据大颗粒度词(长度长的)越多越好,非词典词和单字词越少越好的原则,选取其中一种分词结果输出。
选择的标准如下: 1.看分词数量,数量越少越好; 2.分词数量相同,看单字词数量,单字词数量越少越好;
通过以上标准选出输出结果即可。
补充
在代码实现中,可以修改lens为1的情况:当lens为1时直接修改star的值(star+1),将单字存放在结果中,lens改为maxlen,重新开始循环。