赞
踩
基于统计的中文分词主要思想是:利用字与字相邻出现的频率来反应成词的可靠度,统计语料中相邻共现的各个字的组合的频度,当组合频度高于某一个临界值时,我们便可认为此字组可能会构成一个词语。
基于统计的分词一般有两个步骤:
1)建立统计语言模型;
2)对句子进行单词划分,然后对划分结果进行概率计算,获得概率最大的分词方式。
一、语言模型
语言模型用概率论的术语来描述就是:为长度为m 的字符串确定其概率分布,其中
到
依次表示文本中的各个词语。一般采用链式法则计算概率值,如下:
=
由于文本过长时,计算难度增大,所以提出了n元模型来降低计算难度。n元模型是值在估算条件概率时,忽略距离大于等于n的上文词的影响,因此上式可简化为:
当n=1时称为一元模型,在一元模型中,整个句子的概率等于各个词语概率的乘积。这样就完全损失了句中的词序信息。
当n=2时称为二元模型,当n=3时称为三元模型。当n>=2时,模型可以保留一定的词序信息,n越大,保留的词序信息越丰富,但计算成本也成指数级增长。
二、隐马尔科夫模型(HMM)
隐马尔科夫模型(HMM)是将分词作为字在字符串中的序列标注任务来实现的。基本思路是:每个字在构造一个特定的词语时都占据着一个特定的构词位置(即词位),规定每个字最多只能有四个构词位置:B(词首)、M(词中)、E(词尾)、S(单独成词)。
例如:中文/分词/是/文本处理/不可或缺/的/一步!
逐字标注:中/B文/E分/B词/E是/S文/B本/M处/M理/E不/B可/M或/M缺/E的/S一/B步/E!/S
用公式表示如下:
其中,表示输入的句子,n是句子长度,
表示字,
代表输出的标签。
在分词时,o是B、E、M、S这四种标记,为句子中的每个字(包含标点等非中文字符)。
通过贝叶斯公式可得:
其中为给定的输入,
计算为常数,可以忽略,因此最大化
等价于最大化
。
接下来要运用到隐马尔科夫模型的两个基本假设:
1)观测独立性假设,即任意时刻的观测只依赖该时刻的状态:
2)齐次马尔科夫假设,即当前输出只与上一个输出有关:
因此,
我们将称为发射概率,
称为状态转移概率,初始概率
。
求解max的常用方法是维特比算法。它是一种动态规划方法,核心思想是:如果最终的最优路径经过某个
,那么从初始节点到
的路径必然也是一个最优路径,因为每一个节点
只会影响前后两个
和
。根据递推的方法,在考虑每个
时只需要求出所有经过各
点的最优路径,然后再与当前
结合比较,找出最优路径。
下面通过python实现HMM,这里的语料格式是:每行一句话,每个词以空格分隔。
- # coding=utf-8
- #根据HMM来进行分词
- class HMM():
- def __init__(self):
- self.pi_prob={}
- self.tran_state={}
- self.emit_prob={}
- self.state_set=['B','M','E','S'] #状态集合
- #根据训练样本来计算初始概率、状态转移概率、发射概率
- def calculate_prob(self):
- count_state = {}
- #初始化初始概率、状态转移概率、发射概率
- for state in self.state_set:
- self.pi_prob[state]=0.0
- self.tran_state[state]={s: 0.0 for s in self.state_set}
- self.emit_prob [state]={}
- count_state[state]=0.0
- #获取训练语料中各个字的词位标记
- def get_marks(w):
- out_text = []
- if len(w) == 1:
- out_text.append('S')
- else:
- out_text += ['B'] + ['M'] * (len(w) - 2) + ['E']
- #print(out_text)
- return out_text
- with open('train.txt',encoding='utf-8') as f:
- line_num=0
- for line in f:
- line_num +=1
- if not line:
- continue
- line=line.strip()
- word_list =[word for word in line if word !=' ']
- #print(word_list)
- state_list=[]
- line_list=line.split()
- for w in line_list:
- state_list.extend(get_marks(w))
- #print(state_list)
- assert len(word_list) == len(state_list)
- #断言,条件为True时正常运行,条件为false时,它先向stderr 打印一条出错信息,然后
- 通过调用 abort 来终止程序运行
- for k,v in enumerate(state_list ): #迭代状态列表,K是索引,v是状态
- count_state[v]+=1
- if k==0:
- self.pi_prob[v]+=1
- #每个句子第一个字的状态统计,用于计算初始状态概率
- else:
- self.tran_state[state_list[k-1]][v]+=1 #计算转移概率
- self.emit_prob[state_list[k]][word_list[k]]=self.emit_prob[state_list[k]].get(word_list[k],0)+1.0 #计算发射概率
- #print(self.emit_prob)
- self.pi_prob={s:self.pi_prob [s]/line_num for s in self.state_set}
- print("初始概率:",self.pi_prob)
- self.tran_state ={k:{k1:(v1+1)/count_state[k] for k1,v1 in v.items()} for k,v in self.tran_state.items()} #加1平滑
- print("转移概率:",self.tran_state)
- self.emit_prob ={k:{k1:v1/count_state[k] for k1,v1 in v.items()} for k,v in self.emit_prob.items()}
- print("发射概率:", self.emit_prob)
- return self
- #维特比算法(动态规划求最大路径)根据上述求得的三种概率计算输入文本的分词结果
- def viterb(self,text):
- path=[]
- v=[{}]
- si_gama=[{}]
- #当t=1时,计算t=1时刻各个状态产生各个观测结果的概率
- for state in self.state_set:
- si_gama[0][state]=self.pi_prob[state] * self.emit_prob[state].get(text[0],0)
- #print(si_gama[0])
- #当t>=2时,计算最大概率路径
- for t in range(1,len(text)):
- # 检验训练的发射概率矩阵中是否有该字
- neverSeen = text[t] not in self.emit_prob['S'].keys() and \
- text[t] not in self.emit_prob['M'].keys() and \
- text[t] not in self.emit_prob['E'].keys() and \
- text[t] not in self.emit_prob['B'].keys()
- si_gama.append({})
- v.append({})
- for state in self.state_set:
- emit = self.emit_prob[state].get(text[t], 0) if not neverSeen else 1 #设置未知字单独成词
- (si_gama[t][state],j)=max([(si_gama[t-1][y0] * self.tran_state[y0].get(state,0) * emit,y0) \
- for y0 in self.state_set if (si_gama[t - 1][y0]) > 0])
- v[t][state]=j
- #根据最优路径概率获取最优路径终点,再逆向寻找其他节点
- p=max(si_gama[-1], key=si_gama[-1].get)
- path.append(p)
- for i in range(len(text) - 1, 0, -1):
- p2=v[i].get(p)
- p=p2
- path.append(p)
- path.reverse()
- print(path)
- return path
-
- #根据序列标注来展示分词结果
- def cut_text(self,text,path):
- s=''
- for i,p in enumerate(path):
- if p == 'B' or p=='M':
- s+=text[i]
- elif p =='E' or p=='S':
- s=s+text[i]+'/'+' '
- print("原文本:",text)
- print("分词结果:",s)
-
- if __name__ =='__main__':
- model=HMM()
- model.calculate_prob()
- text="这是一年里股市的最高纪录"
- path=model.viterb( text)
- model.cut_text(text, path)
-
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。