一、写在前面
本文是《统计学习方法》第10章隐马尔可夫模型的读书笔记。用一段百余行的Python代码实现了隐马模型观测序列的生成、前向算法、维特比算法。公式与代码相互对照,循序渐进。
HMM算是个特别常见的模型,在自然语言处理中有很多的应用,比如基于字符序列标注的分词和词性标注了。但我的理解仅仅停留在“前向算法”“Viterbi”等层次。这次静下心来,从头到尾将这章认真看完,与自己原有的理解做一个对照,加深理解。
二、隐马尔可夫模型基本概念
隐马尔可夫模型是关于时序的概率模型,描述由一个隐藏的马尔可夫链随机生成不可观测的状态随机序列,再由各个状态生成一个观测而产生观测随机序列的过程。隐藏的马尔可夫链随机生成的状态的序列,称为状态序列(state sequence);每个状态生成一个观测,而由此产生的观测的随机序列,称为观测序列(observation sequence)。序列的每一个位置又可以看作是一个时刻。
什么叫隐马尔科夫链呢?简单说来,就是把时间线看做一条链,每个节点只取决于前N个节点。就像你打开朋友圈,发现你可以根据你的基友最近的几条状态猜测出接下来TA狗嘴里要吐什么东西一样。
接下来引入一些符号来表述这个定义——
设Q是所有可能的状态的集合,V是所有可能的观测的集合。
其中,N是可能的状态数,M是可能的观测数。
状态q是不可见的,观测v是可见的。应用到词性标注系统,词就是v,词性就是q。
I是长度为T的状态序列,O是对应的观测序列。
这可以想象为相当于给定了一个词(O)+词性(I)的训练集,于是我们手上有了一个可以用隐马尔可夫模型解决的实际问题。
定义A为状态转移概率矩阵:
其中,
是在时刻t处于状态qi的条件下在时刻t+1转移到状态qj的概率。
这实际在表述一个一阶的HMM,所作的假设是每个状态只跟前一个状态有关。
B是观测概率矩阵:
其中,
是在时刻t处于状态qj的条件下生成观测vk的概率(也就是所谓的“发射概率”)。
这实际上在作另一个假设,观测是由当前时刻的状态决定的,跟其他因素无关,这有点像Moore自动机。
π是初始状态概率向量:
其中,
是时刻t=1处于状态qj的概率。
隐马尔可夫模型由初始状态概率向量π、状态转移概率矩阵A和观测概率矩阵B决定。π和A决定状态序列,B决定观测序列。因此,隐马尔可夫模型屏幕快照 2016-06-28 下午9.59.49.png可以用三元符号表示,即:
A , B , π A,B,\pi A,B,π称为隐马尔可夫模型的三要素。如果加上一个具体的状态集合Q和观测序列V,则构成了HMM的五元组,又是一个很有中国特色的名字吧。
状态转移概率矩阵A与初始状态概率向量π确定了隐藏的马尔可夫链,生成不可观测的状态序列。观测概率矩阵B确定了如何从状态生成观测,与状态序列综合确定了如何产生观测序列。
从定义可知,隐马尔可夫模型作了两个基本假设:
(1)齐次马尔可夫性假设,即假设隐藏的马尔可夫链在任意时刻t的状态只依赖于其前一时刻的状态,与其他时刻的状态及观测无关。
从上式左右两边的复杂程度来看,齐次马尔可夫性假设简化了许多计算。
(2)观测独立性假设,即假设任意时刻的观测只依赖于该时刻的马尔可夫链的状态,与其他观测及状态无关。
三、隐马尔可夫python实例
让我们抛开教材上拗口的红球白球与盒子模型吧,来看看这样一个来自wiki的经典的HMM例子:
你本是一个城乡结合部修电脑做网站的小码农,突然春风吹来全民创业。于是跟村头诊所的老王响应总理号召合伙创业去了,有什么好的创业点子呢?对了,现在不是很流行什么“大数据”么,那就搞个“医疗大数据”吧,虽然只是个乡镇诊所……但管它呢,投资人就好这口。
数据从哪儿来呢?你把老王,哦不,是王老板的出诊记录都翻了一遍,发现从这些记录来看,村子里的人只有两种病情:要么健康,要么发烧。但村民不确定自己到底是哪种状态,只能回答你感觉正常、头晕或冷。有位村民是诊所的常客,他的病历卡上完整地记录了这三天的身体特征(正常、头晕或冷),他想利用王老板的“医疗大数据”得出这三天的诊断结果(健康或发烧)。
这时候王老板掐指一算,说其实感冒这种病,只跟病人前一天的病情有关,并且当天的病情决定当天的身体感觉。
于是你一拍大腿,天助我也,隐马尔可夫模型的两个基本假设都满足了,于是统计了一下病历卡上的数据,撸了这么一串Python代码:
states = ('Healthy', 'Fever')
observations = ('normal', 'cold', 'dizzy')
start_probability = {
'Healthy': 0.6, 'Fever': 0.4}
transition_probability = {
'Healthy': {
'Healthy': 0.7, 'Fever': 0.3},
'Fever': {
'Healthy': 0.4, 'Fever': 0.6},
}
emission_probability = {
'Healthy': {
'normal': 0.5, 'cold': 0.4, 'dizzy': 0.1},
'Fever': {
'normal': 0.1, 'cold': 0.3, 'dizzy': 0.6},
}
states代表病情,observations表示最近三天观察到的身体感受,start_probability代表病情的分布,transition_probability是病情到病情的转移概率,emission_probability则是病情表现出身体状况的发射概率。隐马的五元组都齐了,就差哪位老总投个几百万了。
有了前面的知识,我们就可以动手写一个一阶HMM模型的定义了:
class HMM:
"""
Order 1 Hidden Markov Model
Attributes
----------
A : numpy.ndarray
State transition probability matrix
B: numpy.ndarray
Output emission probability matrix with shape(N, number of output types)
pi: numpy.ndarray
Initial state probablity vector
"""
def __init__(self,A,B,pi):
self.A=A
self.B=B
self.pi=pi
此外,我们还需要一些utility来辅助我们实现该马尔可夫模型:
def generate_index_map(lables):
index_label = {
}
label_index = {
}
i = 0
for l in lables:
index_label[i] = l
label_index[l] = i
i += 1
return label_index, index_label
states_label_index, states_index_label = generate_index_map(states)
observations_label_index, observations_index_label = generate_index_map(observations)
def convert_observations_to_index(observations, label_index):
list = []
for o in observations:
list.append(label_index[o])
return list
def convert_map_to_vector(map, label_index):
v = np.empty(len(map), dtype=float)
for e in map:
v[label_index[e]] = map[e]
return v
def convert_map_to_matrix(map, label_index1, label_index2):
m = np.