.
|--analyse
|--finalseg
|--posseg
|--__init__.py
|--__main__.py
|--_compat.py
|--dict.txt
其中analyse是对分词结果进行分析的文件夹,提供了TF-IDF算法和textrank算法。finalseg提供了vertbit算法需要的初始矩阵。posseg是进行词性标注的代码。
结巴分词的核心代码在根目录下的__init__.py中。
官方文档中关于算法的说明:
1、基于Trie树结构实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图(DAG)
2、采用了动态规划查找最大概率路径, 找出基于词频的最大切分组合
3、对于未登录词,采用了基于汉字成词能力的HMM模型,使用了Viterbi算法
__init__.py:
全局变量:
DICTIONARY = "dict.txt" #默认字典名
DICT_LOCK = threading.RLock() #线程锁
FREQ = {} # to be initialized #tire树
total = 0 #计数器
user_word_tag_tab = {}
initialized = False #标记是否初始化
pool = None
tmp_dir = None
_curpath = os.path.normpath(os.path.join(os.getcwd(), os.path.dirname(__file__)))#得到当前绝对路径
log_console = logging.StreamHandler(sys.stderr) #日志相关
logger = logging.getLogger(__name__)logger.setLevel(logging.DEBUG)
logger.addHandler(log_console)
关键函数:
1、 函数名:gen_pfdict(f_name) 这个是算法说明1中基于Trie树结构实现高效的词图扫描,但是通过代码可以看出这里所谓的Tire树,只是是一个字典,并且没有嵌套。
通过代码54-57行看出这里对每一个词从第一个字开始逐字增加判断是否包含在Tire树(字典)中。
2、 函数名:initialize(dictionary=None)。这里是对程序的初始化,主要工作是载入词典,这里运用了缓存技术(tempfile库),还没看这个库(mark)。
3、 函数名:get_DAG(sentence) 函数功能为把输入的句子生成有向无环图。测试sentence="英语单词的词形变化主要是增加前后缀"。
运行结果为:{"0": [0, 1, 3], "1": [1], "2": [2, 3], "3": [3], "4": [4], "5": [5, 6, 8], "6": [6, 7], "7": [7, 8], "8": [8], "9": [9, 10], "10": [10, 11], "11": [11], "12": [12, 13], "13": [13], "14": [14, 15], "15": [15, 16], "16": [16]}
这个字典即为DAG,key为字所在的位置,value为从字开始能在FREQ中的匹配到的词末尾位置所在的list。句子中的第一个字为'英',所在位置即key为0,
value为[0, 1, 3],表示'英'、'英语'、'英语单词'可以再FREQ中找到。
4、函数名:__cut_all(sentence) 函数功能就是结巴分词的全模式分词,作用即把DAG中的结果全部显示出来。这里使用了yield来迭代返回结果。
5、函数名:__cut_DAG_NO_HMM(sentence)。函数功能:对sentence进行不加hmm的精确分词。精确分词是在全分词的基础上计算各种路径的概率,选取概率最大的路径。
函数calc(sentence, DAG, route)就是计算概率的过程。其中语句 xrange(N - 1, -1, -1)是从句子的末尾开始计算,
route[idx] = max((log(FREQ.get(sentence[idx:x + 1]) or 1) -
logtotal + route[x + 1][0], x) for x in DAG[idx])
max函数返回的是一个元组,计算方法是log(freq/total)+后一个字得到的最大概率路径的概率。这里即为动态规划查找最大概率路径。注意的是动态规划的方向是
从后往前。
6、函数名:__cut_DAG(sentence)功能是对语句进行精确分词并且使用HMM模型,对比函数__cut__DAG_NO_HMM(sentence)的主要区别是有一个未登录词的识别功能。代码为
if not FREQ.get(buf):
recognized = finalseg.cut(buf)
for t in recognized:
yield t
可以看到调用的是finalseg.cut()函数
7、函数名:cut(sentence, cut_all=False, HMM=True) 这里源代码中带有注释,函数根据参数的不同调用上面不同的函数4、5、6
测试代码:
#coding=utf-8
#author:zhangyang
#2015-5-27
#程序用于结巴分词根目录__init__.py测试
from __future__ import absolute_import, unicode_literals
import os
from math import log
import json
dirname = os.path.dirname(__file__)
print dirname
cwd=os.getcwd()
print cwd
ww=os.path.join(os.getcwd(), os.path.dirname(__file__))
print ww
FREQ={}
total=0
def gen_pfdict(f_name):
lfreq = {}
ltotal = 0
with open(f_name, 'rb') as f:
lineno = 0
for line in f.read().rstrip().decode('utf-8').splitlines():
lineno += 1
try:
word, freq = line.split(' ')[:2]
freq = int(freq)
lfreq[word] = freq
ltotal += freq
for ch in xrange(len(word)):
wfrag = word[:ch + 1]
if wfrag not in lfreq:
lfreq[wfrag] = 0
except ValueError as e:
logger.debug('%s at line %s %s' % (f_name, lineno, line))
raise e
return lfreq, ltotal
def get_DAG(sentence):
global FREQ
DAG = {}
N = len(sentence)
for k in xrange(N):
tmplist = []
i = k
frag = sentence[k]
while i < N and frag in FREQ:
if FREQ[frag]:
tmplist.append(i)
i += 1
frag = sentence[k:i + 1]
if not tmplist:
tmplist.append(k)
DAG[k] = tmplist
return DAG
def calc(sentence, DAG, route):
N = len(sentence)
route[N] = (0, 0)
logtotal = log(total)
for idx in xrange(N - 1, -1, -1):
route[idx] = max((log(FREQ.get(sentence[idx:x + 1]) or 1) -
logtotal + route[x + 1][0], x) for x in DAG[idx])
print json.dumps(route)
#for k,v in route.items():
#print k,str(v)
def main():
global FREQ,total
dictfile='./dict.txt'
FREQ,total=gen_pfdict(dictfile)
print "total frequnce is "+str(total)
print "dict length is "+str(len(FREQ))
#i=0
#g = lambda m: '\n'.join([ '%s=%d'%(k, v) for k, v in m.items() ])
sent="英语单词很难记忆"
dag=get_DAG(sent)
print json.dumps(dag)
route={}
calc(sent,dag,route)
N=len(sent)
x=0
while x < N:
y = route[x][1] + 1
print y
lword = sent[x:y]
print lword
x=y
if __name__=='__main__':
main()