一、jieba的使用举例
- jieba的简单使用
我们根据作者的来编写一个自己的例子,代码如下:
import jieba
seg_list = jieba.cut("去北京大学玩123", cut_all=True)
print("Full Mode: " + "/".join(seg_list))
seg_list = jieba.cut("去北京大学玩123", cut_all=False)
print("Default Mode: " + "/".join(seg_list))
seg_list = jieba.cut("他来到了南七技校")
print("/".join(seg_list))
seg_list = jieba.cut_for_search("今天是2015年9月3号,去天安门广场庆祝抗战胜利70周年")
print("/".join(seg_list))
运行前需要,输出结果为:
Full Mode: 去/北京/北京大学/大学/玩/123
Default Mode: 去/北京大学/玩/123
他/来到/了/南七/技校
今天/是/2015/年/9/月/3/号/,/去/天安/广场/天安门/天安门广场/庆祝/抗战/胜利/70/周年
通过上面的例子可以看出,jieba分词具有三种模式:
1. 精确模式,试图将句子最精确地切开,适合文本分析;
2. 全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义;
3. 搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
童鞋们肯定比较好奇 jieba是什么实现这些功能的?
作者主要利用如下算法实现分词的:
1. 基于前缀词典实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图 (DAG);
作者这个版本中使用前缀字典实现了词库的存储(即dict.txt文件中的内容),而弃用之前版本的trie树存储词库,想想也是,python中实现的trie树是基于dict类型的数据结构而且dict中又嵌套dict 类型,这样嵌套很深,导致内存耗费严重,详情见作者把trie树改成前缀词典的 , 具体实现见 。接着说DAG有向无环图, 生成句子中汉字所有可能成词情况所构成的有向无环图。DAG根据我们生成的前缀字典来构造一个这样的DAG,对一个sentence DAG是以{key:list[i,j…], …}的字典结构存储,其中key是词的在sentence中的位置,list存放的是在sentence中以key开始且词sentence[key:i+1]在我们的前缀词典中 的以key开始i结尾的词的末位置i的列表,即list存放的是sentence中以位置key开始的可能的词语的结束位置,这样通过查字典得到词, 开始位置+结束位置列表。例如:句子“抗日战争”生成的DAG中{0:[0,1,3]} 这样一个简单的DAG, 就是表示0位置开始, 在0,1,3位置都是词, 就是说0~0,0~1,0~3 即 “抗”,“抗日”,“抗日战争”这三个词 在dict.txt中是词。
2. 采用了动态规划查找最大概率路径, 找出基于词频的最大切分组合;
基于上面的DAG利用动态规划查找最大概率路径,这个理解DP算法的很容易就能明白了。根据动态规划查找最大概率路径的基本思路就是对句子从右往左反向计算最大概率,..依次类推, 最后得到最大概率路径, 得到最大概率的切分组合(这里满足最优子结构性质,可以利用反证法进行证明),这里代码实现中有个小trick,概率对数(可以让概率相乘的计算变成对数相加,防止相乘造成下溢,因为在语料、词库中每个词的出现概率平均下来还是很小的浮点数).
3. 对于未登录词,采用了基于汉字成词能力的 HMM 模型,使用了 Viterbi 算法
未登录词()中说的OOV, 其实就是词典 dict.txt 中没有记录的词。这里采用了HMM模型,HMM是个简单强大的模型,可以参考网络资源进行学习,HMM在实际应用中主要用来解决3类问题:
二、jieba分词步骤
通过上面的举例即分析,想必大家对jieba分词应该有个大概的了解了。在上面的例子中我们注意到了,分词都是调用jieba.cut 这个函数,cut函数即是分词的入口,这个函数在文件jieba/__init__.py ,代码如下:
def cut(self, sentence, cut_all=False, HMM=True):
'''
The main function that segments an entire sentence that contains
Chinese characters into seperated 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.
'''
sentence = strdecode(sentence)
if cut_all:
re_han = re_han_cut_all
re_skip = re_skip_cut_all
else:
re_han = re_han_default
re_skip = re_skip_default
if cut_all:
cut_block = self.__cut_all
elif HMM:
cut_block = self.__cut_DAG
else:
cut_block = self.__cut_DAG_NO_HMM
blocks = re_han.split(sentence)
for blk in blocks:
if not blk:
continue
if re_han.match(blk):
for word in cut_block(blk):
yield word
else:
tmp = re_skip.split(blk)
for x in tmp:
if re_skip.match(x):
yield x
elif not cut_all:
for xx in x:
yield xx
else:
yield x
其中参数sentence是需要分词的句子样本;cut_all是分词的模式,精确模式,全模式,默认使用HMM模型。下面根据cut函数来绘制出相应的流程图:
具体的分词流程概括起来如下:
1. 给定待分词的句子, 使用正则(re_han)获取匹配的中文字符(和英文字符)切分成的短语列表;
2. 利用get_DAG(sentence)函数获得待切分句子的DAG,首先检测(check_initialized)进程是否已经加载词库,若未初始化词库则调用initialize函数进行初始化,initialize中判断有无已经缓存的前缀词典cache_file文件,若有相应的cache文件则直接使用 marshal.load 方法加载前缀词典,若无则通过gen_pfdict对指定的词库dict.txt进行计算生成前缀词典,到jieba进程的初始化工作完成后就调用get_DAG获得句子的DAG;
3. 根据cut_block指定具体的方法(__cut_all,__cut_DAG,__cut_DAG_NO_HMM)对每个短语使用DAG进行分词 ,如cut_block=__cut_DAG时则使用DAG(查字典)和动态规划, 得到最大概率路径, 对DAG中那些没有在字典中查到的字, 组合成一个新的片段短语, 使用HMM模型进行分词, 也就是作者说的识别新词, 即识别字典外的新词;
4. 使用python的yield 语法生成一个词语生成器, 逐词语返回;
具体执行流程总结为下图:
这一节的具体源码注释见,接下来的几节将对源码进行进一步的说明。
参考: