一、规则分词
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+'------')
result.reverse()
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 |
概率 | P1 | P2 | ... | Pn |
即:Z1对应个n维的向量。
上面这个n维的向量就是初始概率分布,记做π。
状态转移矩阵
但Z2就不能简单的“同上”完事了,因为Z2和Z1不独立,所以Z2是状态1的概率有:Z1是状态1时Z2是状态1,Z1是状态2时Z2是状态1,..., Z1是状态n时Z2是状态1,于是就是下面的表
Z2 Z1 | 状态1 | 状态2 | ... | 状态n |
状态1 | P11 | P12 | ... | P1n |
状态2 | P21 | P22 | ... | P2n |
... | ... | ... | ... | ... |
状态n | Pn1 | Pn2 | ... | 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,所以有如下矩阵:
X Z | 观测1 | 观测2 | ... | 观测m |
状态1 | P11 | P12 | ... | P1m |
状态2 | P21 | P22 | ... | P2m |
... | ... | ... | ... | ... |
状态n | Pn1 | Pn2 | ... | 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=“迎”这个字,这样,最终生成了这句话。
可详细参考:https://www.cnblogs.com/sddai/p/8475424.html
# 统计分词
# 1、先建立语言模型
# 2、对句子进行单词划分,对划分结果进行概率计算,获得概率最大的分词方式
# HMM
class HMM(object):
def __init__(self):
import os
# 保存训练的模型
self.model_file='./data/hmm_model.pkl'
# 状态特征值集合
self.state_list=['B','M','E','S']
# 判断是否需要重新加载模型
self.load_para=False
def try_load_model(self,trained):
if trained:
import pickle
with open(self.model_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):
# 重置几个概率矩阵
self.try_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:
out_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:
line_state.extend(makeLabel(w))
assert len(word_list)==len(line_state)
for k, v in enumerate(line_state):
Count_dic[v]+=1
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 open(self.model_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:
self.try_load_model(os.path.exists(self.model_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()
hmm.train('./data/trainCorpus.txt_utf8')
2.2 CRF
三、中文分词工具_JieBa
jieba分词结合了基于规则和基于统计的两种方法
基于汉字成词的HMM模型,采用了Verterbi算法进行推导
3.1高频词提取
高频词就是NLP中的TF策略
进行数据的读取
def get_content(path):
with open(path,'r',encoding='utf-8',errors='ignore') as f:
content=''
for l in f:
l=l.strip()
content+=l
return content
def stop_words(path):
with open(path,encoding='utf-8') as f:
return [l.strip() for l in f]
定义高频词统计的函数,输入是一个词的数组
def get_TF(words,topK=10):
tf_dic={ }
for w in words:
tf_dic[w]=tf_dic.get(w,0)+1
return sorted(tf_dic.items(),key =lambda x:x[1],reverse=True)[:topK]
def main():
import glob
import random
import jieba
files=glob.glob('./data/news/C000013/*.txt')
corpus=[get_content(x) for x in files]
sample_inx=random.randint(0,len(corpus))
split_words=[x for x in jieba.cut(corpus[sample_inx]) if x not in stop_words('./data/stop_words.utf8')]
print('样本之一:' + corpus[sample_inx])
print('样本分词效果:' + '/ '.join(split_words))
print('样本的topK(10)词:' + str(get_TF(split_words)))
main()