HanLP — HMM隐马尔可夫模型 -- 维特比(Viterbi)算法 --示例代码 - Python
1.HanLP — HMM隐马尔可夫模型 -- 语料库2.HanLP — HMM隐马尔可夫模型 -- 训练3.HanLP — HMM隐马尔可夫模型 -- 训练--归一化,计算概率4.HanLP — HMM隐马尔可夫模型 - 路径规划算法 - 求解最短路径 - 维特比(Viterbi)算法
5.HanLP — HMM隐马尔可夫模型 -- 维特比(Viterbi)算法 --示例代码 - Python
6.HanLP — HMM隐马尔可夫模型 -- 维特比(Viterbi)算法 --示例代码 - Java语料库 => 标注 => 训练得到三数组 => 归一化算概率 => 生成模型文件
加载模型文件 => 标注 => 预测 => 维特比
可以对着这篇贴子看代码
import pickle
from tqdm import tqdm
import numpy as np
import os
def make_label(text_str):
"""从单词到label的转换, 如: 今天 ----> BE 麻辣肥牛: ---> BMME 的 ---> S"""
text_len = len(text_str)
if text_len == 1:
return "S"
return "B" + "M" * (text_len - 2) + "E" # 除了开头是 B, 结尾是 E,中间都是M
def text_to_state(train_file, state_file):
""" 将原始的语料库转换为 对应的状态文件 """
if os.path.exists(state_file): # 如果存在该文件, 就直接退出
os.remove(state_file)
# 打开文件并按行切分到 all_data 中 , all_data 是一个list
all_data = open(train_file, "r", encoding="utf-8").read().split("\n")
with open(state_file, "w", encoding="utf-8") as f: # 代开写入的文件
for d_index, data in tqdm(enumerate(all_data)): # 逐行 遍历 , tqdm 是进度条提示 , data 是一篇文章, 有可能为空
if data: # 如果 data 不为空
state_ = ""
for w in data.split(" "): # 当前 文章按照空格切分, w是文章中的一个词语
if w: # 如果 w 不为空
state_ = state_ + make_label(w) + " " # 制作单个词语的label
if d_index != len(all_data) - 1: # 最后一行不要加 "\n" 其他行都加 "\n"
state_ = state_.strip() + "\n" # 每一行都去掉 最后的空格
f.write(state_)
# 定义 HMM类, 其实最关键的就是三大矩阵
class HMM:
def __init__(self, file_text, file_state):
self.all_states = open(file_state, "r", encoding="utf-8").read().split("\n")[:200] # 按行获取所有的状态
self.all_texts = open(file_text, "r", encoding="utf-8").read().split("\n")[:200] # 按行获取所有的文本
self.states_to_index = {"B": 0, "M": 1, "S": 2, "E": 3} # 给每个状态定义一个索引, 以后可以根据状态获取索引
self.index_to_states = ["B", "M", "S", "E"] # 根据索引获取对应状态
self.len_states = len(self.states_to_index) # 状态长度 : 这里是4
# 初始矩阵 : 1 * 4 , 对应的是 BMSE,
self.init_matrix = np.zeros((self.len_states))
# 转移状态矩阵: 4 * 4 ,
self.transfer_matrix = np.zeros((self.len_states, self.len_states))
# 发射矩阵, 使用的 2级 字典嵌套,
# # 注意这里初始化了一个 total 键 , 存储当前状态出现的总次数, 为了后面的归一化使用
self.emit_matrix = {"B": {"total": 0}, "M": {"total": 0}, "S": {"total": 0}, "E": {"total": 0}}
# 计算 初始矩阵,统计每行第一个字出现的频次
def cal_init_matrix(self, state):
self.init_matrix[self.states_to_index[state[0]]] += 1 # BMSE 四种状态, 对应状态出现 1次 就 +1
# 计算 转移矩阵,当前状态到下一状态的概率
def cal_transfer_matrix(self, states):
sta_join = "".join(states) # 状态转移 从当前状态转移到后一状态, 即 从 sta1 每一元素转移到 sta2 中
sta1 = sta_join[:-1]
sta2 = sta_join[1:]
for s1, s2 in zip(sta1,
sta2): # 同时遍历 s1 , s2 -- (('B', 'E'), ('E', 'B'), ('B', 'E'), ('E', 'S'), ('S', 'B'), ('B', 'E'), ('E', 'S'))
self.transfer_matrix[self.states_to_index[s1], self.states_to_index[s2]] += 1
# 计算 发射矩阵,在特定状态下,出现某个字的概率
def cal_emit_matrix(self, words, states):
"""计算 发射矩阵,在特定状态下,出现某个字的概率
[
'今天 天气 真 不错 。',
'麻辣肥牛 好吃 !',
'我 喜欢 吃 好吃 的 !'
]
[
'BE BE S BE S',
'BMME BE S',
'S BE S BE S S '
]
{
'B': {'total': 7, '今': 1, '天': 1, '不': 1, '麻': 1, '好': 2, '喜': 1},
'M': {'total': 2, '辣': 1, '肥': 1},
'S': {'total': 7, '真': 1, '。': 1, '!': 2, '我': 1, '吃': 1, '的': 1},
'E': {'total': 7, '天': 1, '气': 1, '错': 1, '牛': 1, '吃': 2, '欢': 1}
}
"""
# print(tuple(zip("".join(words), "".join(states))))
for word, state in zip("".join(words), "".join(states)): # 先把words 和 states 拼接起来再遍历, 因为中间有空格
self.emit_matrix[state][word] = self.emit_matrix[state].get(word, 0) + 1
self.emit_matrix[state]["total"] += 1 # 注意这里多添加了一个 total 键 , 存储当前状态出现的总次数, 为了后面的归一化使用
# 将矩阵归一化
def normalize(self):
self.init_matrix = self.init_matrix / np.sum(self.init_matrix)
self.transfer_matrix = self.transfer_matrix / np.sum(self.transfer_matrix, axis=1, keepdims=True)
self.emit_matrix = {
state: {word: t / word_times["total"] * 1000 for word, t in word_times.items() if word != "total"} for
state, word_times in
self.emit_matrix.items()}
# 训练开始, 其实就是3个矩阵的求解过程
def train(self, file_model):
for words, states in tqdm(zip(self.all_texts, self.all_states)): # 按行读取文件, 调用3个矩阵的求解函数
words = words.split(" ") # 在文件中 都是按照空格切分的
states = states.split(" ")
self.cal_init_matrix(states[0]) # 初始矩阵,统计每行第一个字出现的频次 [2. 0. 1. 0.]
self.cal_transfer_matrix(states) # 转移矩阵,当前状态到下一状态的概率
self.cal_emit_matrix(words, states) # 发射矩阵,在特定状态下,出现某个字的概率
self.normalize() # 矩阵求完之后进行归一化
pickle.dump([self.init_matrix, self.transfer_matrix, self.emit_matrix], open(file_model, "wb")) # 保存参数
def viterbi_t(text, model_file):
with open(model_file, 'rb') as file:
loaded_matrix = pickle.load(file)
states_to_index = {"B": 0, "M": 1, "S": 2, "E": 3} # 给每个状态定义一个索引, 以后可以根据状态获取索引
states = ["B", "M", "S", "E"]
start_p = loaded_matrix[0]
trans_p = loaded_matrix[1]
emit_p = loaded_matrix[2]
V = [{}] # 存路径的概率
path = {} # 存路径,用于最后根据 S、E 判断增加空格
# START 到第一个字的概率
for y in states:
# 初始矩阵 * 第一个字在 BMSE 中的概率 [{'B': 95.23809523809524, 'M': 0.0, 'S': 0.0, 'E': 0.0}]
V[0][y] = start_p[states_to_index[y]] * emit_p[y].get(text[0], 0)
path[y] = [y] # {'B': ['B'], 'M': ['M'], 'S': ['S'], 'E': ['E']}
# 从第二个字开始算 “今天” 找到它的最优路径,从今到天,有16条路径,4种状态,每种状态下假设一个最优的,再从四条里面取最优
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()
# B M S E 分别计算四条路径,然后取最大概率的,做为最优路径
for y in states:
emitP = emit_p[y].get(text[t], 0) if not neverSeen else 1.0 # 设置未知字单独成词,到发射矩阵中,找到字的概率
temp = []
# 根据前一个字,做逻辑处理
for y0 in states:
pre_val = V[t - 1][y0]
if pre_val > 0: # 排除掉前一个字为0的路径,前路径概率 * 发射矩阵 * 转移矩阵 , 转移矩阵可以排队掉一些无效的路径,如 B->B
temp.append((pre_val * trans_p[states_to_index[y0], states_to_index[y]] * emitP, y0))
(prob, state) = max(temp) # 找出最大概率 及对应的状态(y0)
V[t][y] = prob # 最大的概率 及 状态,赋给 V 用于后面找出最大路径
newpath[y] = path[state] + [y] # 前一个字的状态 + 当前状态
path = newpath
# 求最大概率的路径,找出最后一个字的 B M S E 的最大值,说明它的路径概率最大(4条路径中找出最大的路径)
(prob, state) = max([(V[len(text) - 1][y], y) for y in states])
result = "" # 拼接结果
for t, s in zip(text, path[state]):
result += t
if s == "S" or s == "E": # 如果是 S 或者 E 就在后面添加空格
result += " "
return result
if __name__ == "__main__":
model_file = "data/train_model.pkl"
if not os.path.exists(model_file):
# 模型不存在,就训练模型
train_file = "data/train_data.txt"
state_file = "data/train_state.txt"
text_to_state(train_file, state_file)
hmm = HMM(train_file, state_file)
hmm.train(model_file)
# 预测
text = "今天的天气不错"
result = viterbi_t(text, model_file)
print(result)
源代码: https://gitee.com/VipSoft/VipPython/tree/master/hmm_viterbi
视频:代码讲解 https://www.bilibili.com/video/BV1aP4y147gA?p=11
本文来自博客园,作者:VipSoft 转载请注明原文链接:https://www.cnblogs.com/vipsoft/p/17970179
分类:
AI
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
2023-01-17 Axure 绘制表格添加删除
2023-01-17 Axure 二维码扫码