news 2026/4/16 15:21:22

【耿直哥机器学习】14-4-隐马尔可夫模型代码实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【耿直哥机器学习】14-4-隐马尔可夫模型代码实现

14-4-隐马尔可夫模型代码实现(详细注释版)


一、完整注释版代码(Jupyter Notebook格式)

数据准备

# 导入numpy库,用于数值计算(矩阵定义、概率运算、数组操作等)importnumpyasnp# 定义HMM的**隐藏状态**:无法直接观测的状态(复习状态)# 3个隐藏状态:0=认真复习,1=简单复习,2=没有复习state=np.array(['认真复习','简单复习','没有复习'])# 定义HMM的**观测状态**:可直接观测的结果(考试成绩)# 9个观测状态:0=A+,1=A,2=A-,3=B+,4=B,5=B-,6=C+,7=C,8=C-grade=np.array(['A+','A','A-','B+','B','B-','C+','C','C-'])# 计算隐藏状态的数量:n_state=3n_state=len(state)# 计算观测状态的数量:m_grade=9m_grade=len(grade)# 定义HMM三要素1:初始概率矩阵pi(初始时刻各隐藏状态的概率)# 均匀分布:初始时“认真/简单/没复习”的概率都是1/3(0.333...)pi=np.ones(n_state)/n_state# 定义HMM三要素2:转移矩阵t(隐藏状态之间的转移概率)# 每行对应“当前隐藏状态”,每列对应“下一刻隐藏状态”# 例如:t[0] = [0.4,0.3,0.3] → 当前“认真复习”,下一刻仍认真复习的概率0.4,简单复习0.3,没复习0.3t=np.array([[0.4,0.3,0.3],# 认真复习 → 认真/简单/没复习的概率[0.3,0.4,0.3],# 简单复习 → 认真/简单/没复习的概率[0.3,0.3,0.4]# 没有复习 → 认真/简单/没复习的概率])# 定义HMM三要素3:发射矩阵e(隐藏状态→观测状态的概率)# 形状[3,9]:3个隐藏状态 × 9个观测状态,初始化为全0e=np.zeros([3,9])# 隐藏状态0(认真复习):所有成绩概率均等,每个观测概率=1/9≈0.111e[0,:9]=1/9# 隐藏状态1(简单复习):仅B+到C-(索引3-8)有概率,每个观测概率=1/6≈0.167e[1,3:9]=1/6# 隐藏状态2(没有复习):仅B-到C-(索引5-8)有概率,每个观测概率=1/4=0.25e[2,5:9]=1/4
# 打印HMM三要素,验证矩阵定义是否正确# 初始概率矩阵:输出[0.333..., 0.333..., 0.333...],均匀分布print("初始概率矩阵:\n",pi)# 转移矩阵:输出3×3矩阵,每行概率和为1(符合概率矩阵要求)print("转移矩阵:\n",t)# 发射矩阵:输出3×9矩阵,每行概率和为1(隐藏状态的观测概率总和为1)print("发射矩阵:\n",e)# 输出说明:# - 认真复习(行0):所有成绩概率都是1/9;# - 简单复习(行1):A+/A/A-概率为0,B+到C-各1/6;# - 没有复习(行2):A+到B概率为0,B-到C-各0.25;# 符合“复习越充分,高分概率越高”的逻辑

hmmlearn

# 安装hmmlearn库(Python中实现HMM的常用库,支持离散/连续观测的HMM)# 注意:Jupyter中用!pip install,PyCharm中需在终端执行该命令pip install hmmlearn
# 从hmmlearn的hmm模块导入CategoricalHMM类(适用于**离散观测状态**的HMM)# 若观测状态是连续的,需用GaussianHMM等其他类fromhmmlearn.hmmimportCategoricalHMM# 初始化CategoricalHMM模型:n_state=3 → 隐藏状态数量为3hmm=CategoricalHMM(n_state)
# 设置HMM模型的初始概率矩阵(对应pi)hmm.startprob_=pi# 设置HMM模型的转移矩阵(对应t)hmm.transmat_=t# 设置HMM模型的发射矩阵(对应e)hmm.emissionprob_=e# 设置观测状态的数量(可选,显式指定9个成绩等级)hmm.n_feature=9# 注意:hmmlearn中CategoricalHMM的发射矩阵必须满足:# 1. 每行概率和为1;2. 列数等于观测状态数量(9)
# 定义观测序列datas:对应grade的索引,[0,4,2,6,1] → [A+, B, A-, C+, A]datas=np.array([0,4,2,6,1])# 转换形状:hmmlearn要求观测序列为二维数组(样本数×特征数),这里特征数=1# 原形状(5,) → 转换后(5,1)datas=np.expand_dims(datas,axis=1)# 预测:给定观测序列,预测最可能的隐藏状态序列(维特比算法)states=hmm.predict(datas)
# 输出预测的隐藏状态序列:array([0, 0, 0, 2, 0])# 对应state的索引:0=认真复习,2=没有复习# 结果解释:# 观测序列[A+, B, A-, C+, A]对应的隐藏状态为[认真复习, 认真复习, 认真复习, 没有复习, 认真复习]# 其中C+(索引6)被预测为“没有复习”,符合“没复习很难得高分,但可能偶然得C+”的逻辑states
# 评分:计算给定观测序列的**对数概率**(log概率)# 对数概率的好处:避免小数相乘导致的下溢(数值过小)prob=hmm.score(datas)# 输出对数概率:-14.00367...(负数,因为log(0~1)为负)prob
# 将对数概率转换为实际概率:np.exp(prob)# 输出≈8.28e-07 → 该观测序列出现的概率约为8.28×10^-7(极低)# 原因:观测序列包含A+、A等高分,也包含C+(低分段),组合概率本身极低print(np.exp(prob))
# 采样:生成指定长度的观测序列和隐藏状态序列(10000个样本)# 返回值:datas=观测序列(形状(10000,1)),states=隐藏状态序列(形状(10000,))datas,states=hmm.sample(10000)
# 基于采样结果,统计**实际转移矩阵t_2**(验证和原始t的一致性)# 初始化转移矩阵t_2为3×3全0t_2=np.zeros([3,3])# 遍历每个隐藏状态(0=认真,1=简单,2=没复习)foriinrange(3):# 找到所有当前状态为i的索引current=np.where(states==i)[0]# 下一个状态的索引(当前索引+1)next_index=current+1# 去掉最后一个索引(无下一个状态)next_index=next_index[:-1]# 获取下一个状态的取值tmp=states[next_index]# 统计当前状态i→下一个状态j的次数,计算转移概率forjinrange(3):# 状态i→j的次数 / 状态i的总转移次数 = 转移概率t_2[i][j]=np.where(tmp==j)[0].shape[0]/np.shape(tmp)[0]# 输出统计的转移矩阵t_2:# [[0.411..., 0.293..., 0.295...],# [0.288..., 0.409..., 0.301...],# [0.296..., 0.309..., 0.394...]]# 结果解释:统计结果和原始转移矩阵t(每行0.4,0.3,0.3)高度接近,验证了HMM模型的正确性print(t_2)
# 基于采样结果,统计**实际发射矩阵e_2**(验证和原始e的一致性)# 初始化发射矩阵e_2为3×9全0e_2=np.zeros([3,9])# 遍历每个隐藏状态foriinrange(3):# 找到所有隐藏状态为i的索引current=np.where(states==i)[0]# (注:原代码中next_index无实际作用,属于笔误,保留原代码结构仅注释说明)next_index=current+1next_index=next_index[:-1]# 获取隐藏状态i对应的观测序列tmp=datas[current]# 统计隐藏状态i→观测状态j的次数,计算发射概率forjinrange(9):# 状态i→观测j的次数 / 状态i的总观测次数 = 发射概率e_2[i][j]=np.where(tmp==j)[0].shape[0]/np.shape(tmp)[0]# 输出统计的发射矩阵e_2:# 行0(认真复习):0-8列都有概率,且接近1/9;# 行1(简单复习):0-2列概率≈0,3-8列接近1/6;# 行2(没有复习):0-4列概率≈0,5-8列接近1/4;# 结果解释:统计结果和原始发射矩阵e高度一致,验证了HMM模型的发射概率设置正确print(e_2)

二、核心知识点梳理

1. 隐马尔可夫模型(HMM)核心三要素(小白易懂版)

组件名称数学符号代码对应核心含义本案例解释
初始概率矩阵π \piπpi初始时刻各隐藏状态的概率分布一开始“认真/简单/没复习”的概率都是1/3
转移矩阵A AAt隐藏状态之间的转移概率(当前→下一刻)比如“认真复习”→“认真复习”的概率0.4,→“没复习”0.3
发射矩阵B BBe隐藏状态→观测状态的概率(生成观测的规则)“没复习”只能生成B-/C+/C/C-,且概率各0.25

2. HMM隐藏状态与观测状态映射表

隐藏状态索引隐藏状态观测状态范围(grade)观测状态索引发射概率规则
0认真复习A+ ~ C-(全部)0~8每个观测概率=1/9≈0.111
1简单复习B+ ~ C-3~8每个观测概率=1/6≈0.167
2没有复习B- ~ C-5~8每个观测概率=1/4=0.25

3. hmmlearn库CategoricalHMM核心属性/方法

名称类型作用输入/输出说明本案例示例
startprob_属性设置初始概率矩阵输入:形状为(n_state,)的数组,和为1hmm.startprob_ = pi
transmat_属性设置转移矩阵输入:形状为(n_state,n_state)的数组,每行和为1hmm.transmat_ = t
emissionprob_属性设置发射矩阵输入:形状为(n_state,n_feature)的数组,每行和为1hmm.emissionprob_ = e
n_feature属性显式指定观测状态数量输入:整数(观测状态数)hmm.n_feature = 9
predict()方法预测隐藏状态序列(维特比算法)输入:观测序列(长度,1);输出:隐藏状态序列(长度,)states = hmm.predict(datas)
score()方法计算观测序列的对数概率输入:观测序列(长度,1);输出:对数概率(浮点数)prob = hmm.score(datas)
sample()方法生成观测/隐藏状态序列输入:序列长度;输出:(观测序列, 隐藏状态序列)datas, states = hmm.sample(10000)

4. 关键数值运算说明

代码片段作用小白解释
np.expand_dims(datas, axis=1)调整观测序列形状把一维数组(5,)变成二维数组(5,1),满足hmmlearn的输入要求
np.where(states == i)[0]查找指定状态的索引找到所有隐藏状态为i的位置,比如i=0时找到所有“认真复习”的时刻
np.exp(prob)对数概率转实际概率log概率是负数,exp后还原为0~1之间的实际概率
np.where(tmp==j)[0].shape[0]统计指定状态的次数统计tmp数组中等于j的元素个数,用于计算概率

三、可直接运行的代码版本

版本1:Jupyter Notebook版(已在上方完整给出,可直接复制运行)

版本2:PyCharm版(无ifname== ‘main’,可直接复制运行)

# 14-4-隐马尔可夫模型代码实现 - PyCharm可直接运行版本# 适配Python 3.8+,hmmlearn 0.2.8+,无需额外配置(需先安装hmmlearn:pip install hmmlearn)# ====================== 1. 导入库并定义HMM三要素 ======================importnumpyasnp# 定义隐藏状态和观测状态state=np.array(['认真复习','简单复习','没有复习'])# 3个隐藏状态grade=np.array(['A+','A','A-','B+','B','B-','C+','C','C-'])# 9个观测状态n_state=len(state)# 隐藏状态数:3m_grade=len(grade)# 观测状态数:9# 初始概率矩阵(均匀分布)pi=np.ones(n_state)/n_state# 转移矩阵(隐藏状态转移概率)t=np.array([[0.4,0.3,0.3],# 认真复习 → 认真/简单/没复习[0.3,0.4,0.3],# 简单复习 → 认真/简单/没复习[0.3,0.3,0.4]# 没有复习 → 认真/简单/没复习])# 发射矩阵(隐藏→观测概率)e=np.zeros([3,9])e[0,:9]=1/9# 认真复习:所有成绩概率1/9e[1,3:9]=1/6# 简单复习:B+到C-概率1/6e[2,5:9]=1/4# 没有复习:B-到C-概率1/4# 打印HMM三要素,验证定义print("===== HMM三要素 =====")print("初始概率矩阵:\n",pi)print("转移矩阵:\n",t)print("发射矩阵:\n",e)# ====================== 2. 初始化hmmlearn模型 ======================fromhmmlearn.hmmimportCategoricalHMM# 初始化离散观测HMM模型hmm=CategoricalHMM(n_state)# 设置模型参数hmm.startprob_=pi hmm.transmat_=t hmm.emissionprob_=e hmm.n_feature=9# 显式指定观测状态数# ====================== 3. 预测隐藏状态 ======================print("\n===== 观测序列预测隐藏状态 =====")# 定义观测序列(对应grade索引:A+, B, A-, C+, A)datas=np.array([0,4,2,6,1])datas_2d=np.expand_dims(datas,axis=1)# 转换为hmmlearn要求的二维形状# 预测隐藏状态states_pred=hmm.predict(datas_2d)print(f"观测序列:{[grade[i]foriindatas]}")print(f"预测隐藏状态索引:{states_pred}")print(f"预测隐藏状态:{[state[i]foriinstates_pred]}")# ====================== 4. 计算观测序列概率 ======================# 对数概率prob_log=hmm.score(datas_2d)# 实际概率(指数还原)prob_actual=np.exp(prob_log)print("\n===== 观测序列概率 =====")print(f"观测序列的对数概率:{prob_log:.6f}")print(f"观测序列的实际概率:{prob_actual:.6e}")# 科学计数法显示# ====================== 5. 采样并统计转移/发射矩阵 ======================print("\n===== 采样并统计转移/发射矩阵 =====")# 采样10000个样本datas_sample,states_sample=hmm.sample(10000)print(f"采样结果形状:观测序列{datas_sample.shape},隐藏状态序列{states_sample.shape}")# 统计转移矩阵t_2=np.zeros([3,3])foriinrange(3):current=np.where(states_sample==i)[0]next_index=current+1next_index=next_index[:-1]tmp=states_sample[next_index]forjinrange(3):t_2[i][j]=np.where(tmp==j)[0].shape[0]/np.shape(tmp)[0]print("统计的转移矩阵:\n",t_2)# 统计发射矩阵e_2=np.zeros([3,9])foriinrange(3):current=np.where(states_sample==i)[0]tmp=datas_sample[current]# 原代码中next_index无作用,此处修正forjinrange(9):e_2[i][j]=np.where(tmp==j)[0].shape[0]/np.shape(tmp)[0]print("统计的发射矩阵:\n",e_2)# ====================== 结果总结 ======================print("\n===== 实验结论 =====")print("1. 采样统计的转移矩阵与原始矩阵高度一致(每行≈0.4,0.3,0.3);")print("2. 采样统计的发射矩阵符合预期:认真复习全成绩有概率,没复习仅低分段有概率;")print("3. HMM可有效预测观测序列对应的隐藏状态,验证了模型的合理性!")

总结

  1. HMM核心逻辑:隐马尔可夫模型通过初始概率、转移矩阵、发射矩阵三要素描述“隐藏状态→观测状态”的生成过程,核心功能包括预测隐藏状态(维特比算法)、计算观测序列概率、生成采样序列。
  2. hmmlearn使用要点
    • 离散观测状态用CategoricalHMM,连续观测用GaussianHMM
    • 观测序列必须转换为二维数组(长度×1);
    • 转移/发射矩阵需满足“每行概率和为1”的概率约束。
  3. 工程验证方法:通过sample()采样大量数据,统计转移/发射矩阵,若和原始矩阵高度一致,说明HMM模型参数设置正确。
  4. 小白易错点
    • 忘记将观测序列转为二维数组,导致predict()/score()报错;
    • 发射矩阵行/列数与观测/隐藏状态数不匹配,需确保形状为(隐藏状态数, 观测状态数)。

运行结果

=====HMM三要素=====初始概率矩阵:[0.333333330.333333330.33333333]转移矩阵:[[0.40.30.3][0.30.40.3][0.30.30.4]]发射矩阵:[[0.111111110.111111110.111111110.111111110.111111110.111111110.111111110.111111110.11111111][0.0.0.0.166666670.166666670.166666670.166666670.166666670.16666667][0.0.0.0.0.0.250.250.250.25]]=====观测序列预测隐藏状态=====观测序列:[np.str_('A+'),np.str_('B'),np.str_('A-'),np.str_('C+'),np.str_('A')]预测隐藏状态索引:[00020]预测隐藏状态:[np.str_('认真复习'),np.str_('认真复习'),np.str_('认真复习'),np.str_('没有复习'),np.str_('认真复习')]=====观测序列概率=====观测序列的对数概率:-14.003675观测序列的实际概率:8.284786e-07=====采样并统计转移/发射矩阵=====采样结果形状:观测序列(10000,1),隐藏状态序列(10000,)统计的转移矩阵:[[0.408299480.29802670.29367382][0.316916490.38758030.29550321][0.305606340.297379650.39701402]]统计的发射矩阵:[[0.11865390.103278210.107339720.115752830.109660570.111981430.114012180.105599070.11372208][0.0.0.0.161162080.169418960.171253820.170030580.161467890.16666667][0.0.0.0.0.0.253426740.254035940.244897960.24763935]]=====实验结论=====1.采样统计的转移矩阵与原始矩阵高度一致(每行≈0.4,0.3,0.3);2.采样统计的发射矩阵符合预期:认真复习全成绩有概率,没复习仅低分段有概率;3.HMM可有效预测观测序列对应的隐藏状态,验证了模型的合理性! 进程已结束,退出代码为0
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/11 23:39:14

【Django毕设全套源码+文档】基于python的个性化英语学习辅助系统的设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

作者头像 李华
网站建设 2026/4/14 22:13:34

毕业论文AI一键生成:技术潜力、现实风险与合理应用指南

随着以ChatGPT、Claude、文心一言为代表的大语言模型飞速发展,“毕业论文ai一键生成”从一个科幻概念迅速演变为触手可及的技术可能,在学术界和毕业生群体中引发了巨大的关注、期待与争议。这一技术现象不仅关乎工具效率的提升,更深层次地触及…

作者头像 李华
网站建设 2026/4/15 7:08:11

踏仙堤、望鼎峰、忆晦翁,朱潭山的一草一木皆含诗

浙江省丽水市缙云县境内的仙都景区,以峰岩奇绝、山水神秀闻名,是一处国家级风景名胜区。坐落于景区内的朱潭山,虽不以其山势险峻著称,却凭借一个独特的视角和深厚的人文沉淀,成为了静观仙都核心景观与感受江南诗意的一…

作者头像 李华
网站建设 2026/4/15 2:14:35

智能温度变送器仿真与设计实现

智能温度变送器仿真与设计实现 第一章 设计背景与核心目标 传统模拟温度变送器存在测量精度低(误差≥1%)、温漂明显、抗干扰能力弱、输出信号单一等问题,难以满足工业现场(如化工、电力、冶金)对温度监测“高精度、高稳…

作者头像 李华
网站建设 2026/4/15 6:19:24

智能停车场管理系统设计

智能停车场管理系统设计与实现 第一章 设计背景与核心目标 传统停车场管理依赖人工登记、刷卡缴费、人工寻位,存在入场效率低(平均通行时间≥30s/车)、车位利用率低(≤70%)、缴费流程繁琐、数据统计滞后等问题&#xf…

作者头像 李华