【Hidden Markov Model】
隐马尔科夫模型(Hidden Markov Model)
与马尔科夫模型相比,隐马尔科夫模型有 隐藏状态 和 观测状态。两者并非对应关系,即隐藏状态数 n n n和观测状态数 m m m无关。
- 当前时刻隐藏状态
x
t
x_t
xt只与上一时刻隐藏状态
x
t
−
1
x_{t-1}
xt−1有关:
状态转移概率 a i j = P ( x t = i ∣ x t − 1 = j ) a_{ij}=P(x_t=i|x_{t-1}=j) aij=P(xt=i∣xt−1=j) - 某个观测状态
o
t
o_t
ot只和生成它的隐藏状态
x
t
x_t
xt有关:
观测状态概率 b j ( k ) = P ( o t = k ∣ x t = j ) b_j(k)=P(o_t=k|x_t=j) bj(k)=P(ot=k∣xt=j) - 初始隐藏状态:
π i = P ( x 1 = i ) \pi_i=P(x_1=i) πi=P(x1=i)
总而言之,隐马尔科夫模型表示如下:
H
M
M
=
(
π
,
A
,
B
)
HMM=(\pi,\mathcal{A},\mathcal{B})
HMM=(π,A,B)
其中
π
\pi
π为初始状态概率,
A
\mathcal{A}
A为隐藏状态转移概率,
B
\mathcal{B}
B为观测状态生成概率。
隐马尔可夫模型解决的问题
求解参数
已知观测状态序列 O = { o 1 , o 2 , . . . , o T } \mathbf{O} = \left \{ o_1,o_2,...,o_T \right \} O={o1,o2,...,oT}、隐藏状态序列 X = { x 1 , x 2 , . . . , x T } \mathbf{X} = \left \{ x_1,x_2,...,x_T \right \} X={x1,x2,...,xT},求解隐马尔科夫模型参数 π , A , B \pi,\mathcal{A},\mathcal{B} π,A,B。
根据 π , A , B \pi,\mathcal{A},\mathcal{B} π,A,B定义,利用大数定律估计 π , A , B \pi,\mathcal{A},\mathcal{B} π,A,B即可。
滤波(filtering)
已知模型 λ = ( π , A , B ) \lambda =(\pi,\mathcal{A},\mathcal{B}) λ=(π,A,B)及观测序列 O = { o 1 , o 2 , . . . , o T } \mathbf{O} = \left \{ o_1,o_2,...,o_T \right \} O={o1,o2,...,oT},求取该观测序列出现的概率 P ( O ∣ λ ) P(\mathbf{O}|\lambda ) P(O∣λ)
直接求解
为求给定模型下观测序列出现的概率,因观测状态只与其对应的隐藏状态有关,则通过所有可能的隐藏状态序列
X
=
{
x
1
,
x
2
,
.
.
.
,
x
T
}
\mathbf{X} = \left \{ x_1,x_2,...,x_T \right \}
X={x1,x2,...,xT}即可求取
P
(
O
∣
λ
)
P(\mathbf{O}|\lambda )
P(O∣λ):
P
(
O
∣
λ
)
=
∑
X
P
(
O
,
X
∣
λ
)
=
∑
X
P
(
X
∣
λ
)
P
(
O
∣
X
,
λ
)
(1)
P(\mathbf{O}|\lambda)=\sum_{\mathbf{X}}P(\mathbf{O},\mathbf{X}|\lambda)=\sum_{\mathbf{X}}P(\mathbf{X}|\lambda)P(\mathbf{O}|\mathbf{X},\lambda)\tag{1}
P(O∣λ)=X∑P(O,X∣λ)=X∑P(X∣λ)P(O∣X,λ)(1)
利用
π
,
A
\pi,\mathcal{A}
π,A可求取:
P
(
X
∣
λ
)
=
P
(
x
1
∣
λ
)
P
(
x
2
,
.
.
.
,
x
T
∣
x
1
,
λ
)
=
π
x
1
P
(
x
2
∣
x
1
,
λ
)
P
(
x
2
,
.
.
.
,
x
T
∣
x
1
,
x
2
,
λ
)
P(\mathbf{X}|\lambda)=P(x_1|\lambda)P(x_2,...,x_T|x_1,\lambda)=\pi_{x_1} P(x_2|x_1,\lambda)P(x_2,...,x_T|x_1,x_2,\lambda)
P(X∣λ)=P(x1∣λ)P(x2,...,xT∣x1,λ)=πx1P(x2∣x1,λ)P(x2,...,xT∣x1,x2,λ)
利用
B
\mathcal{B}
B可求取:
P
(
O
∣
X
,
λ
)
=
∏
k
=
1
T
P
(
o
k
∣
x
k
,
λ
)
P(\mathbf{O}|\mathbf{X},\lambda)=\prod_{k=1}^{T}P(o_k|x_k,\lambda)
P(O∣X,λ)=∏k=1TP(ok∣xk,λ)
则式
(
1
)
(1)
(1)为:
P
(
O
∣
λ
)
=
∑
X
P
(
O
,
X
∣
λ
)
=
∑
x
1
,
x
2
,
.
.
.
,
x
T
π
x
1
b
x
1
(
o
1
)
a
x
1
x
2
b
x
2
(
o
2
)
a
x
2
x
3
.
.
.
a
x
T
−
1
x
T
b
x
T
(
o
T
)
(2)
P(\mathbf{O}|\lambda)=\sum_{\mathbf{X}}P(\mathbf{O},\mathbf{X}|\lambda)=\sum_{x_1,x_2,...,x_T}\pi_{x_1}b_{x_1}(o_1)a_{x_1x_2}b_{x_2}(o_2)a_{x_2x_3}...a_{x_{T-1}x_T}b_{x_T}(o_T)\tag{2}
P(O∣λ)=X∑P(O,X∣λ)=x1,x2,...,xT∑πx1bx1(o1)ax1x2bx2(o2)ax2x3...axT−1xTbxT(oT)(2)
若隐藏状态数为
n
n
n,则式
(
2
)
(2)
(2)复杂度为
O
(
T
n
T
)
O(Tn^T)
O(TnT)
前向算法
假设
t
t
t时刻隐藏状态
x
t
=
i
x_t=i
xt=i,观测序列
O
=
{
o
1
,
o
2
,
.
.
.
,
o
t
}
\mathbf{O} = \left \{ o_1,o_2,...,o_t \right \}
O={o1,o2,...,ot}的概率被称为前向概率:
α
i
(
t
)
=
P
(
o
1
,
o
2
,
.
.
.
,
o
t
,
x
t
=
i
∣
λ
)
(3)
\alpha_i(t)=P( o_1,o_2,...,o_t,x_t=i|\lambda) \tag{3}
αi(t)=P(o1,o2,...,ot,xt=i∣λ)(3)
当
t
=
T
t=T
t=T时,根据式
(
3
)
(3)
(3)有
α
i
(
T
)
=
P
(
o
1
,
o
2
,
.
.
.
,
o
T
,
x
T
=
i
∣
λ
)
\alpha_i(T)=P( o_1,o_2,...,o_T,x_T=i|\lambda)
αi(T)=P(o1,o2,...,oT,xT=i∣λ),则此时可求取
P
(
O
∣
λ
)
P(\mathbf{O}|\lambda )
P(O∣λ):
P
(
O
∣
λ
)
=
∑
i
=
1
n
α
i
(
T
)
(4)
P(\mathbf{O}|\lambda )=\sum_{i=1}^{n} \alpha_i(T)\tag{4}
P(O∣λ)=i=1∑nαi(T)(4)
利用
α
i
(
1
)
=
P
(
o
1
,
x
t
=
i
∣
λ
)
=
π
i
b
i
(
o
1
)
\alpha_i(1)=P( o_1,x_t=i|\lambda)=\pi_ib_i(o_1)
αi(1)=P(o1,xt=i∣λ)=πibi(o1)、
α
i
(
t
+
1
)
=
(
∑
j
=
1
n
α
j
(
t
)
a
j
i
)
b
i
(
o
t
+
1
)
\alpha_i(t+1)=(\sum_{j=1}^{n}\alpha_j(t)a_{ji})b_i(o_{t+1})
αi(t+1)=(∑j=1nαj(t)aji)bi(ot+1)可求取式
(
4
)
(4)
(4),复杂度减少为
O
(
T
n
2
)
O(Tn^2)
O(Tn2)
与之类似,后向算法:
β
i
(
t
)
=
P
(
o
t
+
1
,
o
t
+
2
,
.
.
.
,
o
T
∣
x
t
=
i
,
λ
)
(5)
\beta_i(t)=P( o_{t+1},o_{t+2},...,o_T|x_t=i,\lambda) \tag{5}
βi(t)=P(ot+1,ot+2,...,oT∣xt=i,λ)(5)
训练(training)
已知观测序列 O = { o 1 , o 2 , . . . , o T } \mathbf{O} = \left \{ o_1,o_2,...,o_T \right \} O={o1,o2,...,oT},求解模型参数 ( π , A , B ) (\pi,\mathcal{A},\mathcal{B}) (π,A,B)使得 P ( O ∣ λ ) P(\mathbf{O}|\lambda ) P(O∣λ)最大
EM算法:Baum-Welch算法
期望最大化(Exoectation maximization, EM)算法可以用于含有隐变量的统计模型的参数最大似然估计,其的核心思想是:
- 初始时随机地给模型的参数赋值λ0,该赋值遵循模型对参数的限制;
- 根据λ0求得模型中隐变量的期望值;
- 由该期望值又可以重新得到模型参数的新估计值λ1;
- 循环进行该过程,直到参数收敛于最大似然估计值。
Baum-Welch算法或前向后向算法是EM算法的一种具体实现。
给定HMM的参数 λ = ( π , A , B ) \lambda=(\pi,\mathcal{A},\mathcal{B}) λ=(π,A,B)及观测状态序列 O = { o 1 , o 2 , . . . , o T } \mathbf{O} = \left \{ o_1,o_2,...,o_T \right \} O={o1,o2,...,oT}
时间
t
t
t时,隐藏状态
x
t
=
i
x_t=i
xt=i的概率为:
γ
i
(
t
)
=
P
(
i
t
=
q
i
∣
O
,
λ
)
=
P
(
i
t
=
q
i
,
O
∣
λ
)
P
(
O
∣
λ
)
=
α
i
(
t
)
β
i
(
t
)
∑
j
=
1
n
α
j
(
t
)
β
j
(
t
)
(6)
\gamma_i(t)=P(i_t=q_i|\mathbf{O},\lambda)=\frac{P(i_t=q_i,\mathbf{O}|\lambda)}{P(\mathbf{O}|\lambda)}=\frac{\alpha_i(t)\beta_i(t)}{\sum_{j=1}^n\alpha_j(t)\beta_j(t)}\tag{6}
γi(t)=P(it=qi∣O,λ)=P(O∣λ)P(it=qi,O∣λ)=∑j=1nαj(t)βj(t)αi(t)βi(t)(6)
时间
t
t
t时隐藏状态
x
t
=
i
x_t=i
xt=i、
t
+
1
t+1
t+1时隐藏状态
x
t
+
1
=
j
x_{t+1}=j
xt+1=j的概率为:
ξ
i
j
(
t
)
=
P
(
x
t
=
i
,
x
t
+
1
=
j
∣
O
,
λ
)
=
P
(
x
t
=
i
,
x
t
+
1
=
j
,
O
∣
λ
)
P
(
O
∣
λ
)
=
α
i
(
t
)
a
i
j
b
j
(
o
t
+
1
)
β
j
(
t
+
1
)
P
(
O
∣
λ
)
=
α
i
(
t
)
a
i
j
b
j
(
o
t
+
1
)
β
j
(
t
+
1
)
∑
i
=
1
n
∑
j
=
1
n
α
i
(
t
)
a
i
j
b
j
(
o
t
+
1
)
β
j
(
t
+
1
)
(7)
\xi_{ij}(t)=P(x_t=i,x_{t+1}=j|\mathbf{O},\lambda)\\=\frac{P(x_t=i,x_{t+1}=j,\mathbf{O}|\lambda)}{P(\mathbf{O}|\lambda)}\\=\frac{\alpha_i(t)a_{ij}b_j(o_{t+1})\beta_j(t+1)}{P(\mathbf{O}|\lambda)}\\=\frac{\alpha_i(t)a_{ij}b_j(o_{t+1})\beta_j(t+1)}{\sum^{n}_{i=1}\sum^{n}_{j=1}\alpha_i(t)a_{ij}b_j(o_{t+1})\beta_j(t+1)}\tag{7}
ξij(t)=P(xt=i,xt+1=j∣O,λ)=P(O∣λ)P(xt=i,xt+1=j,O∣λ)=P(O∣λ)αi(t)aijbj(ot+1)βj(t+1)=∑i=1n∑j=1nαi(t)aijbj(ot+1)βj(t+1)αi(t)aijbj(ot+1)βj(t+1)(7)
根据式
(
6
)
、
(
7
)
(6)、(7)
(6)、(7)可得参数估计:
{
π
i
^
=
γ
i
(
1
)
a
i
j
^
=
∑
t
=
1
T
−
1
ξ
i
j
(
t
)
∑
t
=
1
T
−
1
γ
i
(
t
)
b
j
(
k
)
^
=
∑
t
=
1
,
o
t
=
k
T
γ
i
(
t
)
∑
t
=
1
T
γ
i
(
t
)
(8)
{\LARGE \left\{\begin{matrix} \hat{\pi_i}=\gamma_i(1)\\ \hat{a_{ij}}=\frac{\sum^{T-1}_{t=1}\xi_{ij}(t)}{\sum^{T-1}_{t=1}\gamma_i(t)}\\ \hat{b_j(k)}=\frac{\sum^{T}_{t=1,o_t=k}\gamma_i(t)}{\sum^{T}_{t=1}\gamma_i(t)} \end{matrix}\right. \tag{8}}
⎩
⎨
⎧πi^=γi(1)aij^=∑t=1T−1γi(t)∑t=1T−1ξij(t)bj(k)^=∑t=1Tγi(t)∑t=1,ot=kTγi(t)(8)
解码(decodin)
已知模型 λ = ( π , A , B ) \lambda =(\pi,\mathcal{A},\mathcal{B}) λ=(π,A,B)及观测序列 O = { o 1 , o 2 , . . . , o T } \mathbf{O} = \left \{ o_1,o_2,...,o_T \right \} O={o1,o2,...,oT},求取最有可能的状态序列 X = { x 1 , x 2 , . . . , x T } \mathbf{X} = \left \{ x_1,x_2,...,x_T \right \} X={x1,x2,...,xT},即使 P ( X ∣ O , λ ) P(\mathbf{X}|\mathbf{O},\lambda) P(X∣O,λ)最大
Viterbi算法
对于时刻
t
t
t,隐藏状态
x
t
=
i
x_t=i
xt=i,所有可能的情况中概率最大路径:
δ
t
(
i
)
=
max
x
1
,
x
2
,
.
.
.
,
x
t
−
1
P
(
x
t
=
i
,
x
t
−
1
,
.
.
.
,
x
1
,
o
t
,
.
.
.
,
o
1
∣
λ
)
(9)
\delta_t(i)=\max_{x_1,x_2,...,x_{t-1}}P(x_t=i,x_{t-1},...,x_1,o_t,...,o_1|\lambda)\tag{9}
δt(i)=x1,x2,...,xt−1maxP(xt=i,xt−1,...,x1,ot,...,o1∣λ)(9)
由此可知:
δ
t
+
1
(
i
)
=
max
x
1
,
x
2
,
.
.
.
,
x
t
P
(
x
t
+
1
=
i
,
x
t
,
.
.
.
,
x
1
,
o
t
+
1
,
.
.
.
,
o
1
∣
λ
)
=
max
1
≤
j
≤
N
[
δ
t
(
j
)
a
j
i
]
b
i
(
o
t
+
1
)
(10)
\delta_{t+1}(i)=\max_{x_1,x_2,...,x_{t}}P(x_{t+1}=i,x_{t},...,x_1,o_{t+1},...,o_1|\lambda)\\ =\max_{1\le j\le N}[\delta_t(j)a_{ji}]b_i(o_{t+1})\tag{10}
δt+1(i)=x1,x2,...,xtmaxP(xt+1=i,xt,...,x1,ot+1,...,o1∣λ)=1≤j≤Nmax[δt(j)aji]bi(ot+1)(10)
概率最大的路径中
t
−
1
t-1
t−1时刻的隐藏状态:
Ψ
t
(
i
)
=
arg
max
1
≤
j
≤
n
[
δ
t
−
1
(
j
)
a
j
i
]
(11)
\Psi _t(i)=\arg \max _{1\le j\le n }[\delta_{t-1}(j)a_{ji}]\tag{11}
Ψt(i)=arg1≤j≤nmax[δt−1(j)aji](11)
- 根据式 ( 10 ) (10) (10)计算 T T T时刻所有隐藏状态对应的值,若 δ T ( i ) \delta_T(i) δT(i)为其中最大的一项,则 x T = i x_T=i xT=i;
- 则 T − 1 T-1 T−1时刻隐藏状态 x T − 1 = Ψ T ( i ) = k x_{T-1}=\Psi _T(i)=k xT−1=ΨT(i)=k, T − 2 T-2 T−2时刻时刻隐藏状态 x T − 2 = Ψ T − 1 ( k ) x_{T-2}=\Psi _{T-1}(k) xT−2=ΨT−1(k),以此类推。
算法实现
创建一个隐马尔科夫模型的类:
class HMM:
"""
状态取值有N个,观测序列取值有M个
"""
def __init__(self, A: np.ndarray, B: np.ndarray, pi):
self.A = A # 状态转移概率矩阵 [N, N]
self.B = B # 输出观测概率矩阵 [N, M]
self.pi = pi # 初设状态概率向量 [N]
self.N = len(pi)
self.M = self.A.shape[1]
self.map_i = {} # 存储状态取值对应的编码, key为编码,value为状态值
self.map_o = {} # 存储观测值对应的编码, key为观测值,value为编码
前向概率计算:
def forward(self, t, i, O):
"""
前向算法
:param t: 时刻t,以0为起点
:param i: 状态值,[0, 1, ...., N-1]
:param O: 观测序列
:return:
"""
if t == 0:
return self.pi[i] * self.B[i][O[t]]
return self.B[i][O[t]] * sum([self.forward(t - 1, j, O) * self.A[j][i] for j in range(self.N)])
后向概率计算:
def backward(self, t, i, O):
"""
后向算法
:param t: 时刻t,以0为起点
:param i: 状态值,[0, 1, ...., N-1]
:param O: 观测序列
:return:
"""
if t == len(O) - 1:
return 1
return sum([self.A[i][j] * self.B[j][O[t+1]] * self.backward(t+1, j, O) for j in range(self.N)])
前向后向算法
计算期望:
def gamma(self, t, i, O):
"""
根据已知隐马尔科夫模型的参数和观测序列O,时刻t处于状态i的概率
:param t: 时刻t,以0为起点
:param i: 状态值,[0, 1, ...., N-1]
:param O: 观测序列
:return:
"""
molecule = self.forward(t, i, O) * self.backward(t, i, O)
denominator = sum([self.forward(t, j, O) * self.backward(t, j, O)] for j in range(self.N))
return molecule / denominator
def xi(self, t, i, j, O):
"""
根据已知隐马尔科夫模型的参数和观测序列O,时刻t处于状态i且时刻t+1处于状态j的概率
:param t: 时刻t,以0为起点
:param i: 状态值,[0, 1, ...., N-1]
:param O: 观测序列
:return:
"""
molecule = self.forward(t, i, O) * self.A[i][j] * self.B[j][O[t+1]] * self.backward(t+1, j, O)
denominator = 0
for n in range(self.N):
for m in range(self.N):
denominator += self.forward(t, n, O) * self.A[n][m] * self.B[m][O[t+1]] * self.backward(t+1, m, O)
return molecule / denominator
参数更新:
def train(self, O):
"""
根据隐马尔科夫模型参数的估计值,极大化模型参数
:param O:
:return:
"""
pi = self.pi.copy()
A = self.A.copy()
B = self.B.copy()
for i in range(self.N):
pi[i] = self.gamma(0, i, O)
# 根据公式计算a(i,j)
for j in range(self.N):
A[i][j] = sum([self.xi(t, i, j, O) for t in range(len(O)-1)]
) / sum([self.gamma(t, i, O) for t in range(len(O)-1)])
# 根据公式计算b_i(k)
B_denominator = sum([self.gamma(t, i, O) for t in range(len(O))]) # 公式中的分母
# 求得观测序列的所有不同的取值,和对应的所有时刻。
# 比如观测序列(1, 2, 1),第一个和最后一个时刻的取值为1,第二个时刻的取值为2
Map = {}
for t in range(len(O)):
if O[t] in Map.keys():
Map[O[t]].append(t)
else:
Map[O[t]] = [t]
# 这里k对应观测序列中的一个不同的值
for k, tl in Map.items():
B[i][k] = sum([self.gamma(t, i, O) for t in tl]) / B_denominator
# 更新模型参数
self.pi = pi
self.A = A
self.B = B
def run(self, train_nums, O):
"""
对训练样本进行训练
:param train_nums: 训练轮数
:param O: 所有训练样本,二维数组
:return:
"""
for i in range(train_nums):
for o in O:
# 将观测文本值映射为对应编码
o = [self.map_o[m] for m in o]
self.train(o) # 对一个观测序列训练,进行参数更新
维特比算法
def viterbi(self, O):
"""
维特比编码求最优路径
:param O:
:return:
"""
pro = np.ones([len(O), self.N]) # 记录累计概率
backpointers = np.ones([len(O), self.N], dtype=np.int32) # 记录最优路径
# 计算时刻t=1,各个状态下观测序列为o_1的概率
pro[0] = [self.pi[i] * self.B[i][O[0]] for i in range(self.N)]
# 时刻t=2,3,....,
for t in range(1, len(O)):
b = [self.B[i][O[t]] for i in range(self.N)] # 计算各个状态下观测序列为o_t的概率
v = np.expand_dims(pro[t - 1], 1) * self.A # 计算前一个时刻状态为j,当前时刻状态为i的概率矩阵
pro[t] = np.max(v, 0) * b # 当前状态为i的最大概率 * 对应状态下观测序列为o_t的概率
backpointers[t] = np.argmax(v, 0) # 记录,当前状态为i的概率最大时,上个时刻的状态
# 开始回溯
viterbi = [np.argmax(pro[-1])] # 最终累计概率最大时,最后一个时刻的状态
# 根据当前状态从最优路径中挑选上个时刻的状态
for bp in reversed(backpointers[1:]):
viterbi.append(bp[viterbi[-1]])
viterbi.reverse()
# 将状态编码转化为对应的状态值
viterbi = [self.map_i[v] for v in viterbi]
viterbi_score = np.max(pro[-1]) # 最高的累计概率
return viterbi, viterbi_score
应用举例
拼音输入/语音识别
- 输入法把拼音看做是观察状态,需要得到的汉字为隐藏状态,这样,输入法的整句解码就变成了维特比解码,其转移概率即是二元语言模型,其输出概率即是多音字对应不同拼音的概率。
- 将上图中的拼音换成语音,就成了语音识别问题,转移概率仍然是二元语言模型,其输出概率则是语音模型,即语音和汉字的对应模型。