🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎
📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
🖍foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟👋
文章目录
概率图模型(Probabilistic Graphical Model, PGM),简称图模型(Graphical Model,GM),是指一种用图结构来描述多元随机变量之间条件独立性的概率模型(注意条件独立性),从而给研究高维空间的概率模型带来了很大的便捷性。
每个位置按照某种分布随机赋予一个值 所构成 的 整体。
假设一个随机过程中,tn 时刻的状态xn 的条件发布,只与其前一状态x_n-1 相关,即:
P(xn|x1,x2,...,xn−1)=P(xn|xn−1)
则将其称为 马尔可夫过程。
对于 马尔可夫过程 的 思想,用一句话去概括:当前时刻状态 仅与上一时刻状态相关,与其他时刻不相关。
可以从 马尔可夫过程 图 去理解,由于 每个状态间 是以 有向直线连接,也就是 当前时刻状态 仅与上一时刻状态相关。
3.1.1 隐马尔科夫算法 是什么?
隐马尔科夫算法是对含有未知参数(隐状态)的马尔可夫链进行建模的生成模型,如下图所示:
在隐马尔科夫模型中,包含隐状态 和 观察状态,隐状态 ii 对于观察者而言是不可见的,而观察状态 oi 对于观察者而言是可见的。隐状态间存在转移概率,隐状态 ii到对应的观察状态 oi 间存在输出概率。
3.1.2 隐马尔科夫算法 中 两个序列 是什么?
3.1.3 隐马尔科夫算法 中 三个矩阵 是什么?
3.1.4 隐马尔科夫算法 中 两个假设 是什么?
3.1.5 隐马尔科夫算法 中 工作流程 是什么?
3.2.1 隐马尔科夫算法 序列概率计算过程 是什么样的?
1.思想
如何对一条序列计算其整体的概率。即目标是计算出 P(O|λ) ;
给定模型 λ=(A,B,π) 和观测序列 O=(o1,o2,...,oT) ,计算在模型 λ 下观测序列 O 出现的概率 P(O|λ)
2.常用方法
由于有隐藏的状态序列 I 的存在,我们是无法计算 P(O|λ) 的。一种常见的做法是把 I 边缘掉,即 P(O|λ)=∑(P(O,I|λ)) ,当然,这种计算复杂度非常高,为 O(TN2)
减少计算量的原因在于每一次计算直接引用前一个时刻的计算结果,避免重复计算,计算复杂度将为$O(T^2 * N)$
3.2.2 隐马尔科夫算法 学习训练过程 是什么样的?
1.思想
找出数据的分布情况,也就是模型参数的确定;
已知观测序列 O=(o1,o2,...,oT) ,估计模型 λ=(A,B,π) 参数,使得在该模型下观测序列概率 P(O|λ) 最大,即用极大似然估计的方法估计参数
2.常用方法
3.2.3 隐马尔科夫算法 序列标注(解码)过程 是什么样的?
1.思想
也就是“预测过程”,通常称为解码过程。在给定的观测序列下找出一条隐状态序列,条件是这个隐状态序列的概率是最大的那个
2.常用方法:Viterbi算法
Viterbi计算有向无环图的一条最大路径:
三个基本问题 存在 渐进关系。首先,要学会用前向算法和后向算法算观测序列出现的概率,然后用Baum-Welch算法求参数的时候,某些步骤是需要用到前向算法和后向算法的,计算得到参数后,我们就可以用来做预测了。因此可以看到,三个基本问题,它们是渐进的,对于做NLP的同学来说,应用HMM模型做解码任务应该是最终的目的。
因为HMM模型其实它简化了很多问题,做了某些很强的假设,如齐次马尔可夫性假设和观测独立性假设,做了假设的好处是,简化求解的难度,坏处是对真实情况的建模能力变弱了。
在序列标注问题中,隐状态(标注)不仅和单个观测状态相关,还和观察序列的长度、上下文等信息相关。例如词性标注问题中,一个词被标注为动词还是名词,不仅与它本身以及它前一个词的标注有关,还依赖于上下文中的其他词。
4.1.1 HMM 存在 什么问题?
HMM中,观测节点 oi 依赖隐藏状态节点 ii ,也就意味着我的观测节点只依赖当前时刻的隐藏状态。但在更多的实际场景下,观测序列是需要很多的特征来刻画的,比如说,我在做NER时,我的标注 ii 不仅跟当前状态 oi 相关,而且还跟前后标注 oj(j≠i) 相关,比如字母大小写、词性等等。
4.2.1 最大熵马尔科夫模型(MEMM) 是什么样?
通过 “定义特征” 的方式,学习条件概率:
并且, P(i|i′,o) 这个概率通过最大熵分类器建模(取名MEMM的原因):
重点来了,这是ME的内容,也是理解MEMM的关键: Z(o,i′) 这部分是归一化; fa(o,i) 是特征函数,具体点,这个函数是需要去定义的; λ 是特征函数的权重,这是个未知参数,需要从训练阶段学习而得。
定义特征函数:
其中,特征函数 fa(o,i) 的 个数 可以任意制定,(a=1,...,n)
所以总体上,MEMM的建模公式这样:
请务必注意,理解判别模型和定义特征两部分含义,这已经涉及到CRF的雏形了。
4.2.2 最大熵马尔科夫模型(MEMM) 如何解决 HMM 问题?
在前面介绍 HMM 时,HMM 提出了 观测节点 oi 依赖隐藏状态节点 ii 假设,该假设不合理的,针对该问题, MEMM 提出 观测节点 ii 依赖隐藏状态节点 oi 以及上一时刻的隐藏节点$i_{i-1}$ 假设。(HMM 和 MEMM 箭头);
1.问题简述
MEMM 容易出现标注偏置问题,MEMM倾向于选择拥有更少转移的状态。
2.问题介绍
用Viterbi算法解码MEMM,状态1倾向于转换到状态2,同时状态2倾向于保留在状态2。 解码过程细节(需要会viterbi算法这个前提):
但是得到的最优的状态转换路径是1->1->1->1,为什么呢?因为状态2可以转换的状态比状态1要多,从而使转移概率降低,即MEMM倾向于选择拥有更少转移的状态。
3.问题原因分析
对于MEMM公式:
求和的作用在概率中是归一化,但是这里归一化放在了指数内部,管这叫local归一化。 来了,viterbi求解过程,是用dp的状态转移公式(MEMM的没展开,请参考CRF下面的公式),因为是局部归一化,所以MEMM的viterbi的转移公式的第二部分出现了问题,导致dp无法正确的递归到全局的最优。
5.1.1 HMM 和 MEMM 存在什么问题?
5.2.1 什么是 CRF?
设 X 与 Y 是随机变量,P(Y|X) 是给定条件 X 的条件下 Y 的条件概率分布,若随机变量 Y 构成一个由无向图G=(V,E)表示的马尔科夫随机场。则称 条件概率分布P(X|Y)为条件随机场。
5.2.2 CRF 的 主要思想是什么?
统计全局概率,在做归一化时,考虑了数据在全局的分布。
5.2.3 CRF 的定义是什么?
给定 X=(x1,x2,...,xn) ,Y=(y_1,y_2,...,y_n) 均为线性链表示的随机变量序列,若在给随机变量序列 X 的条件下,随机变量序列 Y 的条件概率分布 P(Y|X) 构成条件随机场,即满足马尔可夫性:
则称为 P(Y|X) 为线性链条件随机场。
通过去除了隐马尔科夫算法中的观测状态相互独立假设,使算法在计算当前隐状态$x_i$时,会考虑整个观测序列,从而获得更高的表达能力,并进行全局归一化解决标注偏置问题。
5.2.4 CRF 的 三个基本问题 是什么?
5.2.4.1 概率计算问题
其中:
Z(x) 为归一化因子,是在全局范围进行归一化,枚举了整个隐状态序列$x_{1…n}$的全部可能,从而解决了局部归一化带来的标注偏置问题。
tk 为定义在边上的特征函数,转移特征,依赖于前一个和当前位置
s1 为定义在节点上的特征函数,状态特征,依赖于当前位置。
5.2.4.2 学习计算问题
5.2.4.3 预测问题
5.2.5 CRF 的 流程是什么?
5.3.1 CRF 的 优点在哪里?
5.3.2 CRF 的 缺点在哪里?
- import numpy as np
- class CRF(object):
- '''实现条件随机场预测问题的维特比算法
- '''
- def __init__(self, V, VW, E, EW):
- '''
- :param V:是定义在节点上的特征函数,称为状态特征
- :param VW:是V对应的权值
- :param E:是定义在边上的特征函数,称为转移特征
- :param EW:是E对应的权值
- '''
- self.V = V #点分布表
- self.VW = VW #点权值表
- self.E = E #边分布表
- self.EW = EW #边权值表
- self.D = [] #Delta表,最大非规范化概率的局部状态路径概率
- self.P = [] #Psi表,当前状态和最优前导状态的索引表s
- self.BP = [] #BestPath,最优路径
- return
-
- def Viterbi(self):
- '''
- 条件随机场预测问题的维特比算法,此算法一定要结合CRF参数化形式对应的状态路径图来理解,更容易理解.
- '''
- self.D = np.full(shape=(np.shape(self.V)), fill_value=.0)
- self.P = np.full(shape=(np.shape(self.V)), fill_value=.0)
- for i in range(np.shape(self.V)[0]):
- #初始化
- if 0 == i:
- self.D[i] = np.multiply(self.V[i], self.VW[i])
- self.P[i] = np.array([0, 0])
- print('self.V[%d]='%i, self.V[i], 'self.VW[%d]='%i, self.VW[i], 'self.D[%d]='%i, self.D[i])
- print('self.P:', self.P)
- pass
- #递推求解布局最优状态路径
- else:
- for y in range(np.shape(self.V)[1]): #delta[i][y=1,2...]
- for l in range(np.shape(self.V)[1]): #V[i-1][l=1,2...]
- delta = 0.0
- delta += self.D[i-1, l] #前导状态的最优状态路径的概率
- delta += self.E[i-1][l,y]*self.EW[i-1][l,y] #前导状态到当前状体的转移概率
- delta += self.V[i,y]*self.VW[i,y] #当前状态的概率
- print('(x%d,y=%d)-->(x%d,y=%d):%.2f + %.2f + %.2f='%(i-1, l, i, y, \
- self.D[i-1, l], \
- self.E[i-1][l,y]*self.EW[i-1][l,y], \
- self.V[i,y]*self.VW[i,y]), delta)
- if 0 == l or delta > self.D[i, y]:
- self.D[i, y] = delta
- self.P[i, y] = l
- print('self.D[x%d,y=%d]=%.2f\n'%(i, y, self.D[i,y]))
- print('self.Delta:\n', self.D)
- print('self.Psi:\n', self.P)
-
- #返回,得到所有的最优前导状态
- N = np.shape(self.V)[0]
- self.BP = np.full(shape=(N,), fill_value=0.0)
- t_range = -1 * np.array(sorted(-1*np.arange(N)))
- for t in t_range:
- if N-1 == t:#得到最优状态
- self.BP[t] = np.argmax(self.D[-1])
- else: #得到最优前导状态
- self.BP[t] = self.P[t+1, int(self.BP[t+1])]
-
- #最优状态路径表现在存储的是状态的下标,我们执行存储值+1转换成示例中的状态值
- #也可以不用转换,只要你能理解,self.BP中存储的0是状态1就可以~~~~
- self.BP += 1
-
- print('最优状态路径为:', self.BP)
- return self.BP
-
- def CRF_manual():
- S = np.array([[1,1], #X1:S(Y1=1), S(Y1=2)
- [1,1], #X2:S(Y2=1), S(Y2=2)
- [1,1]]) #X3:S(Y3=1), S(Y3=1)
- SW = np.array([[1.0, 0.5], #X1:SW(Y1=1), SW(Y1=2)
- [0.8, 0.5], #X2:SW(Y2=1), SW(Y2=2)
- [0.8, 0.5]])#X3:SW(Y3=1), SW(Y3=1)
- E = np.array([[[1, 1], #Edge:Y1=1--->(Y2=1, Y2=2)
- [1, 0]], #Edge:Y1=2--->(Y2=1, Y2=2)
- [[0, 1], #Edge:Y2=1--->(Y3=1, Y3=2)
- [1, 1]]])#Edge:Y2=2--->(Y3=1, Y3=2)
- EW= np.array([[[0.6, 1], #EdgeW:Y1=1--->(Y2=1, Y2=2)
- [1, 0.0]], #EdgeW:Y1=2--->(Y2=1, Y2=2)
- [[0.0, 1], #EdgeW:Y2=1--->(Y3=1, Y3=2)
- [1, 0.2]]])#EdgeW:Y2=2--->(Y3=1, Y3=2)
-
- crf = CRF(S, SW, E, EW)
- ret = crf.Viterbi()
- print('最优状态路径为:', ret)
- return
-
- if __name__=='__main__':
- CRF_manual()