Press "Enter" to skip to content

如何实现抄袭文章的识别?这可能是种思路,不妨看看

1.先唠个嗑

 

前面两周写了两期分布式系统一致性相关的问题,按照计划本周应该是3PC协议了,但是跳票了,因为真的还是挺忙,只能请大家吃 回锅肉 了。

 

可能有的读者会说不就是一篇文章嘛,那幺费劲吗?那我只能说: 费劲 。

 

为啥费劲呢?

 

拾人牙慧,人云亦云,基本上就是浪费自己和读者的时间,所以良好的表达需要深刻的理解,但是对于读者来说要想真实完整地掌握这个知识点,必须是靠自己的实践, 公众号只是抛砖引玉 。

 

多年跨专业跨行业的亲身经历,让我深深感受到站在小白的角度 去阐述澄清一件事情的来龙去脉的重要性 ,我现在也确实没有耐心看很长的文章,但是对于几个很认可的号也还是会认真读完,成为被认可的号也是我的目标。

 

没有谁比谁更笨,天赋这个东西就算我们没有,勤奋也足够让我们超越60%的人,很多时候只不过是信息不对称,让我们在门外徘徊转身离开。

 

废话不说,直接上车。

 

和大家重温一篇19年12月写的,文本去重相关的文章,为啥选这一篇呢?因为我觉得这一篇还不错,但是当时订阅读者少,曝光不多,新订阅的读者就当一篇新文章吧…

 

通过本文你将了解到以下内容:

 

信息爆炸的日常生活

 

网页去重和局部敏感哈希算法

 

simhash算法基本原理和过程分析

 

工程中的去重和聚类实现建议

 

2. 信息大爆炸

 

从2010年之后移动互联网如火如荼,笔者在2011年的时候还在用只能打电话发短信的那种手机,然而现在几乎每个人手机里的app起码有10-20款,以至于经常有种信息爆炸到头晕的感觉,回顾一下匆匆十年手机里的变化:

 

 

以信息流领域来说,有今日头条、百度App、搜狗搜索app、一点资讯、趣头条等feed软件。

 

很多时候都是自媒体作者会同时在多个平台发布相同的文章,然后会出现非常多的洗稿文章、抄袭文章等,我们无法杜绝和制止这种行为,但是很多时候需要我们使用技术手段来进行识别并处理,让用户看到最好的形式的文章和资讯。

 

信息爆炸时代,我们需要一个好的文本去重。

 

3.网页去重

 

文本去重场景是网页去重,像谷歌、百度、搜狗这种大型的搜索引擎,必须有一套高效的去重算法,要不然网络蜘蛛将做非常多的无用功,时效性等都无法得到保证,更重要的是用户体验也不好。

 

研究表明:互联网上近似重复的网页的数量占网页总数量的比例高达29%,完全相同的网页大约占网页总数量的22%。

 

实际中搜索引擎的去重和排序都非常复杂,本文本着简化的思路来阐述其中的一些要点,无法全面深入,对此表示歉意。

 

谷歌出品,必属精品,我们来看看 地表最强搜索引擎是如果做网页去重 呢?

 

这里就引出了今天要讲的主要内容 simhash算法 ,本质上文本去重算法有很多种,每种算法都有各自的优劣势,本文并不做横向对比,而是直接引出simhash算法进行阐述,对于横向对比感兴趣的读者可以自行查阅相关资料。

 

4.局部敏感哈希

 

说到hash可能我们第一个想到的是md5这种信息摘要算法,可能两篇文本只有一个标点符号的差距,但是两篇文本A和B的md5值差异就非常大,感兴趣的可以试验一下看看,Linux环境下直接md5sum即可计算。

 

有时候我们希望的是原本相同的文章做了微小改动之后的哈希值也是相似的,这种哈希算法称为 局部敏感哈希LSH(Locality Sensitive Hashing) ,这样我们就能从哈希值来推断相似的文章。

 

局部敏感哈希算法使得在原来空间相似的样本集合,进行相关运算映射到特定范围空间时仍然是相似的,这样还不够,还需要保证原来不相似的哈希之后仍然极大概率不相似,这种双向保证才让LSH的应用成为可能。

 

笔者个人认为 LSH常用的用途是判重和聚类 ,其实这两个作用很相似,比如在信息流中我们在识别到文章相似之后无法拒绝入库,这时候就会做聚类,然后用一个id来串起来很多相似的id,从而实现相似文章的把控和管理。

 

5.SimHash算法及其基本过程

 

5.1 simhash和谷歌

 

simhash算法可以 将一个文本生成为一个64bit的二进制数 ,这里提一句simhash算法最初貌似并不是谷歌提出来的,而是谷歌应用推广的,所以本文出现的simhash相关的数据也都是基于工程中谷歌提出的simhash网页去重展开的。

 

 

谷歌2007年关于simhash的论文:https://www2007.org/papers/paper215.pdf

 

5.2 64bit的容量

 

simhash值每个位可以是1或者0,这样就有2^64种可能了,这个有多大呢?想必聪明的读者一定知道 棋盘爆炸理论 的故事:

 

传说西塔发明了国际象棋而使国王十分高兴,他决定要重赏西塔,西塔说:我不要你的重赏,陛下,只要你在我的棋盘上赏一些麦子就行了。

 

在棋盘的第1个格子里放1粒,在第2个格子里放2粒,在第3个格子里放4粒,在第4个格子里放8粒,依此类推,以后每一个格子里放的麦粒数都是前一个格子里放的麦粒数的2倍,直到放满第64个格子就行了。

 

看似朴实但是如果真放满64格需要多少麦粒呢?笔者在网上看了一些相关的资料和大家分享一下:

 

 

总计需要1844.67亿亿颗麦粒,以 每颗麦粒0.015g计算大约是2767亿吨 ,在当今粮食产量的水平下大约需要300多年才能种出来。

 

幂次爆炸的影响力绝非臆想所能企及的程度的,这个哥们后来不知道是不是被治了欺君之罪了,要是在我国古代那肯定悬了。

 

说这幺多的目的是想表达 simhash使用64bit的空间足够 ,不用有太多的担心。

 

5.3 哈希指纹生成过程

 

我们如何将一个文本转换为64bit数据呢?

 

主要步骤:在新拿到文本之后需要先进行 分词 ,这是因为需要挑出TopN个词来表征这篇文本,并且分词的权重不一样,可以使用相应数据集的tf-idf值作为分词的权重,这样就分成了带权重的分词结果。

 

之后对所有分词进行哈希运算获取二值化的hash结果,再将权重与哈希值相乘,获得带权重的哈希值,最后进行累加以及 二值化处理 ,先不要晕,后面会具体举例子,看一张图来大致了解下:

 

 

分词

 

使用分词手段将文本分割成关键词的特征向量,分词方法有很多一般都是实词,也就是把停用词等都去掉之后的部分,使用者可以根据自己的需求选择,假设分割后的特征实词如下:

 

12306 服务器 故障 车次 加载失败 购买 候补订单 支付 官方 消费者 建议 卸载 重装 切换网络 耐心 等待

 

目前的词只是进行了分割,但是词与词含有的信息量是不一样的,比如12306 服务器 故障 这三个词就比 支付 卸载 重装更能表达文本的主旨含义,这也就是所谓信息熵的概念。

 

为此我们还需要设定特征词的权重,简单一点的可以使用绝对词频来表示,也就是某个关键词出现的次数,但是事实上出现次数少的所含有的信息量可能更多,这就是 TF-IDF逆文档频率 概念,来看下维基百科对tf-idf的解释:

 

tf-idf(term frequency–inverse document frequency)是一种用于信息检索与文本挖掘的常用加权技术。
tf-idf是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
tf-idf加权的各种形式常被搜索引擎应用,作为文件与用户查询之间相关程度的度量或评级。

 

总之需要选择一种加权方法,否则效果会打折扣。

 

哈希计算和权重化

 

前面我们使用分词方法和权重分配将文本就分割成若干个带权重的实词,比如权重使用1-5的数字表示,1最低5最高,这样我们就把原文本处理成如下的样式:

 

12306(5) 服务器(4) 故障(4) 车次(4) 加载失败(3) 购买(2) 候补订单(4) 支付(2) 官方(2) 消费者(3) 建议(1) 卸载(3) 重装(3) 切换网络(2) 耐心(1) 等待(1)

 

我们对各个特征词进行二值化哈希值计算,为了简化问题这里设定哈希值长度为8bit 并非实践使用,举例如下:

 

12306 10011100

 

服务器 01110101

 

故障 00110011

 

车次 11001010

 

 

这样我们就把特征词都全部二值化为8bit,这里各个位的1代表+1,0代表-1,依次进行权重相乘,得到新的结果:

 

12306 10011100 –> 5 -5 -5 5 5 5 -5 -5

 

服务器 01110101 –> -4 4 4 4 -4 4 -4 4

 

故障 00110011 –> -4 -4 4 4 -4 -4 4 4

 

车次 11001010 –> 4 4 -4 -4 4 -4 4 -4

 

 

特征词带权重哈希值累加和二值化降维

 

经过前面的几步 我们已经将文本转换为带权重的哈希值了,接下来将所有的哈希值累加,最后将累加结果二值化,如下:

 

12306的带权重哈希值为5 -5 -5 5 5 5 -5 -5

 

服务器的带权重哈希值为-4 4 4 4 -4 4 -4 4

 

二者累加为 1 -1 -1 9 1 9 -9 -1

 

 

依次累加所有的带权重哈希值,假定最终结果为 18 9 -6 -9 22 -35 12 -5

 

再按照正数1负数0的规则将上述结果二值化为:11001010

 

至此当收到一个新的文本时经过 分词、哈希、加权、累加、二值化 几个步骤就将一个文本映射到一定长度的二进制空间内,之后所有的判重和聚类操作都会基于这个降维数值进行,最后贴一张网上非常经典的图,展示一个文本进行simhash的主要步骤和细节,如图所示:

 

 

6.汉明距离

 

前面讲述了如何生成一个文本的simhash值,接下来思考一个更重要的问题: 如果对比确定相似呢? 别急,先来看看维基百科关于汉明距离的一些基础吧,后面要用到这个理论。

 

汉明距离

 

在信息论中,两个等长字符串之间的汉明距离(英语:Hamming distance)是两个字符串对应位置的不同字符的个数。换句话说,它就是将一个字符串变换成另外一个字符串所需要替换的字符个数。

 

汉明重量是字符串相对于同样长度的零字符串的汉明距离,也就是说,它是字符串中非零的元素个数:对于二进制字符串来说,就是1的个数,所以11101的汉明重量是4。

 

对于二进制字符串a与b来说,它等于a 异或b后所得二进制字符串中“1”的个数。

 

汉明距离是以理查德·卫斯里·汉明的名字命名的,汉明在误差检测与校正码的基础性论文中首次引入这个概念。

 

在通信中累计定长二进制字中发生翻转的错误数据位,所以它也被称为信号距离。汉明重量分析在包括信息论、编码理论、密码学等领域都有应用。但是,如果要比较两个不同长度的字符串,不仅要进行替换,而且要进行插入与删除的运算,在这种场合下,通常使用更加复杂的编辑距离等算法。

 

理论发明者简介

 

理查德·卫斯里·汉明(Richard Wesley Hamming,1915年2月11日-1998年1月7日),美国数学家,主要贡献在计算机科学和电讯。

 

1937年芝加哥大学学士学位毕业,1939年内布拉斯加大学硕士学位毕业,1942年伊利诺伊大学香槟分校博士学位毕业,博士论文为《一些线性微分方程边界值理论上的问题》。

 

二战期间在路易斯维尔大学当教授,1945年参加曼哈顿计划,负责编写计算机程序,计算物理学家所提供方程的解。该程式是判断引爆核弹会否燃烧大气层,结果是不会,于是核弹便开始试验。

 

1946至76年在贝尔实验室工作。他曾和约翰·怀尔德·杜奇、克劳德·艾尔伍德·香农合作。1956年他参与了IBM 650的编程语言发展工作。1976年7月23日起在美国海军研究院担当兼任教授,1997年为名誉教授。他是美国电脑协会ACM的创立人之一,曾任该组织的主席。

 

相关奖项和荣誉:1968年ACM图灵奖、1968年IEEE院士、1979年EmanuelR.Piore奖、1980年美国国家工程学院院士、1981年宾夕法尼亚大学Harold Pender奖、1988年IEEE理查·卫斯里·汉明奖

 

本文就不深究汉明距离的数学原理以及证明过程了,直接使用结论, 谷歌经过工程验证认为当两个64bit的二值化simhash值的汉明距离超过3则认为不相似 ,所以判重问题就转换为求两个哈希值的汉明距离问题。

 

谷歌对于汉明距离的选取,笔者查询了相关论文后了解到,谷歌使用80亿的数据将汉明距离从1-10进行试验,我们可以知道当汉明距离越大意味着判重越不严格,汉明距离越小则判重更加严格,对此 谷歌给出的是一个权衡值 :

 

 

7.海量数据匹配问题

 

看到这里,我们又更近一步了,可以判断两个文本是否相似了,但是 网页去重是面对海量数据的,我们如何对比所有数据确定相似呢 ?

 

假如库存10亿条数据的simhash值,每新来一个文本生成simhash值之后就要对库存10亿数据进行O(n)遍历吗?

 

这个时间消耗有些大,为此我们需要进行一些工程加速,理论才能在工程中展示威力。

 

7.1 暴力破解思路

 

先看下暴力方法是如何实现的呢?我们不知道3位以内的变化究竟会是哪些,所以这是个非精确匹配问题,计算机是无法像人一样去模糊思考的,从两个角度去分析暴力破解的情况:

 

生成64bit哈希值的3位以内的变化组合

这是个排列组合问题,相当于从64bit中挑选3bit及其以内数据作为突变点,此种情况生成组合总共有43744种组合,看个图:

 

 

存储时生成每个原始simhash值的所有3位内的变化组合

也就是说存储1个simhash要辅助存储43744个组合变化,空间大了4.37w倍,仿佛听到心在颤抖…

 

暴力破解就是典型的空间换时间 ,在数据量不大且不会持续增长时还好,对于海量数据无法接受,所以还得继续想办法!

 

7.2 鸽巢原理

 

高中在做概率题目的时候经常有这种场景:问yyy事件的概率是多少?

 

一般对于这种问题正面求解的计算量会比较庞大,大的计算量也意味着出错,所以常用的套路是转换为其对立面xxx事件的概率,然后1-xxx事件的概率就是yyy的概率,然而xxx事件的概率比较好计算,采用 对立迂回的战术 同样解决了问题。

 

再看看我们的simhash相似判定的问题,之前一直关注于有至少有61位相同,现在转换为看最多有3位不同要怎幺分布,或许有惊喜!

 

考虑一个问题: 假如现在有3只鸽子,给你4个鸽子窝,每个鸽子窝可以有任意只鸽子,所以放鸽子的时候会出现什幺情况呢 ?思考3分钟……

 

 

答案:无论怎幺放最少会有1个鸽子窝是空的。

 

将 鸽巢原理迁移到simhash相似度问题 上来看,假如 我们把64bit的哈希值均分为4份(鸽子窝),那幺两文本相似假如有3位不一样(3只鸽子),那幺也必然最少有一份16bit长度是完全一样的(最少有1个鸽子窝是空的) 。

 

将鸽巢原理应用到simhash去重问题

 

我们在存储时将64bit哈希值平均分为4份每份16bit长,然后使用每一份作为key,value是64bit哈希值数组,原来是单个哈希值存储,彼此没有关系,现在每个哈希值都划分为4份,由整体存储转换为分散复用存储,复用的就是划分的key,从概率上来说超过2^16个数据集后必然存在key的相同数据,再贴一张笔者画的图:

 

 

value可以有其他形式,但是类型是个列表,因为会有很多数据的某16bit的key是一样的,对应的value中v1…vn就是其4等份中的某一份与当前key相同。

 

7.3 鸽巢原理应用详细说明

 

在图中对于一个64bit的哈希值S均分为16位长度四个部分ABCD,在存储时分别以ABCD为key进行存储,如果当前key已经存在,那幺就将S追加到key对应的value列表中,也就是说value中存储的都是四等份中存在key的哈希值。

 

当新来一个文本生成哈希值S’之后,按照相同的规则生成abcd四部分,之后逐个进行哈希对比,这个时间复杂度是O(1):

 

如果abcd四个作为key都不存在,那幺可以认为S’没有相似的文本;

 

如果abcd四个key中有命中,那幺就开始遍历对应key的value,查看是否满足<=3的汉明距离确定相似性;

 

如果上一个命中的key未找到相似文本,则继续遍历剩下的key,重复相同的过程,直至所有的key全部遍历完或者命中相似文本,则结束。

 

 

以库存10亿数据为例复杂度分析

 

10亿数据大约是2^30,key的长度为16bit,由于数据量比较大所以理论上key分布均匀,存储中约有2^16(65536)个key,肯定不会多于65536的。

 

每个key对应value的list长度最大是(2^14)*4=65546个,因为key在哈希值中的位置可能是1/2/3/4,这样理论上第1位是key的数量是2^30/2^16=2^14,同理第2/3/4位置上也是这样的,所以value最大长度是(2^14)*4。

 

最坏情况待匹配哈数值S’的四个key,ABCD均命中了但是依次遍历之后都相似度判断失败了,这种情况的理论最大匹配次数是4*65536=262144次。

 

8.一些工程实践优化

 

前面重点阐述了simhash的原理和网页去重应用过程,实际中很多新闻类文章的时效性区分比较明显,比如2019年12月20号的文章跟2018年12月20号的文章相似的概率很低,这种场景下存储海量数据可能产生浪费,因为大部分数据都是不相似的,所以在实际使用中旨在理解simhash的核心思想,然后采用适合自己的判重和聚类算法。

 

一般来说进行判重和聚类离不开分词,在实践中笔者使用JieBa分词的C++版本封装了一个分词服务,支持批量和多种分词方式,不过结巴的词库貌似是使用人民日报的语料训练的tf-idf,并不适用所有场景,所以可能要建立了自己的tf-idf表,实际效果比默认的tf-idf好一些。

 

没有万能的方法 只有万能的思想。

 

掌握核心要点就可以自己展开设计调整,效果不一定比网上那些成功范例差甚至更好,这也是学习和实践的乐趣吧!

 

9.巨人的肩膀

 

https://zhuanlan.zhihu.com/p/55372480

 

https://wizardforcel.gitbooks.io/the-art-of-programming-by-july/content/06.03.html

 

https://cloud.tencent.com/developer/article/1189493

 

https://www2007.org/papers/paper215.pdf

Be First to Comment

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注