jieba源碼研讀筆記(八) - 分詞函數入口cut及tokenizer函數
前言
根據jieba文檔,jieba的分詞共包含三種模式,分別是:全模式、精確模式及搜索引擎模式。
其中的精確模式又分為不使用HMM兩種模式或使用HMM(在jieba中為默認模式)兩種。
所以分詞總共有四種模式可以使用。
在前三篇文章:全模式,精確模式(使用動態規劃),精確模式(使用HMM維特比算法發現新詞)當中,己經看到了前三種模式,它們分別對應到:__cut_all
,__cut_DAG_NO_HMM
及__cut_DAG
函數。
本篇介紹的cut
函數將作為上述分詞函數的入口,依據傳入參數的不同,來選擇要調用哪一個函數。
tokenize
函數作為cut
函數的wrapper,將cut
函數的回傳結果進一步包裝,除了輸出分好的詞以外,還輸出它們原本在句中的起始及終止位置。
jieba分詞的第四種模式:搜索引擎模式,將會調用本篇介紹的cut
函數。這將是下一篇的主要內容。
分詞函數cut
cut
並不包含核心算法,它只是多種不同分詞函數(如:__cut_all
,__cut_DAG_NO_HMM
,__cut_DAG
)的入口。
至於具體使用哪一種分詞函數,則是透過傳入的參數cut_all
,HMM
來決定。
以下用到了jieba/__init__.py
中定義的數個正則表達式,具體可以參考jieba源碼研讀筆記(四) - 正則表達式。
def cut(self, sentence, cut_all=False, HMM=True):
'''
The main function that segments an entire sentence that contains
Chinese characters into separated 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.
'''
# 在Python3中,是將sentence轉為str型別
sentence = strdecode(sentence)
if cut_all:
re_han = re_han_cut_all #用於全模式,只包含漢字
re_skip = re_skip_cut_all #用於全模式,配對非英數字及非+#\n者
else:
re_han = re_han_default #用於精確模式,包含漢字、英數字及+#&._%-
re_skip = re_skip_default #用於精確模式,包含換行及空白符
if cut_all:
# 全模式
# __cut_all函數只適用於全是漢字的句子
cut_block = self.__cut_all
elif HMM:
# 精確模式,使用DAG及動態規劃算法
# __cut_DAG可以處理句中有英數字的情況
cut_block = self.__cut_DAG
else:
# 精確模式,使用HMM維特比算法發現新詞
# __cut_DAG_NO_HMM可以處理句中有英數字的情況
cut_block = self.__cut_DAG_NO_HMM
"""
re_han的用意是將cut_block可處理與不可處理的字段分開
對全模式來說,cut_block可處理的只有漢字
對精確模式來說,cut_block可處理漢字,英數字及其它符號
"""
# 先用空白字元把句子切分成好幾個blocks
blocks = re_han.split(sentence)
for blk in blocks:
if not blk: #略過空字串
continue
if re_han.match(blk):
# 能跟re_han match就代表可被cut_block所處理
for word in cut_block(blk):
yield word
else:
# 無法被cut_block處理的
tmp = re_skip.split(blk)
for x in tmp:
if re_skip.match(x):
yield x
elif not cut_all: #elif只用於精確模式
# 在精確模式下,re_split是換行符及空白符
# 如果x進入elif這個區塊,代表它不是換行符及空白符
# 另外x也不是精確模式下的cut_block可以處理的(即x不是英數字)
# 在這種情況下,會把x一個字一個字地切開
for xx in x:
yield xx
else:
yield x
# 對全模式來說,就只是以re_skip來切割blk
測試:
In [11]: jieba.lcut("小明會說ABC!?") #默認是使用HMM的精確模式
Out[11]: ['小明會', '說', 'ABC', '!', '?']
"""
可以看到無法與re_han及re_skip配對的'!?'在
for xx in x:
yield xx
中被切開了
"""
tokenize函數
cut
函數回傳的是一個generator
變數,其中每個元素是切好的字串。
tokenize
函數可以說是cut
函數的wrapper,它基於cut
回傳值,將generator
裡的字串包裝成一個三元組。
三元組裡的三個元素分別代表切好的字串,字串的起始位置及字串的終止位置。
class Tokenizer(object):
#...
def tokenize(self, unicode_sentence, mode="default", HMM=True):
"""
Tokenize a sentence and yields tuples of (word, start, end)
Parameter:
- sentence: the str(unicode) to be segmented.
- mode: "default" or "search", "search" is for finer segmentation.
- HMM: whether to use the Hidden Markov Model.
"""
# 在Python3下,text_type是str
if not isinstance(unicode_sentence, text_type):
raise ValueError("jieba: the input parameter should be unicode.")
start = 0
if mode == 'default':
for w in self.cut(unicode_sentence, HMM=HMM):
width = len(w)
# 把切好的字段打包成三元組
yield (w, start, start + width)
start += width
else:
for w in self.cut(unicode_sentence, HMM=HMM):
width = len(w)
if len(w) > 2:
# 檢查w中是否包含有二字詞
for i in xrange(len(w) - 1):
gram2 = w[i:i + 2]
if self.FREQ.get(gram2):
yield (gram2, start + i, start + i + 2)
if len(w) > 3:
# 檢查w中是否包含有三字詞
for i in xrange(len(w) - 2):
gram3 = w[i:i + 3]
if self.FREQ.get(gram3):
yield (gram3, start + i, start + i + 3)
yield (w, start, start + width)
start += width
測試:
# 默認模式
In [14]: list(jieba.tokenize('北京市人民政府'))
Out[14]: [('北京市人民政府', 0, 7)]
# 搜索模式,會切分長詞
In [15]: list(jieba.tokenize('北京市人民政府', mode='search'))
Out[15]:
[('北京', 0, 2),
('京市', 1, 3),
('人民', 3, 5),
('民政', 4, 6),
('政府', 5, 7),
('北京市', 0, 3),
('北京市人民政府', 0, 7)]
參考連結
jieba文檔
全模式
精確模式(使用動態規劃)
精確模式(使用HMM維特比算法發現新詞)
jieba源碼研讀筆記(四) - 正則表達式