中文分词算法
中文分词算法的几种方法(带源码)
规则分词
基于规则的分词是一种机械分词方法,主要通过维护字典,在切分语句时将语句的每个字符串与词表中的词进行注意匹配,找到则切分,否则不予切分。
按照切分方式,主要有正向最大匹配法、逆向最大匹配法以及双向最大匹配法三种
特征:从左向右切分
示例代码:
"""
中文分词技术
编辑者:馒头
博客:https://www.cnblogs.com/mantou0/
"""
# 正向最大匹配法
class MM(object):
def __init__(self):
self.windowSize = 3 # 词典中最长的词的词长
self.dic = ["研究","研究生","生命","命","的","起源"] # 词典
def cut(self,text):
result = [] # 存放分割出的词
index = 0 # 初始化进行要分割的词的初始索引
text_length = len(text)
while text_length > index:
for size in range(self.windowSize+index,index,-1):
piece = text[index:size]
if piece in self.dic:
index = size -1
break
index = index + 1
result.append(piece+"---")
print(result)
if __name__ == '__main__':
text = "研究生命的起源"
tokenizer = MM()
tokenizer.cut(text)
特征:从右向左切分
示例代码:
"""
中文分词技术
编辑者:馒头
博客:https://www.cnblogs.com/mantou0/
"""
# 逆向最大匹配法
class RMM(object):
def __init__(self):
self.windowSize = 3 # 词典中最长的词的词长
self.dic = ["研究","研究生","生命","命","的","起源"] # 词典
def cut(self,text):
result = [] # 存放分割出的词
index = len(text) # 初始化进行要分割的词的初始索引
while index > 0:
for size in range(index - self.windowSize,index):
piece = text[size:index]
if piece in self.dic:
index = size + 1
break
index = index - 1
result.append(piece+"---")
result.reverse()
print(result)
if __name__ == '__main__':
text = "研究生命的起源"
tokenizer = RMM()
tokenizer.cut(text)
特征:正逆对比,然后按照最大匹配原则
示例代码:
"""
中文分词技术
编辑者:馒头
博客:https://www.cnblogs.com/mantou0/
"""
class M(object):
def __init__(self):
self.windowSize = 3 # 词典中最长的词的词长
self.dic = ["研究","研究生","生命","命","的","起源"] # 词典
#正向最大匹配算法
def MM(self,text):
result = [] # 存放分割出的词
index = 0 # 初始化进行要分割的词的初始索引
text_length = len(text)
while text_length > index:
for size in range(self.windowSize+index,index,-1):
piece = text[index:size]
if piece in self.dic:
index = size -1
break
index = index + 1
result.append(piece+"---")
return result
#逆向最大匹配算法
def RMM(self, text):
result = [] # 存放分割出的词
index = len(text) # 初始化进行要分割的词的初始索引
while index > 0:
for size in range(index - self.windowSize, index):
piece = text[size:index]
if piece in self.dic:
index = size + 1
break
index = index - 1
result.append(piece + "---")
result.reverse()
return result
def BMM(self, MM_result,RMM_result):
"""
比较两个分词方法分词的结果
比较方法:
1. 如果正反向分词结果词数不同,则取分词数量较少的那个
2. 如果分词结果词数相同:
2.1 分词结果相同,说明没有歧义,可返回任意一个
2.2 分词结果不同,返回其中单字较少的那个
:param MM_result: 正向最大匹配法的分词结果
:param RMM_result: 逆向最大匹配法的分词结果
:return:
1.词数不同返回词数较少的那个
2.词典结果相同,返回任意一个(MM_result)
3.词数相同但是词典结果不同,返回单字最少的那个
"""
if len(MM_result) != len(RMM_result):
# 如果两个结果词数不同,返回词数较少的那个
return MM_result if (len(MM_result) < len(RMM_result)) else RMM_result
else:
if MM_result == RMM_result:
# 因为RMM的结果是取反了的,所以可以直接匹配
# 词典结果相同,返回任意一个
return MM_result
else:
# 词数相同但是词典结果不同,返回单字最少的那个
MM_word_1 = 0
RMM_word_1 = 0
for word in MM_result:
# 判断正向匹配结果中单字出现的词数
if len(word) == 1:
MM_word_1 += 1
for word in RMM_result:
# 判断逆向匹配结果中单字出现的词数
if len(word) == 1:
RMM_word_1 += 1
if (MM_word_1 < RMM_word_1):
return MM_result
else:
return RMM_result
if __name__ == '__main__':
text = "研究生命的起源"
token = M()
token.BMM(token.MM(text),token.RMM(text))
统计分词
建立统计语言模型,对句子进行单词划分,然后对划分的结果进行概率计算,获得概率最大的分词方式。这里就用到了统计学习算法,隐含马尔可夫模型(HMM)、条件随机场(CRF)
需要提前准备好词典 需要词典和源码可以去我的github下载,例如

示例代码:
"""
中文分词技术
编辑者:馒头
博客:https://www.cnblogs.com/mantou0/
"""
# encoding=utf-8
class HMM(object):
def __init__(self):
"""
方法:初始化参数
"""
# 主要是用于存取算法中间结果,不用每次都训练模型
self.model_file = './data/hmm_model.pkl'
# 状态值集合
self.state_list = ['B', 'M', 'E', 'S']
# 参数加载,用于判断是否需要重新加载model_file
self.load_para = False
def try_load_model(self, trained):
"""
方法:用于加载已计算的中间结果,当需要重新训练时,需初始化清空结果
输入:trained :是否已经训练好
"""
if trained:
import pickle
with open(self.model_file, 'rb') as f:
self.A_dic = pickle.load(f)
self.B_dic = pickle.load(f)
self.Pi_dic = pickle.load(f)
self.load_para = True
else:
# 状态转移概率(状态->状态的条件概率)
self.A_dic = {}
# 发射概率(状态->词语的条件概率)
self.B_dic = {}
# 状态的初始概率
self.Pi_dic = {}
self.load_para = False
def train(self, path):
"""
方法:计算转移概率、发射概率以及初始概率
输入:path:训练材料路径
"""
# 重置几个概率矩阵
self.try_load_model(False)
# 统计状态出现次数,求p(o)
Count_dic = {}
# 初始化参数
def init_parameters():
for state in self.state_list:
self.A_dic[state] = {s: 0.0 for s in self.state_list}
self.Pi_dic[state] = 0.0
self.B_dic[state] = {}
Count_dic[state] = 0
def makeLabel(text):
"""
方法:为训练材料每个词划BMES
输入:text:一个词
输出:out_text:划好的一个BMES列表
"""
out_text = []
if len(text) == 1:
out_text.append('S')
else:
out_text += ['B'] + ['M'] * (len(text) - 2) + ['E']
return out_text
init_parameters()
line_num = -1
# 观察者集合,主要是字以及标点等
words = set()
with open(path, encoding='utf8') as f:
for line in f:
line_num += 1
line = line.strip()
if not line:
continue
word_list = [i for i in line if i != ' ']
words |= set(word_list) # 更新字的集合
linelist = line.split()
line_state = []
for w in linelist:
line_state.extend(makeLabel(w))
assert len(word_list) == len(line_state)
for k, v in enumerate(line_state):
Count_dic[v] += 1
if k == 0:
self.Pi_dic[v] += 1 # 每个句子的第一个字的状态,用于计算初始状态概率
else:
self.A_dic[line_state[k - 1]][v] += 1 # 计算转移概率
self.B_dic[line_state[k]][word_list[k]] = \
self.B_dic[line_state[k]].get(
word_list[k], 0) + 1.0 # 计算发射概率
self.Pi_dic = {k: v * 1.0 / line_num for k, v in self.Pi_dic.items()}
self.A_dic = {k: {k1: v1 / Count_dic[k] for k1, v1 in v.items()}
for k, v in self.A_dic.items()}
# 加1平滑
self.B_dic = {k: {k1: (v1 + 1) / Count_dic[k] for k1, v1 in v.items()}
for k, v in self.B_dic.items()}
# 序列化
import pickle
with open(self.model_file, 'wb') as f:
pickle.dump(self.A_dic, f)
pickle.dump(self.B_dic, f)
pickle.dump(self.Pi_dic, f)
return self
def viterbi(self, text, states, start_p, trans_p, emit_p):
"""
方法:维特比算法,寻找最优路径,即最大可能的分词方案
输入:text:文本
states:状态集
start_p:第一个字的各状态的可能
trans_p:转移概率
emit_p:发射概率
输出:prob:概率
path:划分方案
"""
V = [{}] # 路径图
path = {}
for y in states: # 初始化第一个字的各状态的可能性
V[0][y] = start_p[y] * emit_p[y].get(text[0], 0)
path[y] = [y]
for t in range(1, len(text)): # 每一个字
V.append({})
newpath = {}
# 检验训练的发射概率矩阵中是否有该字
neverSeen = text[t] not in emit_p['S'].keys() and \
text[t] not in emit_p['M'].keys() and \
text[t] not in emit_p['E'].keys() and \
text[t] not in emit_p['B'].keys()
for y in states: # 每个字的每个状态的可能
emitP = emit_p[y].get(
text[t], 0) if not neverSeen else 1.0 # 设置未知字单独成词
# y0上一个字可能的状态,然后算出当前字最可能的状态,prob则是最大可能,state是上一个字的状态
(prob, state) = max(
[(V[t - 1][y0] * trans_p[y0].get(y, 0) *
emitP, y0)
for y0 in states if V[t - 1][y0] > 0])
V[t][y] = prob
newpath[y] = path[state] + [y] # 更新路径
path = newpath
if emit_p['M'].get(text[-1], 0) > emit_p['S'].get(text[-1], 0): # 最后一个字是词中的可能大于单独成词的可能
(prob, state) = max([(V[len(text) - 1][y], y) for y in ('E', 'M')])
else: # 否则就直接选最大可能的那条路
(prob, state) = max([(V[len(text) - 1][y], y) for y in states])
return (prob, path[state])
# 用维特比算法分词,并输出
def cut(self, text):
import os
if not self.load_para:
self.try_load_model(os.path.exists(self.model_file))
prob, pos_list = self.viterbi(
text, self.state_list, self.Pi_dic, self.A_dic, self.B_dic)
begin, next = 0, 0
for i, char in enumerate(text):
pos = pos_list[i]
if pos == 'B':
begin = i
elif pos == 'E':
yield text[begin: i + 1]
next = i + 1
elif pos == 'S':
yield char
next = i + 1
if next < len(text):
yield text[next:]
hmm = HMM()
hmm.train('./data/trainCorpus.txt_utf8')
text = '研究生命的起源'
res = hmm.cut(text)
print(text)
print(str(list(res)))
今年来,随着NLP技术的日益成熟,开源实现的分词工具越来越多,如,Ansj,盘古分词等。Jieba分词结合了基于规则和基于统计这两类方法。
Jieba提供了三种分词模式:
精准模式:
试图将橘子最精准的切开,适合文本分析。
全模式:
把句子中所有可以成词的词语都扫描出来,速度非常快,但是不能解决歧义。
搜索引擎模式:
在精准模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。
示例代码:
"""
中文分词技术
编辑者:馒头
博客:https://www.cnblogs.com/mantou0/
"""
import jieba
sent = '中文分词工具是文本处理不可或缺的一步!'
seg_list = jieba.cut(sent,cut_all=True)
print('全模式:','/'.join(seg_list))
seg_list = jieba.cut(sent,cut_all=False)
print('精准模式:','/'.join(seg_list))
seg_list = jieba.cut(sent)
print('默认精准模式:','/'.join(seg_list))
seg_list = jieba.cut_for_search(sent)
print('搜索引擎模式:','/'.join(seg_list))


正向最大匹配法(MM),逆向最大匹配法(RMM),双向最大匹配法(BMM),隐含马尔可夫模型(HMM),中文分词工具——jieba
浙公网安备 33010602011771号