jieba分词源码阅读 (一)

结巴源码阅读记录

功能:
1、实现定制化分词
2、词性标注
3、关键词抽取

具体可以结合源代码看

目录

代码结构

​​​​​​在这里插入图片描述结巴代码机构

init 模块

主要实现函数为 cut
使用方法为:


    import jieba
    sent = '我爱中华人民共和国'
    words = jieba.cut(sent, HMM=False)
    
    print('type sent = ', type(sent), len(sent))
    for word in words:
        print(word)

输出为:
我
爱
中华人民共和国

cut 为init 模块中,token类中的一个方法,为分词的入口,具体代码如下,下面一行一行的看代码。

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)
    # 正则找出汉字, re_han = re.compile("([\u4E00-\u9FD5]+)", re.U)
    # “\u4e00”和“\u9fa5”是unicode编码,并且正好是中文编码的开始和结束的两个值,
    # 所以这个正则表达式可以用来判断字符串中是否包含中文
    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

下面这段代码是怎么分词算法的核心,总共有两种分词方法,一种是基于前缀词典实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图 (DAG),另一种是带HMM的DAG。下面分别阅读这两段代码。

for blk in blocks:
        if not blk:
            continue
        if re_han.match(blk):
            for word in **cut_block**(blk):
                yield word

cut_block = self.__cut_DAG_NO_HMM。
__cut_DAG_NO_HMM为token类中的方法,代码如下:

    def __cut_DAG_NO_HMM(self, sentence):
        DAG = self.get_DAG(sentence)   # 重点
        route = {}
        self.calc(sentence, DAG, route)
        x = 0
        N = len(sentence)
        buf = ''
        while x < N:
            y = route[x][1] + 1
            l_word = sentence[x:y]
            if re_eng.match(l_word) and len(l_word) == 1:
                buf += l_word
                x = y
            else:
                if buf:
                    yield buf
                    buf = ''
                yield l_word
                x = y
        if buf:
            yield buf
            buf = ''

上述代码的主要执行步骤为:
1、生成DAG:
构建需要切词的句子的有向无环图,对应代码为 DAG = self.get_DAG(sentence)
返回每个词开头的所有可能得词
2、

对应代码分别为:
1、生成DAG

    def get_DAG(self, sentence):
        self.check_initialized()
        DAG = {}
        N = len(sentence)
        for k in xrange(N):
            tmplist = []
            i = k
            frag = sentence[k]
            while i < N and frag in self.FREQ:
                if self.FREQ[frag]:
                    tmplist.append(i)
                i += 1
                frag = sentence[k:i + 1]
            if not tmplist:
                tmplist.append(k)
            DAG[k] = tmplist
        return DAG

self.FREQ 为dict类型,在token类的initialize方法中赋值,具体语句为:
self.FREQ, self.total = marshal.load(cf)
输出前5条为:
key = 野蛮冲 value = 0
key = 野蛮女友 value = 3
key = 野蛮女 value = 0
key = 野蛮暴行 value = 3
key = 野蛮暴 value = 0
key为字典中的词,value为字典中词的频率

还是以 “我爱中华人民共和国” 为例
DAG输出为:
DAG = {0: [0], 1: [1], 2: [2, 3, 5, 8], 3: [3, 4], 4: [4, 5, 8], 5: [5], 6: [6, 7, 8], 7: [7], 8: [8]}
其中key为句子中的每个字的索引,value从索引构成词的个数。以 “中”为例,
以它开头的,有 中、中华、中华人民、中华人民共和国这四个词,索引它的结果为:
2: [2, 3, 5, 8]

2、生成route:根据DAG图生成route信息,也就是对于每个索引,根据词的频率取最大的一个,这个考虑的词和下一个字生成的词的频率(两个方面的权重)。主要代码为:

    def calc(self, sentence, DAG, route):
        N = len(sentence)
        route[N] = (0, 0)
        logtotal = log(self.total)
        print('self total = ', self.total)
        for idx in xrange(N - 1, -1, -1):
            route[idx] = max((log(self.FREQ.get(sentence[idx:x + 1]) or 1) -
                              logtotal + route[x + 1][0], x) for x in DAG[idx])
            # route[idx] = max((log(self.FREQ.get(sentence[idx:x + 1]) or 1) -
            #               logtotal + route[x + 1][0], x) for x in DAG[idx])
        print('route = ', route)

附录: 计算机术语
DAG 有向无环图 Directed acyclic [eiˈsaiklik] graphs

猜你喜欢

转载自blog.csdn.net/qq_16761099/article/details/90052385