【NLP CODE】基于统计的中文分词算法2:隐马尔可夫模型(维特比算法)
HMM算法实现:
# https://blog.csdn.net/wk19951125/article/details/88862095 class HMM(object): def __init__(self): import os # 主要是用于存取算法中间结果,不用每次都训练模型 self.model_file = 'hmm_model.pkl' # 状态值集合 self.state_list = ['B', 'M', 'E', 'S'] # 参数加载,用于判断是否需要重新加载model_file self.load_para = False # 用于加载已计算的中间结果,当需要重新训练时,需初始化清空结果 def try_load_model(self, 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): # 重置几个概率矩阵 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): 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): # 维特比算法 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 #设置未知字单独成词 (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算法的链接里面,原作者有下载。第二个第三个网上很多,搜:icwb2-data)
1.自定义语料库
#####测试 from hmm import HMM hmm_test = HMM() hmm_test.train('CWS_HMM/trainCorpus.txt_utf8.txt') text='我们在野生动物园玩' res=hmm_test.cut(text) print(text) print(str(list(res))) text='他是研究生物化学的' res=hmm_test.cut(text) print(text) print(str(list(res))) text='商品和服务' res=hmm_test.cut(text) print(text) print(str(list(res))) text='南京市长江大桥' res=hmm_test.cut(text) print(text) print(str(list(res))) text='下雨天留客天留我不留' res=hmm_test.cut(text) print(text) print(str(list(res)))
2.微软语料库
# icwb2-data 数据集是由北京大学、香港城市大学、台湾 CKIP, Academia Sinica 及中国微软研究所联合发布的数据集, # 用以进行中文分词模型的训练。 # 其中 AS 和 CityU 为繁体中文数据集,PK 和 MSR 为简体中文数据集。 from hmm import HMM hmm_test = HMM() hmm_test.train('icwb2-data/training/msr_training.utf8.txt') text='我们在野生动物园玩' res=hmm_test.cut(text) print(text) print(str(list(res))) text='他是研究生物化学的' res=hmm_test.cut(text) print(text) print(str(list(res))) text='商品和服务' res=hmm_test.cut(text) print(text) print(str(list(res))) text='南京市长江大桥' res=hmm_test.cut(text) print(text) print(str(list(res))) text='下雨天留客天留我不留' res=hmm_test.cut(text) print(text) print(str(list(res)))
3.北大语料库
# icwb2-data 数据集是由北京大学、香港城市大学、台湾 CKIP, Academia Sinica 及中国微软研究所联合发布的数据集, # 用以进行中文分词模型的训练。 # 其中 AS 和 CityU 为繁体中文数据集,PK 和 MSR 为简体中文数据集。 from hmm import HMM hmm_test = HMM() hmm_test.train('icwb2-data/training/pku_training.utf8.txt') text='我们在野生动物园玩' res=hmm_test.cut(text) print(text) print(str(list(res))) text='他是研究生物化学的' res=hmm_test.cut(text) print(text) print(str(list(res))) text='商品和服务' res=hmm_test.cut(text) print(text) print(str(list(res))) text='南京市长江大桥' res=hmm_test.cut(text) print(text) print(str(list(res))) text='下雨天留客天留我不留' res=hmm_test.cut(text) print(text) print(str(list(res)))
结果对比:
|
自定义语料库 |
微软语料库 |
北大语料库 |
我们在野生动物园玩 |
['我们', '在', '野', '生动物园', '玩'] |
['我们', '在', '野', '生动', '物园', '玩'] |
['我们', '在', '野生', '动物', '园玩'] |
他是研究生物化学的 |
['他是', '研究', '生物', '化学', '的'] |
['他是', '研究', '生物', '化学', '的'] |
['他', '是', '研究', '生物', '化学', '的'] |
商品和服务 |
['商品', '和', '服务'] |
['商品', '和', '服务'] |
['商品', '和', '服务'] |
南京市长江大桥 |
['南京', '市长', '江', '大桥'] |
['南京', '市长', '江大桥'] |
['南京', '市长', '江', '大桥'] |
下雨天留客天留我不留 |
['下雨', '天', '留', '客天', '留我', '不', '留'] |
['下雨', '天留', '客天', '留', '我', '不留'] |
['下雨', '天', '留客', '天留', '我', '不', '留'] |
再来跟前面基于字典的方法比较下:
|
正向最大匹配法 |
反向最大匹配法 |
双向最大匹配法 |
我们在野生动物园玩 |
我们/在野/生动/物/园/玩 |
我们/在/野生动物园/玩 |
我们/在/野生动物园/玩 |
他是研究生物化学的 |
他/是/研究生/物化/学/的/ |
他/是/研究/生物化学/的/ |
他/是/研究/生物化学/的/ |
商品和服务 |
商品/和服/务/ |
商品/和/服务/ |
|
南京市长江大桥 |
南京市/长江/大桥/ |
南京/市长/江大桥/ |
|
下雨天留客天留我不留 |
下雨/天/留客/天/留/我/不留/ |
下/雨天/留客/天/留/我/不留/ |
|
看看当前的在线平台效果:
|
|||||
我们在野生动物园玩 |
我们|r 在|p 野生|f 动物园|n 玩|v |
我们_r 在_p 野生_v 动物园_n 玩_v |
|
|
|
他是研究生物化学的 |
他|r 是|v 研究|v 生物|n 化学|n 的|u | 他_r 是_v 研究_v 生物化学_n 的_u | |
||
商品和服务 |
商品|n 和|c 服务|v |
商品_n 和_c 服务_v |
|
||
南京市长江大桥 |
|
南京市|n 长江|n 大桥|n |
南京市_ns 长江_ns 大桥_n |
||
下雨天留客天留我不留 |
下|n 雨天|n 留客|v 天|n 留|v 我|r 不留|v |
下雨_v 天_t 留_v 客天_n 留_v 我_r 不_d 留_v |