0. 引言
前情提要:
《NLP深入学习(一):jieba 工具包介绍》
《NLP深入学习(二):nltk 工具包介绍》
《NLP深入学习(三):TF-IDF 详解以及文本分类/聚类用法》
《NLP深入学习(四):贝叶斯算法详解及分类/拼写检查用法》
1. 什么是 HMM
隐马尔可夫模型(Hidden Markov Model, HMM)是一种统计学习方法,用于描述含有隐藏状态的随机过程。在 HMM 中,系统的当前状态无法直接观测,但可以通过该状态下生成的可观测序列来推断。它由两部分构成:一个不可见的马尔可夫链(即隐藏状态),和每个隐藏状态生成观测值的概率分布。
基本结构与概念:
-
S S S 隐藏状态(Hidden States): 系统可能处于的一系列状态,通常用 S = S 1 , S 2 , . . . , S N S = S^{1}, S_{2}, ..., S_N S=S1,S2,...,SN 表示,其中 N 为状态的数量。这些状态是不直接可观测的。
-
O O O 观测序列(Observations): 每个隐藏状态生成一个观测值,观测值构成的时间序列是可见的。例如,在拼写检查器中,观测序列可能是字符序列,而在语音识别中,观测序列可能是声学特征序列。
-
π \pi π 初始概率分布(Initial Probability Distribution): 定义了系统开始时处于各个状态的概率,记作 π = ( π 1 , π 2 , . . . , π N ) \pi = ( \pi_{1}, \pi_{2}, ..., \pi_{N} ) π=(π1,π2,...,πN),满足 ∑ i = 1 N π i = 1 \sum_{i=1}^{N}\pi_i = 1 ∑i=1Nπi=1。
-
A A A 状态转移概率矩阵(Transition Probability Matrix): 表示从一个状态转移到另一个状态的概率,记作 A = [ a i j ] A = [a_{ij}] A=[aij],其中 a i j a_{ij} aij 是从状态 S i S_i Si转移到状态 S j S_j Sj的概率。满足对于所有 i i i, ∑ j = 1 N a i j = 1 \sum_{j=1}^{N}a_{ij} = 1 ∑j=1Naij=1。
-
B B B 发射概率(Emission Probability): 表示在某个隐藏状态下生成特定观测值的概率,通常定义为条件概率分布 b i ( o k ) b_i(o_k) bi(ok),表示在状态 S i S_i Si下产生观测值 o k o_k ok的概率。
HMM 模型可描述为:
λ
=
(
A
,
B
,
π
)
λ=(A, B, π)
λ=(A,B,π)
三个基本问题:
-
评估问题(Evaluation Problem): 给定一个 HMM 模型 λ λ λ 和一个观测序列 O O O,计算该观测序列出现的概率 P ( O ∣ λ ) P(O|λ) P(O∣λ)。
-
解码问题(Decoding Problem): 给定一个 HMM 模型 λ λ λ 和一个观测序列 O O O,找出最有可能生成这个观测序列的状态序列,也称为最大似然路径问题,常通过维特比算法(Viterbi Algorithm)解决。
-
学习问题(Learning Problem): 根据已有的观测序列 O O O 估计出 HMM 的参数 λ λ λ,包括初始状态概率 π \pi π、状态转移概率 A A A 以及发射概率 B B B。
2. HMM 的例子
2.1 字母序列识别
假设我们有一个由"M"和"N"两个隐藏状态组成的 HMM 模型,用来识别由"X"和"Y"两个字母构成的观测序列。
模型参数:
-
状态集合(M, N)
-
观测值(X, Y)
-
初始状态分布 π:
π = [0.6, 0.4] # 开始时是状态M的概率为0.6,是状态N的概率为0.4
-
状态转移矩阵 A:
A = [ [0.7, 0.3], # 从状态M转移到状态M的概率为0.7,转移到状态N的概率为0.3 [0.4, 0.6] # 从状态N转移到状态M的概率为0.4,转移到状态N的概率为0.6 ]
-
发射概率 B:
B(M) = [0.8, 0.2] # 在状态M下生成观测值'X'的概率为0.8,生成观测值'Y'的概率为0.2 B(N) = [0.1, 0.9] # 在状态N下生成观测值'X'的概率为0.1,生成观测值'Y'的概率为0.9
观测序列与问题描述:
给定观测序列 O = [“X”, “Y”],我们的目标是计算在给定模型
λ
=
(
π
,
A
,
B
)
λ=(π,A,B)
λ=(π,A,B) 下观测序列出现的概率
P
(
O
∣
λ
)
P(O|λ)
P(O∣λ)。
前向算法求解过程:
使用前向算法,我们可以按照以下步骤计算 α
序列:
def forward_algorithm(observations, pi, A, B):
num_states = len(pi)
T = len(observations)
alpha = [[0 for _ in range(num_states)] for _ in range(T)]
# 初始化
for s in range(num_states):
alpha[0][s] = pi[s] * B[s][observations[0]]
# 迭代计算
for t in range(1, T):
for s in range(num_states):
alpha[t][s] = sum(alpha[t - 1][prev_s] * A[prev_s][s] * B[s][observations[t]] for prev_s in range(num_states))
# 计算总概率
prob_observation = sum(alpha[T - 1][s] for s in range(num_states))
return alpha, prob_observation
# 实例化模型参数
pi = [0.6, 0.4]
A = [[0.7, 0.3], [0.4, 0.6]]
B = [[0.8, 0.2], [0.1, 0.9]]
# 观测序列
observations = ['M', 'N']
# 调用函数计算观察序列概率
alpha, prob_observation = forward_algorithm(observations, pi, A, B)
print(f"The probability of the observation sequence is: {prob_observation}")
以上 Python 代码定义了一个名为forward_algorithm
的函数,它接收观测序列、初始状态分布、状态转移矩阵和发射概率作为输入,并返回前向概率数组以及观测序列的概率。通过调用该函数并传入相应参数,可以计算出给定观测序列的概率。
2.2 天气预测
假设我们有一个天气模型,用 HMM 来表示天气的变化。我们有两种天气状态:晴天(Sunny)和雨天(Rainy)。我们通过观察温度来获取观测序列,观测值为高温(Hot)、中温(Mild)和低温(Cold)。
模型参数:
-
状态集合: {Sunny, Rainy}
-
观测值集合: {Hot, Mild, Cold}
-
初始概率分布:
- P(Sunny) = 0.8
- P(Rainy) = 0.2
-
状态转移概率矩阵:
A = [ [ P(Sunny|Sunny) P(Rainy|Sunny) ] [ P(Sunny|Rainy) P(Rainy|Rainy) ] ] A = [0.7, 0.3], [0.4, 0.6]
其中,A[i, j] 表示在时刻 t 处于状态 i 的情况下,在时刻 t+1 转移到状态 j 的概率。
-
发射概率矩阵:
B = [ [ P(Hot|Sunny) P(Mild|Sunny) P(Cold|Sunny) ] [ P(Hot|Rainy) P(Mild|Rainy) P(Cold|Rainy) ] ] B = [0.2 0.4 0.4], [0.5 0.4 0.1]
其中,B[i, j] 表示在时刻 t 处于状态 i 的情况下生成观测值 j 的概率。
计算公式:
-
概率计算问题(Evaluation):
给定观测序列 O 和模型 λ = ( A , B , π ) λ=(A, B, π) λ=(A,B,π),计算在模型 λ 下观测序列 O 出现的概率 P(O|λ)。
计算公式:
P ( O ∣ λ ) = ∑ i P ( O , X t = i ∣ λ ) = ∑ i α t ( i ) P(O|λ) = \sum_{i} P(O, X_t=i|λ) = \sum_{i} \alpha_t(i) P(O∣λ)=i∑P(O,Xt=i∣λ)=i∑αt(i)其中:
- X t X_t Xt 表示在时刻 t 的隐藏状态为 i
- α t ( i ) \alpha_t(i) αt(i) 是在时刻 t 处于状态 i 的情况下,观测序列 O O O 的概率。
-
解码问题(Decoding):
给定观测序列 O 和模型 λ = ( A , B , π ) λ=(A, B, π) λ=(A,B,π),找到使得观测序列的概率最大的状态序列。
计算公式:
arg max X P ( X ∣ O , λ ) = arg max X P ( O , X ∣ λ ) = arg max X ∏ t = 1 T P ( O t , X t ∣ λ ) \arg \max_{X} P(X|O,λ) = \arg \max_{X} P(O, X|λ) = \arg \max_{X} \prod_{t=1}^{T} P(O_t, X_t|λ) argXmaxP(X∣O,λ)=argXmaxP(O,X∣λ)=argXmaxt=1∏TP(Ot,Xt∣λ)其中,T 表示观测序列的长度。
-
学习问题(Learning):
给定观测序列 O,调整模型参数 λ=(A, B, π) 使得观测序列的概率最大。
计算公式:
- 初始概率分布: π i = P ( X 1 = i ∣ O , λ ) \pi_i = P(X_1=i|O,λ) πi=P(X1=i∣O,λ)
- 状态转移概率: a i j = ∑ t = 1 T − 1 P ( X t = i , X t + 1 = j ∣ O , λ ) ∑ t = 1 T − 1 P ( X t = i ∣ O , λ ) a_{ij} = \frac{\sum_{t=1}^{T-1} P(X_t=i, X_{t+1}=j|O,λ)}{\sum_{t=1}^{T-1} P(X_t=i|O,λ)} aij=∑t=1T−1P(Xt=i∣O,λ)∑t=1T−1P(Xt=i,Xt+1=j∣O,λ)
- 发射概率: b i j = ∑ t = 1 , O t = j T P ( X t = i ∣ O , λ ) ∑ t = 1 T P ( X t = i ∣ O , λ ) b_{ij} = \frac{\sum_{t=1, O_t=j}^{T} P(X_t=i|O,λ)}{\sum_{t=1}^{T} P(X_t=i|O,λ)} bij=∑t=1TP(Xt=i∣O,λ)∑t=1,Ot=jTP(Xt=i∣O,λ)
这是一个简单的天气模型的例子,展示了 HMM 模型的基本结构和计算公式。在实际应用中,通常会使用算法来进行计算和解码。
import numpy as np
# 模型参数
states = ['Sunny', 'Rainy']
observations = ['Hot', 'Mild', 'Cold']
initial_prob = np.array([0.8, 0.2])
transition_prob = np.array([
[0.7, 0.3],
[0.4, 0.6]
])
emission_prob = np.array([
[0.2, 0.4, 0.4],
[0.5, 0.4, 0.1]
])
# 观测序列
observations_sequence = ['Hot', 'Mild', 'Cold']
# 概率计算问题(Evaluation)
def forward_algorithm(observations_sequence):
T = len(observations_sequence)
alpha = np.zeros((T, len(states)))
# 初始化时刻 t=1 的 alpha
alpha[0] = initial_prob * emission_prob[:, observations.index(observations_sequence[0])]
# 递推计算 alpha
for t in range(1, T):
for j in range(len(states)):
alpha[t, j] = np.sum(alpha[t-1] * transition_prob[:, j] * emission_prob[j, observations.index(observations_sequence[t])])
# 返回观测序列的概率
return np.sum(alpha[T-1])
# 解码问题(Decoding)
def viterbi_algorithm(observations_sequence):
T = len(observations_sequence)
delta = np.zeros((T, len(states)))
psi = np.zeros((T, len(states)), dtype=int)
# 初始化时刻 t=1 的 delta
delta[0] = initial_prob * emission_prob[:, observations.index(observations_sequence[0])]
# 递推计算 delta 和 psi
for t in range(1, T):
for j in range(len(states)):
delta[t, j] = np.max(delta[t-1] * transition_prob[:, j]) * emission_prob[j, observations.index(observations_sequence[t])]
psi[t, j] = np.argmax(delta[t-1] * transition_prob[:, j])
# 回溯得到最优路径
best_path = np.zeros(T, dtype=int)
best_path[T-1] = np.argmax(delta[T-1])
for t in range(T-2, -1, -1):
best_path[t] = psi[t+1, best_path[t+1]]
return best_path
# 执行概率计算问题
probability = forward_algorithm(observations_sequence)
print(f"Probability of observing {observations_sequence}: {probability:.4f}")
# 执行解码问题
best_path = viterbi_algorithm(observations_sequence)
print("Most likely states:")
for t, state in enumerate(best_path):
print(f"Time {t+1}: {states[state]}")
请注意,这是一个简单的演示,实际应用中可能需要更复杂的模型和算法。上述代码中的 forward_algorithm
函数用于概率计算问题,而 viterbi_algorithm
函数用于解码问题。
3. 参考
《NLP深入学习(一):jieba 工具包介绍》
《NLP深入学习(二):nltk 工具包介绍》
《NLP深入学习(三):TF-IDF 详解以及文本分类/聚类用法》
《NLP深入学习(四):贝叶斯算法详解及分类/拼写检查用法》
欢迎关注本人,我是喜欢搞事的程序猿; 一起进步,一起学习;
也欢迎关注我的wx公众号:一个比特定乾坤