您的位置:首页 > 其它

成功的匹配两篇文章的相似度,准确率接近100%

2017-06-17 00:00 246 查看

开始

最近写了很多MySQL全文索引的知识,是因为工作中使用到了它。由于效率以及准确率的问题,我维护的语篇查重系统一直受到编辑小伙伴的吐槽,在近一个月的苦思冥想中,终于想到了不错的解决办法,初步测试效果理想。

问题叙述

查重系统顾名思义就是将编辑们输入的文章与已经录入的文章进行匹配,如果有重复的再决定文章是否进行使用。

碰到的问题是,线上数据量80万左右,每条数据都是由一套试卷组成,里面包含各种符号,中文英文等大量的数据。我们就要在这样的数据源里面寻找是否有已经使用过的文章。系统在我接手之前使用的MySQL全文索引,而MySQL全文索引的机制也就决定了这个系统的准确率一定会饱受诟病,这就是我要解决的问题。具体机制可查看其他翻译篇。

思路变化

首先想到的是去查看除了MySQL全文索引之外的解决方案,例如Sphinx、SimHash等。但是这些都被否定了。Sphinx搜索引擎性能可能会比较高,但是准确率不高。因为MySQL的全文索引与Sphinx是类似的,适合使用关键字搜索,而不适合整片文章进行比对。SimHash算法是我们领导告诉我的,我看了一下,感觉该算法入门有点难(数学比较渣),而且基于文章生成的hash值,再通过海明距离判断相似度并不太适合我们的系统,因为hash值是整篇文章的,但是编辑可能输入其中的某一个段落,这样海明距离比较大,不会被视为相似。(对Sphinx和SimHash的理解较浅,有错误请指正)。

正在我纠结的时候,结识了公司的实习生,聊天聊到论文查重,我突然意识到公司需要的可能正是像论文查重这样基于段落或者句子的对比方式。 于是开始动手实现基于短句的查重方式。

还是利用MySQL全文索引的相似度计算系统,我想要的只是让其分词技术不是按单词分,而是按句子进行分离。
首先想到的是添加专用的字符集和校对规则,将空格视为单词的一部分,这样可以利用MySQL全文索引天然的按句分词了。但是这个方法有一个问题就是需要重启MySQL,经过与运维小伙伴的沟通,得知重启数据库还是比较棘手的,所以pass掉了这个方案。
第二个方案就是通过php应用将单词组合成句,下划线在MySQL内置的全文引擎中会被视为单词的一部分。
删除原数据表的全文索引,创建一个索引表,包含一个主键id和一个全文索引列。全文索引列存储的是一个文章,文章为按下划线组成的句子组成,例如:’i_love_you,mysql_database',那么辅助索引表就会存储形如‘i_love_you'和’mysql_database'的列。通过实验发现效果不行,因为试卷的文章结构特别乱,导致分词长度比较杂乱,中文和标点处理的也不是特别理想,有的索引特长,即使方案可行效率也不会特别高。

最终想出了现在使用的方案。思路与3类似,但是优化了对特殊字符的替换,核心的变化是将拆分过滤好的句子生成hash码,拼接在一起存储在新表的全文索引列,例如现在的全文索引列的内容是‘1982398349,2398203483’,MySQL的辅助索引表里面的内容就是‘1982398349’和‘2398203483’,每个hash码代表一句话,长度基本固定,对查询速率有很大提高。

结果

通过测试收到的成效不错,本地20万数据量,查询秒级,基本没有出现所查篇章在排名第二的情况。

这个解决思路主要是建立在对全文索引的理解之上。

相关代码

// 普通hash函数,函数形式并不是特别关键,碰撞几率越小越好
function myHash($str) {
$md5 = substr(md5($str), 0, 8);
$seed = 31;
$hash = 0;

for($i = 0; $i < 8; $i++){
$hash = $hash * $seed + ord($md5{$i});
}

return $hash & 0x7FFFFFFF;
}

还有影响比较大的是html标签,字母大小写,html实体,转义字符,空格的个数等,比如html实体' ',这里面的分号会影响分词,而数据库存入的数据可能会转实体之后的数据,如果不进行过滤同一句话的hash值也不同。可以用php的stripslashes,html_entity_decode,strip_tags,str_replace等函数进行处理,目的是尽量让字符串简单,保证同一句话的hash值相同。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息