用Python从0开始实现一个中文拼音输入法的思路详解


Posted in Python onJuly 20, 2019

众所周知,中文输入法是一个历史悠久的问题,但也实在是个繁琐的活,不知道这是不是网上很少有人分享中文拼音输入法的原因,接着这次NLP Project的机会,我觉得实现一发中文拼音输入法,看看水有多深,结果发现还挺深的,但是基本效果还是能出来的,而且看别的组都做得挺好的,这次就分 享一下我们做的结果吧。 (注:此文假设读者已经具备一些隐马尔可夫模型的知识)

任务描述

实现一个中文拼音输入法。

经过分析,分为以下几个模块来对中文拼音输入法进行实现:

  • 核心功能包括拼音切分(SplitPinyin.py)
  • HMM模型训练(TrainMatrix.py)
  • Trie树构建与搜索接口实现(PinyinTrie.py)
  • 维特比算法实现以及提供给UI的服务接口(GodTian_Pinyin.py)
  • 最后的UI实现(gui.py)

技术路线

在中文拼音输入法中,我们需要完成拼音序列到汉字序列的转换,比如输入“nihao”,输入法会给出我们想输入的字“你好”,到这里我们就可以问出几个问题:

  • **如何切分拼音? **
  • 如: 用户输入”xiana”, 输入法应该判断用户想输入”xian a”(闲啊) 还是”xia na”(夏娜) 还是”xi an a”(西安啊)?
  • 如何实时给用户以反馈?
  • 对于切分好的拼音,怎样找出用户最想输入的一串中文显示给用户?
  • 用户输入的拼音是错的的情况下,如何容忍这种错误?该如何显示?

也许我们还能问出更多的问题,中文拼音输入法就是这样,总有可以继续抠下去的细节。

那么我们如何解决上面的问题?我们的方案如下:

如何切分拼音?

这 里我们暂时采用最长匹配的方式,也就是说,如果用户输入的首个串是拼音或者是某个合法拼音的前缀,那么我们会继续向后发现,等待用户输入,直到用户输完后 发现这个字符(假设是第n个)与原来n-1个不是合法的拼音也不是合法的拼音的前缀,那么此时将前面n-1串切分成拼音,这就完成了一个拼音的发现,比如 说输入”xiant”(想输xiantian),则我们会扫描这个串,一直到”xian”,到”xiant”的时候发现既不是合法拼音的前缀也不是合法拼 音,那么从t前面划分开,得到”xian't”,同样的道理发现后续的拼音。

在实时任务中,用户即使没有输完我们仍应该显示东西,那么我们先切分 拼音,最多只会有最后一个是不完整的拼音前缀,那么我们将完整的和不完整的分开处理。假设是”xian't”的情况,我们将”xian”放入 viterbi算法中,通过HMM得出概率最大的一个输出串,然后将最后的”t”在训练过的Trie树中搜索出所有以”t”为前缀的字,以及他们出现的频 率,取频率最高的若干个,作为viterbi算法的下一个状态的可能集合,然后得到他们的拼音,与前面n-1个拼音组合起来跑Viterbi算法,得到最 可能的一个中文串,由于这些频率最高的字的拼音(即我们可能的观测值)可能不相同,我们只能将相同音的字作为一次viterbi算法运行的下一状态,这样 viterbi跑的次数就是这些字里面不同音的个数,但是由于总数固定,异音越多,每个音对应的越少,所以总时间是没有差别的。

具体Trie树会在后面讲解。学习过程中有不懂的可以加入我们的学习交流秋秋圈784中间758后面214,与你分享Python企业当下人才需求及怎么从零基础学习Python,和学习什么内容。相关学习视频资料、开发工具都有分享

 用Python从0开始实现一个中文拼音输入法的思路详解

如何实时给用户以反馈?

上 面其实已经初步解释了如何实时反馈,实时反馈我们要做的就是用户每输一个字母,我们就能够显示出用户可能想要打的字,那么,以一个字母开头的拼音有很多, 每个拼音对应的字也可能有很多,也即结果有很多,但是我们又不能漏掉,所以只能考虑所有的字,比较选出概率最大的若干个字,这时候我们可以采用Trie树 来解决。Trie树就是前缀树,说白了就是将拼音的字母按顺序顺着根插入到树中,每个叶子节点就是一个拼音,这个拼音就是顺着根一路走下来取的字母的顺序 组合,这样我们就可以找出以任意字符串为前缀的所有拼音,方法就是dfs遍历每一个以其为前缀的子树的叶子节点,这时候我们叶子节点存的其实是一个字 典,key为这个拼音对应的可能的字,value为这个字出现的频率,以作为比较。

对于切分好的拼音,怎样找出用户最想输入的一串中文显示给用户?

这里我们使用隐马尔可夫模型,将用户想输入的中文字作为隐状态,用户输入的拼音为显状态,通过最大似然估计即频率估计出HMM的三个矩阵的值,最后通过viterbi算法找出概率最大的若干个中文字串显示出来。

用户输入的拼音是错的的情况下,如何容忍这种错误?该如何显示?

由于考虑到实现高度容错的复杂性,我们假设用户会输入正确的拼音,在想分割的时候会自行添加分隔符”‘“,由于大部分输入法用户绝大部分时间都会输入正确的拼音,所以,这样一个假设既简化了实现的过程,又没有损失太大的用户体验。

用到的数据

由于训练HMM模型的需要,我们从搜狗实验室找到了SogouQ用户查询数据集,预处理成合法的句子之后大约有360M,且为了避免查询句太短,我们也增加了将近30M的搜狐新闻数据作为训练语料,这里面包含了很多的长句子。

通过这两个语料的训练,我们得到了长句和短句皆可表现较好效果的HMM模型。并且我们还可以继续拓展语料,以增加我们HMM模型的准确性,这是后话,不提。

遇到的问题及解决方案,

UI界面的问题,由于UI设计的复杂性与不同系统的考虑,出现了许多莫名其妙的BUG,这使得我们花了许多时间。

viterbi算法的效率问题,由于以某个字母开头的拼音对应的字有很多个,假设我们取最优的K个,我们需要将这K个与前面已有的拼音组 合,然后跑一遍Viterbi算法,由于Viterbi算法从一个状态转移到另一个状态的计算量很大,我们使用了记忆(cache)的方法来加速,具体方 法就是记录下某一个完整拼音串所对应的viterbi算法的最后一个状态的相关情况,这样如果我们再次遇到这个拼音串(A) 加上另一个拼音(B)跑viterbi的情况,我们就不需要从这个组合串的开头开始跑viterbi算法了,而是直接从A 串跑完viterbi的最后一个状态(从记忆单元读取)开始,向B进行转移。

这个记忆单元会随着程序而一直存在,并且我们对这个对象做了持久化, 在输入法启动时我们会读取这个文件(记忆单元),这也就意味着,如果我们曾经输入过某个拼音串,那么我们以后再输入同样的拼音串的时候,不再需要跑核心算 法,而是直接显示结果,这样在速度上就取得了显著的提高,就会出现,输入法越用越好用,越用越快的好处,当然这牺牲了一些存储空间,但是如今我们都不缺存 储空间。

重复计算的问题,比如在用户觉得打错了的时候,往后退格,这时就会退到某一个前缀,但

是其实这个前缀我们是算过了的,也显示过了的,就是说 我们退回到我们以前显示过的内容的时候,如果不加优化,那么又会重新跑一遍核心的viterbi算法,这样就会很慢,那么我们还是利用cache思想,将 输入的拼音串以及对应的显示结果相对应并且存起来,这样我们就做到了飞速的退格操作。

Python语言固有的性能问题,解决这个问题只有更换语言,事实上用C++语言实现的话我相信会快很多,这在后面可以考虑用C++实现,这也是完全可行的。

性能评价

输入比较迅速,绝大多数输入能在1秒以内显示。输入过的句子再输入和退格操作都是毫秒级别的。

给出程序的运行环境

1.Python 2.7

2.需要安装的Python包: Tkinter, cPickle, pypinyin等模块 执行方法及参数

在项目Project目录下,运行

$ python gui.py

即可。

Future Works

由上面我们可以看到其实可以做的工作还很多,比如

  • 改换编译型语言,如C++,大幅减小计算开销
  • 不断随着用户的输入更新HMM模型
  • 将软件嵌入系统中
  • 我们观察到,长句输入很少有多个是想打的,不想短句可能想打的情况很多,所以很多与输入拼音串长度相同的句子我们可以换成短句

总结

以上所述是小编给大家介绍的用Python从0开始实现一个中文拼音输入法的思路详解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Python 相关文章推荐
Python脚本实现代码行数统计代码分享
Mar 10 Python
Python使用BeautifulSoup库解析HTML基本使用教程
Mar 31 Python
Windows中安装使用Virtualenv来创建独立Python环境
May 31 Python
Python 递归函数详解及实例
Dec 27 Python
Python 自动刷博客浏览量实例代码
Jun 14 Python
windows下python和pip安装教程
May 25 Python
Python创建一个空的dataframe,并循环赋值的方法
Nov 08 Python
Python中捕获键盘的方式详解
Mar 28 Python
Python语言进阶知识点总结
May 28 Python
Python开发之pip安装及使用方法详解
Feb 21 Python
anaconda3安装及jupyter环境配置全教程
Aug 24 Python
python文件目录操作之os模块
May 08 Python
python搜索包的路径的实现方法
Jul 19 #Python
Pycharm 文件更改目录后,执行路径未更新的解决方法
Jul 19 #Python
在自动化中用python实现键盘操作的方法详解
Jul 19 #Python
Python的Tkinter点击按钮触发事件的例子
Jul 19 #Python
python实现图片九宫格分割
Mar 07 #Python
django最快程序开发流程详解
Jul 19 #Python
python打印9宫格、25宫格等奇数格 满足横竖斜相加和相等
Jul 19 #Python
You might like
PHP与SQL注入攻击[三]
2007/04/17 PHP
php 301转向实现代码
2008/09/18 PHP
PHP 工厂模式使用方法
2010/05/18 PHP
php的ajax框架xajax入门与试用介绍
2010/12/19 PHP
谈谈从phpinfo中能获取哪些值得注意的信息
2017/03/28 PHP
php封装单文件上传到数据库(路径)
2017/10/15 PHP
关于恒等于(===)和非恒等于(!==)
2007/08/20 Javascript
JavaScript 设计模式 安全沙箱模式
2010/09/24 Javascript
html dom节点操作(获取/修改/添加或删除)
2014/01/23 Javascript
JS判断文本框内容改变事件的简单实例
2014/03/07 Javascript
jQuery响应enter键的实现思路
2014/04/18 Javascript
js实现新年倒计时效果
2015/12/10 Javascript
使用jQuery判断Div是否在可视区域的方法 判断div是否可见
2016/02/17 Javascript
jquery实现ajax加载超时提示的方法
2016/07/23 Javascript
AngularJs验证重复密码的方法(两种)
2016/11/25 Javascript
JavaScript运动框架 链式运动到完美运动(五)
2017/05/18 Javascript
基于代数方程库Algebra.js解二元一次方程功能示例
2017/06/09 Javascript
AngularJS中的路由使用及实现代码
2017/10/09 Javascript
4个顶级开源JavaScript图表库
2018/09/29 Javascript
工作中常用js功能汇总
2020/11/07 Javascript
详解python里使用正则表达式的分组命名方式
2017/10/24 Python
Python数据结构与算法之图的基本实现及迭代器实例详解
2017/12/12 Python
python之super的使用小结
2018/08/13 Python
Python 获取项目根路径的代码
2019/09/27 Python
Python基于locals返回作用域字典
2020/10/17 Python
python3.7中安装paddleocr及paddlepaddle包的多种方法
2020/11/27 Python
python asyncio 协程库的使用
2021/01/21 Python
css3.0 图形构成实例练习一
2013/03/19 HTML / CSS
通息工程毕业生自荐信
2013/10/16 职场文书
工程业务员工作职责
2013/12/07 职场文书
大一新生学期自我评价
2014/04/09 职场文书
当幸福来敲门观后感
2015/06/01 职场文书
学校食堂管理制度
2015/08/04 职场文书
2016年春季趣味运动会开幕词
2016/03/04 职场文书
SpringBoot集成Druid连接池连接MySQL8.0.11
2021/07/02 Java/Android
SQL Server内存机制浅探
2022/04/06 SQL Server