2021SC@SDUSC
__cut_DAG_NO_HMM()
re_eng = re.compile('[a-zA-Z0-9]', re.U)
def __cut_DAG_NO_HMM(self, sentence):
# 构建有向无环图
DAG = self.get_DAG(sentence)
route = {}
# 计算最大概率路径
self.calc(sentence, DAG, route)
x = 0
N = len(sentence)
buf = ''
# 使用最大概率路径进行分词
while x < N:
y = route[x][1] + 1
# 选择最大概率路劲的词汇
l_word = sentence[x:y]
# 如果匹配到字母或数字或词汇长度为1,则添加到buf中
if re_eng.match(l_word) and len(l_word) == 1:
buf += l_word
x = y
# 否则向迭代器返回非空的buf/将词汇传给迭代器
else:
if buf:
yield buf
buf = ''
yield l_word
x = y
# 若最后buf非空返回给迭代器,buf置空
if buf:
yield buf
buf = ''
例子:
_cut_DAG()
源码:
def __cut_DAG(sentence):
# 构建有向无环图
DAG = jieba.get_DAG(sentence)
route = {}
# 计算最大概率路径
jieba.calc(sentence, DAG, route)
x = 0
N = len(sentence)
buf = ''
# 使用最大概率路径进行分词
while x < N:
y = route[x][1] + 1
l_word = sentence[x:y]
# l_word长度为一,将词汇添加到buf
if y - x == 1:
buf += l_word
else:
if buf:
# buf长度为1,向迭代器返回buf,buf置空
if len(buf) == 1:
yield buf
buf = ''
else:
# buf长度大于1
# 如果没在词频字典中找到,使用finalseg.cut切分,结果逐个返回给迭代器
if not jieba.dt.FREQ.get(buf):
recognized = jieba.finalseg.cut(buf)
for t in recognized:
yield t
else:
# buf在词频字典中,则逐个返回给迭代器
for elem in buf:
yield elem
buf = ''
#此时buf为空,将l_word返回给迭代器
yield l_word
x = y
# 处理完成后避免buf中还有词汇,再次操作一次。
if buf:
if len(buf) == 1:
yield buf
elif not jieba.dt.FREQ.get(buf):
recognized = jieba.finalseg.cut(buf)
for t in recognized:
yield t
else:
for elem in buf:
yield elem
其中recognized变量使用 finalseg.cut()进行分词的结果,看到finalseg。
finalseg是基于HMM模型的中文分词,使用了viterbi算法。
def cut(sentence):
sentence = strdecode(sentence)
# 切割句子
blocks = re_han.split(sentence)
for blk in blocks:
# 处理中文
if re_han.match(blk):
for word in __cut(blk):
if word not in Force_Split_Words:
yield word
else:
for c in word:
yield c
#处理英文、数字
else:
tmp = re_skip.split(blk)
for x in tmp:
if x:
yield x
可以看到finalseg.cut()对中文进行了切分、分词(使用__cut()方法)
def __cut(sentence):
global emit_P
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:]
__cut()方法主要是对viterbi算法的结果进行一些处理,对sentence的每个字符进行了处理后返回给迭代器。
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.如果概率最大的路径p(或者说最短路径)经过某个点,比如图中的X22,那么这条路径上的起始点S到X22的这段子路径Q,一定是S到X22之间的最短路径。否则,用S到X22的最短路径R替代Q,便构成一条比P更短的路径,这显然是矛盾的。证明了满足最优性原理。
2.从S到E的路径必定经过第i个时刻的某个状态,假定第i个时刻有k个状态,那么如果记录了从S到第i个状态的所有k个节点的最短路径,最终的最短路径必经过其中一条,这样,在任意时刻,只要考虑非常有限的最短路即可。
3. 结合以上两点,假定当我们从状态i进入状态i+1时,从S到状态i上各个节的最短路径已经找到,并且记录在这些节点上,那么在计算从起点S到第i+1状态的某个节点Xi+1的最短路径时,只要考虑从S到前一个状态i所有的k个节点的最短路径,以及从这个节点到Xi+1,j的距离即可。