0、写在前面

在jieba源码分析(一)里面已经jieba分词的一部分进行了分析,本文主要解决分词的另一块:未登陆词,也就是我们常说的新词。对于这些新词,我们前面所说的前缀词典中是不存在的,那么之前的分词方法自然就不能适用了。为了解决这一问题,jieba使用了隐马尔科夫(HMM)模型。关于HMM模型的具体细节,这里不会过多介绍,网上也已经有很多资源可以参考

54nlp网站HMM相关资源汇总

刘建平博客——隐马尔科夫模型HMM(一)HMM模型

1、算法简介及实例

利用HMM模型进行分词,主要就是将分词问题看作是一个序列标注(sequence labeling)问题。其中,句子为观测序列,分词结果为状态序列。现在我们的问题就转变成了:已知句子和HMM模型,求解最有可能的对于的状态序列(即分词结果)。熟悉HMM的同学应该知道,对应于这个问题,使用的算法就是Viterbi。

举个栗子

今天我们不“去北京大学玩”,我们“去上海交通大学玩”。我们可以很明显地看出分词结果是“去/上海交通大学/玩”。

对于分词状态,由于jieba分词中使用的是4-tag,因此我们以4-tag进行计算。4-tag,也就是每个字处在词语中的4种可能状态,B、M、E、S,分别表示Begin(这个字处于词的开始位置)、Middle(这个字处于词的中间位置)、End(这个字处于词的结束位置)、Single(这个字是单字成词)。其中,B后面只能接M或者E;而M后面只能接M或E;E后面只能接S或B;S后面只能接B或S。具体可以看下图。

注意,这里“去上海交通大学玩”是观测状态序列,而对应的“SBMMMMES”则是隐藏状态序列。

HMM模型有几个重要的参数:

  • 初始状态概率π
  • 状态转移概率A:比如上图中的S→B的概率
  • 状态发射概率B:比如上图中的S→去的概率

初始状态概率

状态初始概率表示,每个词初始状态的概率;jieba分词训练出的状态初始概率模型如下所示。其中的概率值都是取对数之后的结果(可以让概率相乘转变为概率相加),其中-3.14e+100代表负无穷,对应的概率值就是0。这个概率表说明一个词中的第一个字属于{B、M、E、S}这四种状态的概率,如下可以看出,E和M的概率都是0,这也和实际相符合:开头的第一个字只可能是每个词的首字(B),或者单字成词(S)。这部分对应jieba/finaseg/prob_start.py,具体可以进入源码查看。

P={'B': -0.26268660809250016,'E': -3.14e+100,'M': -3.14e+100,'S': -1.4652633398537678}

状态转移概率

再看jieba中的状态转移概率,其实就是一个嵌套的词典,数值是概率值求对数后的值,如下所示。这部分在jieba/finaseg/prob_trans.py,具体可以查看源码。

P={'B': {'E': -0.510825623765990, 'M': -0.916290731874155},'E': {'B': -0.5897149736854513, 'S': -0.8085250474669937},'M': {'E': -0.33344856811948514, 'M': -1.2603623820268226},'S': {'B': -0.7211965654669841, 'S': -0.6658631448798212}}

状态发射概率

根据HMM模型中观测独立性假设,发射概率,即观测值只取决于当前状态值,这部分对应jieba/finaseg/prob_emit.py,具体可以查看源码。

2、源码分析

ieba分词中HMM模型识别未登录词的源码目录在jieba/finalseg/下,

__init__.py 实现了HMM模型识别未登录词;

prob_start.py 存储了已经训练好的HMM模型的状态初始概率表;

prob_trans.py 存储了已经训练好的HMM模型的状态转移概率表;

prob_emit.py 存储了已经训练好的HMM模型的状态发射概率表;

基于HMM的分词流程

jieba分词会首先调用函数cut(sentence),cut函数会先将输入句子进行解码,然后调用__cut函数进行处理。__cut函数就是jieba分词中实现HMM模型分词的主函数。__cut函数会首先调用viterbi算法,求出输入句子的隐藏状态,然后基于隐藏状态进行分词。

def __cut(sentence):"""jieba首先会调用函数cut(sentence),cut函数会先将输入的句子进行解码,然后调用__cut()函数进行处理。该函数是实现HMM模型分词的主函数__cut()函数首先调用viterbi算法, 求出输入句子的隐藏状态,然后基于隐藏状态分词"""global emit_P# 通过viterbi算法求出隐藏状态序列prob, pos_list = viterbi(sentence, 'BMES', start_P, trans_P, emit_P)begin, nexti = 0, 0# print pos_list, sentence# 基于隐藏状态进行分词for i, char in enumerate(sentence):pos = pos_list[i]# 如果字所处的位置是开始位置 Beginif pos == 'B':begin = i# 如果字所处的位置是结束位置 ENDelif pos == 'E':# 这个子序列就一个分词yield sentence[begin:i + 1]nexti = i + 1# 如果单独成字 Singleelif pos == 'S':yield charnexti = i + 1# 剩余的直接作为一个分词,返回if nexti < len(sentence):yield sentence[nexti:]

Viterbi算法

关于Viterbi算法的具体细节参考李航老师的小蓝书,后面还有一个具体的例子。

def viterbi(obs, states, start_p, trans_p, emit_p):"""viterbi函数会先计算各个初始状态的对数概率值,然后递推计算,每时刻某状态的对数概率值取决于上一时刻的对数概率值、上一时刻的状态到这一时刻的状态的转移概率、这一时刻状态转移到当前的字的发射概率三部分组成。"""V = [{}]  # tabular表示Viterbi变量,下标表示时间path = {}  # 记录从当前状态回退路径# 时刻t=0,初始状态for y in states:  # initV[0][y] = start_p[y] + emit_p[y].get(obs[0], MIN_FLOAT) path[y] = [y]# 时刻t = 1,...,len(obs) - 1for t in xrange(1, len(obs)):V.append({})newpath = {}# 当前时刻所处的各种可能的状态for y in states:# 获得发射概率对数em_p = emit_p[y].get(obs[t], MIN_FLOAT)# 分别获得上一时刻的状态的概率对数、该状态到本时刻的状态的转移概率对数# 以及本时刻的状态的发射概率(prob, state) = max([(V[t - 1][y0] + trans_p[y0].get(y, MIN_FLOAT) + em_p, y0) for y0 in PrevStatus[y]])V[t][y] = prob# 将上一时刻最有的状态 + 这一时刻的状态newpath[y] = path[state] + [y]path = newpath# 最后一个时刻(prob, state) = max((V[len(obs) - 1][y], y) for y in 'ES')#返回最大概率对数和最有路径return (prob, path[state])

以上就是jieba关于分词部分的大致内容,还有一些比如全模式分词cut_all()、搜索模式cut_for_search()等会补充到代码里上传至Github。

enjoy yourself~

jieba源码分析(二)相关推荐

  1. jieba tfidf_【NLP】【三】jieba源码分析之关键字提取(TF-IDF/TextRank)

    [一]综述 利用jieba进行关键字提取时,有两种接口.一个基于TF-IDF算法,一个基于TextRank算法.TF-IDF算法,完全基于词频统计来计算词的权重,然后排序,在返回TopK个词作为关键字 ...

  2. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  3. SpringBoot源码分析(二)之自动装配demo

    SpringBoot源码分析(二)之自动装配demo 文章目录 SpringBoot源码分析(二)之自动装配demo 前言 一.创建RedissonTemplate的Maven服务 二.创建测试服务 ...

  4. gSOAP 源码分析(二)

    gSOAP 源码分析(二) 2012-5-24 flyfish 一 gSOAP XML介绍 Xml的全称是EXtensible Markup Language.可扩展标记语言.仅仅是一个纯文本.适合用 ...

  5. Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现

    写在之前 这两篇文章是我2021年3月初看KeyMaster的笔记,本来打算等分析完KeyMaster和KeyStore以后再一起做成一系列贴出来,后来KeyStore的分析中断了,这一系列的文章就变 ...

  6. 【投屏】Scrcpy源码分析二(Client篇-连接阶段)

    Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...

  7. Nouveau源码分析(二):Nouveau结构体的基本框架

    Nouveau源码分析(二) 在讨论Nouveau对Nvidia设备的初始化前,我准备先说一下Nouveau结构体的基本框架 Nouveau的很多结构体都可以看作是C++中的类,之间有很多相似的东西, ...

  8. ENS最新合约源码分析二

    ENS(以太坊域名服务)智能合约源码分析二 0.简介 ​ 本次分享直接使用线上实际注册流程来分析最新注册以太坊域名的相关代码.本次主要分析最新的关于普通域名注册合约和普通域名迁移合约,短域名竞拍合约不 ...

  9. 【转】ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数必然已完成了执行 ...

最新文章

  1. 10个具体项目生动精彩讲述JavaScript;超级Web应用,构建不再困难
  2. 机器学习(MACHINE LEARNING)MATLAB非线性曲线拟合方法
  3. 浅谈Socket编程
  4. 【收藏】Kubernetes学习之路(二十二)之Pod资源调度
  5. 云游戏是大厂的“游戏”之腾讯云云游戏指南
  6. 关于ORACLE数据传输加密的介绍
  7. ef mysql dbfirst_.NetCore教程之 EFCore连接Mysql DBFirst模式
  8. Android基础 淡入淡出、上下弹出动画的
  9. 1.4_select_sort_选择排序
  10. bitcoin: 何为燃烧地址
  11. [Python图像处理] .获取图像属性、兴趣ROI区域及通道处理
  12. 《深入浅出通信原理》一句话短评
  13. iOS-OC-集成PayPal支付方式
  14. iMazing v2021绿色便携版iOS设备数据管理工具
  15. SAP SD客户主数据
  16. 【刷题】【2.两数之和绝对值最小】
  17. Windows徽标键(WinKey)的屏蔽和恢复
  18. MySQL自定义函数
  19. C语言推荐书籍从入门到进阶带你走上大牛之路(珍藏版)
  20. 小程序源码:AI微信小程序源码下载人脸照片AI转换动漫照片全新源码安装简单无需服务器域名-多玩法安装简单

热门文章

  1. Highcharts 显示图表
  2. 基于uFUN开发板的心率计(三)Qt上位机的实现
  3. hdu5791(DP)
  4. Qt Creator 预览QtCreator中的界面
  5. 在virtualenv中安装NumPy、 SciPy、 scikit-learn、 matplotlib
  6. HDOJ2024C语言合法标识符
  7. LeetCode: Word Search
  8. 线段树(单点更新,区间查询) HDU 1754 I Hate It
  9. 【图灵杯 E也即POJ 3368】简单的RMQ
  10. MSSQLServer基础07(事务,存储过程,分页的存储过程,触发器)