摘要: 隐马尔可夫模型(hidden Markov model,HMM)是可用于标注问题的统计学习模型,描述由隐藏的马尔可夫链随机生成观测序列的过程,属于生成模型。隐马尔可夫模型在语音识别,自然语言处理,生物信息,模式识别等领域有着广泛的应用。
1、马尔科夫模型与HMM
要讲隐马尔科夫模型,需要先从马尔科夫模型讲起。已知N个有序随机变量,根据贝叶斯定理,他们的联合分布可以写成条件分布的连乘积:
P
(
x
1
,
x
2
,
⋯
,
x
N
)
=
∏
n
=
1
N
P
(
x
n
∣
x
n
−
1
,
⋯
,
x
1
)
(1)
P(x_1,x_2,\cdots,x_N)=\prod_{n=1}^NP(x_n|x_{n-1},\cdots,x_1)\tag{1}
P(x1,x2,⋯,xN)=n=1∏NP(xn∣xn−1,⋯,x1)(1)
马尔科夫模型是指,假设序列中的任何一个随机变量在给定它的前一个变量时的分布与更早的变量无关,即:
P
(
x
n
∣
x
n
−
1
,
⋯
,
x
1
)
=
P
(
x
n
∣
x
n
−
1
)
(2)
P(x_n|x_{n-1},\cdots,x_1)=P(x_n|x_{n-1})\tag{2}
P(xn∣xn−1,⋯,x1)=P(xn∣xn−1)(2)
在此假设下,N个随机变量的联合分布可以简化为:
P
(
x
1
,
x
2
,
⋯
,
x
N
)
=
∏
n
=
2
N
P
(
x
n
∣
x
n
−
1
)
(3)
P(x_1,x_2,\cdots,x_N)=\prod_{n=2}^NP(x_n|x_{n-1})\tag{3}
P(x1,x2,⋯,xN)=n=2∏NP(xn∣xn−1)(3)
一阶马尔科夫性只能表达当前变量与前一个变量的关系,然而很多实际问题往往没有这么简单。为了表达当前变量与更早的变量之间的关系,可以引入高阶马尔科夫性。概括来说,M阶马尔科夫性是指当前随机变量在给定其之前的M个变量时与更早的变量无关,用公式表达就是:
P
(
x
n
∣
x
n
−
1
,
⋯
,
x
1
)
=
P
(
x
n
∣
x
n
−
1
,
⋯
,
x
n
−
M
)
(4)
P(x_n|x_{n-1},\cdots,x_1)=P(x_n|x_{n-1},\cdots,x_{n-M})\tag{4}
P(xn∣xn−1,⋯,x1)=P(xn∣xn−1,⋯,xn−M)(4)
高阶马尔科夫性虽然达到了关联当前变量与更早的变量的目的,但有一个问题就是指数爆炸问题,即参数数量随着M的增大呈指数增长。假设每个随机变量有K
种状态,对于一阶马尔科夫模型而言,要表达条件分布
P
(
x
n
∣
x
n
−
1
)
P(x_n|x_{n-1})
P(xn∣xn−1),因为对于
x
n
−
1
x_{n-1}
xn−1的每个取值(共有K
个取值)需要K
个
x
n
x_n
xn的取值,根据条件概率的定义,这K
个值的和为1,因此实际有个K-1
个自由参数,因此共需要K(K-1)
个参数。同理,对于M
阶马尔科夫模型而言,要表达条件分布
P
(
x
n
∣
x
n
−
1
,
⋯
,
x
n
−
M
)
P(x_n|x_{n-1},\cdots,x_{n-M})
P(xn∣xn−1,⋯,xn−M),则需要
K
M
(
K
−
1
)
K^M(K-1)
KM(K−1)个参数(对于
x
n
,
⋯
,
x
n
−
M
x_n,\cdots,x_{n-M}
xn,⋯,xn−M的每一个取值的组合(共
K
M
K^M
KM个),都需要K-1
个
x
n
x_n
xn的取值)。
那么,有没有一种方法即能将当前变量与更早的变量关联起来,又不需要那么多参数呢?当然,这里有一种非常强大的手段,即引入隐变量,这是机器学习中用简单的基础部件表达丰富的模型的有力手段。这里如果假设隐变量构成一阶马尔科夫模型,而每个观测变量与一个隐变量关联,则可以得到一类模型的基础结构,即状态空间模型。如下图, z n z_n zn为隐藏变量, x n x_n xn为观测变量。
该类模型的关键是隐藏变量之间满足如下条件独立性,即在给定 z n z_n zn时, z n − 1 z_{n-1} zn−1和 z n + 1 z_{n+1} zn+1条件独立:
这类模型的联合分布可以表示为:
P
(
x
1
,
⋯
,
x
N
,
z
1
,
⋯
,
z
N
)
=
P
(
z
1
)
P(x_1,\cdots,x_N,z_1,\cdots,z_N)=P(z_1)
P(x1,⋯,xN,z1,⋯,zN)=P(z1)
可见,看似很复杂的模型被分解成了简单的
P
(
z
1
)
P(z_1)
P(z1)、
P
(
z
n
∣
z
n
−
1
)
P(z_n|z_{n-1})
P(zn∣zn−1)和
P
(
x
n
∣
z
n
)
P(x_n|z_n)
P(xn∣zn)部分,这三者分别叫做初始概率模型、转移概率模型和观测概率模型,对状态空间模型建模实际就是对这三者进行建模。而且此时观测变量之间不再具有任何马尔科夫性,因为此时
x
n
x_n
xn的分布与其之前所有的观测变量都相关,无法从的
P
(
x
n
∣
x
n
−
1
,
⋯
,
x
1
)
P(x_n|x_{n-1},\cdots,x_1)
P(xn∣xn−1,⋯,x1)条件变量中拿掉任何一个变量,这样就将一个变量与其之前所有的变量关联起来了。这就是隐变量的能力。
当 z n z_n zn为离散变量时,该状态空间模型即为隐马尔科夫模型。现在可以解释一下“隐马尔科夫”这个名字的含义了。“马尔科夫”自然是表示随机变量之间构成一个马尔科夫链了。而“隐”是指我们要推测的变量是未知的、隐藏的。正是这些隐藏的变量构成了马尔科夫链,所以就叫“隐马尔科夫”了。
2、隐马尔可夫模型
隐藏的马尔可夫链随机生成的状态的序列,称为状态序列;每个状态生成一个一个观测,而由此产生的观测的随机序列,称为观测序列。
设Q是所有可能的状态的集合,V是所有可能的观测的集合:
Q
=
{
q
1
,
q
2
,
⋯
,
q
N
}
,
V
=
{
v
1
,
v
2
,
⋯
,
v
M
}
Q=\lbrace q_1,q_2,\cdots,q_N \rbrace,V=\lbrace v_1,v_2,\cdots,v_M \rbrace
Q={q1,q2,⋯,qN},V={v1,v2,⋯,vM}
其中,N是所有可能的状态的集合,V是所有可能的观测的集合。
I是长度为T的状态序列,O是对应的观测序列:
I
=
(
i
1
,
i
2
,
⋯
,
i
T
)
,
O
=
(
o
1
,
o
2
,
⋯
,
o
T
)
I=(i_1,i_2,\cdots,i_T),O=(o_1,o_2,\cdots,o_T)
I=(i1,i2,⋯,iT),O=(o1,o2,⋯,oT)
A是状态转移概率矩阵:
A
=
[
a
i
j
]
N
×
N
A=[a_{ij}]_{N\times N}
A=[aij]N×N
其中,
a
i
j
=
P
(
i
t
+
1
=
q
j
∣
i
t
=
q
i
)
,
i
=
1
,
2
,
⋯
,
N
;
j
=
1
,
2
,
⋯
,
N
a_{ij}=P(i_{t+1}=q_j|i_t=q_i),i=1,2,\cdots,N;~~~j=1,2,\cdots,N
aij=P(it+1=qj∣it=qi),i=1,2,⋯,N; j=1,2,⋯,N
是在时刻
t
t
t处于状态
q
i
q_i
qi的条件下在时刻
t
+
1
t+1
t+1转移到状态
q
j
q_j
qj的概率。
B是观测概率矩阵:
B
=
[
b
j
(
k
)
]
N
×
M
B=[b_j(k)]_{N\times M}
B=[bj(k)]N×M
其中,
b
j
(
k
)
=
P
(
o
t
=
v
k
∣
i
t
=
q
j
)
,
k
=
1
,
2
,
⋯
,
M
;
j
=
1
,
2
,
⋯
,
N
b_j(k)=P(o_t=v_k|i_t=q_j),~~k=1,2,\cdots,M;~~~j=1,2,\cdots,N
bj(k)=P(ot=vk∣it=qj), k=1,2,⋯,M; j=1,2,⋯,N
是在时刻
t
t
t处于状态
q
j
q_j
qj的条件下生成观测
v
k
v_k
vk的概率。
π
\pi
π是初始状态概率向量:
π
=
(
π
i
)
\pi=(\pi_i)
π=(πi)
其中,
π
i
=
P
(
i
1
=
q
i
)
,
i
=
1
,
2
,
⋯
,
N
\pi_i=P(i_1=q_i),i=1,2,\cdots,N
πi=P(i1=qi),i=1,2,⋯,N
是时刻
t
=
1
t=1
t=1处于状态
q
i
q_i
qi的概率。
3、隐马尔可夫模型的3个基本问题
- 概率计算问题
- 学习问题
- 预测问题
为了解决上述的三个问题,由此产生了一些算法。
3.1、概率计算算法
- 直接计算法(计算复杂度达到了 T N T TN^T TNT,复杂度太高,因此在实际中不使用)
- 前向算法
- 后向算法
1)前向算法:
定义:给定马尔可夫模型
λ
\lambda
λ,定义到时刻
t
t
t部分观测序列为
o
1
,
o
2
,
⋯
,
o
t
o_1,o_2,\cdots,o_t
o1,o2,⋯,ot且状态为
q
i
q_i
qi的概率为前向概率,记作
α
t
(
i
)
=
P
(
o
1
,
o
2
,
⋯
,
o
t
,
i
t
=
q
i
∣
λ
)
\alpha_t(i)=P(o_1,o_2,\cdots,o_t,i_t=q_i|\lambda)
αt(i)=P(o1,o2,⋯,ot,it=qi∣λ)
2)后向算法:
定义:给定马尔可夫模型
λ
\lambda
λ,定义在时刻
t
t
t状态为
q
i
q_i
qi的条件下,从
t
+
1
t+1
t+1到
T
T
T的 部分观测序列为
o
t
+
1
,
o
t
+
2
,
⋯
,
o
T
o_{t+1},o_{t+2},\cdots,o_T
ot+1,ot+2,⋯,oT的概率为后向概率,记作
β
t
(
i
)
=
P
(
o
t
+
1
,
o
t
+
2
,
⋯
,
o
T
∣
i
t
=
q
i
,
λ
)
\beta_t(i)=P(o_{t+1},o_{t+2},\cdots,o_T|i_t=q_i,\lambda)
βt(i)=P(ot+1,ot+2,⋯,oT∣it=qi,λ)
3.2、学习算法
- 监督学习算法
- 无监督学习算法——Baum-Welch算法(也就是EM算法)
1)监督学习算法:
使用极大似然估计法来估计模型参数
a
^
i
j
=
A
i
j
∑
j
=
1
N
A
i
j
,
i
=
1
,
2
,
⋯
,
N
;
j
=
1
,
2
,
⋯
,
N
b
^
j
(
k
)
=
B
j
k
∑
k
=
1
M
B
j
k
,
j
=
1
,
2
,
⋯
,
N
;
k
=
1
,
2
,
⋯
,
M
\hat{a}_{ij}=\frac{A_{ij}}{\sum_{j=1}^NA_{ij}},i=1,2,\cdots,N;~~~j=1,2,\cdots,N \\ \hat{b}_j(k)=\frac{B_{jk}}{\sum_{k=1}^MB_{jk}},j=1,2,\cdots,N;~~~k=1,2,\cdots,M
a^ij=∑j=1NAijAij,i=1,2,⋯,N; j=1,2,⋯,Nb^j(k)=∑k=1MBjkBjk,j=1,2,⋯,N; k=1,2,⋯,M
2)无监督学习算法:
a
i
j
(
n
+
1
)
=
∑
t
=
1
T
−
1
ζ
t
(
i
,
j
)
∑
t
=
1
T
−
1
γ
t
(
i
)
b
j
(
k
)
(
n
+
1
)
=
∑
t
=
1
,
o
t
=
v
k
T
γ
t
(
j
)
∑
t
=
1
T
γ
t
(
j
)
π
i
(
n
+
1
)
=
γ
1
(
i
)
a_{ij}^{(n+1)}=\frac{\sum_{t=1}^{T-1}\zeta_t(i,j)}{\sum_{t=1}^{T-1}\gamma_t(i)}\\ \\ b_j(k)^{(n+1)}=\frac{\sum_{t=1,o_t=v_k}^T\gamma_t(j)}{\sum_{t=1}^T\gamma_t(j)}\\ \\ \pi_i^{(n+1)}=\gamma_1(i)
aij(n+1)=∑t=1T−1γt(i)∑t=1T−1ζt(i,j)bj(k)(n+1)=∑t=1Tγt(j)∑t=1,ot=vkTγt(j)πi(n+1)=γ1(i)
3.3、预测算法
- 近似算法
- 维特比算法
1)近似算法:
γ
t
(
i
)
=
α
t
(
i
)
β
t
(
i
)
P
(
O
∣
λ
)
=
α
t
(
i
)
β
t
(
i
)
∑
j
=
1
N
α
t
(
j
)
β
t
(
j
)
i
t
∗
=
a
r
g
m
a
x
1
≤
i
≤
N
[
γ
t
(
i
)
]
,
t
=
1
,
2
,
⋯
,
T
I
∗
=
(
i
1
∗
,
i
2
∗
,
⋯
,
i
T
∗
)
\gamma_t(i)=\frac{\alpha_t(i)\beta_t(i)}{P(O|\lambda)}=\frac{\alpha_t(i)\beta_t(i)}{\sum_{j=1}^N\alpha_t(j)\beta_t(j)} \\ i_t^*=arg ~\mathop{max}_{1\le i\le N}~[\gamma_t(i)],t=1,2,\cdots,T \\ I^*=(i_1^*,i_2^*,\cdots,i_T^*)
γt(i)=P(O∣λ)αt(i)βt(i)=∑j=1Nαt(j)βt(j)αt(i)βt(i)it∗=arg max1≤i≤N [γt(i)],t=1,2,⋯,TI∗=(i1∗,i2∗,⋯,iT∗)
2)维特比算法:
这个算法就要隆重的来学习一下了。维特比算法实际是用动态规划解隐马尔可夫模型预测问题,即用动态规划求解概率最大路径(最优路径)。(动态规划看另外一个博客)
1)如果概率最大的路径p(或者说最短路径)经过某个点,比如途中的 X 22 X_{22} X22,那么这条路径上的起始点S到X22的这段子路径Q,一定是S到 X 22 X_{22} X22之间的最短路径。否则,用S到 X 22 X_{22} X22的最短路径R替代Q,便构成一条比P更短的路径,这显然是矛盾的。证明了满足最优性原理。
2)从S到E的路径必定经过第i个时刻的某个状态,假定第i个时刻有k个状态,那么如果记录了从S到第i个状态的所有k个节点的最短路径,最终的最短路径必经过其中一条,这样,在任意时刻,只要考虑非常有限的最短路即可。
3)结合以上两点,假定当我们从状态i进入状态i+1时,从S到状态i上各个节的最短路径已经找到,并且记录在这些节点上,那么在计算从起点S到第i+1状态的某个节点 X i + 1 X_{i+1} Xi+1的最短路径时,只要考虑从S到前一个状态i所有的k个节点的最短路径,以及从这个节点到 X i + 1 X_{i+1} Xi+1,j的距离即可。
4、代码实践
class HiddenMarkov:
def forward(self, Q, V, A, B, O, PI): # 使用前向算法
N = len(Q) #可能存在的状态数量
M = len(O) # 观测序列的大小
alphas = np.zeros((N, M)) # alpha值
T = M # 有几个时刻,有几个观测序列,就有几个时刻
for t in range(T): # 遍历每一时刻,算出alpha值
indexOfO = V.index(O[t]) # 找出序列对应的索引
for i in range(N):
if t == 0: # 计算初值
alphas[i][t] = PI[t][i] * B[i][indexOfO] # P176(10.15)
print(
'alpha1(%d)=p%db%db(o1)=%f' % (i, i, i, alphas[i][t]))
else:
alphas[i][t] = np.dot(
[alpha[t - 1] for alpha in alphas],
[a[i] for a in A]) * B[i][indexOfO] # 对应P176(10.16)
print('alpha%d(%d)=[sigma alpha%d(i)ai%d]b%d(o%d)=%f' %
(t, i, t - 1, i, i, t, alphas[i][t]))
# print(alphas)
P = np.sum([alpha[M - 1] for alpha in alphas]) # P176(10.17)
# alpha11 = pi[0][0] * B[0][0] #代表a1(1)
# alpha12 = pi[0][1] * B[1][0] #代表a1(2)
# alpha13 = pi[0][2] * B[2][0] #代表a1(3)
def backward(self, Q, V, A, B, O, PI): # 后向算法
N = len(Q) # 可能存在的状态数量
M = len(O) # 观测序列的大小
betas = np.ones((N, M)) # beta
for i in range(N):
print('beta%d(%d)=1' % (M, i))
for t in range(M - 2, -1, -1):
indexOfO = V.index(O[t + 1]) # 找出序列对应的索引
for i in range(N):
betas[i][t] = np.dot(
np.multiply(A[i], [b[indexOfO] for b in B]),
[beta[t + 1] for beta in betas])
realT = t + 1
realI = i + 1
print(
'beta%d(%d)=[sigma a%djbj(o%d)]beta%d(j)=(' %
(realT, realI, realI, realT + 1, realT + 1),
end='')
for j in range(N):
print(
"%.2f*%.2f*%.2f+" % (A[i][j], B[j][indexOfO],
betas[j][t + 1]),
end='')
print("0)=%.3f" % betas[i][t])
# print(betas)
indexOfO = V.index(O[0])
P = np.dot(
np.multiply(PI, [b[indexOfO] for b in B]),
[beta[0] for beta in betas])
print("P(O|lambda)=", end="")
for i in range(N):
print(
"%.1f*%.1f*%.5f+" % (PI[0][i], B[i][indexOfO], betas[i][0]),
end="")
print("0=%f" % P)
def viterbi(self, Q, V, A, B, O, PI):
N = len(Q) #可能存在的状态数量
M = len(O) # 观测序列的大小
deltas = np.zeros((N, M))
psis = np.zeros((N, M))
I = np.zeros((1, M))
for t in range(M):
realT = t + 1
indexOfO = V.index(O[t]) # 找出序列对应的索引
for i in range(N):
realI = i + 1
if t == 0:
deltas[i][t] = PI[0][i] * B[i][indexOfO]
psis[i][t] = 0
print('delta1(%d)=pi%d * b%d(o1)=%.2f * %.2f=%.2f' %
(realI, realI, realI, PI[0][i], B[i][indexOfO],
deltas[i][t]))
print('psis1(%d)=0' % (realI))
else:
deltas[i][t] = np.max(
np.multiply([delta[t - 1] for delta in deltas],
[a[i] for a in A])) * B[i][indexOfO]
print(
'delta%d(%d)=max[delta%d(j)aj%d]b%d(o%d)=%.2f*%.2f=%.5f'
% (realT, realI, realT - 1, realI, realI, realT,
np.max(
np.multiply([delta[t - 1] for delta in deltas],
[a[i] for a in A])), B[i][indexOfO],
deltas[i][t]))
psis[i][t] = np.argmax(
np.multiply(
[delta[t - 1] for delta in deltas],
[a[i]
for a in A])) + 1 #由于其返回的是索引,因此应+1才能和正常的下标值相符合。
print('psis%d(%d)=argmax[delta%d(j)aj%d]=%d' %
(realT, realI, realT - 1, realI, psis[i][t]))
print(deltas)
print(psis)
I[0][M - 1] = np.argmax([delta[M - 1] for delta in deltas
]) + 1 #由于其返回的是索引,因此应+1才能和正常的下标值相符合。
print('i%d=argmax[deltaT(i)]=%d' % (M, I[0][M - 1]))
for t in range(M - 2, -1, -1):
I[0][t] = psis[int(I[0][t + 1]) - 1][t + 1]
print('i%d=psis%d(i%d)=%d' % (t + 1, t + 2, t + 2, I[0][t]))
print("状态序列I:", I)
Reference: