1. 掌握HMM的必要性
有关于隐马尔可夫模型,我去年整理过一次,最后应该是直接把PPT截图到CSDN里了,似乎那篇文章也没有什么人看,或许是因为做成PPT索引不到,或许是大家对HMM没啥兴趣。但其实隐马的优势有点类似于朴素贝叶斯,我们比较好做人工干预。比如在HanLP的人名识别中就是利用的隐马模型,如果你对隐马基本原理没有一个清晰认识,那么直接上隐马的人名识别时,会有很多并非人名的词语被识别出来,这时候,就要利用HMM的3个概率表,即发射概率表,状态转移概率表,初始状态表,通过修改调整概率表,来提高系统识别准确率。
2. HMM的缺陷
按照何晗的《自然语言处理入门》第4章所述,HMM应用于分词的效果并不是特别理想,特别是很多在词典中的词没有被正确识别,也就是说HMM对于分词消岐并不理想,作者的结论是HMM的分词效果甚至不如词典分词。作者同时也利用HanLP做了二阶HMM的分词,作者的结论是增加HMM的阶数并不能提高分词器的准确率,单纯靠提高转移概率矩阵的复杂度并不能提高模型的拟合能力。
3. HMM解决序列标注问题的过程
训练过程:
(1)统计状态的概率分布,也就是初始概率矩阵pi
以分词为例,就是统计状态B、M、E、S的出现频率。其中B表示分词后词语的首字,M表示分词后词语的非首非尾自(如果词语字数>=3),E表示词语的尾字,S表示单字成词。比如“北京/ 信息/ 科技/ 大学”,对应的状态序列为“BEBEBEBE”。我想统计符号B、M、E、S在训练语料中的出现频率这件事的公式我就不用再在这里写出了吧?
(2)统计状态转移概率矩阵A
还以分词为例,我们应该用训练语料统计出下边的转移概率矩阵
转移概率矩阵顾名思义,就是说从一种转态到另一种状态的概率。比如训练语料中的一个词语“大学”,那么该词语对应的状态序列为“BE”,此时由B到E的转移次数就应该+1,按照这样的统计方法,就可以统计出状态转移概率矩阵A。(当然注意概率指的是出现次数除以总次数,这里认为频率就等于概率)
(3)统计发射概率矩阵B
依然以分词为例,发射概率就是一个状态可能对应的汉字的概率分布。就是下边的概率矩阵。
还是假设训练语料中出现了“大学”“BE”的对应关系,那么由“B”状态发射出“大”这个字的次数应该+1,由“E”状态发射出“学”这个字的次数应该+1,按照这样的统计方法,就可以统计出发射概率矩阵B。
至此,我们已经利用训练语料统计出了隐马模型的所有参数,也就是通常所说的三要素,接下来我们介绍预测。
预测过程:
(4)维特比算法
预测过程比较简单,就是利用维特比算法。我们先以分词为例说明维特比要做的事情。给定一个观测的序列如“北京信息科技大学”,利用维特比算法,系统可以求出一个最佳的状态序列比如是“BEBEBEBE”。好,就到这里,你应该已经知道维特比要做的事情了。那么,维特比是如何做到的呢,下边我们贴出李航《统计学习方法》中的计算过程。
首先给出两个定义:
接下来给出维特比算法
好了,读到这里你可能不想看了,没有关系,我们在下一小结求解李航《统计学习方法》书中第10章的10.3题,通过求解你就明白维特比的计算过程了。如果求解了这道习题,还是不明白,没有关系,文章最后一节给出一个Python实现的简单分词器,看了这段代码我想你应该了解了HMM分词的训练和预测过程了。
(5)根据状态序列执行分词
有了状态序列,就可以根据状态值进行分词了。比如“北京信息科技大学”HMM模型预测出的状态序列为“BEBEBEBE”,显然分词结果就是“北京/信息/科技/大学”。当然,我们这个例子刚好HMM预测正确了,实践过程中HMM很有可能会预测错误,比如预测出“SE”这样的序列,显然这两个状态不可能连续出现,因此,我们需要设计一定的规则将HMM输出的状态序列转换为合理的分词结果。HanLP的规则是只要遇到B或S这两个状态就切出1个词。
4. 一道例题理解HMM的训练和预测过程
(1)HMM的两个基本假设
前边我们都是以分词这个任务来叙述HMM的,这里,我们再回归到HMM的一些数学原理。
假设1:隐马的任一时刻状态只依赖于前一状态,而与其他时刻状态无关。
假设2:隐马的任意时刻观测值只依赖于对应该时刻的状态,而与其他时刻状态无关。
个人认为假设1在分词场景还是可以接受的,假设2忽略了两个以上汉字成词的概率这个特征。
(2)HMM中“隐”指什么?
实际上,前边我们都是说的状态,实际上对应于观测序列,可以将状态序列称为隐含状态,因为我们看到的只是汉字序列而没有看到对应于词的状态序列。
(3)例题:HMM的训练过程
这道例题是李航《统计学习方法》例10.1,通过这个例子,再次熟悉一下HMM的术语。
你能根据题目的3个叙述,写出3个概率矩阵和右侧的图吗?如果可以写出,那么你也就明白了HMM的训练过程。
(4)例题:HMM的预测过程
显然,最优状态序列为3 3 3,也就是3号盒子 3号盒子 3号盒子。
5. 基于HMM分词的Python示例
下边的代码是我用Python写的一个示例代码,代码长度在200行左右,希望通过该代码小伙伴们能彻底明白HMM的训练和预测过程。
#coding:utf-8
"""
基于隐马尔可夫模型的分词
"""
import codecs
import pickle
import numpy as npidx_to_state = ['b', 'm', 'e', 's']
state_to_idx = {'b':0, 'm':1, 'e':2, 's':3}
# 极大似然估计法训练模型参数
def train():# 构建字表character_set = set()with codecs.open('RenMinData.txt_utf8', 'rb', 'utf-8', 'ignore') as infile:for line in infile:line = line.strip()if line:word_li = line.split()if word_li:for word in word_li:if word:character_set.update(word)# 输出字表print('输出字表...')with open('character_list.txt', 'wb') as outfile:idx = 0for character in character_set:out_str = u'%dt%sn' % (idx, character)outfile.write(out_str.encode('utf-8', 'ignore'))idx += 1# 加载字索引以及索引字character_to_idx = dict()idx_to_character = []with codecs.open('character_list.txt', 'rb', 'utf-8', 'ignore') as infile:for line in infile:line = line.strip()if line:idx, character = line.split(u't')character_to_idx[character] = int(idx)idx_to_character.append(character)print('加载字索引表 长度=%d' % len(character_to_idx))print('加载索引字表 长度=%d' % len(idx_to_character))global state_li# 矩阵初始化print('初始化模型矩阵')A = np.zeros((len(idx_to_state), len(idx_to_state)))print('A=n', A)B = np.zeros((len(idx_to_state), len(idx_to_character)))print('B.shape', B.shape)PI = np.zeros(len(idx_to_state))print('PI=', PI)# 训练with codecs.open('RenMinData.txt_utf8', 'rb', 'utf-8', 'ignore') as infile:for line_ser, line in enumerate(infile):line = line.strip()if line:# 对句子中的每个字打状态标记word_li = line.split()character_li = []if word_li:for word in word_li:if len(word) == 0:continueelif len(word) == 1:character_li.append((word[0],'s'))elif len(word) == 2:character_li.append((word[0],'b'))character_li.append((word[1], 'e'))else:character_li.append((word[0], 'b'))character_li.extend([(w, 'm') for w in word[1:-1]])character_li.append((word[-1], 'e'))# 统计相关次数# 更新PI列表PI[state_to_idx[character_li[0][1]]] += 1# 更新B字典for character, state in character_li:B[state_to_idx[state], character_to_idx[character]] += 1# 更新A字典if len(character_li) >= 2:for ser_num, cur_state in enumerate([w[1] for w in character_li[:-1]]):next_state = character_li[ser_num+1][1]cur_state_idx = state_to_idx[cur_state]next_state_idx = state_to_idx[next_state]A[cur_state_idx][next_state_idx] += 1# 计算PIPI = PI/sum(PI)# 计算Afor row_ser in range(A.shape[0]):A[row_ser,::] /= sum(A[row_ser,::])# 计算Bfor row_ser in range(B.shape[0]):B[row_ser,::] /= sum(B[row_ser, ::])# 输出PIprint("输出PI矩阵...")with open('model_PI', 'wb') as outfile:pickle.dump(PI, outfile)# 输出Aprint("输出A矩阵...")with open('model_A', 'wb') as outfile:pickle.dump(A, outfile)# 输出Bprint("输出B矩阵...")with open('model_B', 'wb') as outfile:pickle.dump(B, outfile)def viterbe(O, PI, A, B, str=u''):"""viterbi算法计算HMM问题2:param O: 观测序列:param PI: 初始状态分布:param A: 状态转移矩阵:param B: 发射矩阵:return:"""# viterbi算法中初始化delta_1delta_1 = PI * B[:, 0]# viterbi算法中初始化kesi_1kesi_1 = np.zeros(PI.size, dtype=np.int)# 最优路径记录初始化kesi = np.array(kesi_1.T)# 递推delta_tplusone = delta_1.copy()for t in range(1, len(O)):max_delta_tminus_aji = np.tile(delta_tplusone, PI.size).reshape(A.shape).T * Adelta_t = np.max(max_delta_tminus_aji, 0) * B[:, O[t]]kesi_t = np.argmax(max_delta_tminus_aji, 0)kesi = np.column_stack((kesi, kesi_t.T))delta_tplusone = delta_t.copy()# 终止P_star = np.max(delta_t)i_T_star = np.argmax(kesi_t)# 最优路径回溯I_star = [i_T_star]i_tplus_star = i_T_starfor t in range(kesi.shape[1] - 1, 0, -1):i_t_star = kesi[:, t][i_tplus_star]I_star.append(i_t_star)i_tplus_star = i_t_starI_star = I_star[::-1]# 输出分词结果if str:out_str = u""state_li = [idx_to_state[w] for w in I_star]i = 0while i
这里我们给出训练语料的格式,每行1个句子,词语之间以空格分隔即可。
这 是 我国 目前 最 大 的 朝鲜 民族 文化 设施 。
这 座 文化宫 建筑 面积 为 二千七百 平方米 ,
内部 设有 剧场 、 排 练 厅 、 舞 厅 ,
可 同时 容 纳 一千 多 人 参加 活动 。
( 新华社 电 ) 文化 天地 空军 某地 空 导弹 旅 歌曲 创作 活跃 新华社 05 旅 有 旅 歌 、
营 有 营 歌 、 连 有 连 歌 。
空军 某地 空 导弹 旅 开展 谱写 军 旅 歌曲 活动 ,
目前 所有 连 以上 单位 都 有 了 反映 自己 工作 特色 的 歌曲 。
这个 旅 是 一 支 屡 建 战功 的 部队 。
全 旅 从 1986年 5月 起 开展 了 群众性 的 谱写 旅 、 营 、 连 歌 活动 。
下边给出实现代码的github链接,如有问题请大家留言。
panda2019-ai/natural_language_processinggithub.com
至此,专栏中有关自然语言处理基本机器学习算法就全部介绍完毕了。我们介绍了HMM,baiziyu:最大熵模型 ,baiziyu:条件随机场 。