拼写检查 二

来源:互联网 发布:营销软件uurjz 编辑:程序博客网 时间:2024/05/22 02:12

摘录: 拼写检查的功能是单一的,分为两块: 

A: 前缀匹配
B: 拼写纠错

前缀匹配,前缀匹配是依据字典来对输入的字符串进行补全匹配的过程,所做的最核心的事情是对字典进行查找之后进行补全,其间不涉及对输入字符串进行修改。所以对于前缀匹配功能模块,所依据的最核心的点在于,如果从字典中查找相应的字词或者字符串的过程。其中所涉及到的数据结构,针对数据结构所使用到的算法。因为并没有将输入字符串进行修改,所以相对比较简单。

拼写纠错:这一功能模块所涉及的比较重要,其中所涉及到对输入进行纠正和补全。流程为,先走字词补全步骤,补全处理之后,对输入进行分词检查的行为,对输入进行分词处理,将输入分为一个一个的分词,以分词为单位做处理。经过分词之后,原本由人输入的字符串,已经被分割成为比较原始的语义,之后,对已经分割出的分词,将其转换成拼音,在这一步中,对拼音进行模糊纠错处理吗,这一步用到了bk树进行模糊纠错。之后,针对经过纠错后的拼音,对其转换为概率最高的汉字,这个过程,用到了HMM隐马尔尔科夫概率模型,一个拼音,针对了好几组的汉字,HMM所要做到,便是选出这几组中,概率最高的汉字。
经过了这步处理之后,基本已经完成了对汉字的转换。

在拼写检查的处理中,在进行转换之前,会预先做一些检车处理,如果检测到在字典中包含相关的字词或语句,那么就直接由字典中匹配出之后进行返回,输出的是字典中存在的内容,如果这一步做不到的话,再进行进行纠错处理。

重要函数:

OSpeller::spell   执行流程入口

OSpeller::correct  拼写修正

OSpeller::match 前缀匹配

输入类型:
A.  纯数字, 股票代码, 基金代码
B.  纯字母, 汉语拼音, 英文,拼音缩写,技术指标(MACD, KDJ, RSI, BOLL, W&R....)
C.  纯汉字, 汉语问句, 中文技术指标(主力), 参考十大经典问句系列
D. 数字和英文的混合
E. 数字和汉字的混合
F. 中英文字符混合, 汉字拼音的混合


处理方法: 
结合输入例子,将每一步的转换进行日志打印输出, 在日志种记录输入的转换过程。
在每一步进行转换的时候,记录堆栈中的调用层次结构,梳理出代码的路程图。

流程:
1. 从输入流中,解读出URL中包含的信息,  http://192.168.201.240:8080/search?tid=spell&q=maemv&dto=stock:1&svcSpellCheck=OSpell_dev 
例如, tid=spell, q=maemv, dto=stock:1 ,这些信息
以及 spellType   0:拼写修正, 1:前缀匹配

OSpeller.cc:93    StrUtil::tokenSplit , 该函数, 将输入的字符串,根据类型不同,存储在vector中, 汉字是一类,标点符号是一类, 英文字符是一类
例如输入 maemv,:awk, 那么

context.word_vec   该vector内,每个向量,存的是已经分类的输入, 将输入按照类型不同,存入vector中
context.word_vec[0] = maemv  
context.word_vec[1] = ,;   
context.word_vec[2] = awk
标点符号是人类进行语义分割的惯常做法,此处对输入的语义做了初步的分割


context.token_vec 之中,是将每个字符,存入vector中, 
context.token_vec[0] = m
context.token_vec[1] = a
context.token_vec[3] = e
...
以此类推

完整的输入字符,赋值给 context.query_for_correct  
context.query_for_correct = " maemv,:awk"
原始的输入字符,赋值给context.original_query


至此,将输入字符转入真正的处理流程之中, context类中的数据,将参与随后的字符串转换过程

流程2 ,OSpeller::correct
当url中包含  tid=spell  时, 将会进行拼写修正处理, 在做完流程1后,将进入 OSpeller::correct执行拼写修正。


 完整的函数原型为
bool OSpeller::correct(const string& sentence, vector<DictTypeOrder>& vec_dict_typeOrder, size_t max_count, FPMessage& out)
const string&                        sentence   输入的有待被修正的字符
vector<DictTypeOrder>&      vec_dict_typeOrder  将要使用的字典文件, 由url中的 dto  属性指定, 该传入变量是个vector,每个字典的名字是一个向量
 size_t                                  max_count  最大匹配的个数, 由url中mac指定, 默认为10

在该函数的处理中,使用到了LTP节点的服务, LTP节点是作为分词存在,例如输入的连续的一段字符,根据语义,分割成不同的字段
如 中国人民大会堂, 将被分割成  中国 人民  大会堂, 一段很长的输入字符,经过LTP分词分割之后, 将被分割成最小的语义组合, 分词过后,每个分词都可以认为的最小的语义单元,在随后的处理中,是以经过分词处理后的最小的词组为单元进行的。


会经过的处理函数有

predefinded_lookup , 如果输入的是数字,则对其进行字典匹配,按照url中指定的字典,如果能在字典中匹配到所输入的数字,则返回正确,随后直接返回

fundext_lookup, 从 fundext 字典中, 对输入进行字典匹配, 匹配到的话,返回正确,随后直接返回

queryCheck, 如果所输入的,是纯标点符号,直接返回, 如果输入的,在字典中存在,(所有字典都进行检索), 则返回正确,随后直接返回。
注意,在

以上三个函数,所做的事情,都是在字典中查找,看所输入的字符是否是字典中已经存在的,如果是,则直接返回,不进行输入纠正转换,以下函数,将执行转换操作

sentence_tuning
ltp_tuning
pinyin_tuning
bk_tuning
prefix_tuning
slm_tuning_score

如果在url中没有指定要使用的字典文件,那么传入的程序中的,将是由前端来指定,现在根据程序推测,应该是所有的字典。
不明确指定字典, 那么将启用全部9个字典。有程序调试得出,即使在不明确指定的情况下,所有的字典组合依然是从前端传入的流中读取,该默认组合依然是前端指定。
如果不指定tid=spell, 程序的默认处理措施,是使用拼写纠正。
如果不指定最大的匹配出的个数,那么默认的个数,是10个。


随后将进入 sentence_tuning, 在该函数中,将会有两个细分的处理逻辑, 
pinyin_tuning, 在这个函数中,如果输入的是汉字,则需要转换为相应的拼音
进行转换的函数,是内部调用的Dict::processCheck, 该函数,先将输入的汉字,进行一个汉字一个汉字的拆分,拆分出的单个的汉字再装入vector向量中, 之后,对于已经被分割成单个的汉字,利用拼音表 pinyin_table_ ,查找相应的拼音,并存储。
这样,在函数  Dict::convert_for_query 中,完成了将输入的汉字转换为拼音的过程。
将汉字转换成拼音的过程,可以理解为,先拆分出单个的汉字,然后利用拼音表,将单独的汉字,转换成对应的拼音。 这个过程,是一一对应的,输入的是什么汉字,在拼音表中就查出相应的拼音,即便输入的汉字是错别字,或者语义有问题, 也依然是按照对照表进行相应的替换。
随后在 processCheck 函数中,将进行 maxMatch ,查询是否能对拼音进行补全,找出与转化后的拼音,最为匹配的拼音,如果没有的花,则返回上一层。
随后将会再次检索原有的汉字输入是否是字典中的已经记录的, 或者输入是 <=6 个字符的, 如果符合,则原有的输入被保留,否则将会被替换为vec_results_。这步骤处理的目的,是为了确保经过以上的步骤,是否需要对原有的输入进行修正。

执行完pinyin_tuning之后,进入 bk_tuning ,该函数的作用,是针对拼音进行修正,寻找到与输入的拼音最为接近的拼音。
该函数将进入  Dict::guessFromBK ,调用 Dict::_doCorrect_bk, 在内部调用 BKTree::find  执行真正的BK树查找和替换过程。关于bk树的检索原理,参考单独的说明。
之后,进行完bk树拼音纠正之后,检查是否需要对原有的拼音进行替换,如果需要则替换原有的拼音,至此bk_tuning执行完毕。

注: 这一块需要单独的做说明,拼音的纠正现在的说明太模糊,需要结合更多例子,说明是如果对错误的拼音进行转换的。


如果在 pinyin_tuning 和 bk_tuning 的执行过程中,并没有对原有的输入进行替换,则并不认为是执行完备的。随后将会进行下列逻辑
ltp_tuning
pinyin_tuning
bk_tuning

 ltp_tuning是分词转换, LTP是单独的另外一个节点的服务, 在这步的处理流程中,是向LTP节点服务发送数据,请求LTP服务进行处理,然后获取LTP分词处理的返回结果,核心是在 ltp_->seg函数中。
随后,再次进行 pinyin_tuning , bk_tuning

注意,bt树转换,并不是只对拼音进行相近字转换,依据的,是原有的输入的汉字。



至此,从代码的跟踪上,查看相应的变量,输入并没有相应的改变。
随后将进入下列的两个流程

prefix_tuning, slm_tuning_score

 prefix_tuning,所做的是前缀匹配, 核心是函数OSpeller::prefix_match, 最底层的调用是 TrieChinese::findPrefix, 先对输入进行分割,英文一类,标点符号是一类,日文是另外一类,
前缀匹配的过程非常有意思, 使用 CNode  为节点的树形结构,从树根开始向节点寻找。

(gdb) bt
#0  Dict::prefixMatch (this=0x10ce4c0, dictType=..., prefix=..., maxCount=@0x7fca9ced00c8, result=..., setNames=...) at Dict.cc:1236
#1  0x00007fcab2c0829f in OSpeller::prefix_match_ (this=0x63bf4bb0, prefix=..., result=..., vec_dict_typeOrder=...) at OSpeller.cc:836
#2  0x00007fcab2c079a8 in OSpeller::prefix_match (this=0x63bf4bb0, prefix=..., result=..., vec_dict_typeOrder=...) at OSpeller.cc:795
#3  0x00007fcab2c096be in OSpeller::prefix_tuning (this=0x63bf4bb0, query=..., chunk=..., vec_results=..., vec_dict_typeOrder=...)
    at OSpeller.cc:925
#4  0x00007fcab2c06774 in OSpeller::correct (this=0x63bf4bb0, sentence=..., vec_dict_typeOrder=..., max_count=10, out=...) at OSpeller.cc:726
#5  0x00007fcab2c0035e in OSpeller::spell (this=0x63bf4bb0, in=..., out=...) at OSpeller.cc:118
#6  0x00007fcab2c3787e in SpellFeed::retrieveFeedResult (this=0x7fca9ced2750) at SpellFeed.cc:72
#7  0x00007fcab2c374ea in SpellFeed::process (this=0x7fca9ced2750) at SpellFeed.cc:26
#8  0x00007fcab2c384e5 in Spell_Lookup (handle=0x10beea0, partid=0, fd=12, queuetime=57696) at SpellService.cc:60
#9  0x000000000042386c in Service::executeStream (this=0x10bed30, req=0x274bc9a0, connsock=0x10c7140, pThreadData=0x0) at Service.cc:1149
#10 0x000000000041cc31 in QueueEntry::execute (this=0x63bf4fe0) at QueueEntry.cc:124
#11 0x0000000000424e7b in Service::worker (this=0x10bed30, puthread=0x0, workerID=0) at Service.cc:1554
#12 0x000000000041d6a3 in workerRun (param=0x10bed30) at Service.cc:111
#13 0x00007fcab3ec88ba in start_thread () from /lib/libpthread.so.0
#14 0x00007fcab329402d in clone () from /lib/libc.so.6
#15 0x0000000000000000 in ?? ()



slm_tuning_score 函数,是对经过 prefix_tuning , 前缀匹配出的字词,进行概率计算,如果是匹配出的,则计算出这些词所出现的概率,按照概率排序后返回。 

但是如果输入的是拼音,且该拼音经过前缀匹配后,没有响应的后缀, 那开始根据输入的拼音,进行修正。
在 slm_tuning_score 函数中, 开始进入 sentence->slmDecode , Sentence::slmDecode  , 进行根据拼音进行修正, 继续进入 HMM::decode ,该处理中,先根据拼音,将每一个拼音对应的所有汉字找出,然后计算这些汉字组合的隐马尔科夫模型。寻找到最大概率最大的组合,予以输出。


pinyin_turning 模块,执行流程为,将输入的汉字转换为拼音, 然后根据将转换后的拼音,转换为汉字,如果通过转为拼音再转为汉字的结果,此结果和原输入的汉字不一样的话,将被改变。
在这个过程中,将转换后的拼音转换为汉字的过程, 是通过查表进行 , map_dictionary_table_ , 该表记录了 拼音 -> 汉字  的映射, 将拼音转换为md5之后,就是 key, 对应的汉字,则是 value。
而这个映射表,map_dictionary_table_ , 则存储了各个字典中的映射关系, 使用这个表的话, 需要指定 dictType,即指定所使用的字典类型, 然后根据该指定的字典,找出该字典中,拼音与汉字的对应关系

这里要对字典文件做一下说明,52服务器提供的字典文件,只包含了所使用的内容,例如汉字或基金代码,或者是各种使用到的英文, 这个文件需要经过服务器的转换后,生成为服务器所使用的格式和内容,其中比较关键的一步,就是,针对给出的字典文件, 在字典文件中,生成给出的各个词组的拼音,放在给出的字词之前。服务器在加载字典的时候,会使用到里面对应的拼音与字词,建立拼音与字典的映射关系。
这样,在 pinyin_turning 这一步,就利用到了这层映射。

bk_turning模块,利用了bk树,直接对输入的内容进行近似查找,寻找到与输入的字词最接近的字词。所以追究起来,又回到了字典本身.

在进行prefix_tuning时, 使用了TrieChinese字典进行处理, TrieChinese 这个字典是什么时候被建立的呢,这个字典并不是单独的完整的存在的, 可以将TrieChinese字典,理解成  map_triePrefix_ 字典的中间状态,map_triePrefix_ 是将所有的字典文件都加载进了里面, 在 bk_turning 这一模块中,依然要指出所使用的字典类型, 然后从 map_triePrefix_ 中,获取相应的 TrieChinese 的对象指针, 然后在该指定的字典的基础上,利用 bk树,寻找最为接近的字词。


slm_tuning_score 可以理解为两种模式,如果 prefix_turning有返回的结果,那么对匹配到的结果,针对每个匹配到的字词,计算他们的概率,然后按照概率 排序,返回结果。
如果prefix_turning没有匹配到,那么,根据输入的拼音,每个拼音单独找出对应的汉字,这样单独的拼音就匹配出了数个汉字,他们之间有很多组合,然后利用隐马尔科夫概率模型对这些组合的概率进行计算,找出概率最高的一个,进行返回输出。









0 0
原创粉丝点击