我是如何用单机实现亿级规模题库去重的?

背景

最近工作中遇到了一个问题:如何对大规模题库去重?公司经过多年的积累,有着近亿道题目的题库,但是由于题目来源不一导致题库中有很多重复的题目,这些重复的题目在检索时,除了增加搜索引擎的计算量外,并不会提高准确率。此外由于题目过多,搜索引擎往往采取了截断策略,只对一部分题目进行计算,这导致了某些正确的题目反而得不到计算,拍搜准确率甚至不增反降。所以对于一个搜索引擎来说,虽然初期增加题目数量往往可以大幅提高拍搜准确率,但是当题目量大到一定程度时,反而会由于计算量跟不上导致准确率下降。如何尽可能的去除重复题目显得尤为重要。

一些尝试方案

比较MD5值

对每道题目计算其MD5值作为签名,这样在新增题目时,只要判断题库中是否有相同的MD5值即可。

这种方案只适用于两道题目一模一样的情况,而现实中题目往往不只是这样。

  • “A比B大10"与"B比A小10”
  • “小红买10本书”与“小明买10本书”
  • “今天空气温度为10度”与“今天的空气温度为10度”
    这些应该是重复题,但是MD5值不同,没法去重。

利用最长公共子序列和最小编辑距离算法

利用最长公共子序列算法与最小编辑距离算法计算两个题目的相似度,如果相似度大于一定比例,例如大于90%,就认为是重复的题目。

这个方法理论上可行,但是计算量太大。假如文档数为N,平均文档长度为M,那么计算量大致为:O(N2∗M2)O(N2∗M2) 。

假设N=1000万,M=200,则计算量约为 4∗10184∗1018 ,笔者线下可用机器有限,没有这么大的计算能力。但是如果能够把相似的题目归拢到一起,然后去比较这一小撮题目中两两相似程度,这个还是可行的。

Jaccard相似度

为此,我特意看了两本书:《信息检索导论》的19.6章节以及《大数据-互联网大规模数据挖掘与分布式处理》的3.2与3.3节。这里面讲述了如何计算两个集合的Jaccard相似度:|A∩B||A∪B||A∩B||A∪B| 。这个公式对于去重来说没什么卵用,因为计算量还是那么大。所以这两本书还特意介绍了与其等价的算法:转换成随机全排列,基于概率算法去计算Jaccard的近似值。这个转换的证明本文不赘述,有兴趣的小伙伴直接去看这两本书。但是这里面有一个有意思的问题也是计算Jaccard相似度最关键的一步:如何对一个超级大的N生成一个0~N-1随机全排列?我这里给出一个近似算法,学过初等数论的小伙伴应该对下面的定理不陌生。

  • 定理: y=(a∗x+b)modny=(a∗x+b)modn ,如果a与n互质(即a与n的最大公约数为1),当x取遍0n-1时,y取遍0n-1。

证明:假如存在两个数 x1x1 和 x2x2,使得 y1=(a∗x1+b)modn=y2=(a∗x2+b)modny1=(a∗x1+b)modn=y2=(a∗x2+b)modn ,则 (a∗x1+b)%n=(a∗x2+b)%n(a∗x1+b)%n=(a∗x2+b)%n ,得出 (a∗x1+b−a∗x2−b)%n=0(a∗x1+b−a∗x2−b)%n=0 ,继而得到 a∗(x1−x2)%n=0a∗(x1−x2)%n=0。由于a与n互质,最大公约数为1,所以得出 x1−x2=k∗nx1−x2=k∗n ,即 x1=x2+k∗nx1=x2+k∗n。当 x1x1 和 x2x2 都小于n时,k只能等于0,即 x1=x2x1=x2。这就说明当x取遍0n-1时,其余数肯定不重复,由于余数的取值范围也是0n-1,所以结论得证。

这样,当我们知道n时,只要找到与n互质的100或者200个数就行,甚至可以找到小于n的100个或者200个素数(素数筛法大家自行百度),然后再随机生成100次到200次b,就能构造出一批这样的函数。
例如,a = 3,b = 4, n = 8

x = 0 y = 4
x = 1 y = 7
x = 2 y = 2
x = 3 y = 5
x = 4 y = 0
x = 5 y = 3
x = 6 y = 6
x = 7 y = 1

虽然这个概率算法能够降低一些计算量,但是我还是不能够接受。因为我们现在的关键问题是找出相似的一小撮,并在这一小撮中进行更精细化的判断策略,怎么找到这一小撮咧?

利用线上拍搜日志进行挖掘

正所谓具体情况具体分析,不能一味追求高科技却忽略现实条件。比如百度也有去重策略,但是其最后应用到线上的并不是Jaccard相似度,而是找文档中最长的几个句子,根据这几个句子是否一样判断两个文档是否重复,而且准确率出奇的好。所以,我们也要具体问题具体分析。

观察一下拍搜流程,检索日志中会记录每次搜索结果中几个匹配程度最高的文档id,那么我就可以认为这几个文档是一个小簇,没有必要再重新聚簇。此外由于拍搜的优化策略极多,准确率极高,这比我自己再重新发明一个聚簇算法要省事并且效果好。有这么好的日志在手,就要充分利用起来。接下来我就详细说说我是如何实现去重策略的。

日志格式如下:

[[1380777178,0.306],[1589879284,0.303],[1590076048,0.303],[1590131395,0.303],[1333790406,0.303],[1421645703,0.303],[1677567837,0.303],[1323001959,0.303],[1440815753,0.303],[1446379010,0.303]]

这是一个json数组,每个数组中有题目的ID和其得分。

日志选取

选取题目ID得分比较高的日志作为候选日志。这么选取是因为线上的图像识别不能保证百分百准确,如果图片质量特别差,那么根据识别内容检索到的题目之间差别较大,可能根本不是一类。

聚簇

初始集合建立

对于每条日志,由排在第一位的ID作为簇标识,其它元素作为簇中的元素。

集合求并

看如下样例:

A -> B,C,D
E -> C,D,F

由于两个集合中有相同的ID,我们推测这两个集合其实属于一个簇,如何实现两个集合的并?利用并查集算法(自行百度之,参加过编程竞赛的小伙伴应该都不陌生,我写的一个样例代码:https://github.com/haolujun/Algorithm/tree/master/union_find_set ),并查集能够出色的完成集合合并操作。例如,可以利用并查集的join操作完成两条日志的合并。

union_find_set.join(A,B)
union_find_set.join(A,C)
union_find_set.join(A,D)union_find_set.join(E,C)
union_find_set.join(E,D)
union_find_set.join(E,F)

调用完操作后,我们会发现A,B,C,D,E,F都属于同一个集合。

集合元素限制

在实际测试时发现,某些集合中的题目数量可能会达到百万,这种情况出现是因为聚类过程中的计算偏差导致的。比如:A与B相似,B与C相似,我们会把A,B,C放到一个簇中,但是实际上A和C可能不相似,这是聚类过程中非常容易出现的问题。簇过大会加大后面的精细计算的计算量,这是一个比在大题库中去重稍简单的问题,但是也非常难。考虑到题库中重复题目不会太多,可以对每个集合大小设置上限元素数目,如果两个将要合并的集合元素总数大于上限,则不将这两个集合合并,这个利用并查集也非常容易实现。

精细计算

如何判断两个题目是否重复

现在得到的簇是一个经过拍搜的结果聚合的,但是拍搜有一个问题就是检索使用的文字是由OCR识别生成的,其中难免会有识别错误,搜索引擎为了能容忍这种错误,加入了一定的模糊策略,这导致簇中的结果并不完全相似,所以精细计算是必要的。那么如何比较两个题目是否是重复的呢?特别是对于数学题这种数字和运算符、汉字混合的题目,该如何办?经过长时间分析发现,不能够把数字、字母与汉字同等比较。数字、字母如果不相等,那么八成这两道题是不同的;如果数字、字母相同那么汉字描述部分可以允许一些差异,但是差异也不要太大。这就得到了我最后的精细去重策略:分别提取题目的汉字和数字、字母、运算符,数字、字母、运算符完全相等并且汉字部分的相似度(可以使用最小编辑距离或者最长公共子序列)大于80%,就可以认为两道题目相同。

“A比B大10"与"B比A小10”                                                 数字与字母组成的字符串不相等,不认为重复
“小红买10本书”与“小明买10本书”                                     数字字母相同,汉字相似度大于80%,认为重复
“今天空气温度为10度”与“今天的空气温度为10度”            数字字母相同,汉字相似度大于80%,认为重复

虽然这个策略不能百分百去重所有重复题,但是能确保它能去重大部分重复题。

保留哪些题目,去除哪些题目?

考虑到搜索引擎在存储倒排是按照题目ID大小进行排序的(存放ID与ID之间的差值),所以留下小的ID去掉大的ID非常必要,这个不难实现。

周期性迭代

我们的去重算法是基于日志进行的去重,那么可以每次去重一部分,上线后再捞取一段时间内的日志进行去重,这样不断的迭代进行。

计算量还大么?

根据单机的计算量,一次捞取一定数量的日志进行去重,单机就可以完成,不需要集群,不需要分布式。

结语

聪明的小伙伴可能发现,我投机取巧了。我并没有直接对题库去蛮力去重,而是从拍搜日志下手,增量的一步步的实现题库去重,只要迭代次数足够,可以最终去重所有题目,并且每次去重可以实实在在看到效果,可以更方便调整策略细节。所以,在面对一个问题时,换一个角度可能会有更简单的做法。

我是如何用单机实现亿级规模题库去重的?相关推荐

  1. 两个listmap合并去重_我是如何用单机实现亿级规模题库去重的?

    题外话:欢迎将公众号设置为星标,技术文章第一时间看到.我们将一如既往精选技术好文,提供有价值的阅读.如有读者想要投稿,可以在公众号任意文章下留言,技术博主奖励丰厚. 作者:haolujun cnblo ...

  2. 两个listmap合并去重_单机亿级规模题库去重,如果是你会怎么做?

    作者:haolujun原文:https://www.cnblogs.com/haolujun/p/8399275.html 背景 最近工作中遇到了一个问题:如何对大规模题库去重?公司经过多年的积累,有 ...

  3. 亿级规模的 Feed 流系统,如何轻松设计?

    阿里妹导读:互联网进入移动互联网时代,最具代表性的产品就是各种信息流,像是朋友圈.微博.头条等.这些移动化联网时代的新产品在过去几年间借着智能手机的风高速成长.这些产品都是Feed流类型产品,由于Fe ...

  4. 动动手指头, Feed 流系统亿级规模不用愁

    戳蓝字"CSDN云计算"关注我们哦! 作者 | 少强 责编 | 阿秃 导读:互联网进入移动互联网时代,最具代表性的产品就是各种信息流,像是朋友圈.微博.头条等.这些移动化联网时代的 ...

  5. 亿级规模的Feed流推荐系统,如何轻松设计?

    导读:互联网进入移动互联网时代,最具代表性的产品就是各种信息流,像是朋友圈.微博.头条等.这些移动互联网时代的新产品在过去几年间借着智能手机的风高速成长.这些产品都是Feed流类型产品,由于Feed流 ...

  6. 半小时训练亿级规模知识图谱,亚马逊AI开源知识图谱嵌入表示框架DGL-KE

    出品 | AI科技大本营(ID:rgznai100) 知识图谱 (Knowledge Graph)作为一个重要的技术,在近几年里被广泛运用在了信息检索,自然语言处理,以及推荐系统等各种领域.学习知识图 ...

  7. 半小时训练亿级规模知识图谱,亚马逊AI开源知识图谱嵌入表示框架

    来源:AI 科技大本营 本文约2300字,建议阅读9分钟 亚马逊 AI 团队开源了一款专门针对大规模知识图谱嵌入表示的新训练框架 DGL-KE,能让研究人员和工业界用户方便.快速地在大规模知识图谱数据 ...

  8. 半小时训练亿级规模知识图谱,亚马逊这个 AI 框架要火

    2020-04-08 20:08:14 出品 | AI科技大本营(ID:rgznai100) 知识图谱 (Knowledge Graph)作为一个重要的技术,在近几年里被广泛运用在了信息检索,自然语言 ...

  9. 半小时训练亿级规模知识图谱,亚马逊这个 AI 框架要火!

    出品 | AI科技大本营(ID:rgznai100) 知识图谱 (Knowledge Graph)作为一个重要的技术,在近几年里被广泛运用在了信息检索,自然语言处理,以及推荐系统等各种领域.学习知识图 ...

最新文章

  1. TSQL语句中的Like用法
  2. 火币网行情获取的websocket客户端
  3. Cpp 对象模型探索 / 成员初始化列表
  4. Python开发以太坊智能合约指南(web3.py)
  5. 番石榴的EventBus –简单的发布者/订阅者
  6. 机器学习解决什么问题_机器学习帮助解决水危机
  7. 匿名内部类编译时生成多个class文件
  8. dede无法在这个位置找到head.html2,织梦搜索:DedeCMS 提示信息
  9. 手把手教你学会用Delve调试Go程序
  10. Python代码规范(PEP8)问题及解决
  11. springcloud 分布式配置中心 config server config client
  12. 洛谷P3509 [POI2010]ZAB-Frog
  13. 数据挖掘FPGrowth算法JAVA实现
  14. instantclient oracle oci
  15. mysql的临时表空间_Mysql临时表空间详解
  16. 机动目标跟踪—当前统计模型(CS模型)扩展卡尔曼滤波/无迹卡尔曼滤波 matlab实现
  17. 计算机应用基础165791,人大网大计算机应用基础试题答案解析.doc
  18. 冒泡排序(C语言版)
  19. 通信原理 | 基本概念
  20. linux zfs raid,Linux中的ZFS RAID和LUKS加密

热门文章

  1. Mac如何自动发邮件给kindle推送文档
  2. tfs需要对防火墙开放的端口
  3. linux top 源码,在Linux系统中下载源码包安装Topgrade的方法,附Topgrade选项说明
  4. 如何用curl做PUT请求?
  5. java json与xml互转工具类
  6. 为什么总是显示服务器或网络错误怎么办,为什么电脑老是没网络异常怎么办啊...
  7. python操作MySQL----增删改查
  8. 408王道计算机组成原理强化——存储系统大题
  9. 北邮大二大物期末_大学物理(北邮大)答案习题11
  10. python神经网络