HMM(Hidden Markov Model,隐马尔可夫模型) 是一种用"隐状态序列 + 观测序列"描述时序数据的概率模型。

- 蓝层 :隐状态 q 1 → q 2 → q 3 → q4
,按转移矩阵 A
沿时间跳转;q1
由初始分布 π
产生 - 橙层 :观测 o 1 , o 2 , o 3 , o4
,每个隐状态按发射矩阵 B
"发射"一个观测 - 参数 :λ= π AB

1. 基本设定
HMM 由五元组构成,notebook 中代码对应:
| 符号 | 含义 | notebook |
|---|---|---|
N ![]() |
隐状态个数 | N=4 |
M ![]() |
观测符号个数 | M=2 |
π ![]() |
初始状态概率 | pi |
A ![]() |
状态转移矩阵 A ij =P q t =j∥ q t-1=i ![]() |
A |
B ![]() |
观测概率矩阵 B ik =P o t =k∥ q t=i ![]() |
B |
两条核心假设:
- 马尔可夫性:下一状态只依赖当前状态
- 观测独立性:当前观测只依赖当前隐状态
生成过程(notebook 的 generate):

只能看到观测 O= o 1 ... oT
,隐状态 Q= q 1 ... qT
不可见。
2. 三大基本问题(本 notebook 覆盖前两个)
| 问题 | 问什么 | notebook 解法 |
|---|---|---|
| 概率计算 | 已知 λ= π AB ,求 PO∥λ ![]() |
前向算法 prob_calc |
| 解码(序列标注) | 已知 λ 和 O ,求最可能隐状态路径 |
维特比 viterbi_decode |
| 学习 | 已知 O ,估计 π,A,B ![]() |
本 notebook 未实现(常用 Baum-Welch) |
3. 前向算法(算观测概率)
定义前向变量:

- 初值 :α 1 i = π i B i o1
→ alpha = pi * B:, O\[0] - 递推:

→ notebook 中 np.sum(A:,j * alpha * Bj,o)
- 终止 :P O∣λ = i α Ti
→ alpha.sum()
例:O= 1 0 1 00
,得到约 0.01983。
直接枚举所有路径是 O NT
,前向降到 O T N2
。
4. 维特比算法(找最优隐状态路径)
目标:

用动态规划:

- 初值 :δ 1 i = π i B i o1

- 递推 :δ t j = maxi δ t-1 i A ij B j ot
,并用 varphi 记下最优前驱 - 回溯 :从终点 argmaxi δ Ti
沿 varphi 往回走,得到整条路径
例:O= 1 0 1 10
→ 最优路径 0,1,2,3,3。
5. 一句话概括
HMM = 隐状态按马尔可夫链跳转,每个状态再"发射"一个观测;
本 notebook 用 前向 算观测似然,用 维特比 做序列标注(解码)。
6.应用场景
HMM 主要用在「观测可见、状态隐藏」的序列问题上,典型场景包括:
| 领域 | 典型应用 | 对应 HMM 问题 |
|---|---|---|
| 自然语言处理 | 中文分词、词性标注(POS)、命名实体识别 | 序列标注(维特比解码) |
| 语音 | 语音识别(声学建模)、说话人相关序列建模 | 概率计算 + 解码 |
| 生物信息 | DNA/蛋白质序列比对与结构预测、基因区域识别 | 解码 / 概率计算 |
| 金融 | 市场隐状态(牛/熊市)推断、波动制度切换 | 解码 / 学习 |
| 行为与传感 | 手势识别、人体活动识别、异常检测 | 分类 / 解码 |
| 通信与信号处理 | 信道解码、符号序列恢复 | 解码 |
结合你 notebook 里的内容:
- 前向算法:判断某段观测有多像该模型(评分、过滤)
- 维特比:给观测打上最可能的隐状态标签(分词、词性标注等最常用)
现在很多 NLP 任务已更多用 CRF、神经网络(BiLSTM-CRF、Transformer),但 HMM 仍是序列标注的经典基线,在语音、生物信息等场景里也仍常见。
7.代码实战
import numpy as np
### 定义HMM模型
class HMM:
def __init__(self, N, M, pi=None, A=None, B=None):
# 可能的状态数
self.N = N
# 可能的观测数
self.M = M
# 初始状态概率向量
self.pi = pi
# 状态转移概率矩阵
self.A = A
# 观测概率矩阵
self.B = B
# 根据给定的概率分布随机返回数据
def rdistribution(self, dist):
r = np.random.rand()
for ix, p in enumerate(dist):
if r < p:
return ix
r -= p
# 生成HMM观测序列
def generate(self, T):
# 根据初始概率分布生成第一个状态
i = self.rdistribution(self.pi)
# 生成第一个观测数据
o = self.rdistribution(self.B[i])
observed_data = [o]
# 遍历生成剩下的状态和观测数据
for _ in range(T-1):
i = self.rdistribution(self.A[i])
o = self.rdistribution(self.B[i])
observed_data.append(o)
return observed_data
# 初始状态概率分布
pi = np.array([0.25, 0.25, 0.25, 0.25])
# 状态转移概率矩阵
A = np.array([
[0, 1, 0, 0],
[0.4, 0, 0.6, 0],
[0, 0.4, 0, 0.6],
[0, 0, 0.5, 0.5]])
# 观测概率矩阵
B = np.array([
[0.5, 0.5],
[0.6, 0.4],
[0.2, 0.8],
[0.3, 0.7]])
# 可能的状态数和观测数
N = 4
M = 2
# 创建HMM实例
hmm = HMM(N, M, pi, A, B)
# 生成观测序列
print(hmm.generate(5))
1, 0, 0, 1, 0
### 前向算法计算条件概率
def prob_calc(O):
'''
输入:
O:观测序列
输出:
alpha.sum():条件概率
'''
# 初值
alpha = pi * B[:, O[0]]
# 递推
for o in O[1:]:
alpha_next = np.empty(4)
for j in range(4):
alpha_next[j] = np.sum(A[:,j] * alpha * B[j,o])
alpha = alpha_next
return alpha.sum()
# 给定观测
O = [1,0,1,0,0]
print(prob_calc(O))
0.01983169125
前向算法(Forward Algorithm) ,用来算:在已知模型 λ= π AB
时,观测序列 O
出现的概率 PO∣λ
。
在算什么
定义前向变量:

即:前 t
个观测已经发生,且第 t
步隐状态是 i
的联合概率。
最终:

对应代码最后的 return alpha.sum()。
和代码的对应关系
1. 初值
alpha = pi * B:, O\[0]

第一个状态按 π
出现,再按 B
发出第一个观测 O0
。
2. 递推
for o in O1::
for j in range(4):
alpha_nextj = np.sum(A:,j * alpha * Bj,o)

含义拆开:
- A:,j * alpha:从所有状态 i
转移到 j
,把上一时刻的 α ti
加权求和 - 再乘 Bj,o:状态 j
发出当前观测 o 
3. 终止
return alpha.sum()
对最后一步所有状态的 α_T (i) 求和,得到整条观测序列的概率。
直观理解
不枚举所有隐状态路径(那是 O(N^T )),而是按时间一步步把概率"向前推":每一步只做 N×N 的转移累加,复杂度 O(TN^2 )。
对例子 O=1,0,1,0,0,输出约 0.01983,就是这条观测在当前 π,A,B 下的似然。
### 序列标注问题和维特比算法
def viterbi_decode(O):
'''
输入:
O:观测序列
输出:
path:最优隐状态路径
'''
# 序列长度和初始观测
T, o = len(O), O[0]
# 初始化delta变量
delta = pi * B[:, o]
# 初始化varphi变量
varphi = np.zeros((T, 4), dtype=int)
path = [0] * T
# 递推
for i in range(1, T):
delta = delta.reshape(-1, 1)
tmp = delta * A
varphi[i, :] = np.argmax(tmp, axis=0)
delta = np.max(tmp, axis=0) * B[:, O[i]]
# 终止
path[-1] = np.argmax(delta)
# 回溯最优路径
for i in range(T-1, 0, -1):
path[i-1] = varphi[i, path[i]]
return path
# 给定观测序列
O = [1,0,1,1,0]
# 输出最可能的隐状态序列
print(viterbi_decode(O))
0, 1, 2, 3, 3
维特比算法(Viterbi) ,用来做 解码 / 序列标注 :在已知模型 λ= π AB
和观测 O
时,找一条最可能的隐状态路径

和前向算法的区别
| 前向 prob_calc | 维特比 viterbi_decode | |
|---|---|---|
| 目标 | 算 PO∣λ ![]() |
找最优路径 Q ![]() |
| 核心运算 | 求和 ![]() |
取最大 max![]() |
| 额外记录 | 不需要 | 用 varphi 记最优前驱,便于回溯 |
核心量

即:第 t
步落在状态 i
,且前缀路径概率最大时的那个值。
和代码的对应
1. 初值
delta = pi * B:, o

和前向一样,只是后面递推用 max 而不是 sum。
2. 递推
tmp = delta * A # δ_{t-1}(i) * A_{ij}
varphii, : = np.argmax(tmp, axis=0) # 记下到达 j 的最优前驱 i*
delta = np.max(tmp, axis=0) * B:, O\[i]

- np.max(..., axis=0):对每个目标状态 j
,在所有来源 i
里取最大 - np.argmax 写入 varphi:记下是从哪个 i
过来的(回溯要用)
3. 终止
path-1 = np.argmax(delta)
最后一步选 δT
最大的状态,作为路径终点。
4. 回溯
for i in range(T-1, 0, -1):
pathi-1 = varphii, path\[i]
从终点往前:已知 qt
,查 varphit, q_t 得到 qt-1
,一路还原整条路径。
直观理解
每一步不为所有路径留账,只保留「到达每个状态的最好一条」;最后从终点顺着指针往回走,得到全局最优路径(在马尔可夫假设下)。
对例子 O=1,0,1,1,0,输出 0,1,2,3,3,就是该观测下最可能的隐状态序列。







,求 PO∥λ 
和 O
,求最可能隐状态路径
,估计 π,A,B 



