jieba源碼研讀筆記(八) - 分詞函數入口cut及tokenizer函數

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_allHMM來決定。

以下用到了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

測試:

扫描二维码关注公众号,回复: 5371738 查看本文章
# 默認模式
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源碼研讀筆記(四) - 正則表達式

猜你喜欢

转载自blog.csdn.net/keineahnung2345/article/details/86773559