字符串相似度算法是指通过一定的方法,来计算两个不同字符串之间的相似程度。通常会用一个百分比来衡量字符串之间的相似程度。字符串相似度算法被应用于许多计算场景,在诸如数据清洗,用户输入纠错,推荐系统, 剽窃检测系统,自动评分系统,以及网页搜索和DNA序列匹配这些方向都有着十分广泛的应用。
常见的字符串相似度算法包括编辑距离算法(EditDistance),n-gram算法,JaroWinkler算法以及Soundex算法。本文接下来大略的介绍一下这几种算法,有兴趣的读者可以在互联网找到一些更详细的资料。
最常见的相似度算法为编辑距离算法(EditDistance),该算法将两个字符串的相似度问题,归结为将其中一个字符串转化成另一个字符串所要付出的代价。转化的代价越高,说明两个字符串的相似度越低。通常可以选择的转化方式包含插入,替换以及删除。
N-Gram算法则是基于这样的一个假设: 即在字符串中第n个词的出现只与前面n-1个词相关,而与其他任何词都不相关,整个字符串出现的概率就是各个词出现的概率的乘积。 N-gram本身也代表目标字符串中长度为n的子串,举例,“ARM”在“ARMY”中,便是一个3-gram。当两个字符串中,相同的n-gram越多时,两个字串就会被认为更加相似。
Jaro Winkler则是将n-gram算法更进了一步。将n-gram中的不匹配的部分同时进行了换位的考虑,使得能获得更准确的相似程度。JaroWinkler在比较两个较短字符串的情况下,能够取得很好的结果。
Soundex算法与前面几种都不太相同。该算法的特点是,它所关注的问题并非两个字符串文本上的相似程度,而是发音的近似。首先,该算法会将两个字符串分别通过一定的hash算法转换成一个hash值,该值由四个字符构成,第一个字符为英文字母,后面三个为数字。进行转化的hash算法并非随机选取,而是利用了该拉丁文字符串的读音近似值。
当获得了两个字符串的读音上的hash值之后,该算法再对两个hash的相似度进行计算,便可以得出输入字符串的读音相似度。
Soundex算法的另一个应用场景在于,用户进行模糊查询时,可以通过Soundex值进行过滤,以提高查询性能。
这些常见的字符串相似度算法在处理拉丁文字的文本匹配时,都能起到非常好的效果。它们本身最初的发明者也是为了解决拉丁文字中遇到的问题。然而,对于象形文字相似度计算,比如说中文,这些算法就显得捉襟见肘了。
举例来说明:
南通市 – 难通市 – 北通市
对于编辑距离算法而言,南通市和难通市之间的相似度,与南通市和北通市的相似度,是一模一样的,因为两者都需要付出相同的代价来转换成另一个。 使用N-Gram算法,得出的也是相同的结果。然而,对于熟悉汉字的人来讲,南通市和难通市理应有着更加接近的相似度。因为两者的发音完全相同。
既然是发音的问题,那么有没有可能利用Soundex算法来解决呢? 目前看来,还是无法做到,因为Soundex算法更多的是针对拉丁文字的发音,对于中文而言,Soundex算法无能为力。
如果说这个例子仅仅说明是发音上的相同,拉丁文字也有相似的问题,那么下面这个例子则描述了只存在于象形文字中的相似度问题:
彬彬有礼 – 杉杉有礼
如果站在解决拉丁文字的相似度的角度来看,那么这两个字符串大约只有50%的相似度,因为在四个字符中,就有两个字符是完全不同的。这两个字符不仅外形不同,即使是发音,也是完全不同。
然而对于熟悉汉字的人来说,这两个输入应该有着相当高的相似度。因为第一个和第二个字符,虽然不同,却有着十分接近的字形。
这样的案例常常出现在录入手写输入时,举例来说,某个顾客填写了一张快递单:
江苏省 南通市 紫琅路 100号
当快递员签收快递时,可能需要在系统中录入该地址,又或者,这家快递公司采用的是先进的扫描仪器,可以将地址通过扫描仪扫入。假设顾客的字写的十分潦草,那么快递员粗心大意或者不够智能的扫描仪,都有可能导致下面的文字被错误的录入:
江苏省 南通市 紫娘路 100号
如何识别这样的相似中文词组,在现有的算法中,很难解决该问题。
中文的字符串相似度有着其独特的特征,不同于其他任何语言,而在现实世界中,我们又却是时常面临这样的问题,正如我们刚才看到的例子,其中最常见的场景便是中文纠错。
我们急需要需找一种新型的算法来解决该问题。
想要解决中文字符串的相似度匹配问题,并且量化中文相似度的结果,必须首先对单个汉字的特性有一定的了解。“琅“和“狼”的相似度,跟“琅”和“娘”之间的相似度比较,究竟哪个更高一些,量化的依据是什么?“篮”和“南”呢?他们之间有相似之处么?只有把这些问题都搞清楚了,我们才能设计出优秀的算法,来计算中文字符串之间的相似度。
经过长时间的调研和准备,在工作中不断的思考总结遇到的中文相似度的问题,我们做出如下的总结,中文的相似度问题,主要归结在三个方面。
汉字中的同音字可谓是外国人学习中文的一大难题,两个截然不同的汉字,可能有着相同的发音。
当我们对两个汉字进行相似度匹配时,发音的相同或是相近,应当在考虑之列。
对于同音字,如果仅仅考虑其发音的相似程度,那么提供这样的一个相似度算法还是十分容易的,只需要现将汉字转化成其对应的拼音,再进行传统的相似度匹配算法,譬如编辑距离算法,即可达到很好的效果。
在中国的各个省市中,不同地区有着各自截然不同的方言。这也导致了一些口音很重的地区无法识别一些拼音之间的区别。
最常见的例子便是,许多南方人很难分别“L”和“N”,他们常常会将这两个音弄混,将“篮球”读作“南球”,而“刘德华”就变成了“牛德华”。
解决方言易混淆发音字的办法和同音字的方法很相似,只需要在将汉字转化成拼音之后,再对一些易混淆的音标再进行一次转化,然后再去识别他们的相似度即可。
当然,也可以在计算近似度的时候,给易混淆音标设置一个相对较高的比值,也可以解决该问题。
还有些常见的易混淆音标包括:
“AN” – “ANG”
“Z” – “ZH”
“C” – “CH”
“EN” – “ENG”
最后一种相似度问题,同时也是最难解决的问题,便是汉字字形上的相似。
汉字,作为世界上仅存的几种象形文字之一,有着和世界主流使用的拉丁语系截然不同的表现形式。拉丁文字作为一种拼音文字,在于表音,即文字形态表示了它的发音。而象形文字,则是表意文字,一个汉字本身,便表达了它所隐含的意思。
在文章开篇中,我们所提到的所有相似度匹配算法,都无法恰当的区分两个不同汉字之间字形上的异同,更罔论计算他们的相似度了。
对于这个问题,一种朴素的思想,便是首先将汉字转化成一组的字母数字的序列,而这个转化所用到的hash算法必须能够将该汉字的字形特征保留下来。利用这样的转化,我们便将汉字字形的相似度问题,变成了两组字母数字序列的相似度问题。而这正是传统相似度匹配算法的强项。
这种解决方案的核心,就在于找到一个恰当的hash算法,能够将汉字进行适当的转化,并在转化结果中,保留住汉字的字形特征。
在另一篇论文中,作者提到了使用一种名为四角编码的汉字检字法来实现这样的算法。四角算法是由王云五于1925年发明,这种编码方式根据汉字所含的单笔或复笔对汉字进行编号,取汉字的左上角,右上角,左下角以及右下角四个角的笔形,将汉字转化成最多五位的阿拉伯数字。 通过将汉字转化成四角编码,再对四角编码的相似度进行计算,便可以得出两个汉字在字形上的相似程度。
关于四角号码的编码方式,并非本文的重点,感兴趣的读者,可以访问站点:http://baike.baidu.com/view/67253.htm?fr=aladdin以了解更多信息。
利用四角编码计算汉字相似度,可以在一定程度上解决形近字的问题。
然而四角编码也有其自身的问题,由于只取汉字的四角笔形,有些外形截然不同的汉字,因为四角结构相同,也拥有同样的四角编码。
举例说明:
量 - 6010
日 - 6010
即使是从未学过中文的人,也能一眼看出这两个字形上的差异,如果我们仅仅使用四角编码,则会得出这两个汉字相似度为100%的可笑结论。
综合以上关于汉字相似度的三种问题,我们发现,每种解决方案都旨在解决整个问题集的的一个子集。这种分不同场景的做法,常常会造成使用者的困扰,因此,我们在想,是否存在一种方法,能够将这三种解决方案合而为一,取其优点而去其缺点,一次性彻底解决中文的相似度问题?
这个问题引发了我的思考。
为了解决文章上面描述的问题,我在工作中积累相关经验,并开发出了音形码这一汉字编码方式,来解决中文的相似度算法问题。
首先,什么是音形码?音形码是一种汉字的编码方式,该编码将一个汉字转化成一个十位字母数字序列,并在一定程度上保留了该汉字的发音及字形的特征。
下图阐述了音形码的序列中每一位的含义:
整个音形码共分两部分,第一部分是音码部分,主要覆盖了韵母,声母,补码以及声调的内容。
第一位,是韵母位,通过简单的替代规则,将汉字的韵母部分映射到一个字符位。汉字的拼音中一共有24种韵母,其中部分为了后期计算目的,采用相同的字符来替代,以下是一张完整的匹配表:
你会发现我们对于an和ang,所使用的是同一种转化,目的便是为了再后期计算相似度的时候,将这种差异弱化。对于没有这种需求的应用来说,完全可以自行生成映射表。
第二位是声母位,同样的,也是利用一张替换表来将声母转换成字符:
可以看到,z和zh用的也是相同的转化。
第三位则是补码,通常用于当声母和韵母之间还有一个辅音的时候,采用的是韵母表相同的替代规则。
第四位是声调位,分别用1,2,3,4来替代汉字中的四声。
第二部分是字形码。
第一位被称为结构位,根据汉字的不同结构,用一个字符来表示该汉字的结构。
接下来的四位,则依然是借用了四角编码,来描述该汉字的形态。由于四角编码表过长,在这里就不一一列举了。
最后一位,是汉字的笔画数位, 从一到九,分别代表该汉字的笔画为一到九,接下来是A代表10位,B代表11位,并依次类推。 Z代表35位,以及任何超过35位的都用z。
举例说明:汉字 “琅”,它的音形码编码是:
通过这样的方式,将汉字首先转换成了一系列的字符序列,这样我们就可以采用一定的办法,来计算他们的相似度。
对于单字的相似度匹配,我们采用了比较复杂的计算公式,以期获得一个比较好的计算结果。
P代表音码的相似度,S代表形码的相似度,两者各占整个单字相似度的50%。
单独拆解音码相似度和形码相似度:
这里我重新定义了 和 的含义已适应该算法,用于代表字符比较操作, 表示,若两字符相同,则返回1,不同,则返回0. 表示,若俩字符相同,则返回1, 两字符不同,则返回-1.
将两个公式进行合并,便得到最终的计算单字相似度的算法:
先看P部分,声母部分占据了整个音码相似度的60%,补码为30%, 而声调部分为10%。于此同时,韵母部分对最终的相似度起到 的调整作用。
形码部分的算法很类似,四位四角编码在形码部分算法中占据相同的比重,而整个四角编码在形码部分中则占据70%的比重,而笔画数则占据了30%的比重。最终,字形结构部分与韵母部分一致,起到了 的调整作用。
我们以“琅”,“狼”和“娘”三字举例。
“琅”字的音形码为:F70211313B
“狼”字的音型码为:F70214323A
“娘”字的音型码为:F74214343A
根据我们刚才所描述的算法,可以得出,“琅”和“狼”的相似度为88.75%。 而“琅”和“娘”的相似度为83.75%
单字相似度的计算并非十分有用,毕竟当两个非常大的字符串进行比较时,两个字之间差异程度的细微差别,整体的相似度结果影响不是很大。
然而在某些场景下,诸如较短字符串的比较,或者是中文纠错的时候,单字相似度的算法则可以起到非常大的作用。
举例来说,用户通过搜索引擎来检索一个短语:“紫娘路”, 而在搜索引擎的词库中,并没有能够发现任何匹配的字符串,相应的,找出了两个与其类似的字符串:
“紫琅路”
“紫薇路”
此时,目前的搜索引擎系统无法区别出这两个字符串与用户输入哪个更加接近,因而无法向用户做出更好的推荐。 相应的,使用本文描述的中文相似度算法,便可以算出,“琅”和“狼”的相似度为88.75%(前文已得出)。 “娘”和“薇”(音形码: 8K0114424G)的相似度为14.3%。由此可以得出,“紫琅路”与输入数据较为接近。
另一种常见应用场景为,服务提供者拥有巨大的词库,用户输入一个错误数据之后,如何尽快的找出所有与其十分接近的词。
在绝对匹配的情况下,做法通常为,为词库中的每一个词,计算出一个hash值,再将hash-字串对插入到一张hash表中。当用户输入一个字串时,现将该字串的hash值计算出,再去表中进行匹配。
这种做法对于绝对匹配而言,效率很高,然而对于模糊查询来说,则毫无用武之地。用户只能一个字符串一个字符串的做相似度比较算法,来选出最佳的结果。该算法的时间复杂度则达到了O(n)。
为了解决这个问题,我们可以设计一种hash值的计算方法,使得相似的字符串拥有相同的hash值,这样当用户的字符串输入时,就可以轻易的找到一群与之十分相似的字符串,再对此进行一一比较,可以将性能提升到最大。只要算法选取合适,性能甚至可以达到O(1)。
而这样的方法就隐藏在音形码的编码当中。
对任意字符串,取每一位字符的音形码的第一位(韵母)和第五位(结构),拼成一个字符串,作为该字符串的hash值,通过这样的方式,我们可以以下字符串进行转化:
“紫琅路”: 41GE5E
“紫娘路”: 41GE5E
“紫薇路”: 41815E
当用户输入“紫狼路”时,将会被转化成:41GE5E,从而与“紫琅路”以及“紫娘路”的hash值一致。再通过更细节的比较,可以得出“紫琅路”为最优结果。
当然,不同的应用可以选取不同的音形码的位数,来得到对应用最合适的hash值。这完全可以根据需求来定制化。
字串相似度的计算可以通过直接将字符串中的每个汉字转化为音形码,再将所有音形码合并起来进行EditDistance算法比较,即可获得。
因为中文的大字符串的比较算法,即使是EditDistance也可以得到较好的结果,在这里就不详细描述了,有兴趣的读者可以自行研究。
整个算法源于我在开发公司的某个实体解析的产品中总结的经验,当时因为遇到这样的问题,却没有很好的解决办法。后来,在公司组织的编程马拉松竞赛中,我便选择了这样的课题来研究,并获得了很好的结果。
该算法还有着这样那样的缺陷,比如音形码过长问题,字串错位如何计算相似度等。但是我想,不能总等一切问题都解决再来做这些工作,而是一步解决一个问题的来不断前进。因此也希望借用这篇文章,给大家一个启发,为中文的相似度算法做出自己的贡献,那它的目的也就达到了。