從0開始的jieba分詞原始碼分析_1_從cut開始
阿新 • • 發佈:2018-12-17
從一個函式入口逐步分析分詞的整個過程,最後對關鍵函式做了簡化實現,附在最後供大家參考
分析
尋根
import jieba
jieba.cut("sentence")
查詢cut的引用:
dt = Tokenizer()
cut = dt.cut#位於jieba/__init__.py
找到cut()方法,首先裡面有很多re開頭的變數,這些都是用於文字和字元分割用的re的例項
# \u4E00-\u9FD5a-zA-Z0-9+#&\._ : All non-space characters. Will be handled with re_han
# \r\n|\s : whitespace characters. Will not be handled.
re_han_default = re.compile("([\u4E00-\u9FD5a-zA-Z0-9+#&\._%]+)", re.U)
re_skip_default = re.compile("(\r\n|\s)", re.U)
re_han_cut_all = re.compile("([\u4E00-\u9FD5]+)", re.U)
re_skip_cut_all = re.compile("[^a-zA-Z0-9+#\n]", re.U)
def cut(self, sentence, cut_all=False, HMM=True):
'''
The main function that segments an entire sentence that contains
Chinese characters into seperated words.
Parameter:
- sentence: The str(unicode) to be segmented.
- cut_all: Model type. True for full pattern, False for accurate pattern.
- HMM: Whether to use the Hidden Markov Model.
'''
sentence = strdecode(sentence)
if cut_all:
re_han = re_han_cut_all
re_skip = re_skip_cut_all
else:
re_han = re_han_default
re_skip = re_skip_default
if cut_all:
cut_block = self.__cut_all
elif HMM:
cut_block = self.__cut_DAG
else:
cut_block = self.__cut_DAG_NO_HMM
blocks = re_han.split(sentence)#將文字和非文字分開成塊
'''
提供一個切分示例:
stri = "我去北京玩遊戲,去了北京找人"
re_han_default.split(stri)
output:
['', '我去北京玩遊戲', ',', '去了北京找人', '']
'''
for blk in blocks:
if not blk:#如果為空
continue
if re_han.match(blk):#如果不是符號
for word in cut_block(blk):
yield word
else:#對字元的處理
tmp = re_skip.split(blk)
for x in tmp:
if re_skip.match(x):
yield x
elif not cut_all:
for xx in x:
yield xx
else:
yield x
我們知道cut方法返回的是一個迭代器,也就是裡面每一個yield返回的值了
可以看到對文字的處理關鍵在:
if re_han.match(blk):#如果不是符號
for word in cut_block(blk):
yield word
找到cut_block,一共有三種實現,依次看一下
if cut_all:
cut_block = self.__cut_all
elif HMM:
cut_block = self.__cut_DAG
else:
cut_block = self.__cut_DAG_NO_HMM
__cut_all
首先是__cut_all,裡面涉及到方法get_DAG()
,這個方法其實就是得到文字的有向無環圖,這個方法我將其稍加修改,將其中涉及到類的引數作為方法變數傳入:
def get_DAG(sentence,FREQ):
#FREQ是{詞:詞頻}的字典,類似於{'北京大學': 2053, '北': 0, '北京': 0, '北京大': 0, '大學': 20025, '大': 0}
# 檢查系統是否已經初始化
# DAG儲存向無環圖的資料,資料結構是dict
DAG = {}
N = len(sentence)
# 依次遍歷文字中的每個位置
for k in range(N):
tmplist = []
i = k
# 位置 k 形成的片段
frag = sentence[k]
# 判斷片段是否在字首詞典中
# 如果片段不在字首詞典中,則跳出本迴圈
# 也即該片段已經超出統計詞典中該詞的長度
while i < N and frag in FREQ:
# 如果該片段的詞頻大於0
# 將該片段加入到有向無環圖中
# 否則,繼續迴圈
if FREQ[frag]:
tmplist.append(i)
# 片段末尾位置加1
i += 1
# 新的片段較舊的片段右邊新增一個字
frag = sentence[k:i + 1]
if not tmplist:
tmplist.append(k)
DAG[k] = tmplist
return DAG#最後返回的DAG的形式類似於
#{0: [0], 1: [1], 2: [2, 3, 5], 3: [3], 4: [4, 5], 5: [5], 6: [6]}
下面迴歸__cut_all()
,get_DAG()
方法就在__cut_all()
方法的第一句(這個方法我也稍微修改了一下,將類變數提取了出來):
def __cut_all(sentence,FREQ):
dag = get_DAG(sentence,FREQ)
print(dag)
print(iter(dag))
old_j = -1#舊的位置
for k, L in iteritems(dag):#iteritems是jieba根據python版本不同寫出來的迭代方法,就是迭代dag的key和value
if len(L) == 1 and k > old_j:#如果節點只有一條邊,說明是單字,返回這個字
yield sentence[k:L[0] + 1]
old_j = L[0]
else:
for j in L: # 對每個邊對應的位置
# 想象一下,先輸出北京,再輸出北京大學
if j > k: # 如果位置比k大,個人感覺因為是有向無環圖圖,這個if應該恆為真吧
yield sentence[k:j + 1] # 返回
old_j = j
以上就是cut中__cut_all的全部分析,對__cut_DAG的實現,由於涉及的函式過多,因此放到下一篇進行分析
簡化實現
將__cut_all的所有涉及的方法抽取為普通方法放到這裡,提供了一個示例,可以直接執行,供除錯和學習
#cut_all_ana.py
from cut_all_ana import get_DAG,gen_pfdict
import re
re_han = re.compile("([\u4E00-\u9FD5]+)")
re_skip = re.compile("([a-zA-Z0-9]+(?:\.\d+)?%?)")
from math import log
def calc(sentence, DAG, route,FREQ,total):
N = len(sentence)
route[N] = (0, 0)
logtotal = log(total)
for idx in range(N - 1, -1, -1):
ini = [(log(FREQ.get(sentence[idx:x + 1]) or 1)#FREQ.get(sentence[idx:x + 1]) or 1 ,如果FREQ有這個單詞就用這個單詞,如果沒有就用1
- logtotal
+ route[x + 1][0], x
) for x in DAG[idx]]
print(ini)
route[idx] = max(ini)#元組的大小比較是按字典序比較,先比較第一個的大小,如果第一個大小相等再比較第二個的大小
def __cut_DAG(sentence,FREQ):
DAG = get_DAG(sentence)
route = {}
calc(sentence, DAG, route)
x = 0
buf = ''
N = len(sentence)
while x < N:
y = route[x][1] + 1
l_word = sentence[x:y]
if y - x == 1:
buf += l_word
else:
if buf:
if len(buf) == 1:
yield buf
buf = ''
else:
if not FREQ.get(buf):
recognized = fcut(buf)
for t in recognized:
yield t
else:
for elem in buf:
yield elem
buf = ''
yield l_word
x = y
if buf:
if len(buf) == 1:
yield buf
elif not FREQ.get(buf):
recognized = fcut.cut(buf)
for t in recognized:
yield t
else:
for elem in buf:
yield elem
if __name__ == "__main__":
k = [
["北京大學", 2053],
["大學", 20025],
["去", 123402],
["玩", 4207],
["北京", 34488],
["北", 17860],
["京", 6583],
["大", 144099],
["學", 17482],
]
ex = "我去北京大學玩"
freq, total = gen_pfdict(k)
dag = get_DAG(ex,freq)
route = {}
calc(ex, dag, route, freq, total)