使用simhash进行海量文章数据相似度去重

1.原理理解:

 

这篇文章相当好:

https://www.cnblogs.com/maybe2030/p/5203186.html

https://www.cnblogs.com/qcloud1001/p/10059709.html

 

理解了整体思路,中间有个地方不好理解:

1.如何分桶,分桶的目的,只是缩小比较次数,尽快找到待比较的文本,进行海明计算:

现在一个text文本,分词处理提取关键词,进行加权算法及simhash处理,变成64位签名串,平均按照位置放入四个桶,找到这个文本对应的其他待比较文本(有任何一个桶是相同的,就是需要的待比较文本)

2.关于比较的方式

1)假设数据量2^34,数据分布到桶里比较均匀,那么每一条数据的重合度大概是2^34/2^16 = 262144个候选结果(想象这么多数据的前16位,每个桶里数据是2^16,这样计算就知道每个数据有多少重复的),那么需要比较的次数就是262144*4,这么多相似的数据是需要拿去做海明计算的,这样就不用拿着64位的串,比较2^34次了

待比较次数公式:

count = (2^34)*64/(2^n)*n

n为每个桶多少位

n越大,比较的次数越小,但是越不精准,桶越小越精准

 

2.代码实现:

https://blog.csdn.net/qq_16912257/article/details/72156277

https://blog.csdn.net/madujin/article/details/53152619

思路:

1)将每个text文本转化为64位签名

2)对应到4个桶的16位签名,一个64位签名对应4个索引

3)新增的数据,走同样的1、2操作,如果发现有重复的桶,要进行海明距离计算,如果相似,鉴定重复,不插入桶中数据,如果不相似,要进行插入

4)数据存储于内存,持久化一份到表里

5)持久化数据结果:id(你的文本库的文章主键)、simhash、bucket1、bucket2、bucket3、bucket4

6)确定存储方式:id主键,16位签名+bucket_num,值是ids(以逗号隔开),64位simhash以逗号隔开,json存的是

API接口设计:

id,content;持久化写库操作,写内存操作;返回是否重复

 

另一种存储方式:

mongodb存储数据

{buckethashcode:'dsdfsdsdvsvvsddsdfsdsdvsvvsd',

'simhashcode_id':{'abc...0000':'100'}}

(内存消耗后续可能相当大)

需要计数,文件中,计数一个最大id,以后从这个id往后取数据,取一万条进行计算和处理;

 

3.思路步骤图

文章(id,title+content)——>分词、权重计算,得出64位sim_hashcode——>分成4个bucket,分别查询,找出需要比较的sim_hashcode——>比较如果有相似,修改数据表状态,存储和之前的相似id;如果没有相似分别存入4个桶对应的16位值

注:多线程情况下,对于内存消耗相当高,需要进行内存的合理分配

1)清洗文章内容:去掉各种标签,只保留文本中文内容

2)拆分方式(准确率如何,取决于自己根据业务场景需求,选取的特定分词方式)

拆分为长文本,比如10个词一组

拆分为词语,使用jieba的TF-IDF方式切分

按照标点符号拆分

分词:使用jieba的TF-IDF,统计每个词的权重,IDF的语料库为jieba默认的idf.txt语料库;查看jieba源码得知,如果语料库不存在该词,默认self.median_idf=11.9547675029,权重值=次数*(self.median_idf/total),total为各个词的次数求和值

经过调整验证,按照标点符号分长句子的方式比较好一点:

处理思路:

1.分为一个一个长段句子;

2.如果最长4句话相同,则认为非常相似文章;

3)根据各个长文本情况,进行64位签名计算

4)计算海明距离

 

代码参见自己修改的,已上传github,处理速度:500万篇文章/h,最高库里可存储173亿亿篇文章!

 

4.经过自己实践踩坑后的总结

jieba分词,采用TF-IDF方式,具体分成哪些词,是jieba内部算法设计(有一套词库和词的可达性算法设计),IDF也有jieba内部的语料库进行统计;

分好词及对应的权重之后就比较好进行计算了,加和每个词的64位签名,计算一个统一的simhash值

两个文本的simhash计算海明距离

 

验证得知:

1.此方法不适合短文本,文本越长统计效果越好

2.此方法敏感度比较高,并非网上说的海明距离<3以内才算相似

 

有人私信我希望要代码案例,所以我将主体代码写在下面

# @Time    : 19/6/25 10:40
# @Author  : 504747754@qq.com(ZengYang)
# @File    : simhash_worker.py
# @Software: PyCharm
# @ToUse  : simhash的海明计算,单独的算法模型
"""
maxid——17642803

去燥
分词(获取中文分词)
计算签名(tokens,)
海明计算(最快的批量计算方式)如果发现有小于3,即不再计算

simhash处理的时候,正则匹配中英文,拆分所有四字短语

准确度优化方案:
1.拆分短句
2.统计短句出现的次数,进行签名计算

计算方式:
1.从数据库一条条拿数据计算simhash,id,放入redis,修改数据表的持久化状态,否则不做调整
2.计算海明距离后,如果相似,记录相似id,并且修改状态
3.不相似,不做任何操作
"""
import json
import logging
import re
import sys

sys.path.append('../../')
import time
import jieba.analyse
import numpy as np

from ant_kandian.settings import MYSQL_HOST
from ant_kandian.tools.dao_tools import Mysql, RedisMsgQueue

str2 = """
"""
REDIS_SIM_KEY = 'simkey'
CHECK_MAX_ID = 'max_id'
ID_SIMHASH_MAP = 'id_simhash'


class Simhash_worker():
    """
    用来进行分布式文章去重check
    """

    pass


def web_content_filter(content):
    """
    只保留汉字
    :param content:
    :return:
    """
    reg = r'[\u4e00-\u9fcc]+'
    # reg = r'[\W\u4e00-\u9fcc]+'
    content = ''.join(re.findall(reg, content))
    return content


def distance_haiming(hash1, hash2):
    t1 = '0b' + str(hash1)
    t2 = '0b' + str(hash2)
    n = int(t1, 2) ^ int(t2, 2)
    i = 0
    while n:
        n &= (n - 1)
        i += 1
    return i


class SimhashManager:
    def __init__(self, content):
        self.simhash = self.simhash(content)

    def __str__(self):
        return str(self.simhash)

    def simhash(self, content):
        content = web_content_filter(content)
        seg = jieba.cut(content)
        # jieba.analyse.set_stop_words('stopword')
        # jieba.analyse.set_idf_path(r'my_idf')
        keyWord = jieba.analyse.extract_tags(
            '|'.join(seg), topK=50, withWeight=True, allowPOS=())  # 在这里对jieba的tfidf.py进行了修改
        # 将tags = sorted(freq.items(), key=itemgetter(1), reverse=True)修改成tags = sorted(freq.items(), key=itemgetter(1,0), reverse=True)
        # 即先按照权重排序,再按照词排序
        keyList = []
        # print(len(keyWord))
        # print(keyWord)
        for feature, weight in keyWord:
            # weight = int(weight * 100)
            feature = self.string_hash(feature)
            temp = []
            for i in feature:
                if (i == '1'):
                    temp.append(weight)
                else:
                    temp.append(-weight)
            keyList.append(temp)
        list1 = np.sum(np.array(keyList), axis=0)
        if (keyList == []):  # 编码读不出来
            return '00'
        simhash = ''
        for i in list1:
            if (i > 0):
                simhash = simhash + '1'
            else:
                simhash = simhash + '0'
        return simhash

    def string_hash(self, source):
        if source == "":
            return 0
        else:
            x = ord(source[0]) << 7
            m = 1000003
            mask = 2 ** 128 - 1
            for c in source:
                x = ((x * m) ^ ord(c)) & mask
            x ^= len(source)
            if x == -1:
                x = -2
            x = bin(x).replace('0b', '').zfill(64)[-64:]
            return str(x)

    def distance(self, com):
        t1 = '0b' + self.simhash
        t2 = '0b' + com.simhash
        n = int(t1, 2) ^ int(t2, 2)
        i = 0
        while n:
            n &= (n - 1)
            i += 1
        return i

    def simhash_by_sentence(self, content):
        simhash = ''
        return simhash



if __name__ == '__main__':
    this_simhash1 = str(SimhashManager('今年来5.1万电信网络诈骗嫌疑人被抓获 关于诈骗的这些坑必须防范'))
    this_simhash2 = str(SimhashManager('报告显示:90后被骗概率高 男性比女性更容易上当报告显示:90后被骗概率高 男性比女性更容易上当'))
    print(distance_haiming(this_simhash1, this_simhash2))

 

 

 

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值