准备工作
中文分词是将句子转化成词的表示,自动识别句子中的词,在词与词之间加入边界分隔符,分割出各个词汇。在汉语中,虽然是以字为最小单位,但是一篇文章的语义表达却仍然是以词来划分的。因此处理中文文本时,需要进行分词处理,将句子转为词的表示,是必不可少的文本数据预处理步骤。而最大匹配算法是是中文分词中常见的算法。
字典是分词的重要工具,可以看到,字典每一行由词语和它的词性构成,我们在分词阶段,只需要中文词语,因此需要处理字典。
# -----------------------------准备工作---------------------
# 获取分词字典
def get_Dic():
with open("chineseDic.txt",'r',encoding='utf-8') as f:
lines=f.readlines()
with open("chineseDic1.txt",'w',encoding='utf-8') as fo:
for line in lines:
p1,p2,p3=line.partition(',') #因为有些词语有两个词性,所以有两个逗号,分成三个部分,取第一部分就好
fo.write(p1)
get_Dic()
#获取分词字典最大长度
def get_maxlen():
maxlen=0
with open("chineseDic1.txt",'r',encoding='utf-8') as f:
for line in f.readlines():
s=len(line)
if s>maxlen:
maxlen=s
return maxlen
maxlen=get_maxlen()
print(maxlen)
#maxlen=33
# -----------------------------准备工作---------------------
一.正向最大匹配算法FMM
从左向右扫描寻找词的最大匹配。规定一个最大长度词,在文本左边开始切割,将切下来的词和字典的中的词匹配,如果找到,就继续切割。 否则,就缩短长度继续寻找,直到找到或者成为单字。找到匹配的词之后从找到的词后面开始,重新切割出最大长度词。
以“对外经济技术合作与交流不断扩大。”为例,详细描述算法如下:
假如字典是: [‘对外’,‘经济’,‘技术’,‘合作’,‘与’,‘交流’,‘不断’,‘扩大’]
我们设置maxlen=5
输入文本s1=“对外经济技术合作与交流不断扩大”,分词结果s2=’’
①s1不为空,从s1左边选出候选字串W=”对外经济技”
②查词表,W不在词表中,将W最右边最后一个词去掉,W=“对外经济”
③查词表,W不在词表中,W=“对外经”
④查词表,W不在词表中,去掉最后一个词,W=“对外”在词表中,加到s2,s2=‘对外/’,s1=’经济技术合作与交流不断扩大’,候选字串W=’经济技术合’
⑤以此类推,直到s1为空,分词结果为‘对外/经济/技术/合作/与/交流/不断/扩大’
import re
import time
start = time.time()
#chineseDic1.txt 是处理后的字典,只保留了中文部分
lst_word=[] #存储字典的词语
with open("chineseDic1.txt",'r',encoding='utf-8') as f:
for line in f.readlines():
lst_word.append(line.rstrip("\n")) #去掉右边的换行符
# print(lst_word)
punc = '~`!#$%^&*()_+-=|\';":/.,?><~·!@#¥%……&*()——+-=“:’;、。,?》《{}'
def positive_max_matching():
text = input()
s=re.sub(r"[%s]+" % punc, "", text) #去除输入文本的所有标点符号,让分词结果更美观
maxlen = 4 #设置最大长度为4(理论上最大长度要设置为字典中词语的最大长度,但是这样跑起来速度会慢很多,maxlen的设置还是得看文本情况)
word = [] # 存储分词结果
len_word=len(s)
while len_word>0:
char=s[0:maxlen] #截取待处理文本的maxlen长度,作为临时字符串char
while char not in lst_word:
if len(char)==1: #如果临时字符串只剩一个字,就退出循环
break
char=char[0:len(char)-1] #最右边裁去一个字
word.append(char) #匹配成功,添加到word列表
s=s[len(char):] #取出剩下未分词的字符串
len_word=len(s)
return word
Word=positive_max_matching() #获取分词列表
sec=""
for i in Word:
if sec=="":
sec+=i
else:
sec+="/"+i
print(sec)
end = time.time()
print("运行时间:"+str(end-start))
二.反向最大匹配算法BMM
如果你理解了FMM,那BMM就是小菜一碟了。
根据事先最备好的词典,从后往前进行匹配,假如有匹配到的词直接分割,若没有的话减少最左边一个词然后继续匹配,直至所有的句子匹配完毕。
主要步骤如下:
1.在句子未匹配部分的末尾截取词典中最长词长度的文本;
2.将截取的文本在词典中进行匹配;
3.若匹配成功,返回第一步,直到全部句子匹配结束;
4.若为匹配成功,将文本去掉最左边的一个词,返回第二步。
import re
import time
start = time.time()
punc = '~`!#$%^&*()_+-=|\';":/.,?><~·!@#¥%……&*()——+-=“:’;、。,?》《{}'
lst_word=[]
with open("chineseDic1.txt",'r',encoding='utf-8') as f:
for line in f.readlines():
lst_word.append(line.rstrip("\n"))
def unpositive_max_matching():
text = input()
s=re.sub(r"[%s]+" % punc, "", text) #去除所有标点符号
maxlen = 4
word = [] # 存储分词后的句子
len_word=len(s)
while len_word>0:
char=s[-maxlen:] #从最后第maxlen开始取
k=0
while char not in lst_word:
if len(char)==1:
break
char=char[k+1:]
word.append(char)
s=s[:len_word-len(char)]
len_word=len(s)
return word
Word=unpositive_max_matching()
sec=""
for i in reversed(Word):
if sec=="":
sec+=i
else:
sec+="/"+i
print(sec)
end = time.time()
print("运行时间:"+str(end-start))
三.双向最大匹配算法
双向最大匹配算法的原理就是将正向最大匹配算法和逆向最大匹配算法进行比较,从而选择正确的分词方式
比较原则/步骤:
1.比较两种匹配算法的结果
2.如果分词数量结果不同:选择数量较少的那个
3.如果分词数量结果相同:
①分词结果相同,返回任意一个
②分词结果不同,返回单字数较少的一个
import time
import re
start = time.time()
lst_word=[]
with open("chineseDic1.txt",'r',encoding='utf-8') as f:
for line in f.readlines():
lst_word.append(line.rstrip("\n"))
punc = '~`!#$%^&*()_+-=|\';":/.,?><~·!@#¥%……&*()——+-=“:’;、。,?》《{}'
text=input()
s=re.sub(r"[%s]+" % punc, "", text) #去除所有标点符号
len_word=len(s) #输入句子的长度
maxlen=5
def positive_max_matching(text):
word_pos = [] # 存储正向最大分词后的句子
len1=len_word #备份一份长度
s1=s #备份输入的语句
while len1>0:
char=s1[0:maxlen]
while char not in lst_word:
if len(char)==1:
break
char=char[0:len(char)-1]
word_pos.append(char)
s1=s1[len(char):]
len1=len(s1)
return word_pos
def unpositive_max_matching(text):
word_unpos = [] # 存储反向最大分词后的句子
len2=len_word #备份一份长度
s2=s #备份输入的语句
while len2>0:
char=s2[-maxlen:]
k=0
while char not in lst_word:
if len(char)==1:
break
char=char[k+1:]
word_unpos.append(char)
s2=s2[:len2-len(char)]
len2=len(s2)
return word_unpos
def get_pos_sen(word):
sec = ""
for i in word:
if sec == "":
sec += i
else:
sec += "/" + i
return sec
def get_unpos_sen(word):
sec=""
for i in reversed(word):
if sec == "":
sec += i
else:
sec += "/" + i
return sec
word1=positive_max_matching(text)
word2=unpositive_max_matching(text)
# 1.如果正反向分词结果词数不同,则取分词数量较少的那个。
if len(word1)>len(word2):
print(get_unpos_sen(word2))
elif len(word1)<len(word2):
print(get_pos_sen(word1))
# 2.如果分词结果词数相同
else:
ls1=[i for i in word1 if len(i)==1]
ls2=[i for i in word2 if len(i)==1]
# a.分词结果不同,返回其中单字较少的那个。
if len(ls1)>len(ls2):
print(get_unpos_sen(word2))
elif len(ls1)<len(ls2):
print(get_pos_sen(word1))
# b.分词结果相同,说明没有歧义,可返回任意一个。
else:
print(get_unpos_sen(word2))
end=time.time()
print("运行时间:"+str(end-start))
四.测试结果
我们用经典的文本来测试下三个算法
五.分析结果
分词算法的评测指标
通常的评测指标包括:正确率、召回率、F度量值
• 正确率通常指测试结果中正确切分的个数占系统所有输出结果的比例
• 召回率是指测试结果中正确结果的个数占标准答案总数的比例
以下面一段文本为例:
“在这一年中,中国的改革开放和现代化建设继续向前迈进。国民经济保持了“高增长、低通胀”的良好发展态势。农业生产再次获得好的收成,企业改革继续深化,人民生活进一步改善。对外经济技术合作与交流不断扩大。”
我们想要的分词结果是:在/ 这/ 一/ 年/ 中/ 中国/ 的/ 改革/ 开放/ 和/ 现代化/ 建设/ 继续/ 向前/ 迈进/ 国民经济/ 保持/ 了/ 高/ 增长/ 低/ 通胀/的/ 良好/ 发展/ 态势/ 农业/ 生产/ 再次/ 获得/ 好/ 的/ 收成/ 企业/ 改革/ 继续/ 深化/ 人民/ 生活/ 进一步/ 改善/ 对外/ 经济/ 技术/ 合作/ 与/ 交流/ 不断/ 扩大/
FMM、BMM、BIMM的分词结果都一样:
在/这/一/年中/中国/的/改革/开放/和/现代化/建设/继续/向前/迈进/国民经济/保持/了/高/增长/低/通胀/”/的/良好/发展/态势/农业/生产/再次/获得/好/的/收成/企业/改革/继续/深化/人民/生活/进一步/改善/对外/经济/技术/合作/与/交流/不断/扩大
正确率:47/48
召回率: 47/49
F-度量值:2*(47/48)*(47/49)/(47/48+47/49)