机器学习之隐马尔科夫模型(HMM)
- 1、隐马尔科夫模型介绍
- 2、隐马尔科夫数学原理
- 3、Python代码实现隐马尔科夫模型
- 4、总结
隐马尔可夫模型介绍
马尔科夫模型(hidden Markov model,HMM)是关于时序的概率模型,描述由一个隐藏的马尔科夫随机生成不可观测的状态随机序列,再由各个状态生成一个观测从而产生观测随机序列的过程,属于一个生成模型。
下面我们来从概率学角度定义马尔科夫模型,从一个典型例子开始:
假设有4个盒子,每个盒子里面有不同数量的红、白两种颜色的球,具体如下表:
盒子编号 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
红球数 | 5 | 3 | 6 | 8 |
白球数 | 5 | 7 | 4 | 2 |
现在从这些盒子中取出T个球,取样规则为每次选择一个盒子取出一个球,记录其颜色,放回。在这个过程中,我们只能观测到球的颜色的序列,观测不到球是从哪个盒子中取出来的,即观测不到盒子的序列,这里有两个随机序列,一个是盒子的序列(状态序列),一个是球的颜色的观测序列(观测序列),前者是隐藏的,只有后者是可观测的。这里就构成了一个马尔科夫的例子。
定义是所有的可能的状态集合,V是所有的可能的观测的集合:
其中,N是可能的状态数,M是可能的观测数,例如上例中N=4,M=2。
是长度为T的状态序列,是对应的观测序列:
A是状态转移概率矩阵:
其中, 是指在时刻处于状态的条件下在时刻转移到状态的概率。
B是观测概率矩阵:
其中, 是指在时刻处于状态的条件下生成观测的概率。
是初始状态概率向量:
其中, 是指在时刻=1处于状态的概率。
由此可得到,隐马尔可夫模型的三元符号表示,即
称为隐马尔可夫模型的三要素。
由定义可知隐马尔可夫模型做了两个基本假设:
(1)齐次马尔科夫性假设,即假设隐藏的马尔科夫链在任意时刻的状态只和-1状态有关;
(2)观测独立性假设,观测只和当前时刻状态有关;
仍以上面的盒子取球为例,假设我们定义盒子和球模型:
状态集合: = {盒子1,盒子2,盒子3,盒子4}, N=4
观测集合: = {红球,白球} M=2
初始化概率分布:
状态转移矩阵:
观测矩阵:
隐马尔可夫模型的三个基本问题
-
1、概率计算问题
给定:
计算:
-
2、学习问题
已知:
估计:,使最大
-
3、预测问题(解码)
已知:
求:使最大的状态序列
下面我们使用python代码写一个HMM模型生成序列的示例代码
import numpy as np
class HMM(object):
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 get_data_with_distribute(self, dist): # 根据给定的概率分布随机返回数据(索引)
r = np.random.rand()
for i, p in enumerate(dist):
if r < p: return i
r -= p
def generate(self, T: int):
'''
根据给定的参数生成观测序列
T: 指定要生成观测序列的长度
'''
result = []
for ind in range(T): # 依次生成状态和观测数据
if ind==0:
i = self.get_data_with_distribute(self.pi)
else:
i = self.get_data_with_distribute(self.A[i])
o = self.get_data_with_distribute(self.B[i])
result.append(o)
return result
if __name__ == "__main__":
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.3, 0.7],
[0.6, 0.4],
[0.8, 0.2]])
hmm = HMM(4, 2, pi, A, B)
print(hmm.generate(10)) # 生成10个数据
# 生成结果如下
[0, 0, 0, 0, 1, 1, 1, 1, 0, 0] # 0代表红球,1代表白球
隐马尔可夫模型数学原理
下面我们从隐马尔可夫模型的三个基本问题出发,逐个解释问题解法:
-
1、概率计算问题
- 直接计算法
- 前向算法
- 后向算法
直接计算法:
状态序列概率:
对固定的状态序列,观测序列的概率:
和同时出现的联合概率为:
对所有的可能的状态序列求和,得到观测序列的概率:
此算法的复杂度为,计算量太大,不可行。
前向算法:
-
前向概率定义:给定隐马尔科夫模型,定义到时刻t部分观测序列为:,且状态为的概率为前向概率,记作:
输入:隐马尔可夫模型,观测序列;输出:观测序列概率
初值:
递推:
终止:
算法复杂度,算法复杂度大大降低
因为:
所以:
-
下面以一个具体的例子来演示前向算法计算过程:
假设T = 3,O = (红,白,红),使用前向算法计算,模型,状态集合,观测集合(1)计算初值
(2)递推计算
(3)终止
后向算法:
-
后向概率定义:给定隐马尔可夫模型,定义在时刻t状态为的条件下,从t+1到T的部分观测序列为:的概率为后向概率,记作:
仍然可以使用递推的方法求得后向概率及观测序列概率输入:隐马尔科夫模型,观测序列O;
输出:观测序列概率.
(1)
(2)对
(3
前后向统一写为:(t=1和t=T-1分别对应)
一些概率和期望值计算方式:1、给定模型和观测,在时刻处于状态的概率,记
2、给定模型和观测,在时刻处于状态且在时刻处于概率,记
3、由1和2可得,(1)在观测O下状态i出现的期望值:,(2)在观测O下由状态i转移的期望值:,(3)在观测O下由状态i转移到状态j的期望值:。
-
2、学习问题
监督学习方法:
- 假设训练数据是包括观测序列O和对应的状态序列I,
- 可以利用极大似然估计发来估计隐马尔可夫模型参数。
已知:
(1)转移概率的估计:假设样本中时刻t处于状态i,时刻t+1转移到状态j的频数为那么转台转移概率的估计是:
(2)观测概率的估计:设样本中状态为j并观测为k的频数是那么状态j观测为k的概率
(3)初始状态概率的估计为S个样本中初始状态为的频率。非监督学习方法:
Baum-Welch算法
假定训练数据只包括{O1,O2,...Os},求模型参数,实质上是有隐变量的概率模型:EM算法。
(1)确定完全数据的对数似然函数
完全数据
完全数据的对数似然函数
(2)EM的E步,求Q函数,则:
对序列总长度T进行(3)EM算法的M步,极大化求模型参数
第一项,由约束条件:利用拉格朗日乘子求偏导数,并令结果等于0
得:第二项可写成由约束条件,拉格朗日乘子法得:
第三项由约束条件,注意,只有在时对的偏导数才不为0,以表示,求得
将以上得到的概率分别用表示
Baum-Welch算法流程:输入:观测数据;
输出:隐马尔可夫模型参数。
(1)初始化
对n=0,选取得到模型
(2)递推,对n=1,2,...,
右侧各值按照观测和模型计算(3)终止,得到模型参数
-
3、预测问题
预测算法
(1)近似算法
想法:在每个时刻t选择在该时刻最后可能的状态,从而得到一个序列状态序列,将它作为预测的结果,在时刻t处于状态的概率:
在每一时刻t最有可能的状态是:从而得到状态序列:
得到的状态有可能实际不发生
(2)维比特算法
用动态规划解概率最大路径,一个路径对应一个状态序列。
最优路径具有这样的特性:如果最优路径在时刻t通过结点,那么这一路径从结点到终点的部分路径,对于从到的所有可能的部分路径来说,必须是最优的。
只需从时刻t=1开始,递推地计算在时刻t状态为i的各条部分路径的最大概率,直至得到时刻t=T状态为i的各条路径的最大概率,时刻t=T的最大概率即为最优路径的概率最有路径的终结点也同时得到。
之后,为了找出最优路径的各个结点,从终结点开始,由后向前逐步求得结点,得到最优路径导入两个变量和,定义在时刻t状态为i的所有单个路径中概率最大值为:
由定义可得变量的递推公式:
定义在时刻t状态为i的所有单个路径中概率最大的路径的第t-1个结点为
输入:模型和观测;输出:最优路径.
①初始化
②递推,对
③终止
④最优路径回溯,对
求得最优路径
示例:
O=(红,白,红),试求最优状态序列,即最优路径①初始化:在t=1时,对每一个状态i,i=1,2,3,求状态i观测O1为红的概率,记为:
代入实际数据:,记
②在t=2时,对每一个状态i,i=1,2,3,求在t=1时状态为j观测O1为红并在t=2时状态为i观测O2为白的路径的最大概率,记为
同时,对每个状态i,记录概率最大路径的前一个状态j,计算:
同样,在t=3时
③以P*表示最优路径的概率:
最优路径的终点是:
④由最优路径的终点,逆向找到
于是求得最优路径,即最优状态序列:
隐马尔科夫模型Python实现
import numpy as np
class HMM(object):
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 get_data_with_distribute(self, dist): # 根据给定的概率分布随机返回数据(索引)
r = np.random.random()
for i, p in enumerate(dist):
if r < p: return i
r -= p
def generate(self, T: int):
'''
根据给定的参数生成观测序列
T: 指定要生成观测序列的长度
'''
result = []
i = 0
for ind in range(T): # 依次生成状态和观测数据
if ind==0:
i = self.get_data_with_distribute(self.pi)
else:
i = self.get_data_with_distribute(self.A[i,:])
o = self.get_data_with_distribute(self.B[i,:])
result.append(o)
return result
def forward(self,X):
'''
根据给定的参数计算条件概率(前向算法)
X:观测数据
'''
alpha = self.pi * self.B[:,X[0]]
for x in X[1:]:
#将alpha构造成(N,1)维度,使用广播机制
alpha = np.sum(self.A * alpha[...,np.newaxis],axis=0) * self.B[:,x]
return alpha.sum()
def backward(self,X):
'''
根据给定的参数计算条件概率(后向算法)
X:观测数据
'''
beta = np.ones(self.N)
for x in X[:0:-1]:
#beta默认为(1,N)维度,使用广播机制
beta = np.sum(self.A * self.B[:,x] * beta,axis=1)
return np.sum(beta * self.pi * self.B[:,X[0]],axis=0)
def get_alpha_beta(self,X):
'''
根据给定数据与参数,计算所有时刻的前向概率和后向概率
'''
T = len(X)
alpha = np.zeros((T,self.N))
alpha[0,:] = self.pi * self.B[:,X[0]]
for t in range(T-1):
alpha[t+1,:] = np.sum(self.A * alpha[t,:][...,np.newaxis],axis=0) * self.B[:,X[t+1]]
beta = np.ones((T,self.N))
for t in range(T-1,0,-1):
beta[t-1,:] = np.sum(self.A * beta[t,:] * self.B[:,X[t]],axis=1)
# print(alpha[T-1,:].sum(),np.sum(beta[0,:] * self.pi * self.B[:,X[0]],axis=0))
return alpha,beta
def fit(self,X):
'''
根据观测序列估计参数
'''
#初始化参数pi,A,B
self.pi = np.random.random(self.N)
self.pi /= np.sum(self.pi)
self.A = np.random.random((self.N,self.N))
self.A /= np.sum(self.A,axis=1)[:,np.newaxis]
self.B = np.random.random((self.N,self.M))
self.B /= np.sum(self.B,axis=1)[:,np.newaxis]
T = len(X)
for epoch in range(10):
#根据公式计算下一时刻参数值
alpha,beta = self.get_alpha_beta(X)
gamma = alpha * beta
for i in range(self.N):
for j in range(self.N):
self.A[i,j] = np.sum(alpha[:-1,i] * beta[1:,j] * self.A[i,j] * self.B[j,X[1:]],axis=0) / np.sum(gamma[:-1,i],axis=0)
for i in range(self.N):
for j in range(self.M):
self.B[i,j] = np.sum(gamma[:,i] * (X == j),axis=0) / np.sum(gamma[:,i],axis=0)
self.pi = gamma[0] / gamma[0].sum()
def decode(self,X):
'''
解码问题,参数已知,根据给定的观测序列,返回最大概率的隐藏序列
'''
T = len(X)
x = X[0]
delta = self.pi[:,np.newaxis] * self.B[:,x]
varphi = np.zeros((T,self.N),dtype=np.int)
path = [0] * T
for t in range(1,T):
# delta = delta.reshape(-1,1)
tmp = delta * self.A
varphi[t,:] = np.argmax(tmp,axis=0)
delta = np.max(tmp,axis=0) * self.B[:,X[t]]
path[-1] = np.argmax(delta)
#回溯最优路径
for t in range(T-1,0,-1):
path[t-1] = varphi[t,path[t]]
return path
if __name__ == "__main__":
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.3, 0.7],
[0.6, 0.4],
[0.8, 0.2]])
hmm = HMM(4, 2, pi, A, B)
print(hmm.generate(10)) # 生成10个数据
X = [0,0,1,1,0] #观测序列为红,红,白,白,红
print(hmm.forward(X))
print(hmm.backward(X))
import matplotlib.pyplot as plt
def triangle_data(T): # 生成三角波形状的序列
data = []
for x in range(T):
x = x % 2
data.append(x if x <= 1 else 2-x)
return data
data = np.array(triangle_data(15))
hmm.fit(data)
gen_obs = hmm.generate(15) # 再根据学习的参数生成数据
x = np.arange(15)
plt.scatter(x, gen_obs, marker='*', color='r')
plt.plot(x, data, color='g')
plt.show()
pi = np.array([0.2, 0.4, 0.4])
A = np.array([
[0.5, 0.2, 0.3],
[0.3, 0.5, 0.2],
[0.2, 0.3, 0.5]])
B = np.array([
[0.5, 0.5],
[0.4, 0.6],
[0.7, 0.3]])
O = [0,1,0]
hmm = HMM(3, 2, pi, A, B)
path = hmm.decode(O)
print(path) #输出(2,2,2)为索引
总结
隐马尔可夫模型是关于时序的概率模型,由初始状态概率向量、状态转移概率矩阵A和观测概率矩阵B决定,可以写成的形式,隐马尔可夫模型是一个生成模型,它的应用很多,在分词、词性标注、NLP、语音识别等领域都有着广泛的应用,是一个非常经典的机器学习模型。