在网上看到一篇关于隐马尔科夫模型的介绍,以为简直不能再神奇,又在网上找到大神的一篇关于如何用隐马尔可夫模型实现中文拼音输入的博客,无奈大神没给能够运行的代码,只能纯手动网上找到告终巴分词的词库,根据此训练得出隐马尔科夫模型,用维特比算法实现了一个简单的拼音输入法。githuh地址:https://github.com/LiuRoy/Pinyin_Demohtml
抄一段网上的定义:git
隐马尔可夫模型 (Hidden Markov Model) 是一种统计模型,用来描述一个含有隐含未知参数的马尔可夫过程。其难点是从可观察的参数中肯定该过程的隐含参数,而后利用这些参数来做进一步的分析。github
拼音输入法中可观察的参数就是拼音,隐含的参数就是对应的汉字。算法
参考https://zh.wikipedia.org/wiki/维特比算法,思想是动态规划,代码比较简单就不赘述。sql
代码见model/table.py文件,针对隐马尔科夫的三个几率矩阵,分别设计了三个数据表存储。这样的好处很明显,汉字的转移几率矩阵是一个很是大的稀疏矩阵,直接文件存储占用空间很大,而且加载的时候也只能一次性读入内存,不只内存占用高并且加载速度慢。此外数据库的join操做很是方便viterbi算法中的几率计算。数据库
数据表定义以下:优化
class Transition(BaseModel): __tablename__ = 'transition' id = Column(Integer, primary_key=True) previous = Column(String(1), nullable=False) behind = Column(String(1), nullable=False) probability = Column(Float, nullable=False) class Emission(BaseModel): __tablename__ = 'emission' id = Column(Integer, primary_key=True) character = Column(String(1), nullable=False) pinyin = Column(String(7), nullable=False) probability = Column(Float, nullable=False) class Starting(BaseModel): __tablename__ = 'starting' id = Column(Integer, primary_key=True) character = Column(String(1), nullable=False) probability = Column(Float, nullable=False)
代码见train/main.py文件,里面的initstarting,initemission,init_transition分别对应于生成隐马尔科夫模型中的初始几率矩阵,发射几率矩阵,转移几率矩阵,并把生成的结果写入sqlite文件中。训练用到的数据集是结巴分词里的词库,由于没有训练长句子,最后运行的结果也证实只能适用于短句输入。spa
统计初始化几率矩阵,就是找出全部出如今词首的汉字,并统计它们出如今词首的次数,最后根据上述数据算出这些汉字出如今词首的几率,没统计的汉字就认为出如今词首的几率是0,不写入数据库。有一点注意的是为了防止几率计算的时候由于越算越小致使计算机没法比较,全部的几率都进行了天然对数运算。统计的结果以下:设计
此处用到的是最简单的一阶隐马尔科夫模型,即认为在一个句子里,每一个汉字的出现只和它前面的的一个汉字有关,虽然简单粗暴,但已经能够知足大部分状况。统计的过程就是找出字典中每一个汉字后面出现的汉字集合,并统计几率。由于这个几率矩阵很是的大,逐条数据写入数据库过慢,后续能够优化为批量写入,提升训练效率。结果以下:3d
上图展现的一后面出现几率最高的十个字,也挺符合平常习惯。
通俗点就是统计每一个汉字对应的拼音以及在平常状况下的使用几率,已暴举例,它有两个读音:bao和pu,难点就是找bao和pu出现的几率。此处统计用到了pypinyin模块,把字典中的短语转换为拼音后进行几率统计,可是某些地方读音也不彻底正确,最后运行的输入法会出现和拼音不匹配的结果。统计结果以下:
代码建input_method/viterbi.py文件,此处会找到最多十个局部最优解,注意是十个局部最优解而不是十个全局最优解,可是这十个解中最优的那个是全局最优解,代码以下:
def viterbi(pinyin_list): """ viterbi算法实现输入法 Aargs: pinyin_list (list): 拼音列表 """ start_char = Emission.join_starting(pinyin_list[0]) V = {char: prob for char, prob in start_char} for i in range(1, len(pinyin_list)): pinyin = pinyin_list[i] prob_map = {} for phrase, prob in V.iteritems(): character = phrase[-1] result = Transition.join_emission(pinyin, character) if not result: continue state, new_prob = result prob_map[phrase + state] = new_prob + prob if prob_map: V = prob_map else: return V return V
运行input_method/viterbi.py文件,简单的展现一下运行结果:
问题统计: