zoukankan      html  css  js  c++  java
  • jieba分词流程及部分源码解读(一)

    首先我们来看一下jieba分词的流程图:

    结巴中文分词简介

       1)支持三种分词模式:

    精确模式:将句子最精确的分开,适合文本分析

    全模式:句子中所有可以成词的词语都扫描出来,速度快,不能解决歧义

    搜索引擎模式:在精确的基础上,对长词再次切分,提高召回

       2)支持繁体分词

       3)支持自定义词典

       4)基于Trie树结构实现高效的词图扫描,生成句子汉字所有可能成词情况所构成的有向无环图(DAG)

       5)  采用了动态规划查找最大概率路径,找出基于词频的最大切分组合 6)对于词库中不存在的词,也就是未登录词,采用了基于汉字成词能力的HMM模型,使用了Viterbi算法

    接下来我们从源码分析一下:

    从github上下载源代码后,打开 文件夹 jieba,找到__init__.py,结巴分词最主要的函数 cut 就定义在这个文件中。

    __cut_DAG 函数调用了 get_DAG(sentence),这是用来生成每一块(sentence)的有向无环图DAG。要生成DAG就必须有语料库的辅助了,所以在 同样在 文件夹 jieba 下,可以找到一个文件:dict.txt。语料库的有3列,第一列是词,第二列是词频,第三列是词性。在程序中初始化语料库的动作在函数  initialize(DICTIONARY) 中,它通过一个包装器 require_initialized 在 get_DAG 函数被调用的时候才执行。代码如下:

    def require_initialized(fn):

    @wraps(fn) #wraps的作用是保留被包装函数的一些属性,比如__doc__
    def wrapped(*args, **kwargs):
    global initialized
    if initialized:
    return fn(*args, **kwargs)
    else:
    initialize(DICTIONARY)
    return fn(*args, **kwargs)

    return wrapped
    有向无环图构建
    语料库Trie树加载完毕后,接下来我们来介绍如何进行DAG分词

    以“正在学习大数据中的结巴分词”为例,作为待分词的输入文本。

    jieba.__init__.py中实现了jieba分词接口函数cut(self, sentence, cut_all=False, HMM=True)。

    jieba分词接口主入口函数,会首先将输入文本解码为Unicode编码,然后根据入参,选择不同的切分方式,本文主要以精确模式进行讲解,因此cut_all和HMM这两个入参均为默认值;

    分词中get_DAG函数实现如下

    # -*- coding: utf-8 -*-
    import marshal

    def get_DAG(sentence):

    N = len(sentence)
    i,j=0,0
    p = trie
    DAG = {}
    while i<N:
    c = sentence[j]
    if c in p:
    p = p[c]
    if '' in p:
    if i not in DAG:
    DAG[i]=[]
    DAG[i].append(j)
    j+=1
    if j>=N:
    i+=1
    j=i
    p=trie
    else:
    p = trie
    i+=1
    j=i
    for i in xrange(len(sentence)):
    if i not in DAG:
    DAG[i] =[i]
    return DAG

    #动态规划,计算最大概率的切分组合
    def calc(self, sentence, DAG, route):
    N = len(sentence)
    route[N] = (0, 0)
    # 对概率值取对数之后的结果(可以让概率相乘的计算变成对数相加,防止相乘造成下溢)
    logtotal = log(self.total)
    # 从后往前遍历句子 反向计算最大概率
    for idx in xrange(N - 1, -1, -1):
    # 列表推倒求最大概率对数路径
    # route[idx] = max([ (概率对数,词语末字位置) for x in DAG[idx] ])
    # 以idx:(概率对数最大值,词语末字位置)键值对形式保存在route中
    # route[x+1][0] 表示 词路径[x+1,N-1]的最大概率对数,
    # [x+1][0]即表示取句子x+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])

    if __name__=='__main__':
    sentence=u'正在学习大数据中的结巴分词'
    trie,FREQ,total,min_freq = marshal.load(open(u'D:jieba.cache','rb'))#使用缓存载入重要变量
    rs=get_DAG(sentence)#获取DAG
    route={}
    calc(sentence,rs,0,route)#根据得分进行初步分词
    print route
    基于词频最大切分组合(从上面get_DAG中部分代码详解)
    我们已经有了词库(dict.txt)的前缀字典和待分词句子sentence的DAG,基于词频的最大切分 要在所有的路径中找出一条概率得分最大的路径,该怎么做呢? 
    jieba中的思路就是使用动态规划方法,从后往前遍历,选择一个频度得分最大的一个切分组合。 
    具体实现见代码,已给详细注释。

    #动态规划,计算最大概率的切分组合
    def calc(self, sentence, DAG, route):
    N = len(sentence)
    route[N] = (0, 0)
    # 对概率值取对数之后的结果(可以让概率相乘的计算变成对数相加,防止相乘造成下溢)
    logtotal = log(self.total)
    # 从后往前遍历句子 反向计算最大概率
    for idx in xrange(N - 1, -1, -1):
    # 列表推倒求最大概率对数路径
    # route[idx] = max([ (概率对数,词语末字位置) for x in DAG[idx] ])
    # 以idx:(概率对数最大值,词语末字位置)键值对形式保存在route中
    # route[x+1][0] 表示 词路径[x+1,N-1]的最大概率对数,
    # [x+1][0]即表示取句子x+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])
    从代码中可以看出calc是一个自底向上的动态规划(重叠子问题、最优子结构),它从sentence的最后一个字(N-1)开始倒序遍历sentence的字(idx)的方式(为什么倒叙遍历,不懂的可以留言或是找我小猪),计算子句sentence[isdx~N-1]概率对数得分(这里利用DAG及历史计算结果route实现)。然后将概率对数得分最高的情况以(概率对数,词语最后一个字的位置)这样的tuple保存在route中。 

    那么登陆词部分解释完毕,下来就是未登陆词,利用Viterbi算法来解决未登录词的处理方法,后续更新
    ---------------------
    作者:Jameslvt
    来源:CSDN
    原文:https://blog.csdn.net/Jameslvt/article/details/81118560
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    SSM之Mybatis整合及使用
    软件设计师08-法律法规与标准化知识
    Spring MVC体系结构
    Spring MVC异常友好展示
    Spring MVC Web.xml配置
    编译型语言解释型语言
    软件设计师07-程序设计语言与语言处理程序基础
    flex布局注意点:
    常见的PC端和移动端表单组件
    js文件的装载和执行
  • 原文地址:https://www.cnblogs.com/jfdwd/p/11097356.html
Copyright © 2011-2022 走看看