首发于 以AI混饭的程序猿
中文分词原理理解+jieba分词详解(二)

中文分词原理理解+jieba分词详解(二)

在写这篇专栏时,我一直在用jieba分词,之前花过一段时间去研究了最新分词的技术,并且做了对比,也有个大致的结论,详细可看我的另一篇专栏

无敌小想法:作为AI从业者,基本工具有哪些?(下篇),其中有一部分我介绍了各种前沿分词的介绍,但是就在今天早上,我在今日头条上看到ACL2019中的一篇文章 Is Word Segmentation Necessary for Deep Learning of Chinese Representations?后,立马刷新了我的价值观,我花了一上午时间去研究这个玩意到底靠不靠谱,当然这个只是在学术角度去论述了它的可用之处,至于能否落地应用,可能还得要一段时间,好了,不说了,我们还是做好我们自己的事吧,来说说jieba分词,下面让我娓娓道来吧(结尾有彩蛋,不想麻烦的小伙伴可以直接看结尾再决定是否好好看这篇专栏,真是为你们操碎了心)。

jieba分词的框架图

- 对于分词结果而言

比如不采用HMM模型得到的结果

In[17]: jieba.lcut('谢先招你好样的',HMM=False)

Out[17]: ['谢', '先', '招', '你', '好样', '的']

比如采用HMM模型得到的结果

In[18]: jieba.lcut('谢先招你好样的')

Out[18]: ['谢先招', '你', '好样', '的']

它的原理就是先采用jieba最大概率分词得到的结果为['谢', '先', '招', '你', '好样', '的'],然后对于单个词(因为只可能是单个字的,因为如果不为单个字的,那说明就在字典中存在),最后连成的buf,将buf进行HMM分词,最后将‘谢先招’分词得到的结果为:谢先招
最后得到的分词结果就如上所示:['谢先招', '你', '好样', '的']

- 对于词性标注结果而言

比如不采用HMM模型得到的结果

Out[18]: posseg.lcut('谢先招是好样的',HMM=False)

Out[18]: [pair('谢', 'v'), pair('先', 'd'), pair('招', 'v'), pair('是', 'v'), pair('好样', 'd'), pair('的', 'uj')]

比如采用HMM模型得到的结果

Out[18]: posseg.lcut('谢先招是好样的')

Out[18]: [pair('谢先招', 'nr'), pair('是', 'v'), pair('好样', 'd'), pair('的', 'uj')]
它的原理也是先得到加载HMM模型得到的分词结果,然后对其分词结果(采用HMM模型)进行词性标注

- 特例

Out[18]: posseg.lcut('谢先招手是好样的',HMM=True)

Out[18]: [pair('谢先', 'nr'), pair('招手', 'v'), pair('是', 'v'), pair('好样', 'd'), pair('的', 'uj')]


下面正式进入介绍jieba分词的相关内容:

分词特点

  1. 支持三种分词模式: 精确模式,试图将句子最精确地切开,适合文本分析; 全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义; 搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
  2. 支持自定义词典
  3. 支持繁体分词
  4. MIT 授权协议

自定义词典,载入词典

开发者可以指定自己自定义的词典,以便包含 jieba 词库里没有的词。虽然 jieba 有新词识别能力,但是自行添加新词可以保证更高的正确率

用法: jieba.load_userdict(file_name) # file_name 为文件类对象或自定义词典的路径

词典格式和 dict.txt 一样,一个词占一行;每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开,顺序不可颠倒。file_name 若为路径或二进制方式打开的文件,则文件必须为 UTF-8 编码。

词频省略时使用自动计算的能保证分出该词的词频。

调整词典

使用 add_word(word, freq=None, tag=None) 和 del_word(word) 可在程序中动态修改词典。

使用 suggest_freq(segment, tune=True) 可调节单个词语的词频,使其能(或不能)被分出来。

注意:自动计算的词频在使用 HMM 新词发现功能时可能无效。

如下示例:

>>> print('/'.join(jieba.cut('如果放到post中将出错。', HMM=False)))

如果/放到/post/中将/出错/。

>>> jieba.suggest_freq(('中', '将'), True)

>>> print('/'.join(jieba.cut('如果放到post中将出错。', HMM=False)))

如果/放到/post/中/将/出错/。

>>> print('/'.join(jieba.cut('「台中」正确应该不会被切开', HMM=False)))

「/台/中/」/正确/应该/不会/被/切开

>>> jieba.suggest_freq('台中', True)

>>> print('/'.join(jieba.cut('「台中」正确应该不会被切开', HMM=False)))

「/台中/」/正确/应该/不会/被/切开

并行分词

原理:将目标文本按行分隔后,把各行文本分配到多个 Python 进程并行分词,然后归并结果,从而获得分词速度的可观提升

基于 python 自带的 multiprocessing 模块,目前暂不支持 Windows

用法:

jieba.enable_parallel(4) # 开启并行分词模式,参数为并行进程数

jieba.disable_parallel() # 关闭并行分词模式

例子: github.com/fxsjy/jieba/

实验结果:在 4 核 3.4GHz Linux 机器上,对金庸全集进行精确分词,获得了 1MB/s 的速度,是单进程版的 3.3 倍。

注意:并行分词仅支持默认分词器 jieba.dt 和 jieba.posseg.dt。

Tokenize:返回词语在原文的起止位置

注意,输入参数只接受 unicode

l 默认模式

result = jieba.tokenize(u'永和服装饰品有限公司')

for tk in result:

print("word %s\t\t start: %d \t\t end:%d" % (tk[0],tk[1],tk[2]))

word 永和 start: 0 end:2

word 服装 start: 2 end:4

word 饰品 start: 4 end:6

word 有限公司 start: 6 end:10

l 搜索模式

result = jieba.tokenize(u'永和服装饰品有限公司', mode='search')

for tk in result:

print("word %s\t\t start: %d \t\t end:%d" % (tk[0],tk[1],tk[2]))

word 永和 start: 0 end:2

word 服装 start: 2 end:4

word 饰品 start: 4 end:6

word 有限 start: 6 end:8

word 公司 start: 8 end:10

word 有限公司 start: 6 end:10

命令行分词

使用示例:python -m jieba news.txt > cut_result.txt

延迟加载机制

jieba 采用延迟加载,import jieba 和 jieba.Tokenizer() 不会立即触发词典的加载,一旦有必要才开始加载词典构建前缀字典。如果你想手工初始 jieba,也可以手动初始化。

import jieba

jieba.initialize() # 手动初始化(可选)

在 0.28 之前的版本是不能指定主词典的路径的,有了延迟加载机制后,你可以改变主词典的路径:

jieba.set_dictionary('data/dict.txt.big')

其他词典

占用内存较小的词典文件 :

支持繁体分词更好的词典文件 :

下载你所需要的词典,然后覆盖 jieba/dict.txt 即可;或者用 jieba.set_dictionary('data/dict.txt.big'),接下来就是重点来了!!!

具体分词原理的实现

1:根据离线统计词典进行前缀词典的构建,如以”去北京大学玩”来进行切词

离线词典如下所示:

...

北京大学 2053 nt

大学 20025 n

去 123402 v

玩 4207 v

北京 34488 ns

北 17860 ns

京 6583 ns

大 144099 a

学 17482 n

...

构建前缀词典

首先是基于统计词典构造前缀词典,如统计词典中的词“北京大学”的前缀分别是“北”、“北京”、“北京大”;词“大学”的前缀是“大”。统计词典中所有的词形成的前缀词典如下所示,你也许会注意到“北京大”作为“北京大学”的前缀,但是它的词频却为0,这是为了便于后面有向无环图的构建。

得到的前缀词典如下:

...

北京大学 2053

北京大 0

大学 20025

去 123402

玩 4207

北京 34488

北 17860

京 6583

大 144099

学 17482

...

有向无环图构建

然后基于前缀词典,对输入文本进行切分,对于“去”,没有前缀,那么就只有一种划分方式;对于“北”,则有“北”、“北京”、“北京大学”三种划分方式;对于“京”,也只有一种划分方式;对于“大”,则有“大”、“大学”两种划分方式,依次类推,可以得到每个字开始的前缀词的划分方式。

在jieba分词中,对每个字都是通过在文本中的位置来标记的,因此可以构建一个以位置为key,相应划分的末尾位置构成的列表为value的映射,如下所示

0: [0]

1: [1,2,4]

2: [2]

3: [3,4]

4: [4]

5: [5]

如图所示如下图:


最大概率计算

从上面有向无环图的构建可以看出,该文本切分的路径有多条,如何得到最合理的路径,接下来就是求最大概率路径问题,

# 路径1

0 -> 1 -> 2 -> 3 -> 4 -> 5

# 分词结果1

去 / 北 / 京 / 大 / 学 / 玩

# 路径2

0 -> 1 , 2 -> 3 -> 4 -> 5

# 分词结果2

去 / 北京 / 大 / 学 / 玩

# 路径3

0 -> 1 , 2 -> 3 , 4 -> 5

# 分词结果3

去 / 北京 / 大学 / 玩

# 路径4

0 -> 1 , 2 , 3 , 4 -> 5

# 分词结果4

去 / 北京大学 / 玩

...

因此,我们需要计算最大概率路径,也即按照这种方式切分后的分词结果的概率最大。在计算最大概率路径时,jieba分词采用从后往前这种方式进行计算。为什么采用从后往前这种方式计算呢?因为,我们这个有向无环图的方向是从前向后指向,对于一个节点,我们只知道这个节点会指向后面哪些节点,但是我们很难直接知道有哪些前面的节点会指向这个节点。

在采用动态规划计算最大概率路径时,每到达一个节点,它前面的节点到终点的最大路径概率已经计算出来

动态规划求解,需要满足两个条件:

l 重复子问题

l 最优子结构

我们来分析一下最大概率路径问题,是否满足动态规划的两个条件。

重复子问题

对于节点wi和其可能存在的多个后继节点Wj和Wk,

任意通过Wi到达Wj的路径的权重 = 该路径通过Wi的路径权重 + Wj的权重,也即{Ri -> j} = {Ri + weight(j)}

任意通过Wi到达Wk的路径的权重 = 该路径通过Wi的路径权重 + Wk的权重,也即{Ri -> k} = {Ri + weight(k)}

即对于拥有公共前驱节点Wi的节点Wj和Wk,需要重复计算达到Wi的路径的概率。

最优子结构

对于整个句子的最优路径Rmax和一个末端节点Wx,对于其可能存在的多个前驱Wi,Wj,Wk...,设到达Wi,Wj,Wk的最大路径分别是Rmaxi,Rmaxj,Rmaxk,有,

Rmax = max(Rmaxi,Rmaxj,Rmaxk,...) + weight(Wx)

于是,问题转化为,求解Rmaxi,Rmaxj,Rmaxk,...等,

组成了最优子结构,子结构里面的最优解是全局的最优解的一部分。

jieba分词中计算最大概率路径的主函数是calc(self, sentence, DAG, route),函数根据已经构建好的有向无环图计算最大概率路径。

函数是一个自底向上的动态规划问题,它从sentence的最后一个字(N-1)开始倒序遍历sentence的每个字(idx)的方式,计算子句sentence[idx ~ N-1]的概率对数得分。然后将概率对数得分最高的情况以(概率对数,词语最后一个位置)这样的元组保存在route中。

函数中,logtotal为构建前缀词频时所有的词频之和的对数值,这里的计算都是使用概率对数值,可以有效防止下溢问题。

jieba分词中calc函数实现如下,

def calc(self, sentence, DAG, route):

N = len(sentence)

# 初始化末尾为0

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])

最终得到最大概率路径为:

{0: [0], 1: [4], 5: [5]}

对于未登录词的识别

那么jieba分词是如何对未登录词进行分词呢?采用的就是HMM模型进行解决的。

利用HMM模型进行分词,主要是将分词问题视为一个序列标注(sequence labeling)问题,其中,句子为观测序列,分词结果为状态序列。首先通过语料训练出HMM相关的模型,然后利用Viterbi算法进行求解,最终得到最优的状态序列,然后再根据状态序列,输出分词结果。

序列标注

序列标注,就是将输入句子和分词结果当作两个序列,句子为观测序列分词结果为状态序列,当完成状态序列的标注,也就得到了分词结果。

以“去北京大学玩”为例,我们知道“去北京大学玩”的分词结果是“去 / 北京大学 / 玩”。对于分词状态,由于jieba分词中使用的是4-tag,因此我们以4-tag进行计算。4-tag,也就是每个字处在词语中的4种可能状态,B、M、E、S,分别表示Begin(这个字处于词的开始位置)、Middle(这个字处于词的中间位置)、End(这个字处于词的结束位置)、Single(这个字是单字成词)。具体如下图所示,“去”和“玩”都是单字成词,因此状态就是S,“北京大学”是多字组合成的词,因此“北”、“京”、“大”、“学”分别位于“北京大学”中的B、M、M、E。


HMM模型

HMM模型作的两个基本假设:

1.齐次马尔科夫性假设,即假设隐藏的马尔科夫链在任意时刻t的状态只依赖于其前一时刻的状态,与其它时刻的状态及观测无关,也与时刻t无关;

P(states[t] | states[t-1],observed[t-1],...,states[1],observed[1]) = P(states[t] | states[t-1]) t = 1,2,...,T

2.观测独立性假设,即假设任意时刻的观测只依赖于该时刻的马尔科夫链的状态,与其它观测和状态无关,

P(observed[t] | states[T],observed[T],...,states[1],observed[1]) = P(observed[t] | states[t]) t = 1,2,...,T

HMM模型有三个基本问题:

1.概率计算问题,给定模型 λ=(A,B,π)λ=(A,B,π) 和观测序列 O=(o1,o2,...,oT)O=(o1,o2,...,oT) ,怎样计算在模型 λλ 下观测序列O出现的概率 P(O|λ)P(O|λ) ,也就是Forward-backward算法;

2.学习问题,已知观测序列 O=(o1,o2,...,oT)O=(o1,o2,...,oT) ,估计模型 λ=(A,B,π)λ=(A,B,π) ,使得在该模型下观测序列的概率 P(O|λ)P(O|λ) 尽可能的大,即用极大似然估计的方法估计参数;

3.预测问题,也称为解码问题,已知模型 λ=(A,B,π)λ=(A,B,π) 和观测序列 O=(o1,o2,...,oT)O=(o1,o2,...,oT) ,求对给定观测序列条件概率 P(S|O)P(S|O) 最大的状态序列 I=(s1,s2,...,sT)I=(s1,s2,...,sT) ,即给定观测序列。求最有可能的对应的状态序列;

其中,jieba分词主要中主要涉及第三个问题,也即预测问题。

HMM模型中的五元组表示:

1.观测序列;

2.隐藏状态序列;

3.状态初始概率;

4.状态转移概率;

5.状态发射概率;

这里仍然以“去北京大学玩”为例,那么“去北京大学玩”就是观测序列。

而“去北京大学玩”对应的“SBMMES”则是隐藏状态序列,我们将会注意到B后面只能接(M或者E),不可能接(B或者S);而M后面也只能接(M或者E),不可能接(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}

状态转移概率是马尔科夫链中很重要的一个知识点,一阶的马尔科夫链最大的特点就是当前时刻T = i的状态states(i),只和T = i时刻之前的n个状态有关,即{states(i-1),states(i-2),...,states(i-n)}。

再看jieba中的状态转移概率,其实就是一个嵌套的词典,数值是概率值求对数后的值,如下所示,

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}}

P['B']['E']代表的含义就是从状态B转移到状态E的概率,由P['B']['E'] = -0.5897149736854513,表示当前状态是B,下一个状态是E的概率对数是-0.5897149736854513,对应的概率值是0.6,相应的,当前状态是B,下一个状态是M的概率是0.4,说明当我们处于一个词的开头时,下一个字是结尾的概率要远高于下一个字是中间字的概率,符合我们的直觉,因为二个字的词比多个字的词更常见。这部分对应jieba/finaseg/prob_trans.py,具体可以查看源码。

状态发射概率,根据HMM模型中观测独立性假设,发射概率,即观测值只取决于当前状态值,也就如下所示,

P(observed[i],states[j]) = P(states[j]) * P(observed[i] | states[j])

其中,P(observed[i] | states[j])就是从状态发射概率中获得的。

P={'B': {'一': -3.6544978750449433,

'丁': -8.125041941842026,

'七': -7.817392401429855,

...

'S': {':': -15.828865681131282,

'一': -4.92368982120877,

'丁': -9.024528361347633,

...

P['B']['一']代表的含义就是状态处于'B',而观测的字是‘一’的概率对数值为P['B']['一'] = -3.6544978750449433。这部分对应jieba/finaseg/prob_emit.py,具体可以查看源码。

Viterbi算法

Viterbi算法实际上是用动态规划求解HMM模型预测问题,即用动态规划求概率路径最大(最优路径)。这时候,一条路径对应着一个状态序列。

根据动态规划原理,最优路径具有这样的特性:如果最优路径在时刻t通过结点 i∗tit∗ ,那么这一路径从结点 i∗tit∗ 到终点 i∗TiT∗ 的部分路径,对于从 i∗tit∗ 到 i∗TiT∗ 的所有可能的部分路径来说,必须是最优的。因为假如不是这样,那么从 i∗tit∗ 到 i∗TiT∗ 就有另一条更好的部分路径存在,如果把它和从 i∗tit∗ 到达 i∗TiT∗ 的部分路径连接起来,就会形成一条比原来的路径更优的路径,这是矛盾的。依据这个原理,我们只需要从时刻t=1开始,递推地计算在时刻t状态i的各条部分路径的最大概率,直至得到时刻t=T状态为i的各条路径的最大概率。时刻t=T的最大概率就是最优路径的概率 P∗P∗ ,最优路径的终结点 i∗TiT∗ 也同时得到。之后,为了找出最优路径的各个结点,从终结点 i∗TiT∗ 开始,由后向前逐步求得结点 i∗T−1,...,i∗1iT−1∗,...,i1∗ ,最终得到最优路径 I∗=(i∗1,i∗2,...,i∗T)I∗=(i1∗,i2∗,...,iT∗) 。

输出分词结果

由Viterbi算法得到状态序列,也就可以根据状态序列得到分词结果。其中状态以B开头,离它最近的以E结尾的一个子状态序列或者单独为S的子状态序列,就是一个分词。以”去北京大学玩“的隐藏状态序列”SBMMES“为例,则分词为”S / BMME / S“,对应观测序列,也就是”去 / 北京大学 / 玩”。

源码分析

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

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

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

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

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

HMM模型参数训练

HMM模型的参数是如何训练出来,此处可以参考jieba中Issue 模型的数据是如何生成的? #7,如下是jieba的开发者的解释:

来源主要有两个,一个是网上能下载到的1998人民日报的切分语料还有一个msr的切分语料另一个是我自己收集的一些txt小说,用ictclas把他们切分(可能有一定误差),然后用python脚本统计词频。

要统计的主要有三个概率表:1)位置转换概率,即B(开头),M(中间),E(结尾),S(独立成词)四种状态的转移概率;2)位置到单字的发射概率,比如P("和"|M)表示一个词的中间出现”和"这个字的概率;3) 词语以某种状态开头的概率,其实只有两种,要么是B,要么是S。

基于HMM模型的分词流程

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

def __cut(sentence):

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]

# 字所处的位置是开始位置

if pos == 'B':

begin = i

# 字所处的位置是结束位置

elif pos == 'E':

# 这个子序列就是一个分词

yield sentence[begin:i + 1]

nexti = i + 1

# 单独成字

elif pos == 'S':

yield char

nexti = i + 1

# 剩余的直接作为一个分词,返回

if nexti < len(sentence):

yield sentence[nexti:]

jieba分词实现Viterbi算法是在viterbi(obs, states, start_p, trans_p, emit_p)函数中实现。viterbi函数会先计算各个初始状态的对数概率值,然后递推计算,每时刻某状态的对数概率值取决于上一时刻的对数概率值、上一时刻的状态到这一时刻的状态的转移概率、这一时刻状态转移到当前的字的发射概率三部分组成。

def viterbi(obs, states, start_p, trans_p, emit_p):

V = [{}] # tabular

path = {}

# 时刻t = 0,初始状态

for y in states: # init

V[0][y] = start_p[y] + emit_p[y].get(obs[0], MIN_FLOAT)

path[y] = [y]

# 时刻t = 1,...,len(obs) - 1

for t in xrange(1, len(obs)):

V.append({})

newpath = {}

# 当前时刻所处的各种可能的状态

for y in states:

# 获取发射概率对数

em_p = emit_p[y].get(obs[t], MIN_FLOAT)

# 分别获取上一时刻的状态的概率对数,该状态到本时刻的状态的转移概率对数,本时刻的状态的发射概率对数

# 其中,PrevStatus[y]是当前时刻的状态所对应上一时刻可能的状态

(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])

相关优化:

1.将每一时刻最优概率路径记录下来,避免了第4步的最优路径回溯;

2.提前建立一个当前时刻的状态到上一时刻的状态的映射表,记录每个状态在前一时刻的可能状态,降低计算量;如下所示,当前时刻的状态是B,那么前一时刻的状态只可能是(E或者S),而不可能是(B或者M);

PrevStatus = {

'B': 'ES',

'M': 'MB',

'S': 'SE',

'E': 'BM'

}

CRF(条件随机场)

HMM隐马模型有个严重缺点是其存在输出独立性假设,由于HMM是单向的模型,不能将上下文的特征考虑进来,大大限制了特征的可用范围。CRF则没有这个限制,而是对所有特征进行全局归一化,进而全局的最优值。因此,在分词问题上,显然作为判别式模型的CRF相比HMM更具优越性。

HMM模型围绕的是一个关于序列X和Y的联合概率分布 ( , ),属于生成式模型,而条件随机场则围绕条件概率分布模型 ( | )展开,属于判别式模型,具体生成式和判别式模型的区别可以去网上自行学习脑补。这里我就不具体讲解其二者的区别,这里主要是实践二者的结果比较,废话不多说,我们开始。

模型大小对比:

HMM相比CRF模型要小得多,我保存一个HMMjava对象也就2mb,但是CRF大致150mb,CRF囊括了HMM,因此它也强大得多,但是代价就是模型大,解码,训练都要慢一些。

为什么CRF更强大?这从原理上基本可以解释,如果你还记得HMM的3个参数,PI,A,B,其中pi是初始状态分布,A是转移概率,B是发射矩阵,也就是状态下的观测分布,最重要的区别就在于CRF可以考察当前序列位置状态下的前后观测分布,也就是说CRF能更利用数据的上下文信息

分词器 语料 总精确率 总召回率 总f值 备注

1HMMSeg 2 3.828388 3.938759 3.882353 一阶HMM

2HMMSeg 2 3.947924 3.94826 3.947588 二阶HMM

CRFSeg 2 4.28639 4.296123 4.290709 特征函数个数:875035

(转载于 中文分词的探索,CRF(条件随机场)和HMM(隐马尔可夫模型)用于分词的对比,以及中文分词的评估)

最终得到的结果是CRF分词的结果要优于HMM模型(这里大家可能会问,既然CRF模型结果优于HMM模型,为什么jieba分词中偏偏用的是HMM模型,我想源作者可能会考虑到模型的性能吧,当然这只是我个人猜测)

训练CRF的过程依然是通过万能的极大似然估计,具体算法形式如梯度下降法、IIS、拟牛顿法等。训练好CRF分词模型后,跟HMM一样, 可以通过Viterbi算法来进行全局的推理,从而得到最优的分词序列。这里同样不展开讲啦。总结一下,与HMM比,使用CRF进行分词有以下优点:

  • CRF可以使用输入文本的全局特征,而HMM只能看到输入文本在当前位置的局部特征
  • CRF是判别式模型,直接对序列标注建模;HMM则引入了不必要的先验信息

讲到这里,你可能觉得jieba分词就到此结束了?那是你想多了吧,凡事我们应该多问个为什么?

1:jieba分词为什么在求最大概率时采用反向的方式呢?

因为我们这个有向无环图的方向是从前向后指向,对于一个节点,我们只知道这个节点会指向后面哪些节点,但是我们很难直接知道有哪些前面的节点会指向这个节点。

在采用动态规划计算最大概率路径时,每到达一个节点,它前面的节点到终点的最大路径概率已经计算出来。这个具体可以去看Verterbi算法的精妙之处

2:为什么jieba没有使用trie树作为前缀词典存储的数据结构?

对于get_DAG()函数来说,用Trie数据结构,特别是在Python环境,内存使用量过大。经实验,可构造一个前缀集合解决问题

该集合储存词语及其前缀,如set(['数', '数据', '数据结', '数据结构'])。在句子中按字正向查找词语,在前缀列表中就继续查找,直到不在前缀列表中或超出句子范围。大约比原词库增加40%词条。

该版本通过各项测试,与原版本分词结果相同。

测试:一本5.7M的小说,用默认字典,64位Ubuntu,Python 2.7.6。

Trie:第一次加载2.8秒,缓存加载1.1秒;内存277.4MB,平均速率724kB/s;

前缀字典:第一次加载2.1秒,缓存加载0.4秒;内存99.0MB,平均速率781kB/s;

此方法解决纯Python中Trie空间效率低下的问题。

同时改善了一些代码的细节,遵循PEP8的格式,优化了几个逻辑判断。

如果还没明白的话,大家可以去好好补补课了哈,大家如果有兴趣的话可以关注我,有什么问题我们都可以讨论,一个悔恨晚写知乎的大白算法攻城狮,记得关注哦!

玻璃钢生产厂家松原玻璃钢花坛厂家直销汉中玻璃钢厂乌海玻璃钢花盆批发吉林玻璃钢公仔雕塑生产厂家宜宾玻璃钢花池批发阜阳玻璃钢景观雕塑批发福州玻璃钢种植池生产厂家张掖玻璃钢景观雕塑制造白银玻璃钢沙发哪家好抚顺玻璃钢家具厂家鹤壁玻璃钢天花吊顶厂家直销廊坊玻璃钢花盆厂家成都玻璃钢医疗外壳厂家直销绵阳玻璃钢座椅定制宿州玻璃钢摆件厂廊坊玻璃钢公仔雕塑厂家德阳玻璃钢产品多少钱六安玻璃钢动物雕塑生产厂家武汉玻璃钢浮雕价格广安玻璃钢前台无锡玻璃钢装饰工程南京玻璃钢沙发厂家贺州玻璃钢花盆定做无锡玻璃钢花槽批发信阳玻璃钢种植池批发抚州玻璃钢家具哪家好曲靖玻璃钢动物雕塑加工辽源玻璃钢装饰工程定制金华不锈钢家具哪家好林芝玻璃钢花槽制作香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声卫健委通报少年有偿捐血浆16次猝死汪小菲曝离婚始末何赛飞追着代拍打雅江山火三名扑火人员牺牲系谣言男子被猫抓伤后确诊“猫抓病”周杰伦一审败诉网易中国拥有亿元资产的家庭达13.3万户315晚会后胖东来又人满为患了高校汽车撞人致3死16伤 司机系学生张家界的山上“长”满了韩国人?张立群任西安交通大学校长手机成瘾是影响睡眠质量重要因素网友洛杉矶偶遇贾玲“重生之我在北大当嫡校长”单亲妈妈陷入热恋 14岁儿子报警倪萍分享减重40斤方法杨倩无缘巴黎奥运考生莫言也上北大硕士复试名单了许家印被限制高消费奥巴马现身唐宁街 黑色着装引猜测专访95后高颜值猪保姆男孩8年未见母亲被告知被遗忘七年后宇文玥被薅头发捞上岸郑州一火锅店爆改成麻辣烫店西双版纳热带植物园回应蜉蝣大爆发沉迷短剧的人就像掉进了杀猪盘当地回应沈阳致3死车祸车主疑毒驾开除党籍5年后 原水城县长再被查凯特王妃现身!外出购物视频曝光初中生遭15人围殴自卫刺伤3人判无罪事业单位女子向同事水杯投不明物质男子被流浪猫绊倒 投喂者赔24万外国人感慨凌晨的中国很安全路边卖淀粉肠阿姨主动出示声明书胖东来员工每周单休无小长假王树国卸任西安交大校长 师生送别小米汽车超级工厂正式揭幕黑马情侣提车了妈妈回应孩子在校撞护栏坠楼校方回应护栏损坏小学生课间坠楼房客欠租失踪 房东直发愁专家建议不必谈骨泥色变老人退休金被冒领16年 金额超20万西藏招商引资投资者子女可当地高考特朗普无法缴纳4.54亿美元罚金浙江一高校内汽车冲撞行人 多人受伤

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化