首先我们来看一下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
版权声明:本文为博主原创文章,转载请附上博文链接!

jieba分词流程及部分源码解读(一)的更多相关文章

  1. CesiumJS 2022^ 源码解读[7] - 3DTiles 的请求、加载处理流程解析

    目录 1. 3DTiles 数据集的类型 2. 创建瓦片树 2.1. 请求入口文件 2.2. 创建树结构 2.3. 瓦片缓存机制带来的能力 3. 瓦片树的遍历更新 3.1. 三个大步骤 3.2. 遍历 ...

  2. Bert系列(二)——源码解读之模型主体

    本篇文章主要是解读模型主体代码modeling.py.在阅读这篇文章之前希望读者们对bert的相关理论有一定的了解,尤其是transformer的结构原理,网上的资料很多,本文内容对原理部分就不做过多 ...

  3. Bert系列(三)——源码解读之Pre-train

    https://www.jianshu.com/p/22e462f01d8c pre-train是迁移学习的基础,虽然Google已经发布了各种预训练好的模型,而且因为资源消耗巨大,自己再预训练也不现 ...

  4. AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager

    让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...

  5. seajs 源码解读

    之前面试时老问一个问题seajs 是怎么加载js 文件的 在网上找一些资料,觉得这个写的不错就转载了,记录一下,也学习一下 seajs 源码解读 seajs 简单介绍 seajs是前端应用模块化开发的 ...

  6. HttpClient 4.3连接池参数配置及源码解读

    目前所在公司使用HttpClient 4.3.3版本发送Rest请求,调用接口.最近出现了调用查询接口服务慢的生产问题,在排查整个调用链可能存在的问题时(从客户端发起Http请求->ESB-&g ...

  7. Laravel 源码解读系列第四篇-Auth 机制

    前言 Laravel有一个神器: php artisan make:auth 能够快速的帮我们完成一套注册和登录的认证机制,但是这套机制具体的是怎么跑起来的呢?我们不妨来一起看看他的源码.不过在这篇文 ...

  8. AbstractQueuedSynchronizer源码解读

    1. 背景 AQS(java.util.concurrent.locks.AbstractQueuedSynchronizer)是Doug Lea大师创作的用来构建锁或者其他同步组件(信号量.事件等) ...

  9. AbstractQueuedSynchronizer源码解读--续篇之Condition

    1. 背景 在之前的AbstractQueuedSynchronizer源码解读中,介绍了AQS的基本概念.互斥锁.共享锁.AQS对同步队列状态流转管理.线程阻塞与唤醒等内容.其中并不涉及Condit ...

随机推荐

  1. sqlyog备份数据和导入备份数据

    使用sqlyog备份数据 Database -> Backup/Export  -> Backup Database As SQL Dump 根据自己实际情况,看需要 导出表结构或者仅仅只 ...

  2. 洛谷3953 (NOIp2017) 逛公园——记忆化搜索+用栈判0环

    题目:https://www.luogu.org/problemnew/show/P3953 因为K只有50,所以想到用dp[ cr ][ j ]表示在点cr.比最短路多走了 j 的方案数.(看了TJ ...

  3. 官网svn提交到代码库,但是不能同步到web目录

    现象:数据库名字存在,但是数据库中表为空:svn提交代码能到svn库中,但不能同步到web目录. 原因:磁盘分区满了,导致内测服务器中数据库先删掉但没能从线上拉取回来,显示数据库中表为空: 磁盘满,导 ...

  4. LintCode_46 主元素

    题目 给定一个整型数组,找出主元素,它在数组中的出现次数严格大于数组元素个数的二分之一. 样例 给出数组[1,1,1,1,2,2,2],返回 1 思路 首先 发现所给的数组是顺序排列好的. 用动态规划 ...

  5. 有意思的DP(CF360B Levko and Array)

    刚才面试了一个蛮有意思的DP题目,脑子断片,没写出来,不过早上状态还是蛮好的 一个长度为n的序列最多改变k次,使相邻两数之差绝对值的最大值最小 三维的dp我先尝试写一下 Codeforces 360B ...

  6. pillow模块

    pillow模块 用于操作图片的模块 安装 pip install pillow 生成验证码 from PIL import Image,ImageDraw,ImageFont from io imp ...

  7. python 布尔值索引

  8. Windows10 +Ubuntu 18.04双系统安装详细教程

    前言: 由于开发需要,很多用户需要安装Linux环境.如果用虚拟机操作会消耗大量内存,一般的电脑会显得卡顿,从而影响开发效率.这里分享一些安装经验,希望能够帮助大家. 教程: 步骤1:下载镜像文件 从 ...

  9. LintCode_67 二叉树中序遍历

    题目 给出一棵二叉树,返回其中序遍历 C++ 非递归 vector<int> inorderTraversal(TreeNode *root) { // write your code h ...

  10. 关于spring中<util:/>的配置

    解决redis设置缓存时间找到的帖子,我这个初学者需要学习的还是很多的. 原文地址:http://www.doc100.net/bugs/t/216322/index.html 探索<util/ ...