python中⽂⾃然语⾔处理_Python⾃然语⾔处理(1)中⽂分词
技术
中⽂分词技术
中⽂⾃动分词可主要归纳为“规则分词”“统计分词”和“混合分词”,规则分词主要是通过⼈⼯设⽴词库,按照⼀定⽅式进⾏匹配切分,实现简单⾼效,但对新词很难进⾏处理,统计分词能够较好应对新词发现能特殊场景,但太过于依赖语料的质量,因此实践中多是采⽤两者的结合,即混合分词。
1.1 规则分词
基于规则的分词是⼀种机械分词⽅法,主要是通过维护词典,在切分语句时,将语句的每个字符串与词表中的词进⾏逐⼀匹配,到则切分,否则不予切分。
按照匹配切分的⽅式,主要有正向最⼤匹配法、逆向最⼤匹配法以及双向最⼤匹配法三种⽅法。
1.1.1 正向最⼤匹配法
正向最⼤匹配(Maximum Match Method, MM)法的基本思想为:假定分词词典中的最长词有 i 个汉字
字符,则⽤被处理⽂档的当前字串中的前 i 个字作为匹配字段,查字典。若字典中存在这样的⼀个 i 字词,则匹配成功,匹配字段被作为⼀个词切分出来。如果字典中不到这样的⼀个 i 字词,则匹配失败,将匹配字段中的最后⼀个字去掉。对剩下的字串重新进⾏匹配处理。如此进⾏下去,直到匹配成功,即切分出⼀个词或剩余字串的长度为零为⽌,这样就完成了⼀轮匹配,然后取下⼀个 i 字字串进⾏匹配处理,直到⽂档被扫描完为⽌。⽰例代码如下:
classMM(object):def __init__(self, dic_path):
self.dictionary=set()
self.maximum=0
with open(dic_path,'r', encoding="utf8") as f:for line inf:
line=line.strip()if notline:continueself.dictionary.add(line)
self.maximum=max(self.maximum, len(line))defcut(self, text):
result=[]
index=0
text_length=len(text)while text_length >index:for size in range(self.maximum + index, index, -1):
piece=text[index:size]if piece inself.dictionary:
index= size - 1
breakindex= index + 1result.append(piece+ '----')print(result)if __name__ == '__main__':
text= '研究⽣命的起源'tokenizer=MM('./data/mm_dic.utf8')print(tokenizer.cut(text))
1.1.2 逆向最⼤匹配法
逆向最⼤匹配(Reverse Maximum Match Method, RMM 法)的基本原理与 MM 法相同,不同的是分词切分的⽅向与 MM 法相反。逆向最⼤匹配法从被处理⽂档的末端开始匹配扫描,每次取最末端的 i 个字符(i 为词典中最长词数)作为匹配字段,若匹配失败,则去掉匹配字段最前⾯的⼀个字,继续匹配。相应地,它使⽤的分词词典是逆序词典,其中的每个词都将按逆序⽅式存放。在实际处理时,先将⽂档进⾏倒排处理,⽣成逆序⽂档。然后,根据逆序词典,对逆序⽂档⽤正向最⼤匹配法处理即可。
1.1.3 双向最⼤匹配法
双向最⼤匹配法(Bi-direction Matching Method)是将正向最⼤匹配法得到的分词结果和逆向最⼤匹配法得到的结果进⾏⽐较,然后按照最⼤匹配原则,选取词数切分最少的作为结果。
双向最⼤匹配的规则是:
如果正反向分词结果词数不同,则取分词数量较少的那个(如“南京市/长江/⼤桥”的分词数量为3,⽽“南京市/长江⼤桥”的分词数量为2,所以返回分词数量为2的)。
如果分词结果词数相同:1)分词结果相同,就说明没有歧义,可返回任意⼀个;2)分词结果不同,返回其中单字较少的那个。
1.2 统计分词
基于统计的中⽂分词算法主要思想是把每个词看做是由词的最⼩单位的各个字组成的,如果相连的字在不同的⽂本中出现的次数越多,就证明这相连的字很可能就是⼀个词。
基于统计的分词,⼀般要做如下两步操作:建⽴统计语⾔模型。
对句⼦进⾏单词划分,然后对划分结果进⾏概率计算,获得概率最⼤的分词⽅式。这⾥就⽤到了统计学习算法,如隐含马尔可夫(HMM)、条件随机场(CRF)等。
1.2.1 HMM 模型
隐含马尔可夫(HMM)是将分词作为字在字串中的序列标注任务来实现的。其基本思路是:每个字在构造⼀个特定的词语时都占据着⼀个特定的构词位置,现规定每个字最多只有四个构词位置:即B(词⾸)、M(词中)、E(词尾)和 S(单独成词),那么下⾯句⼦1)的分词结果可以表⽰成2)所⽰的逐字标注形式:
1)中⽂/分词/是/.⽂本处理/不可或缺/的/⼀步!
2)中/B⽂/E分/B词/E是/S⽂/B本/M处/M理/M不/B可/M或/M缺/E的/S⼀/B步/E!/S
⽤数学抽象表⽰如下:⽤ λ = λ1λ2...λn代表输⼊的句⼦,n 为句⼦长度, λi表⽰字,ο = ο1ο2...οn 代表输出的标签,那么理想的输出即为:
max = maxP(ο1ο2...οn|λ1λ2...λn)
在分词任务上,ο 即为B、M、E、S 这 4 种标记,λ 为诸如“中”、“⽂”等句⼦中的每个字(包括标点等⾮中⽂字符)。引⼊观测独⽴性假设,即每个字的输出仅仅与当前字有关,于是就能得到下式:
P(ο1ο2...οn|λ1λ2...λn) = P(ο1|λ1)P(ο2|λ2)...P(οn|λn)
通过贝叶斯公式得到:
P(ο|λ) = P(ο, λ)/P(λ) = P(λ|ο)P(ο)/P(λ)
λ 为给定的输⼊,因此 P(λ) 计算为常数,可以忽略,因此最⼤化 P(ο|λ) 等价于最⼤化 P(λ|ο)P(ο)。针对 P(λ|ο)P(ο) 作马尔可夫假设,得到:
P(λ|ο) = P(λ1|ο1)P(λ2|ο2)...P(λn|οn)
对 P(ο) 做齐次马尔可夫假设,每个输出仅仅与上⼀个输出有关,那么:
P(ο) = P(ο1)P(ο2|ο1)P(ο3|ο2)...P(οn|οn-1)
于是:
P(λ|ο)P(ο) ~ P(λ1|ο1)P(ο2|ο1)P(λ2|ο2)P(ο3|ο2)...P(οn|οn-1)P(λn|οn)
在 HMM 中,将 P(λk|οk) 称为发射概率,P(οk|οk-1) 称为转移概率。通过设置某些 P(οk|οk-1) = 0,可以排除类似 BBB、EM等不合理的组合。
在 HMM 中,求解 maxP(λ|ο)P(ο) 的常⽤⽅法是 Veterbi 算法。它是⼀种动态规划⽅法,核⼼思想是:
如果最终的最优路径经过某个οi,那么从初始节点到 οi-1点的路径必然也是⼀个最优路径——因为每⼀个节点 οi只会影响前后两个 P(οi-1|οi) 和 P(οi|οi+1)。
下⾯是通过 python 来实现的 HMM,将其封装成⼀个类:
classHMM(object):def __init__(self):
self.load_para=Falsedeftry_load_model(self, trained):iftrained:importpickle
with del_file,'rb') as f:
self.A_dic=pickle.load(f)
self.B_dic=pickle.load(f)
self.Pi_dic=pickle.load(f)
self.load_para=Trueelse:
python官方文档中文版self.A_dic={}
self.B_dic={}
self.Pi_dic={}
self.load_para=Falsedeftrain(self, path):
<_load_model(False)
Count_dic={}definit_parameters():for state inself.state_list:
self.A_dic[state]= {s: 0.0 for s inself.state_list}
self.Pi_dic[state]= 0.0self.B_dic[state]={}
Count_dic[state]=0defmakeLabel(text):
out_text=[]if len(text) == 1:
out_text.append('S')else:
out_text+= ['B'] + ['M'] * (len(text) - 2) + ['E']returnout_text
init_parameters()
line_num= -1words=set()
with io.open(path, encoding='utf8') as f:for line inf:
line_num+= 1line=line.strip()if notline:continueword_list= [i for i in line if i != ' ']
words|=set(word_list)
linelist=line.split()
line_state=[]for w inlinelist:
d(makeLabel(w))assert(len(word_list) ==len(line_state))for k, v inenumerate(line_state):
Count_dic[v]+= 1
if k ==0:
self.Pi_dic[v]+= 1
else:
self.A_dic[line_state[k-1]][v] += 1self.B_dic[line_state[k]][word_list[k]]=\
self.B_dic[line_state[k]].get(word_list[k], 0)+ 1.0self.Pi_dic= {k: v * 1.0 / line_num for k, v inself.Pi_dic.items()} self.A_dic= {k: {k1: v1 / Count_dic[k] for k1, v1 inv.items()}for k, v inself.A_dic.items()}
self.B_dic= {k: {k1: (v1 + 1) / Count_dic[k] for k1, v1 inv.items()}for k, v inself.B_dic.items()}importpickle
with del_file,'wb') as f:
pickle.dump(self.A_dic, f)
pickle.dump(self.B_dic, f)
pickle.dump(self.Pi_dic, f)returnselfdefviterbi(self, text, states, start_p, trans_p, emit_p):
V=[{}]
path={}for y instates:
V[0][y]= start_p[y] *emit_p[y].get(text[0], 0)
path[y]=[y]print(V)print(path)for t in range(1, len(text)):
V.append({})
newpath={}
neverSeen= text[t] not in emit_p['S'].keys() and\
text[t]not in emit_p['M'].keys() and\
text[t]not in emit_p['E'].keys() and\
text[t]not in emit_p['B'].keys()for y instates:
emitP= emit_p[y].get(text[t], 0) if not neverSeen else 1.0(prob, state)=max(
[(V[t-1][y0] * trans_p[y0].get(y, 0) * emitP, y0) for y0 in states if V[t-1][y0] >0]
)
V[t][y]=prob
newpath[y]= path[state] +[y]
path=newpathif emit_p['M'].get(text[-1], 0) > emit_p['S'].get(text[-1], 0):
(prob, state)= max([(V[len(text) - 1][y], y) for y in ('E', 'M')])else:
(prob, state)= max([(V[len(text) - 1][y], y) for y instates])return(prob, path[state])defcut(self, text):importosif notself.load_para:
<_load_model(del_file))
prob, pos_list=self.viterbi(text, self.state_list, self.Pi_dic, self.A_dic, self.B_dic)
begin, next=0, 0for i, char inenumerate(text):
pos=pos_list[i]if pos == 'B':
begin=ielif pos == 'E':yield text[begin: i+1]
next= i + 1
elif pos == 'S':yieldchar
next= i + 1
if next
hmm=HMM()#ain('./_utf8')
text = '这是⼀个⾮常棒的⽅案'res=hmm.cut(text)print(list(res))
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论