从一个函数入口逐步分析分词的整个过程,最后对关键函数做了简化实现,附在最后供大家参考
分析
寻根
import jieba
jieba.cut("sentence")
查找cut的引用:
dt = Tokenizer()
cut = dt.cut#位于jieba/__init__.py
找到cut()方法,首先里面有很多re开头的变量,这些都是用于文字和字符分割用的re的实例
# \u4E00-\u9FD5a-zA-Z0-9+#&\._ : All non-space characters. Will be handled with re_han
# \r\n|\s : whitespace characters. Will not be handled.
re_han_default = re.compile("([\u4E00-\u9FD5a-zA-Z0-9+#&\._%]+)", re.U)
re_skip_default = re.compile("(\r\n|\s)", re.U)
re_han_cut_all = re.compile("([\u4E00-\u9FD5]+)", re.U)
re_skip_cut_all = re.compile("[^a-zA-Z0-9+#\n]", re.U)
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)#将文字和非文字分开成块
'''
提供一个切分示例:
stri = "我去北京玩游戏,去了北京找人"
re_han_default.split(stri)
output:
['', '我去北京玩游戏', ',', '去了北京找人', '']
'''
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
我们知道cut方法返回的是一个迭代器,也就是里面每一个yield返回的值了
可以看到对文字的处理关键在:
if re_han.match(blk):#如果不是符号
for word in cut_block(blk):
yield word
找到cut_block,一共有三种实现,依次看一下
if cut_all:
cut_block = self.__cut_all
elif HMM:
cut_block = self.__cut_DAG
else:
cut_block = self.__cut_DAG_NO_HMM
__cut_all
首先是__cut_all,里面涉及到方法get_DAG()
,这个方法其实就是得到文字的有向无环图,这个方法我将其稍加修改,将其中涉及到类的参数作为方法变量传入:
def get_DAG(sentence,FREQ):
#FREQ是{词:词频}的字典,类似于{'北京大学': 2053, '北': 0, '北京': 0, '北京大': 0, '大学': 20025, '大': 0}
# 检查系统是否已经初始化
# DAG存储向无环图的数据,数据结构是dict
DAG = {}
N = len(sentence)
# 依次遍历文本中的每个位置
for k in range(N):
tmplist = []
i = k
# 位置 k 形成的片段
frag = sentence[k]
# 判断片段是否在前缀词典中
# 如果片段不在前缀词典中,则跳出本循环
# 也即该片段已经超出统计词典中该词的长度
while i < N and frag in FREQ:
# 如果该片段的词频大于0
# 将该片段加入到有向无环图中
# 否则,继续循环
if FREQ[frag]:
tmplist.append(i)
# 片段末尾位置加1
i += 1
# 新的片段较旧的片段右边新增一个字
frag = sentence[k:i + 1]
if not tmplist:
tmplist.append(k)
DAG[k] = tmplist
return DAG#最后返回的DAG的形式类似于
#{0: [0], 1: [1], 2: [2, 3, 5], 3: [3], 4: [4, 5], 5: [5], 6: [6]}
下面回归__cut_all()
,get_DAG()
方法就在__cut_all()
方法的第一句(这个方法我也稍微修改了一下,将类变量提取了出来):
def __cut_all(sentence,FREQ):
dag = get_DAG(sentence,FREQ)
print(dag)
print(iter(dag))
old_j = -1#旧的位置
for k, L in iteritems(dag):#iteritems是jieba根据python版本不同写出来的迭代方法,就是迭代dag的key和value
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: # 如果位置比k大,个人感觉因为是有向无环图图,这个if应该恒为真吧
yield sentence[k:j + 1] # 返回
old_j = j
以上就是cut中__cut_all的全部分析,对__cut_DAG的实现,由于涉及的函数过多,因此放到下一篇进行分析
简化实现
将__cut_all的所有涉及的方法抽取为普通方法放到这里,提供了一个示例,可以直接运行,供调试和学习
#cut_all_ana.py
from cut_all_ana import get_DAG,gen_pfdict
import re
re_han = re.compile("([\u4E00-\u9FD5]+)")
re_skip = re.compile("([a-zA-Z0-9]+(?:\.\d+)?%?)")
from math import log
def calc(sentence, DAG, route,FREQ,total):
N = len(sentence)
route[N] = (0, 0)
logtotal = log(total)
for idx in range(N - 1, -1, -1):
ini = [(log(FREQ.get(sentence[idx:x + 1]) or 1)#FREQ.get(sentence[idx:x + 1]) or 1 ,如果FREQ有这个单词就用这个单词,如果没有就用1
- logtotal
+ route[x + 1][0], x
) for x in DAG[idx]]
print(ini)
route[idx] = max(ini)#元组的大小比较是按字典序比较,先比较第一个的大小,如果第一个大小相等再比较第二个的大小
def __cut_DAG(sentence,FREQ):
DAG = get_DAG(sentence)
route = {}
calc(sentence, DAG, route)
x = 0
buf = ''
N = len(sentence)
while x < N:
y = route[x][1] + 1
l_word = sentence[x:y]
if y - x == 1:
buf += l_word
else:
if buf:
if len(buf) == 1:
yield buf
buf = ''
else:
if not FREQ.get(buf):
recognized = fcut(buf)
for t in recognized:
yield t
else:
for elem in buf:
yield elem
buf = ''
yield l_word
x = y
if buf:
if len(buf) == 1:
yield buf
elif not FREQ.get(buf):
recognized = fcut.cut(buf)
for t in recognized:
yield t
else:
for elem in buf:
yield elem
if __name__ == "__main__":
k = [
["北京大学", 2053],
["大学", 20025],
["去", 123402],
["玩", 4207],
["北京", 34488],
["北", 17860],
["京", 6583],
["大", 144099],
["学", 17482],
]
ex = "我去北京大学玩"
freq, total = gen_pfdict(k)
dag = get_DAG(ex,freq)
route = {}
calc(ex, dag, route, freq, total)