jieba分词的原理

 jieba介绍:

一、支持三种分词模式:

  • 精确模式,试图将句子最精确地切开,适合文本分析;
  • 全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
  • 搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。

二、jieba自带了一个叫做dict.txt的词典, 里面有2万多条词, 包含了词条出现的次数(这个次数是于作者自己基于人民日报语料等资源训练得出来的)和词性. 这个第一条的trie树结构的词图扫描, 说的就是把这2万多条词语, 放到一个trie树中, 而trie树是有名的前缀树, 也就是说一个词语的前面几个字一样, 就表示他们具有相同的前缀, 就可以使用trie树来存储, 具有查找速度快的优势。

三、jieba分词应该属于概率语言模型分词

       概率语言模型分词的任务是:在全切分所得的所有结果中求某个切分方案S,使得P(S)最大。

jieba用到的算法:

一、基于Trie树结构实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图(DAG)

       1. 根据dict.txt生成trie树。字典在生成trie树的同时, 也把每个词的出现次数转换为了频率;

       2. 对待分词句子, 根据dict.txt生成的trie树, 生成DAG, 实际上通俗的说, 就是对待分词句子, 根据给定的词典进行查词典操作, 生成几种可能的句子切分。jieba的作者在DAG中记录的是句子中某个词的开始位             置, 从0到n-1(n为句子的长度), 每个开始位置作为字典的键, value是个list, 其中保存了可能的词语的结束位置(通过查字典得到词, 开始位置+词语的长度得到结束位置)注:所以可以联想到,jieba支持全模             式分词,能把句子中所有的可以成词的词语都扫描出来

       例如:{0:[1,2,3]} 这样一个简单的DAG, 就是表示0位置开始, 在1,2,3位置都是词, 就是说0~1, 0~2,0~3这三个起始位置之间的字符, 在dict.txt中是词语.可看示例切分词图

二、采用了动态规划查找最大概率路径, 找出基于词频的最大切分组合

       1.查找待分词句子中已经切分好的词语(我觉得这里应该是全模式下的分词list), 对该词语查找该词语出现的频率(次数/总数), 如果没有该词(既然是基于词典查找进行的分词, 应该是有的), 就把词典中出现频率最小的那个词语的频率作为该词的频率, 也就是说P(某词语)=FREQ.get(‘某词语’,min_freq)

       2.根据动态规划查找最大概率路径的方法, 对句子从右往左反向计算最大概率(一些教科书上可能是从左往右, 这里反向是因为汉语句子的重心经常落在后面, 就是落在右边, 因为通常情况下形容词太多, 后面的才是主干, 因此, 从右往左计算, 正确率要高于从左往右计算, 这个类似于逆向最大匹配), P(NodeN)=1.0, P(NodeN-1)=P(NodeN)*Max(P(倒数第一个词))…依次类推, 最后得到最大概率路径, 得到最大概率的切分组合.

 1 def calc(sentence,DAG,idx,route):  #动态规划,计算最大概率的切分组合  
 2     #输入sentence是句子,DAG句子的有向无环图  
 3     N = len(sentence)  #句子长度  
 4     route[N] = (0.0,'')  
 5     for idx in xrange(N-1,-1,-1):  #和range用法一样,不过还是建议使用xrange  
 6         #可以看出是从后往前遍历每个分词方式的  
 7   
 8         #下面的FREQ保存的是每个词在dict中的频度得分,打分的公式是 log(float(v)/total),其中v就是被打分词语的频数  
 9          #FREQ.get(sentence[idx:x+1],min_freq)表示,如果字典get没有找到这个key,那么我们就使用最后的frequency来做  
10          #由于DAG中是以字典+list的结构存储的,所以确定了idx为key之外,  
11          #仍然需要for x in DAG[idx]来遍历所有的单词结合方式(因为存在不同的结合方法,例如“国”,“国家”等)  
12          #以(频度得分值,词语最后一个字的位置)这样的tuple保存在route中  
13         candidates = [ ( FREQ.get(sentence[idx:x+1],min_freq) + route[x+1][0] , x ) for x in DAG[idx] ]  
14         route[idx] = max(candidates) 

 

三、对于未登录词,采用了基于汉字成词能力的HMM模型,使用了Viterbi算法

        未登录词:词典 dict.txt 中没有记录的词(注: 就算把dict.txt中所有的词汇全部删掉, jieba依然能够分词, 不过分出来的词, 大部分的长度为2.这个就是基于HMM来预测分词了)

        1.中文词汇按照BEMS四个状态来标记, B是开始begin位置, E是end, 是结束位置, M是middle, 是中间位置, S是singgle, 单独成词的位置, 没有前, 也没有后. jieba作者就是采用了状态为(B,E,M,S)这四种状态来标记中文词语, 比如北京可以标注为 BE, 即 北/B 京/E, 表示北是开始位置, 京是结束位置, 中华民族可以标注为BMME, 就是开始, 中间, 中间, 结束.

        2.作者对大量语料进行训练, 得到了三个概率表。训通过练得到的概率表和viterbi算法, 就可以得到一个概率最大的BEMS序列, 按照B打头, E结尾的方式, 对待分词的句子重新组合, 就得到了分词结果. 比如 对待分词的句子 ‘全世界都在学中国话’ 得到一个BEMS序列 [S,B,E,S,S,S,B,E,S] 这个序列只是举例, 不一定正确, 通过把连续的BE凑合到一起得到一个词, 单独的S放单, 就得到一个分词结果了: 上面的BE位置和句子中单个汉字的位置一一对应, 得到全/S 世界/BE 都/S 在/S 学/S 中国/BE 话/S 从而将句子切分为词语.

jeiba分词过程:

生成全切分词图:根据trie树对句子进行全切分,并且生成一个邻接链表表示的词图(DAG),查词典形成切分词图的主体过程如下所示:

 1 for(int i=0;i<len;){  
 2     boolean match = dict.getMatch(sentence, i,
 3 wordMatch);//到词典中查询   
 4     if (match) {// 已经匹配上  
 5         for (String word:wordMatch.values)
 6 {//把查询到的词作为边加入切分词图中  
 7             j = i+word.length();  
 8             g.addEdge(new CnToken(i, j, 10, word));  
 9         }  
10         i=wordMatch.end;  
11     }else{//把单字作为边加入切分词图中  
12         j = i+1;  
13         g.addEdge(new CnToken(i,j,1,sentence.substring(i,j)));  
14         i=j;  
15     }  
16 }  

计算最佳切分路径:在这个词图的基础上,运用动态规划算法生成切分最佳路径。

使用了HMM模型对未登录词进行识别:如进行中国人名、外国人名、地名、机构名等未登录名词的识别。

重新计算最佳切分路径。

以上整理于对Python中文分词模块结巴分词算法过程的理解和分析

疑问:

1.识别出的未登录词貌似是没有频率的以及用户自定义词典中词频和词性都是可省略的,那jieba重新计算最佳切分组合是怎么算的?,是把词典中频率最小的那个词语的频率作为该词的频率?

答:词频省略时,jieba会自动计算词频

 

posted @ 2017-12-03 20:46  hello_echo  阅读(38546)  评论(0编辑  收藏  举报