热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

结巴分词原理

介绍结巴分词是一个受大家喜爱的分词库,源码地址为github,今天我们就跟进源码,看一下结巴分词的原理原理defcut(self,sent

介绍

结巴分词是一个受大家喜爱的分词库,源码地址为github,今天我们就跟进源码,看一下结巴分词的原理

原理

def cut(self, sentence, cut_all=False, HMM=True):'''The main function that segments an entire sentence that containsChinese characters into separated 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.'''

使用结巴分词的时候,有三种模式,这三种模式的进入条件分别为:

if cut_all:cut_block = self.__cut_allelif HMM:cut_block = self.__cut_DAGelse:cut_block = self.__cut_DAG_NO_HMM

首先我们看一下这三种模式

  • __cut_all:

    1. 原句:我来到北京清华大学 结果:我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
    2. 原句:他来到了网易杭研大厦 结果:他/ 来到/ 了/ 网易/ 杭/ 研/ 大厦
  • __cut_DAG:

    1. 原句:我来到北京清华大学 结果:我/ 来到/ 北京/ 清华大学
    2. 原句:他来到了网易杭研大厦 结果:他/ 来到/ 了/ 网易/ 杭研/ 大厦
  • __cut_DAG_NO_HMM:

    1. 原句:我来到北京清华大学 结果:我/ 来到/ 北京/ 清华大学
    2. 原句:他来到了网易杭研大厦 结果:他/ 来到/ 了/ 网易/ 杭/ 研/ 大厦

下面我们就来分析一下这三种模式:
这三种模式有一个共同点,第一步都是先构造DAG,也就是构造有向无环图。
源码如下:

def get_DAG(self, sentence):self.check_initialized()DAG = {}N = len(sentence)for k in xrange(N):tmplist = []i = kfrag = sentence[k]while i

如果sentence是'我来到北京清华大学‘,那么DAG为

{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[5]=[5,6,8]的意思就是,以’清‘开头的话,分别以5、6、8结束时,可以是一个词语,即’清‘、’清华‘、’清华大学‘
get_DAG方法中,最重要的也就是self.FREQ了,它是怎么来的呢?


其实就是通过jieba目录下,dict.txt文件来产生的self.FREQ,方法如下:
dict.txt共有349046行,每一行格式为:

一 217830 m
一一 1670 m
一一二 11 m
一一例 3 m
一一分 8 m
一一列举 34 i

第一部分为词语,第二部分为该词出现的频率,第三部分为该词的词性。
以读取’一一列举‘为例子,首先执行self.FREQ['一一列举']=34,然后会检查’一‘、’一一‘、’一一列‘、’一一列举‘之前是否在self.FREQ中存储过,如果之前存储过,则跳过,否则执行self.FREQ['一']=0,self.FREQ['一一']=0,self.FREQ['一一列']=0
所以self.FREQ中不止存储了正常的词语和它出现的次数,同时也存储了所有词语的前缀,并将前缀出现的次数设置为0,以和正常词语区别开。

好了,现在DAG这部分我们介绍完了,然后我们分开来介绍一下这三种模式:

__cut_all

源码如下:

def __cut_all(self, sentence):dag = self.get_DAG(sentence)old_j = -1for k, L in iteritems(dag):if len(L) == 1 and k > old_j:yield sentence[k:L[0] + 1]old_j = L[0]else:for j in L:if j > k:yield sentence[k:j + 1]old_j = j

这个具体的遍历方式我们就不细说了,大家自行看源码吧

__cut_DAG

def __cut_DAG(self, sentence):DAG = self.get_DAG(sentence)route = {}self.calc(sentence, DAG, route)......

首先我们先看一下self.calc方法

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((log(self.FREQ.get(sentence[idx:x + 1]) or 1) -logtotal + route[x + 1][0], x) for x in DAG[idx])

这里使用了一个技巧,也就是log(a) + log(b) = log(ab),从而巧妙的避过了乘法,也就避免了溢出的风险。
其实calc函数就是实现了vertibi算法,不了解vertibi算法的同学自行百度吧。

然后再贴上整个__cut_DAG的源码:

def __cut_DAG(self, sentence):DAG = self.get_DAG(sentence)route = {}self.calc(sentence, DAG, route)x = 0buf = ''N = len(sentence)while x

其中,重点关注这一部分

if not self.FREQ.get(buf):recognized = finalseg.cut(buf)for t in recognized:yield t

什么时候会进入finalseg.cut(buf)呢?实际上,就是当遇到一些dict.txt中没出现的词的时候,才会进入这个函数:
在这个函数中,就是使用HMM的方法,对这些未识别成功的词进行标注,然后我们来介绍一下项目中相关的内容:


其中,prob_start.py存储的是HMM的起始状态相关的信息,文件中的数字都经过log处理过:

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

B代表begin,E代表end,M代表middle,S代表single。所以在开始时,HMM的状态只可能是S或者B,而E和M为负无穷
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}}

prob_emit.py中存储的是在该状态下出现该汉字的概率,例如p('刘'|S)=-0.916

P={'B': {'\u4e00': -3.6544978750449433,'\u4e01': -8.125041941842026,'\u4e03': -7.817392401429855,'\u4e07': -6.3096425804013165,'\u4e08': -8.866689067453933,'\u4e09': -5.932085850549891,'\u4e0a': -5.739552583325728,'\u4e0b': -5.997089097239644,'\u4e0d': -4.274262055936421,'\u4e0e': -8.355569307500769,......

通过这种方式,也就可以进行分词了。
‘我/ 来到/ 北京/ 清华大学’对应的状态应该为'SBEBEBMME'

__cut_DAG_NO_HMM

其实__cut_DAG_NO_HMM和__cut_DAG的区别就是:对vertibi未成功切分的部分,__cut_DAG_NO_HMM没有使用HMM进行分词。源码如下:

def __cut_DAG_NO_HMM(self, sentence):DAG = self.get_DAG(sentence)route = {}self.calc(sentence, DAG, route)x = 0N = len(sentence)buf = ''while x



推荐阅读
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 标题: ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • 本文介绍了在处理不规则数据时如何使用Python自动提取文本中的时间日期,包括使用dateutil.parser模块统一日期字符串格式和使用datefinder模块提取日期。同时,还介绍了一段使用正则表达式的代码,可以支持中文日期和一些特殊的时间识别,例如'2012年12月12日'、'3小时前'、'在2012/12/13哈哈'等。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Commit1ced2a7433ea8937a1b260ea65d708f32ca7c95eintroduceda+Clonetraitboundtom ... [详细]
author-avatar
恋之-风景-
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有