jieba分词流程及部分源码解读(一)
首先我們來看一下jieba分詞的流程圖:
?
結(jié)巴中文分詞簡介
?? 1)支持三種分詞模式:
精確模式:將句子最精確的分開,適合文本分析
全模式:句子中所有可以成詞的詞語都掃描出來,速度快,不能解決歧義
搜索引擎模式:在精確的基礎(chǔ)上,對(duì)長詞再次切分,提高召回
?? 2)支持繁體分詞
?? 3)支持自定義詞典
? ?4)基于Trie樹結(jié)構(gòu)實(shí)現(xiàn)高效的詞圖掃描,生成句子漢字所有可能成詞情況所構(gòu)成的有向無環(huán)圖(DAG)
? ?5)? 采用了動(dòng)態(tài)規(guī)劃查找最大概率路徑,找出基于詞頻的最大切分組合 6)對(duì)于詞庫中不存在的詞,也就是未登錄詞,采用了基于漢字成詞能力的HMM模型,使用了Viterbi算法
接下來我們從源碼分析一下:
從github上下載源代碼后,打開 文件夾 jieba,找到__init__.py,結(jié)巴分詞最主要的函數(shù) cut 就定義在這個(gè)文件中。
__cut_DAG 函數(shù)調(diào)用了?get_DAG(sentence),這是用來生成每一塊(sentence)的有向無環(huán)圖DAG。要生成DAG就必須有語料庫的輔助了,所以在 同樣在 文件夾 jieba 下,可以找到一個(gè)文件:dict.txt。語料庫的有3列,第一列是詞,第二列是詞頻,第三列是詞性。在程序中初始化語料庫的動(dòng)作在函數(shù) ?initialize(DICTIONARY) 中,它通過一個(gè)包裝器?require_initialized 在 get_DAG 函數(shù)被調(diào)用的時(shí)候才執(zhí)行。代碼如下:
def require_initialized(fn):
 @wraps(fn) #wraps的作用是保留被包裝函數(shù)的一些屬性,比如__doc__
 def wrapped(*args, **kwargs):
 global initialized
 if initialized:
 return fn(*args, **kwargs)
 else:
 initialize(DICTIONARY)
 return fn(*args, **kwargs)
 return wrapped
有向無環(huán)圖構(gòu)建
語料庫Trie樹加載完畢后,接下來我們來介紹如何進(jìn)行DAG分詞
以“正在學(xué)習(xí)大數(shù)據(jù)中的結(jié)巴分詞”為例,作為待分詞的輸入文本。
jieba.__init__.py中實(shí)現(xiàn)了jieba分詞接口函數(shù)cut(self, sentence, cut_all=False, HMM=True)。
jieba分詞接口主入口函數(shù),會(huì)首先將輸入文本解碼為Unicode編碼,然后根據(jù)入?yún)?#xff0c;選擇不同的切分方式,本文主要以精確模式進(jìn)行講解,因此cut_all和HMM這兩個(gè)入?yún)⒕鶠槟J(rèn)值;
?
分詞中g(shù)et_DAG函數(shù)實(shí)現(xiàn)如下
# -*- coding: utf-8 -*-
import marshal
def get_DAG(sentence):
 N = len(sentence)
 i,j=0,0
 p = trie
 DAG = {}
 while i<N:
 c = sentence[j]
 if c in p:
 p = p[c]
 if '' in p:
 if i not in DAG:
 DAG[i]=[]
 DAG[i].append(j)
 j+=1
 if j>=N:
 i+=1
 j=i
 p=trie
 else:
 p = trie
 i+=1
 j=i
 for i in xrange(len(sentence)):
 if i not in DAG:
 DAG[i] =[i]
 return DAG
 #動(dòng)態(tài)規(guī)劃,計(jì)算最大概率的切分組合
 def calc(self, sentence, DAG, route):
 N = len(sentence)
 route[N] = (0, 0)
 # 對(duì)概率值取對(duì)數(shù)之后的結(jié)果(可以讓概率相乘的計(jì)算變成對(duì)數(shù)相加,防止相乘造成下溢)
 logtotal = log(self.total)
 # 從后往前遍歷句子 反向計(jì)算最大概率
 for idx in xrange(N - 1, -1, -1):
 # 列表推倒求最大概率對(duì)數(shù)路徑
 # route[idx] = max([ (概率對(duì)數(shù),詞語末字位置) for x in DAG[idx] ])
 # 以idx:(概率對(duì)數(shù)最大值,詞語末字位置)鍵值對(duì)形式保存在route中
 # route[x+1][0] 表示 詞路徑[x+1,N-1]的最大概率對(duì)數(shù),
 # [x+1][0]即表示取句子x+1位置對(duì)應(yīng)元組(概率對(duì)數(shù),詞語末字位置)的概率對(duì)數(shù)
 route[idx] = max((log(self.FREQ.get(sentence[idx:x + 1]) or 1) -
 logtotal + route[x + 1][0], x) for x in DAG[idx])
if __name__=='__main__':
 sentence=u'正在學(xué)習(xí)大數(shù)據(jù)中的結(jié)巴分詞'
 trie,FREQ,total,min_freq = marshal.load(open(u'D:\jieba.cache','rb'))#使用緩存載入重要變量
 rs=get_DAG(sentence)#獲取DAG
 route={}
 calc(sentence,rs,0,route)#根據(jù)得分進(jìn)行初步分詞
 print route
基于詞頻最大切分組合(從上面get_DAG中部分代碼詳解)
我們已經(jīng)有了詞庫(dict.txt)的前綴字典和待分詞句子sentence的DAG,基于詞頻的最大切分 要在所有的路徑中找出一條概率得分最大的路徑,該怎么做呢??
jieba中的思路就是使用動(dòng)態(tài)規(guī)劃方法,從后往前遍歷,選擇一個(gè)頻度得分最大的一個(gè)切分組合。?
具體實(shí)現(xiàn)見代碼,已給詳細(xì)注釋。
 #動(dòng)態(tài)規(guī)劃,計(jì)算最大概率的切分組合
 def calc(self, sentence, DAG, route):
 N = len(sentence)
 route[N] = (0, 0)
 # 對(duì)概率值取對(duì)數(shù)之后的結(jié)果(可以讓概率相乘的計(jì)算變成對(duì)數(shù)相加,防止相乘造成下溢)
 logtotal = log(self.total)
 # 從后往前遍歷句子 反向計(jì)算最大概率
 for idx in xrange(N - 1, -1, -1):
 # 列表推倒求最大概率對(duì)數(shù)路徑
 # route[idx] = max([ (概率對(duì)數(shù),詞語末字位置) for x in DAG[idx] ])
 # 以idx:(概率對(duì)數(shù)最大值,詞語末字位置)鍵值對(duì)形式保存在route中
 # route[x+1][0] 表示 詞路徑[x+1,N-1]的最大概率對(duì)數(shù),
 # [x+1][0]即表示取句子x+1位置對(duì)應(yīng)元組(概率對(duì)數(shù),詞語末字位置)的概率對(duì)數(shù)
 route[idx] = max((log(self.FREQ.get(sentence[idx:x + 1]) or 1) -
 logtotal + route[x + 1][0], x) for x in DAG[idx])
從代碼中可以看出calc是一個(gè)自底向上的動(dòng)態(tài)規(guī)劃(重疊子問題、最優(yōu)子結(jié)構(gòu)),它從sentence的最后一個(gè)字(N-1)開始倒序遍歷sentence的字(idx)的方式(為什么倒敘遍歷,不懂的可以留言或是找我小豬),計(jì)算子句sentence[isdx~N-1]概率對(duì)數(shù)得分(這里利用DAG及歷史計(jì)算結(jié)果route實(shí)現(xiàn))。然后將概率對(duì)數(shù)得分最高的情況以(概率對(duì)數(shù),詞語最后一個(gè)字的位置)這樣的tuple保存在route中。?
那么登陸詞部分解釋完畢,下來就是未登陸詞,利用Viterbi算法來解決未登錄詞的處理方法,后續(xù)更新
--------------------- 
作者:Jameslvt 
來源:CSDN 
原文:https://blog.csdn.net/Jameslvt/article/details/81118560 
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請(qǐng)附上博文鏈接!
總結(jié)
以上是生活随笔為你收集整理的jieba分词流程及部分源码解读(一)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: Python os.getcwd() 方
 - 下一篇: Python isinstance()