python_NLP实战之中⽂分词技术
⼀、规则分词
1.1 正向最⼤匹配算法
# 正向最⼤匹配算法 MM法规则分词
class MM(object):
def __init__(self):
self.window_size=3
def cut(self,text):
result=[]
index=0
text_length=len(text)
dic=['研究','研究⽣','⽣命','命','的','起源']
while text_length>index:
for size in range(self.window_size+index,index,-1):
piece=text[index:size]
if piece in dic:
index=size-1
break
index=index+1
result.append(piece+'-------')
print(result)
if __name__=='__main__':
text='研究⽣命的起源'
tokenizer=MM()
print(tokenizer.cut(text))
1.2 逆向最⼤匹配算法
# RMM逆向最⼤匹配算法规则分词
class RMM(object):
def __init__(self):
self.window_size=3
def cut(self,text):
result=[]
index=len(text)
dic=['研究','研究⽣','⽣命','命','的','起源']
while index>0:
for size in range(index-self.window_size,index):
piece=text[size:index]
if piece in dic:
index=size+1
break
index=index-1
result.append(piece+'------')
print(result)
if __name__=='__main__':
text = '研究⽣命的起源'
tokenizer = RMM()
print(tokenizer.cut(text))
⼆、统计分词
2.1 HMM模型
初始概率分布
z1可能是状态1,状态2 ... 状态n,于是z1就有个N点分布:
Z1状态1状态2...状态n
概率Pn
即:Z1对应个n维的向量。
上⾯这个n维的向量就是初始概率分布,记做π。
状态转移矩阵
但Z2就不能简单的“同上”完事了,因为Z2和Z1不独⽴,所以Z2是状态1的概率有:Z1是状态1时Z2是状态1,Z1是状态2时Z2是状态1,..., Z1是状态n时Z2是状态1,于是就是下⾯的表
状态1状态2...状态n
Z2
Z1
状态1P11P12 (1)
状态2P21P22 (2)
...............
状态Pnn
即:Z1->Z2对应个n*n的矩阵。
同理:Zi -> Zi+1对应个n*n的矩阵。
上⾯这些n*n的矩阵被称为状态转移矩阵,⽤An*n表⽰。
当然了,真要说的话,Zi -> Zi+1的状态转移矩阵⼀定都不⼀样,但在实际应⽤中⼀般将这些状态转移矩阵定为同⼀个,即:只有⼀个状态转移矩阵。
图1的第⼀⾏就搞定了,下⾯是第⼆⾏。
观测矩阵
如果对于zi有:状态1, 状态2, ..., 状态n,那zi的每⼀个状态都会从下⾯的m个观测中产⽣⼀个:观测1, 观测2, ..., 观测m,所以有如下矩阵:
观测1观测2...观测m
X
Z
状态1P11P12 (1)
状态2P21P22 (2)
...............
状态Pnm
这可以⽤⼀个n*m的矩阵表⽰,也就是观测矩阵,记做Bn*m。
由于HMM⽤上⾯的π,A,B就可以描述了,于是我们就可以说:HMM由初始概率分布π、状态转移概率分布A以及观测概率分布B确定,为了⽅便表达,把A, B, π ⽤ λ 表⽰,即:
λ = (A, B, π)
例⼦
假设我们相对如下这⾏话进⾏分词:
欢迎来到我的博客
再假设我们是这样分的:到“终⽌字”,然后根据终⽌字来分词。即:对于这⾏字,“迎、到、我、的、客”是终⽌字,于是最终这么分词:欢迎/来到/我/的/博客
下⾯⽤上⾯的知识对这个例⼦建⽴HMM的A, B, π:
初始概率分布的确定:
1,对于每个样本,我们的⽬标是确定其是不是“终⽌字”,因此对于每个样本,其状态只有n=2个:状态1 -- 是、状态2 -- 不是。
2,因此初始概率分布π为:
π = {p1,p2}
P1:整个句⼦中第⼀个字是⾮终⽌字的概率
P2:整个句⼦中第⼀个字是终⽌字的概率
状态转移矩阵的确定:
刚才已经知道状态有n=2个,于是状态转移矩阵就⽴马得出了,即状态转移矩阵是个n*n的矩阵,如下:
A=
p11:⾮终⽌字 -> ⾮终⽌字的概率。
p12:⾮终⽌字 -> 终⽌字的概率。
p21:终⽌字 -> ⾮终⽌字的概率。
p22:终⽌字 -> 终⽌字的概率。
观测矩阵的确定:
如果我们的⽬标⽂字使⽤Unicode编码,那么上⾯的任何⼀个字都是0~65535中的⼀个数,于是我们的观测就会有m=65536个,于是观测矩阵就是个n*m的矩阵,如下:
B=
p1,0:Unicode编码中0对应的汉字是⾮终⽌字的概率
p1,65535:Unicode编码中65535对应的汉字是⾮终⽌字的概率
p2,0:Unicode编码中0对应的汉字是终⽌字的概率
p2,65535:Unicode编码中65535对应的汉字是终⽌字的概率
PS:为什么x会有65535个观测啊?“欢迎来到我的博客”这个明明只有8个字。原因是因为真正的HMM⾯临的情况,即:现有了 Z1=“⾮终⽌字”这个状态,然后根据这个状态从65535个字中选出x1=“欢”这个字,然后根据状态转移矩阵,下⼀次转移到了Z2
=“终⽌字”,然后根据Z2从65535个字中选出了x2=“迎”这个字,这样,最终⽣成了这句话。
可详细参考:
# 统计分词
#  1、先建⽴语⾔模型
#  2、对句⼦进⾏单词划分,对划分结果进⾏概率计算,获得概率最⼤的分词⽅式
# HMM
class HMM(object):
def __init__(self):
import os
import os
# 保存训练的模型
# 状态特征值集合
self.state_list=['B','M','E','S']
# 判断是否需要重新加载模型
self.load_para=False
def try_load_model(self,trained):
if trained:
import pickle
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=True
else:
# 状态转移概率(状态-》状态的条件概率)
self.A_dic={}
# 发射概率(状态-》词语的条件概率
self.B_dic={}
# 状态的初始概率
self.Pi_dic={}
self.load_para=False
#        计算转移概率,初始概率,发射概率
def train(self,path):
# 重置⼏个概率矩阵
<_load_model(False)
# 统计状态出现次数
Count_dic={}
def init_parameters():
for state in self.state_list:
self.A_dic[state]={s:0.0 for s in self.state_list}                self.Pi_dic[state]=0.0
self.B_dic[state]={}
Count_dic[state]=0
def makeLabel(text):
out_text=[]
if len(text)==1:
out_text.append(['S'])
else:
import pickleout_text+=['B']+['M']*(len(text)-2)+['E']
return out_text
init_parameters()
line_num=-1
words=set()
with open(path,encoding='utf-8') as f:
for line in f:
line_num+=1
line=line.strip()
if not line:
continue
word_list=[i for i in line if i!='']
words |=set(word_list)
linelist=line.split()
line_state=[]
for w in linelist:
d(makeLabel(w))
assert len(word_list)==len(line_state)
for k, v in enumerate(line_state):
Count_dic[v]+=1
if k==0:
if k==0:
self.Pi_dic[v]+=1
else:
self.A_dic[line_state[k-1]][v]+=1
self.B_dic[line_state[k]][word_list[k]]=self.B_dic[line_state[k]].get(word_list[k],0)+1.0
self.Pi_dic={k: v*1.0/line_num for k,v in self.Pi_dic.items()}
self.A_dic={k:{k1: v1/Count_dic[k] for k1,v1 in v.items()}for k,v in self.A_dic.items()}
self.B_dic={k: {k1:(v1+1)/Count_dic[k] for k1,v1 in v.items()} for k,v in self.B_dic.items()}
import pickle
with del_file,'wb') as f:
pickle.dump(self.A_dic,f)
pickle.dump(self.B_dic,f)
pickle.dump(self.Pi_dic,f)
return self
def viterbi(self,text,states,start_p,train_p,emit_p):
V=[{}]
path={}
for y in states:
V[0][y]=start_p[y]*emit_p[y].get(text[0],0)
path[y]=[y]
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 in states:
emitP=emit_p[y].get(text[t],0) if not neverSeen else 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 in states])
return (prob,path[state])
def cut(self,text):
import os
if not self.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,0
for i ,char in enumerate(text):
pos=pos_list[i]
if pos=='B':
begin=i
elif pos=='E':
yield text[begin:i+1]
next=i+1
elif pos=='S':
yield char
next=i+1
if next<len(text):
yield text[next:]
hmm=HMM()
2.2 CRF

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。