HMM算法原理
前置知识
概率相加
若完成一件事情有很多方法,注意,每个方法都能独立完成这个事情,又每个方法都有一定的概率,那么完成这个事情的概率就用相加。
概率相乘
若完成一件事情需要N个步骤才能完成,而每一个步骤都有一定的概率(注意每个步骤只是完成这个事情的一个部分),则完成整个事情的概率用的就是相乘。
条件概率公式
全概率公式
贝叶斯公式
HMM模型的5个参数
先举个例子:现在有某地一周的天气预报,观测到的结果是,周一: Sun, 周二: Sun, 周三: Rain, 周四: Sun, 周五: Rain, 周六: Rain, 周日: Sun
假设我们知道某地以往所有的天气,那我们就可以统计出Sun的天数,Rain的天数,Cloud的天数,然后计算出Sun的初始化概率,Rain的初始化概率,Cloud的初始化概率以及这三个状态之间的转移概率。假设统计出的结果如下:
Sun的初始化概率 = 0.6,Rain的初始化概率 = 0.2, Cloud的初始化概率 = 0.2,状态转移矩阵为
sun rain cloud sun 0.6 0.1 0.3 rain 0.1 0.7 0.2 cloud 0.3 0.3 0.4
那么我们就可以很轻松地求出这一周天气为周一: Sun, 周二: Sun, 周三: Rain, 周四: Sun, 周五: Rain, 周六: Rain, 周日: Sun
的发生概率。
但是突然有一天,你被关进了一个小黑屋,你不能直接知道外面的天气,你只能看看小屋子里面苔藓的干湿情况,并且你知道天气能影响苔藓的干湿情况。那么现在如果你想了解天气情况以及相关的信息,你该怎么办?这就是HMM算法需要做的事。
这里先解释下HMM模型出现的5个参数,以及各自代表的含义
- S:隐藏状态的集合(Sun, Cloud, Rain),N为隐状态个数 N = 3
- K: 输出状态或者说观测状态的集合(Soggy, Damp, Dryish, Dry),M为观测状态个数M = 4
- π: 对应隐藏状态的初始化概率 (sun : 0.6, cloud : 0.2, rain : 0.2)
- A: 隐藏状态的状态转移概率,是一个
N*N
的概率矩阵,就是上面所列举出来的矩阵。 - B: 隐藏状态到观测状态的混淆矩阵,是一个
N*M
的发射概率矩阵
HMM模型需要解决的三个问题
观测序列发生的概率
给定观测序列O(o1,o2,…,oT)和模型μ = (π,A,B),求出P(O|u),即给定模型下观测序列发生的概率是多少?
前向算法
基本思想:定义前向变量αt(i)
:表示观测序列是O1, O2, ..., Ot,且t时刻的隐藏状态是Si的概率,其公式表示如下
通俗地讲,就是我现在观测到了这个观测序列,并且知道在t时刻是由隐藏状态Si观测到的。
上面定义有限定了两个条件, 一个是截止到t时刻的观测序列是O1, O2, ..., Ot;另一个是t时刻的隐藏状态是Si。举个例子说明前向变量的作用。
HMM参数以下图为例:
假设现在要求第二天(t = 2)出现观测序列是(Dizzy, Cold)的概率是多少?
因为总共有两个隐藏条件,所以就需要求出α2(Fever)
,即截止到第二天(t=2)的观测序列是(Dizzy, Cold),且第二天的隐藏状态是Fever的概率,和α2(Healthy)
,即截止到第二天(t=2)的观测序列是(Dizzy, Cold),且第二天的隐藏状态是Healthy的概率,最后把这两个概率加起来(α2(Fever) + α2(Healthy))就是最终的结果。因为由每一个隐藏状态都可能映射到相同的观测状态,把所有可能的概率加起来就是最终的概率。
因此,如果可以高效地计算αt(i)
,就可以高效地计算P(O|μ)。
前向算法公式推导
参考博客:https://zhuanlan.zhihu.com/p/85454896
里面用到了全概率公式和条件概率公式
前向算法代码实现
# 状态转移矩阵 self.A = array([(0.5, 0.2, 0.3), (0.3, 0.5, 0.2), (0.2, 0.3, 0.5)]) # 发射概率矩阵,其shape为 隐藏状态个数*观测状态个数 = 3 * 2 self.B = array([(0.5, 0.5), (0.4, 0.6), (0.7, 0.3)]) # 隐藏状态的初始化概率 self.pi = array([(0.2), (0.4), (0.4)]) # 观测状态序列,观测状态只有两种:0和1。 self.o = [0, 1, 0] self.t = len(self.o) # 观测序列长度 self.m = len(self.A) # 状态集合个数 self.n = len(self.B[0]) # 观测集合个数
求解问题:通过前向算法求得观测序列[0, 1, 0]
发生的概率
def forward(self): # α矩阵大小行数为观测序列数,列数为状态个数 self.x = array(zeros((self.t, self.m))) # 先计算出t1时刻,观测状态为0的概率 for i in range(self.m): # 用每一个隐藏状态的初始化概率 * 发射概率矩阵中该隐藏状态下能观察到观察状态0的概率 self.x[0][i] = self.pi[i] * self.B[i][self.o[0]] # 计算其他时刻每一个隐藏状态下观测到当前时刻的观测值的概率 for j in range(1, self.t): for i in range(self.m): # 前一时刻所有状态的概率乘以转移概率得到i状态概率 # i状态的概率*i状态到j观测的概率 temp = 0 for k in range(self.m): temp = temp + self.x[j - 1][k] * self.A[k][i] self.x[j][i] = temp * self.B[i][self.o[j]] result = 0 for i in range(self.m): result = result + self.x[self.t - 1][i] print(u"前向概率矩阵及当前观测序列概率如下:") print(self.x) print(result)
概率最大的隐藏状态序列
解决的问题:利用模型和观测序列找出最优的隐藏状态序列
参考博客:https://blog.csdn.net/shenxiaoming77/article/details/79228378
实现代码:
def viterbi(self): # 利用模型和观测序列找出最优的隐藏状态序列 # 每个路径都有自己的概率,最大的概率用矩阵z记录,前一个状态用d矩阵记录 self.z = array(zeros((self.t, self.m))) self.d = array(zeros((self.t, self.m))) for i in range(self.m): self.z[0][i] = self.pi[i] * self.B[i][self.o[0]] self.d[0][i] = 0 for j in range(1, self.t): for i in range(self.m): maxnum = self.z[j - 1][0] * self.A[0][i] node = 1 for k in range(1, self.m): temp = self.z[j - 1][k] * self.A[k][i] if maxnum < temp: maxnum = temp node = k + 1 self.z[j][i] = maxnum * self.B[i][self.o[j]] self.d[j][i] = node # 找到T时刻概率最大的路径 max_probability = self.z[self.t - 1][0] last_node = [1] temp = 0 for i in range(1, self.m): if max_probability < self.z[self.t - 1][i]: max_probability = self.z[self.t - 1][i] last_node[0] = i + 1 temp = i i = self.t - 1 # self.d[t][p],t时刻状态为p的时候,t-1时刻的状态 while i >= 1: last_node.append(self.d[i][temp]) i = i - 1 temp = ['o'] temp[0] = int(last_node[len(last_node) - 1]) j = len(last_node) - 2 while j >= 0: temp.append(int(last_node[j])) j = j - 1 print(u'路径节点分别为') print(temp) print(u'该路径概率为' + str(max_probability))
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话