zoukankan      html  css  js  c++  java
  • jieba源码解析(二):jieba.cut

    前一章介绍了jieba分词之前关于前缀词典的构建,本章介绍jieba的主体:jieba.cut
    jieba分词有三种模式:全模式、精确模式、搜索引擎模式。全模式和精确模式通过jieba.cut实现,搜索引擎模式对应cut_for_search,且三者均可以通过参数HMM决定是否使用新词识别功能。官方例子:

    # encoding=utf-8
    import jieba
    
    seg_list = jieba.cut("我来到北京清华大学", cut_all=True)
    print("Full Mode: " + "/ ".join(seg_list))  # 全模式
    # 【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
    
    seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
    print("Default Mode: " + "/ ".join(seg_list))  # 精确模式
    # 【精确模式】: 我/ 来到/ 北京/ 清华大学
    
    seg_list = jieba.cut("他来到了网易杭研大厦")  # 默认是精确模式
    print(", ".join(seg_list))
    # 【新词识别】:他, 来到, 了, 网易, 杭研, 大厦    (此处,“杭研”并没有在词典中,但是也被Viterbi算法识别出来了)
    
    seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造")  # 搜索引擎模式
    print(", ".join(seg_list))
    # 【搜索引擎模式】: 小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大学, 深造
    

    jieba.cut

    def cut(self, sentence, cut_all=False, HMM=True):
            '''
            jieba分词主函数,返回generator
            参数:
                - sentence: 待切分文本.
                - cut_all: 切分模式. True 全模式, False 精确模式.
                - HMM: 是否使用隐式马尔科夫.
            '''
            sentence = strdecode(sentence)  # sentence转unicode
    
            if cut_all:
                # re_han_cut_all = re.compile("([u4E00-u9FD5]+)", re.U)   
                re_han = re_han_cut_all  
                # re_skip_cut_all = re.compile("[^a-zA-Z0-9+#
    ]", re.U)  
                re_skip = re_skip_cut_all    
            else:
                # re_han_default = re.compile("([u4E00-u9FD5a-zA-Z0-9+#&._%]+)", re.U)
                re_han = re_han_default                
                # re_skip_default = re.compile("(
    |s)", re.U)
                re_skip = re_skip_default   
    
            if cut_all:   
                cut_block = self.__cut_all   # cut_all=True, HMM=True or False
            elif HMM:
                cut_block = self.__cut_DAG   # cut_all=False, HMM=True
            else:
                cut_block = self.__cut_DAG_NO_HMM   # cut_all=False, HMM=False
            blocks = re_han.split(sentence)
            for blk in blocks:
                if not blk:
                    continue
                if re_han.match(blk):    #  符合re_han匹配的串
                    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
    

    可以看出jieba.cut返回一个可迭代的generator,可以使用 for 循环来获得分词后得到的每一个词语(也可以用jieba.lcut直接返回分词list结果)。

    • cut_all=True, HMM=_对应于全模式,即所有在词典中出现的词都会被切分出来,实现函数为__cut_all;
    • cut_all=False, HMM=False对应于精确模式且不使用HMM;按Unigram语法模型找出联合概率最大的分词组合,实现函数为__cut_DAG;
    • cut_all=False, HMM=True对应于精确模式且使用HMM;在联合概率最大的分词组合的基础上,HMM识别未登录词,实现函数为__cut_DAG_NO_HMM。

    严格来说,jieba.cut不能算是分词主体,分词结果实际在cut_block里。下面以精确模式(无新词发现)为例具体讲解:

    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 = ''
    

    通过这个函数,可以看出jieba分词具体流程:构建有向无环图-->计算最大概率路径。

    构建有向无环图

    有向无环图,directed acyclic graphs,简称DAG,是一种图的数据结构,顾名思义,就是没有环的有向图。
    jieba采用了Python的dict结构,可以更方便的表示DAG。最终的DAG是以{k : [k , j , ..] , m : [m , p , q] , ...}的字典结构存储,其中k和m为词在文本sentence中的位置,k对应的列表存放的是文本中以k开始且词sentence[k: j + 1]在前缀词典中的 以k开始j结尾的词的列表,即列表存放的是sentence中以k开始的可能的词语的结束位置,这样通过查找前缀词典就可以得到词。
    get_DAG(self, sentence)函数进行对系统初始化完毕后,会构建有向无环图。

    def get_DAG(self, sentence):
        self.check_initialized()
        DAG = {}
        N = len(sentence)
        for k in range(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
    

    例如:

    text = '我来到北京清华大学'
    print(jieba.get_DAG(text))
    {0: [0], 
    1: [1, 2], 
    2: [2], 
    3: [3, 4], 
    4: [4], 
    5: [5, 6, 8], 
    6: [6, 7], 
    7: [7, 8], 
    8: [8]}
    

    DAG是用dict表示的,key为边的起点,value为边的终点集合,比如:上述例子中1 -> 2表示词“来到”。

    计算最大概率

    将log(词频/总词频)作为有向无环图边的权值,并假设词与词之间相互独立,从图论的角度出发,将最大概率组合问题变成了最大路径问题。即:

    [arg max∏_iP(w_i)=arg max log∏_iP(w_i)=arg max∑_ilogP(w_i) ]

    def calc(self, sentence, DAG, route):
        N = len(sentence)
        route[N] = (0, 0)
        logtotal = log(self.total)
        for idx in range(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])
    

    Jieba用动态规划(DP)来求解最大路径问题,假设用(d_i)标记源节点到节点i的最大路径的值,则有

    [d_i=max_{(j,i)∈E} {d_j+w(j,i)} ]

    其中,(w(j,i))表示词词(c_{ij})的词频log值,(w(i,i))表示字符(c_i)独立成词的词频log值。在求解上述式子时,需要知道所有节点i的前驱节点j;然后DAG中只有后继结点list。在这里,作者巧妙地用到了一个trick——从尾节点m-1往前推算的最优解等价于从源节点0往后推算的。那么,用(r_i)标记节点i到尾节点的最大路径的值,则

    [r_i= max_{(i,j)∈E} {r_j+w(i,j)} ]

    参考文献

  • 相关阅读:
    LSMW TIPS
    Schedule agreement and Delfor
    Running VL10 in the background 13 Oct
    analyse idoc by creation date
    New Journey Prepare
    EDI error
    CBSN NEWS
    Listen and Write 18th Feb 2019
    Microsoft iSCSI Software Target 快照管理
    通过 Microsoft iSCSI Software Target 提供存储服务
  • 原文地址:https://www.cnblogs.com/aloiswei/p/11567616.html
Copyright © 2011-2022 走看看