为您推荐:
精华内容
最热下载
问答
  • 5星
    38.01MB weixin_44510615 2021-07-08 11:02:17
  • 5星
    716KB weixin_44510615 2021-07-08 10:50:31
  • 5星
    48.46MB weixin_44510615 2021-07-08 11:20:34
  • 5星
    9.13MB weixin_44510615 2021-06-27 16:54:33
  • 5星
    6.58MB duxiaokang2014 2021-06-22 08:30:36
  • 5星
    406.57MB qq_42324086 2021-08-05 16:13:28
  • 5星
    592KB weixin_44510615 2021-07-08 10:33:15
  • 5星
    422KB ZOMB1E123456 2021-03-19 09:43:24
  • 5星
    1.41MB weixin_54707168 2021-06-18 16:57:14
  • 5星
    360KB weixin_46931877 2021-08-04 13:42:34
  • 本文使用python的gensim通过tf-idf计算文本相似性。 相似度计算部分参考:https://www.jianshu.com/p/edf666d3995f 1)导入相关包 import jieba.posseg as pseg import codecs from gensim import corpora, models, ...

    本文使用python的gensim通过tf-idf计算文本相似性。
    相似度计算部分参考:

    https://www.jianshu.com/p/edf666d3995f

    1)导入相关包

    import jieba.posseg as pseg
    import codecs
    from gensim import corpora, models, similarities
    import pandas as pd
    import re
    import string
    from nltk.corpus import stopwords
    from nltk.tokenize import word_tokenize
    from nltk.stem.porter import PorterStemmer
    

    2)文本预处理

    这块我用的成熟代码,细节我也搞不太明白

    comment=pd.read_csv(r"E:\IdeaContent.csv",usecols=['ideacontent'] ,encoding='utf-8')
    m=len(comment)
    print(m)
    texts=[]
    
    def removePunctuation(text):
        text = re.sub(r'[{}]+'.format(punctuation),'',text)
        return text.strip().lower()
    
    # create English stop words list
    en_stop = stopwords.words('english')
    # 英文词干提取
    p_stemmer = PorterStemmer()
    
    for j in range(0,m):
        print(comment['ideacontent'][j])
        csvfile=comment['ideacontent'][j]
        punctuation = '.!,;:?"\'"&/()+*=~@#$%^_{}[]|`°...、——【】‘’“”?《》,。·!……:;-<>'
        lower = csvfile.lower()
        #str.maketrans创建转化表
        remove = str.maketrans('','',string.punctuation) 
        without_punctuation = lower.translate(remove)
        without_punctuation1=removePunctuation(without_punctuation)
        for c in string.digits: #去数字
            without_punctuation1 = without_punctuation1.replace(c, '')
        tokens=word_tokenize(without_punctuation1)
    
        length1=len(tokens)
        x1=0
        while x1 < length1:
            if tokens[x1] in ['...','°','—','_','?','。',',','》','《','”','“',';',':','、','【','】','=','-','——','……','!','·','~','‘','’','/','^','-','+','<','>','{','}','*','//',',','.',':',';','?','(',')','[',']','&','!','$','%','*','@','|','`','#']:
                del tokens[x1]
                x1 -= 1
                length1 -= 1
            x1 += 1
        print(tokens)
            
        stopped_tokens = [w for w in tokens if not w in en_stop]
        print(stopped_tokens)
        # stem token词干化
        stemmed_tokens = [p_stemmer.stem(i) for i in stopped_tokens]
        print(stemmed_tokens)
        
        # add tokens to list
        texts.append(stemmed_tokens)
    print(texts)
    

    3)建立词袋模型和TF-IDF模型

    #建立词袋模型
    dictionary = corpora.Dictionary(texts)
    print (dictionary)
    doc_vectors = [dictionary.doc2bow(text) for text in texts]
    print (len(doc_vectors))
    print (doc_vectors)
    
    #建立TF-IDF模型
    tfidf = models.TfidfModel(doc_vectors)
    tfidf_vectors = tfidf[doc_vectors]
    print (len(tfidf_vectors))
    print (len(tfidf_vectors[0]))
    

    4)构建一个query文本,其他文本都与这个比较

    #构建停用词表stopwords1,stop_flag是标记词性,tokenization是分词、去停用词
    stop_words = r'E:\stopwords.txt'
    stopwords1 = codecs.open(stop_words,'r',encoding='utf8').readlines()
    stopwords1 = [ w.strip() for w in stopwords1 ]
    stop_flag = ['x', 'c', 'u','d', 'p', 't', 'uj', 'm', 'f', 'r']
    def tokenization(filename):
        result = []
        with open(filename, 'r') as f:
            text = f.read()
            words = pseg.cut(text)
        for word, flag in words:
            if flag not in stop_flag and word not in stopwords1:
                result.append(word)
        return result
    query = tokenization(r'E:\1.txt')
    #利用词袋模型的字典将其映射到向量空间
    query_bow = dictionary.doc2bow(query)
    print (len(query_bow))
    print (query_bow)
    index = similarities.MatrixSimilarity(tfidf_vectors)
    
    

    5)计算相似度

    sims = index[query_bow]
    print (list(enumerate(sims)))
    sim=[]
    sim.append(sims)
    for i in sim:
         print(i)
    dd=pd.DataFrame(i)
    
    dd.to_csv(r"E:\xiangsixing.csv")
    

    最后:
    ①代码中有很多不合理的地方,希望各位同行批评指正。
    ②文中部分文件的下载与使用:
    IdeaContent.csv位置:

    https://download.csdn.net/download/wyj95anan/19436039

    stopwords.txt位置:

    https://download.csdn.net/download/wyj95anan/19436047

    1.txt内容:

    This idea was created to track feedback and progress about enabling Dynamic Forms for the Opportunity standard object. This is a continuation of the work that was started based on one of the top IdeaExchange ideas: "Dependent page layouts - data rules to show, hide, or make fields/sections req'd" (https://trailblazer.salesforce.com/ideaView?id=08730000000Brox), which resulted in the creation of the Dynamic Forms capability. Help us understand the value of having Dynamic Forms for Opportunity by adding use cases and the impact it would have at your organization in the comments below.
    
    New to Dynamic Forms? The more fields that you have on your page layout, the more that the Record Detail component becomes a monolithic block of fields that you can’t customize. With Dynamic Forms, you can migrate the fields and sections from your page layout as individual components into the Lightning App Builder. Then, you can configure them just like the rest of the components on the page, and give the users of that page only the fields and sections that they need. Learn more here: https://help.salesforce.com/articleView?id=dynamic_forms_overview.htm&type=5
    
    Also, check out the Dynamic Forms and Actions group in the Trailblazer Community: https://sfdc.co/dfactionstb
    
    展开全文
    wyj95anan 2021-06-07 18:36:38
  • 文本相似性计算之编辑距离详解 概述: 编辑距离(Edit Distance):是一个度量两个字符序列之间差异的字符串度量标准,两个单词之间的编辑距离是将一个单词转换为另一个单词所需的单字符编辑(插入、删除或替换)的最小...

    文本相似性计算之编辑距离详解

    概述:

    编辑距离(Edit Distance):是一个度量两个字符序列之间差异的字符串度量标准,两个单词之间的编辑距离是将一个单词转换为另一个单词所需的单字符编辑(插入、删除或替换)的最小数量。一般来说,编辑距离越小,两个串的相似度越大。
    编辑距离1965年由苏联数学家Vladimir Levenshtein发明的。Levenshtein Distance也被称为编辑距离(Edit Distance)。

    一、Levenshtein.distance(str1, str2);

    计算编辑距离(也称Levenshtein距离) 。算法实现:动态规划
    定义
    对于两个字符串a、b,长度分别为|a|、|b|,它们的Levenshtein Distance为:
    在这里插入图片描述

    i和j分别表示字符串a和字符串b的下标。下标从1开始。
    例如将kitten-字转成sitting: (kitten’和’sitting’的编辑距离为3) .
    在这里插入图片描述

    相似度=1-编辑距离/Math.Max(str1.length,str2.length)=1-3/7=0.571

    运算过程:

    建立一个矩阵,用来存储上一步计算好的距离

    1. 建立一个矩阵,用来存储上一步计算好的距离
    matrix_ed=np.zeros((len(str_a)+1,len(str_b)+1),dtype=np.int)
    

    在这里插入图片描述

    1. 初始化第一行和第一列所有的距离。即:
      在这里插入图片描述
    matrix_ed[0]=np.arange(len(str_b)+1)
    matrix_ed[:,0] = np.arange(len(str_a) + 1)
    

    在这里插入图片描述

    1. 开始循环计算所有的距离,直到最后一个字符。例如:
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    最后可以得到:
    在这里插入图片描述
    扫描完后,返回矩阵的最后一个值d[n][m]即是它们的距离。
    所以,我们得到字符串a和b的编辑距离为2.

    def lev(str_a,str_b):
        """
        ED距离,用来衡量单词之间的相似度
        :param str_a:
        :param str_b:
        :return:
        """
        str_a=str_a.lower()
        str_b=str_b.lower()
        matrix_ed=np.zeros((len(str_a)+1,len(str_b)+1),dtype=np.int)
        matrix_ed[0]=np.arange(len(str_b)+1)
        matrix_ed[:,0] = np.arange(len(str_a) + 1)
        for i in range(1,len(str_a)+1):
            for j in range(1,len(str_b)+1):
                # 表示删除a_i
                dist_1 = matrix_ed[i - 1, j] + 1
                # 表示插入b_i
                dist_2 = matrix_ed[i, j - 1] + 1
                # 表示替换b_i
                dist_3 = matrix_ed[i - 1, j - 1] + (1 if str_a[i - 1] != str_b[j - 1] else 0)
                #取最小距离
                matrix_ed[i,j]=np.min([dist_1, dist_2, dist_3])
        print(matrix_ed)
        return matrix_ed[-1,-1]
    

    思考:
    假设字符串a=‘love’,b=‘sffg’,c=‘lovefghaa’ 那么如果我们用上面计算出a和b,c的距离:
    lev(a,b)=4,lev(a,c)=5

    看到a和b的距离比a和c的距离要小,这种是不合理的。那么我们可以讲字符串替换的距离变大一点,改成2.
    在这里插入图片描述

    这样,就可以比较有效区分这种情况

    二、Levenshtein.hamming(str1, str2);

    计算汉明距离。要求str1和str2必须长度一致。是描述两个等长字串之间对应位置上不同字符的个数。

    三、Levenshtein.ratio(str1, str2);

    计算莱文斯坦比:
    r= (sum-ldist) / sum
    其中sum是指str1和str2字串的长度总和,l_dist是类编辑距离。注意这里是类编辑距离,在类编辑距离中删除、插入依然+1,但是替换+2.

    四、Levenshtein.jaro(s1, s2);

    计算jaro距离(又称Jaro similarity), 这是由Matthew A. Jaro在1989年提出的算法. Jaro Distance据说是用来判定健康记录上两个名字是否相同,也有说是用于人口普查。完全不同的字符串为0,相同的字符串为1。我们先来看一下Jaro Distance的定义。
    两个给定字符串s_1和s_2的Jaro Distance为:
    在这里插入图片描述
    其中〖|s〗_i |是字符串s_i的长度;m为s_1、s_2匹配的字符数量;t是字符转换的次数;
    两个分别来自s_1与s_2的字符如果相距不超过匹配窗口
    m_w=⌊max⁡〖(|s_1 |,|s_2 |)〗/2⌋-1
    (下文解释)时,我们就认为这两个字符是匹配的(具体例子见下节匹配窗口m_w);
    而这些相互匹配的字符则决定了换位的数目t。简单来说将s_1与s_2匹配的字符进行比较,相同位置但字符不同的字符数除以2就是要转换的次数t。
    举例来说, MARTHA与MARHTA的字符都是匹配的,所以m=6;但是这些匹配的字符中,T和H要换位才能把MARTHA变为MARHTA,那么T和H就是不同的顺序的匹配字符, t=2/2=1;
    两个字符串的Jaro Distance即为:
    在这里插入图片描述
    匹配窗口m_w
    上面提到了字符匹配的问题,那么什么情况下才是匹配的呢?这里就需要提到匹配窗口(matching window)的概念了。
    Jaro算法的字符之间的比较是限定在一个范围内的,如果在这个范围内两个字符相等,那么表示匹配成功,如果超出了这个范围,表示匹配失败。而这个范围就是匹配窗口m_w,在Jaro算法中,它被定义为不超过(小于等于)下面表达式的值:

    在这里插入图片描述
    比如说字符串A(“bacde”)和B(“abed”),它的匹配窗口大小
    在这里插入图片描述
    在匹配的过程中,字符"a’、‘b’、d都是匹配的, indexinA(‘d’)=3, indexinB(‘d’)=3,二者的距离是0,小于匹配窗口大小。但对于e’,虽然两字符串都有e’这个字符,但它们却是不匹配的,因为’e的下标分别为4和2,距离为2 >m_w,所以e’是不匹配的。
    在这个例子中,由于有3个字符匹配,因此m=3,换位数目表示不同顺序的匹配字符的个数。同样看这个例子, 'a和’b’都是匹配的,但’a’和’b’在两个字符串的表示为"ba…’’ .和"ab…"它们的顺序不同,因此这里换位数目transpositions =2,而t = transpositions /2 =1。
    对于匹配窗口的含义,笔者的理解是:匹配窗口是一个阈值,在这个國值之内两个字符相等,可以认为是匹配的;超过了这个阈值,即使存在另一个字符与该字符相等,但由于它们的距离太远了,二者的相关性太低了,不能认为它们是匹配的。
    从上面的公式可以看出,该算法强调的是局部相似度。对于任意字符串s_1和s_2,能求出它们长度〖|s〗_i |,,m和t,这样便能代入公式求得二者的相似度(Jaro similarity)。
    从刚才的例子A(“bacde”)和B(“abed”)中, |s_1 |=5,|s_2 |=4, m=3, t=1,代入公式可得:
    在这里插入图片描述
    python 运行

    1. 安装lib
    pip install jaro_winkler
    
    1. 程序运行
    print(jaro.jaro_metric("MARTHA".decode("utf-8"),"MARHTA".decode("utf-8")))
    

    五、Levenshtein.jaro_winkler(s1, s2);

    是一个度量两个字符序列之间的编辑距离的字符串度量标准,是由William E. Winkler在1990年提出的Jaro Distance度量标准的一种变体。
    计算Jaro-Winkler距离,而Jaro-Winkler则给予了起始部分就相同的字符串更多的权重, 因为拼写错误更可能发生在单词结尾附近。他定义了一个前缀𝒫,给予两个字符串。如果前缀部分有长度为𝓁的部分相同,则jaro_winkler Distance为:
    在这里插入图片描述
    d_j是两个字符串s_1与s_2的jaro Distance;
    𝓁是前缀的相同的长度,但是规定最大为4;
    𝒫则是调整分数的常数,规定不能超过0.25,不然可能出现相似度d_w大于1的情况, Winkler将这个常数值设为0.1;
    这样,上面提及的MARTHA和MARHTA的Jaro-Winkler Distance为:
    在这里插入图片描述
    Jaro-Winkler Distance通过前缀因子𝒫使Jaro Distance相同时共同前缀长度𝓁越大的相似度越高。
    图解Jaro-Winkler similarity求解过程

    下面以字符串s_1(“abcdefgh”)和字符串s_2(“abehc”)为例来介绍整个算法的流程。这里以短字符串为行元素,长字符串为列元素,建立(|s_1|+1)×(|s_1|+1)的矩阵,这里匹配窗口的大小
    在这里插入图片描述

    (注意包括距离为0的匹配),然后根据公式不断运算:
    在匹配的过程中,字符"a’、‘b’、“c’、‘e’都是匹配的,所以m =4
    如:indexins_1(‘a’)=0, indexins_1(‘a’)=0,二者的距离是0,小于匹配窗口大小。故字符’a’是匹配的;
    indexins_1(‘b’)=1, indexins_1(‘b’)=1,二者的距离是0,小于匹配窗口大小。故字符’b’是匹配的;
    indexins_1(‘c’)=2, indexins_1(‘c’)=4,二者的距离是2,小于匹配窗口大小。故字符’c’是匹配的;
    indexins_1(‘e’)=4, indexins_1(‘e’)=2,二者的距离是2,小于匹配窗口大小。故字符’b’是匹配的;
    indexins_1(‘h’)=7, indexins_1(‘h’)=3,二者的距离是4,大于匹配窗口大小。故字符’h’不匹配的;
    m = 4,t = transportation/2=2/2 =1("c’、‘e’位置不一致,需要转换,transportation=2)
    故字符’f’、’g’、’d’不匹配;
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    从上面的图以及公式,我们可以总结出求解的过程:字符串s1作为行元素,字符串s2作为列元素,窗口大小为m_w,同时建立两个布尔型数组,大小分别为s1和s2的长度,布尔型数组对应下标的值True表示已匹配, false表示不匹配。
    对于行元素的每一个字符c1,根据c1在该字符串s1中的下标k,定位到s2的k位置,然后在该位置往前遍历m_w个单位,往后遍历m_w个单位,如果寻找到相等的字符,记在s2中的下标为p。经过这样的一次遍历,找到了k和p,我们分别标记布尔型数组s1的k和布尔型数组s2的p为已匹配(true),下次遍历时就跳过该已匹配的字符。当对s1的所有元素都遍历完毕时,就找到了所有已匹配的字符,我们统计已匹配的字符便能得到m。
    然后对两个布尔型数组同时按照顺序比较,如果出现了true,但二者对应字符串相应位置的字符不相等,表示这是非顺序的匹配,这样就可以得到。这样就能根据m和求出Jaro similarity了。
    至于Jaro-Winkler similarity,需要p参数,也不难,求出俩字符串最大共同前缀的大小即可。
    如果读者对上面的过程还有疑问,笔者再提一点,关键就在于判断来自俩字符串的相等字符的距离是不是超过了阈值(即匹配窗口长度),这里的判断方法是在某个位置进行前后的搜索,包括当前位置。
    在这里插入图片描述
    其中,字符串s_1(“abcdefgh”)和字符串s_2(“abehc”)共同前缀ab的长度𝓁 =2 < 4
    在这里插入图片描述
    在这里插入图片描述
    经过上面的学习,我们已经掌握了这个算法的原理以及实现方法,下面我们接着来探究它的特性以及适用场景。
    我们来看下面的一组实验结果
    在这里插入图片描述
    关键字是fox,另外的字符串是包含有fox几个字符的字符串,可以看出最高相似度的是"fox"在开始几位的情况下,而"afoxbcd"反而比"foaxbcd"更低,虽然前者含有完整的"fox"而后者是分开的。同时"abcdfox"的相似度为0,即使它末尾含有"fox”。
    上面这几个例子说明了jaro-winkler相似度对于前缀匹配更友好,并且越往前面匹配成功带来的权重更大。由此可以看出,该算法可以用在单词的匹配上,比如对于一个单词"appropriate",找出数据库中与它最匹配的一个词语,可以是"appropriation",也可以是"appropriately"等。但是,该算法不适用在句子匹配上,因为如果关键字在句子的后面部分,相似度会急剧下降,甚至为0。
    个人觉得算法可以完善的点:

    • 去除停用词(主要是标点符号的影响)
    • 针对中文进行分析,按照词比较是不是要比按照字比较效果更好?

    参考链接:
    https://zhuanlan.zhihu.com/p/91667128
    https://blog.csdn.net/sinat_25394043/article/details/108404210
    https://www.jb51.net/article/98449.htm
    https://en.wikipedia.org/wiki/Jaro%E2%80%93Winkler_distance
    http://www.coli.uni-saarland.de/courses/LT1/2011/slides/Python-Levenshtein.html#Levenshtein-inverse

    展开全文
    wenjieh_chen 2021-04-26 15:21:06
  • 此外,假如,N个集合中只有少数几对集合相似,绝大多数集合都不相似,该方法在两两比较过程中“浪费了计算时间”。所以,如果能找到一种算法,将大体上相似的集合聚到一起,缩小比对的范围,这样只用检测较少的集合...

      给定N个集合,从中找到相似的集合对,如何实现呢?直观的方法是比较任意两个集合。那么可以十分精确的找到每一对相似的集合,但是时间复杂度是O(n2)。此外,假如,N个集合中只有少数几对集合相似,绝大多数集合都不相似,该方法在两两比较过程中“浪费了计算时间”。所以,如果能找到一种算法,将大体上相似的集合聚到一起,缩小比对的范围,这样只用检测较少的集合对,就可以找到绝大多数相似的集合对,大幅度减少时间开销。虽然牺牲了一部分精度,但是如果能够将时间大幅度减少,这种算法还是可以接受的。接下来的内容讲解如何使用Minhash和LSH(Locality-sensitive Hashing)来实现上述目的,在相似的集合较少的情况下,可以在O(n)时间找到大部分相似的集合对。

    一、Jaccard相似度

    购物返利 https://m.cpa5.cn/

      判断两个集合是否相等,一般使用称之为Jaccard相似度的算法(后面用Jac(S1,S2)来表示集合S1和S2的Jaccard相似度)。举个列子,集合X = {a,b,c},Y = {b,c,d}。那么Jac(X,Y) = 2 / 4 = 0.50。也就是说,结合X和Y有50%的元素相同。下面是形式的表述Jaccard相似度公式:

    Jac(X,Y) = |X∩Y| / |X∪Y|

      也就是两个结合交集的个数比上两个集合并集的个数。范围在[0,1]之间。 

    二、降维技术Minhash

      原始问题的关键在于计算时间太长。如果能够找到一种很好的方法将原始集合压缩成更小的集合,而且又不失去相似性,那么可以缩短计算时间。Minhash可以帮助我们解决这个问题。举个例子,S1 = {a,d,e},S2 = {c, e},设全集U = {a,b,c,d,e}。集合可以如下表示:

    行号

    元素

    S1

    S2

    类别

    1

    a

    1

    0

    Y

    2

    b

    0

    0

    Z

    3

    c

    0

    1

    Y

    4

    d

    1

    0

    Y

    5

    e

    1

    1

    X

    表1

      表1中,列表示集合,行表示元素,值1表示某个集合具有某个值,0则相反(X,Y,Z的意义后面讨论)。Minhash算法大体思路是:采用一种hash函数,将元素的位置均匀打乱,然后将新顺序下每个集合第一个元素作为该集合的特征值。比如哈希函数h1(i) = (i + 1) % 5,其中i为行号。作用于集合S1和S2,得到如下结果:

    行号

    元素

    S1

    S2

    类别

    1

    e

    1

    1

    X

    2

    a

    1

    0

    Y

    3

    b

    0

    0

    Z

    4

    c

    0

    1

    Y

    5

    d

    1

    0

    Y

    Minhash

    e

    e

     

     

     

    表2

      这时,Minhash(S1) = e,Minhash(S2) = e。也就是说用元素e表示S1,用元素e表示集合S2。那么这样做是否科学呢?进一步,如果Minhash(S1) 等于Minhash(S2),那么S1是否和S2类似呢?

      MinHash的合理性分析

      首先给出结论,在哈希函数h1均匀分布的情况下,集合S1的Minhash值和集合S2的Minhash值相等的概率等于集合S1与集合S2的Jaccard相似度,即: 

    P(Minhash(S­1) = Minhash(S2)) = Jac(S1,S2)

      下面简单分析一下这个结论。

      S1和S2的每一行元素可以分为三类:

      X类 均为1。比如表2中的第1行,两个集合都有元素e。

      Y类 一个为1,另一个为0。比如表2中的第2行,表明S1有元素a,而S2没有。

      Z类 均为0。比如表2中的第3行,两个集合都没有元素b。

      这里忽略所有Z类的行,因为此类行对两个集合是否相似没有任何贡献。由于哈希函数将原始行号均匀分布到新的行号,这样可以认为在新的行号排列下,任意一行出现X类的情况的概率为|X|/(|X|+|Y|)。这里为了方便,将任意位置设为第一个出现X类行的行号。所以P(第一个出现X类) = |X|/(|X|+|Y|) = Jac(S1,S2)。这里很重要的一点就是要保证哈希函数可以将数值均匀分布,尽量减少冲撞。 

      一般而言,会找出一系列的哈希函数,比如h个(h << |U|),为每一个集合计算h次Minhash值,然后用h个Minhash值组成一个摘要来表示当前集合(注意Minhash的值的位置需要保持一致)。举个列子,还是基于上面的例子,现在又有一个哈希函数h2(i) = (i -1)% 5。那么得到如下集合:

    行号

    元素

    S1

    S2

    类别

    1

    b

    0

    0

    Z

    2

    c

    0

    1

    Y

    3

    d

    1

    0

    Y

    4

    e

    1

    1

    X

    5

    a

    1

    0

    Y

    Minhash

    d

    c

     

     

     

    表3 

      所以,现在用摘要表示的原始集合如下:

    哈希函数

    S1

    S2

    h1(i) = (i + 1) % 5

    e

    e

    h2(i) = (i - 1) % 5

    d

    c

    表4

      从表四还可以得到一个结论,令X表示Minhash摘要后的集合对应行相等的次数(比如表4,X=1,因为哈希函数h1情况下,两个集合的minhash相等,h2不等):

    X ~ B(h,Jac(S1,S2))

      X符合次数为h,概率为Jac(S1,S2)的二项分布。那么期望E(X) = h * Jac(S1,S2) = 2 * 2 / 3 = 1.33。也就是每2个hash计算Minhash摘要,可以期望有1.33元素对应相等。所以,Minhash在压缩原始集合的情况下,保证了集合的相似度没有被破坏。 

    三、LSH – 局部敏感哈希

      现在有了原始集合的摘要,但是还是没有解决最初的问题,仍然需要遍历所有的集合对,才能所有相似的集合对,复杂度仍然是O(n2)。所以,接下来描述解决这个问题的核心思想LSH。其基本思路是将相似的集合聚集到一起,减小查找范围,避免比较不相似的集合。仍然是从例子开始,现在有5个集合,计算出对应的Minhash摘要,如下:

     

     

     

    S1

    S2

    S3

    S4

    S5

    区间1

    b

    b

    a

    b

    a

    c

    c

    a

    c

    b

    d

    b

    a

    d

    c

    区间2

    a

    e

    b

    e

    d

    b

    d

    c

    f

    e

    e

    a

    d

    g

    a

    区间3

    d

    c

    a

    h

    b

    a

    a

    b

    b

    a

    d

    e

    a

    b

    e

    区间4

    d

    a

    a

    c

    b

    b

    a

    c

    b

    a

    d

    e

    a

    b

    e

    表5

      上面的集合摘要采用了12个不同的hash函数计算出来,然后分成了B = 4个区间。前面已经分析过,任意两个集合(S1,S2)对应的Minhash值相等的概率r = Jac(S1,S2)。先分析区间1,在这个区间内,P(集合S1等于集合S2) = r3。所以只要S­1和S2的Jaccard相似度越高,在区间1内越有可能完成全一致,反过来也一样。那么P(集合S1不等于集合S2) = 1 - r3。现在有4个区间,其他区间与第一个相同,所以P(4个区间上,集合S1都不等于集合S2) = (1 – r3)4。P(4个区间上,至少有一个区间,集合S1等于集合S2) = 1 - (1 – r3)4。这里的概率是一个r的函数,形状犹如一个S型,如下:

    图1

      如果令区间个数为B,每个区间内的行数为C,那么上面的公式可以形式的表示为:

    P(B个区间中至少有一个区间中两个结合相等) = 1 - (1 – rC)B

      令r = 0.4,C=3,B = 100。上述公式计算的概率为0.9986585。这表明两个Jaccard相似度为0.4的集合在至少一个区间内冲撞的概率达到了99.9%。根据这一事实,我们只需要选取合适的B和C,和一个冲撞率很低的hash函数,就可以将相似的集合至少在一个区间内冲撞,这样也就达成了本节最开始的目的:将相似的集合放到一起。具体的方法是为B个区间,准备B个hash表,和区间编号一一对应,然后用hash函数将每个区间的部分集合映射到对应hash表里。最后遍历所有的hash表,将冲撞的集合作为候选对象进行比较,找出相识的集合对。整个过程是采用O(n)的时间复杂度,因为B和C均是常量。由于聚到一起的集合相比于整体比较少,所以在这小范围内互相比较的时间开销也可以计算为常量,那么总体的计算时间也是O(n)。

    四、代码实现

      方法一:引用python包datasketch

      安装:

    pip install datasketch

      使用示例如下:

      MinHash

    from datasketch import MinHash
    
    data1 = ['minhash', 'is', 'a', 'probabilistic', 'data', 'structure', 'for',
            'estimating', 'the', 'similarity', 'between', 'datasets']
    data2 = ['minhash', 'is', 'a', 'probability', 'data', 'structure', 'for',
            'estimating', 'the', 'similarity', 'between', 'documents']
    
    m1, m2 = MinHash(), MinHash()
    for d in data1:
        m1.update(d.encode('utf8'))
    for d in data2:
        m2.update(d.encode('utf8'))
    print("Estimated Jaccard for data1 and data2 is", m1.jaccard(m2))
    
    s1 = set(data1)
    s2 = set(data2)
    actual_jaccard = float(len(s1.intersection(s2)))/float(len(s1.union(s2)))
    print("Actual Jaccard for data1 and data2 is", actual_jaccard)
     
                              
    MinHash LSH
     
                              
    from datasketch import MinHash, MinHashLSH
    
    set1 = set(['minhash', 'is', 'a', 'probabilistic', 'data', 'structure', 'for',
                'estimating', 'the', 'similarity', 'between', 'datasets'])
    set2 = set(['minhash', 'is', 'a', 'probability', 'data', 'structure', 'for',
                'estimating', 'the', 'similarity', 'between', 'documents'])
    set3 = set(['minhash', 'is', 'probability', 'data', 'structure', 'for',
                'estimating', 'the', 'similarity', 'between', 'documents'])
    
    m1 = MinHash(num_perm=128)
    m2 = MinHash(num_perm=128)
    m3 = MinHash(num_perm=128)
    for d in set1:
        m1.update(d.encode('utf8'))
    for d in set2:
        m2.update(d.encode('utf8'))
    for d in set3:
        m3.update(d.encode('utf8'))
    
    # Create LSH index
    lsh = MinHashLSH(threshold=0.5, num_perm=128)
    lsh.insert("m2", m2)
    lsh.insert("m3", m3)
    result = lsh.query(m1)
    print("Approximate neighbours with Jaccard similarity > 0.5", result)
    MinHash LSH Forest——局部敏感随机投影森林
    from datasketch import MinHashLSHForest, MinHash
    
    data1 = ['minhash', 'is', 'a', 'probabilistic', 'data', 'structure', 'for',
            'estimating', 'the', 'similarity', 'between', 'datasets']
    data2 = ['minhash', 'is', 'a', 'probability', 'data', 'structure', 'for',
            'estimating', 'the', 'similarity', 'between', 'documents']
    data3 = ['minhash', 'is', 'probability', 'data', 'structure', 'for',
            'estimating', 'the', 'similarity', 'between', 'documents']
    
    # Create MinHash objects
    m1 = MinHash(num_perm=128)
    m2 = MinHash(num_perm=128)
    m3 = MinHash(num_perm=128)
    for d in data1:
        m1.update(d.encode('utf8'))
    for d in data2:
        m2.update(d.encode('utf8'))
    for d in data3:
        m3.update(d.encode('utf8'))
    
    # Create a MinHash LSH Forest with the same num_perm parameter
    forest = MinHashLSHForest(num_perm=128)
    
    # Add m2 and m3 into the index
    forest.add("m2", m2)
    forest.add("m3", m3)
    
    # IMPORTANT: must call index() otherwise the keys won't be searchable
    forest.index()
    
    # Check for membership using the key
    print("m2" in forest)
    print("m3" in forest)
    
    # Using m1 as the query, retrieve top 2 keys that have the higest Jaccard
    result = forest.query(m1, 2)
    print("Top 2 candidates", result)

    方法二

    minHash源码实现如下:

     
                                
    from random import randint, seed, choice, random
    import string
    import sys
    import itertools
    
    def generate_random_docs(n_docs, max_doc_length, n_similar_docs):
        for i in range(n_docs):
            if n_similar_docs > 0 and i % 10 == 0 and i > 0:
                permuted_doc = list(lastDoc)
                permuted_doc[randint(0,len(permuted_doc))] = choice('1234567890')
                n_similar_docs -= 1
                yield ''.join(permuted_doc)
            else:
                lastDoc = ''.join(choice('aaeioutgrb ') for _ in range(randint(int(max_doc_length*.75), max_doc_length)))
                yield lastDoc
    
    def generate_shingles(doc, shingle_size):
        shingles = set([])
        for i in range(len(doc)-shingle_size+1):
            shingles.add(doc[i:i+shingle_size])
        return shingles
    
    def get_minhash(shingles, n_hashes, random_strings):
        minhash_row = []
        for i in range(n_hashes):
            minhash = sys.maxsize
            for shingle in shingles:
                hash_candidate = abs(hash(shingle + random_strings[i]))
                if hash_candidate < minhash:
                    minhash = hash_candidate
            minhash_row.append(minhash)
        return minhash_row
    
    def get_band_hashes(minhash_row, band_size):
        band_hashes = []
        for i in range(len(minhash_row)):
            if i % band_size == 0:                        
                if i > 0:
                    band_hashes.append(band_hash)
                band_hash = 0
            band_hash += hash(minhash_row[i])        
        return band_hashes
    
    def get_similar_docs(docs, n_hashes=400, band_size=7, shingle_size=3, collectIndexes=True):
        hash_bands = {}
        random_strings = [str(random()) for _ in range(n_hashes)]
        docNum = 0
        for doc in docs:
            shingles = generate_shingles(doc, shingle_size)
            minhash_row = get_minhash(shingles, n_hashes, random_strings)
            band_hashes = get_band_hashes(minhash_row, band_size)
            
            docMember = docNum if collectIndexes else doc
            for i in range(len(band_hashes)):
                if i not in hash_bands:
                    hash_bands[i] = {}
                if band_hashes[i] not in hash_bands[i]:
                    hash_bands[i][band_hashes[i]] = [docMember]
                else:
                    hash_bands[i][band_hashes[i]].append(docMember)
            docNum += 1
    
        similar_docs = set()
        for i in hash_bands:
            for hash_num in hash_bands[i]:
                if len(hash_bands[i][hash_num]) > 1:
                    for pair in itertools.combinations(hash_bands[i][hash_num], r=2):
                        similar_docs.add(pair) 
    
        return similar_docs
            
    if __name__ == '__main__':
        n_hashes = 200
        band_size = 7
        shingle_size = 3
        n_docs = 1000
        max_doc_length = 40
        n_similar_docs = 10
        seed(42)
        docs = generate_random_docs(n_docs, max_doc_length, n_similar_docs)
    
        similar_docs = get_similar_docs(docs, n_hashes, band_size, shingle_size, collectIndexes=False)
    
        print(similar_docs)
        r = float(n_hashes/band_size)
        similarity = (1/r)**(1/float(band_size))
        print("similarity: %f" % similarity)
        print("# Similar Pairs: %d" % len(similar_docs))
    
        if len(similar_docs) == n_similar_docs:
            print("Test Passed: All similar pairs found.")
        else:
            print("Test Failed.")
     
                                 

     

     

    参考:

    https://www.cnblogs.com/bourneli/archive/2013/04/04/2999767.html

    https://blog.csdn.net/weixin_43098787/article/details/82838929

    展开全文
    nidongla 2021-03-22 22:44:18
  • 如果能将所有的物料按照其属性分到不同的块中,每个块中都是相似的文章,然后只计算块内元素的相似度,其计算量就能极大的简化了,就像哈希查找一样,通过一次哈希计算找到其相似文章所在的块,然后再逐个计算块内...

      最近在做互联网热点发现时需要将全网一段时间内每一篇文章和它所有相关的报道聚集在一起形成一个事件,再对事件下报道的数量进行汇总和排序得到不同维度的热点事件。
    在这里插入图片描述
      其中相关的报道定义为相似度较高的文章,相似度较高指的是文章间的关键词重合度超过一定阈值或者事件以及事件属性相似度超过一定阈值。

    input :

    60岁的穆罕默德在贾巴里亚难民营附近的家 中被火箭弹射杀

    关键词:

    穆罕默德:0.6198;贾巴里亚:0.5796;难民营:0.2304;火箭弹:0.2012;射杀:0.1718;附近:0.1701;60岁:0.1018;家中:0.0563;

    事件:

    图片名称

      这其中涉及一个报道间两两计算相似度的问题。而一天内在各个网站,app,微博,微信等媒体文章的数量为4000万左右。假设一个报道取20个关键词,采用onehot编码,使用稀疏矩阵,两遍文章相似度计算复杂度为,其中,假设位置查找 + 对应位置加以及乘的计算量大约为100次。这样4000万篇文章两两计算复杂度为:

    ( 4 × 1 0 7 ) 2 × 1 × 1 0 2 = 1.6 × 1 0 17 (4 \times 10^7)^2 \times 1 \times 10^2 = 1.6 \times 10^{17} (4×107)2×1×102=1.6×1017

      现在计算及单核的计算速度为2ghz,即每秒钟20亿次浮点运算,这里还有操作系统占用资源以及程序调度,io等等占用的资源,真正用来计算的假设为单核2亿次( 2 × 1 0 8 2\times 10^8 2×108),那要完成这个计算任务,单核大约需要 8 × 1 0 8 8 \times 10^8 8×108秒,大约25年左右,即使加大投入10000核,理想状态下也需要1天来完成计算。

      所以这里需要一个策略能将时间复杂度由 O ( N 2 ) O(N^2) O(N2)降为 O ( N ) O(N) O(N)或者 O ( N L o g N ) O(NLogN) O(NLogN)

      查询算法中最快的为哈希查询,能够在常数时间内找到具体的某一个物料。如果能将所有的物料按照其属性分到不同的块中,每个块中都是相似的文章,然后只计算块内元素的相似度,其计算量就能极大的简化了,就像哈希查找一样,通过一次哈希计算找到其相似文章所在的块,然后再逐个计算块内物料和查找文章相似度。如下图,假如分成10000个块。

    图片名称

      将4千万个报道平均分布在这1万个块中,每个块大约4千个报道,则总的计算量估计为

    ( 4 × 1 0 7 10000 ) 2 × 100 × 10000 = 1.6 × 1 0 13 (\frac{4\times10^7}{10000})^2\times100 \times 10000 = 1.6 \times 10^{13} (100004×107)2×100×10000=1.6×1013

      同上单核计算能力为 2 × 1 0 8 / s 2\times 10^8/s 2×108/s大约需要 8 ∗ 1 0 4 8*10^4 8104秒,大约要22小时计算完,如果100核的集群,大约13分钟就能计算完。

      实际操作中全量数据并不是均匀分布在每个块上的,当一个块上数据量超过10万时,因为每一个块是单独计算的,多核并不能加快其计算,单块的计算时间为2小时,会拖慢整体任务进度。(实际使用中测试一个container中数量超过5万就会造成内存溢出)

      实际操作中也不是分为10000个块,一般使用 2 32 − 5 2^{32}-5 2325个块,具体原因在实现和参数设置篇描述。

      所以接下来的任务为,如何将全量数据中相似的项分到相同的块中,不相似的项分到不同的块中,且每块数据量均衡且不超过5万。

      分块的方式很多,如先按照报道的地域分块,再按照报道的领域分块,或者交叉分块,但是这种根据业务进行分块的方式一般为kdtree算法,但是并不能处理高纬度数据。

    LSH的历史

      当前,在大规模高维数据集上近似最近邻问题最好的解决方案是位置敏感哈希,它将高维向量映射到低维空间,并且以较大的概率使映射前相近的映射后仍然相近。LSH虽然采用近似的方法,不保证得出精确的结果,但是它能以较低的代价返回精确的或接近精确的结果。LSH算法有多种,在不同度量空间及不同相似度量条件下有不同的方案。它们在多种场合得到应用,如计算生物学、音乐识别、图像检索、音乐检索、复制检测、近似重复检测和名词聚类等。

      LSH是由Indyk在斯坦福大学和他的导师Motwani 与1998年提出的,主要解决汉明空间的高维搜索问题。

      2004年,Datar、 Immorlica和 Indyk在斯坦福大学将p - 稳定分布函数引入LSH,并将LSH的使用范围扩展到欧氏空间。

      2005年,Indyk的学生Andoni给出了欧氏空间LSH具体实现方案,并称之为E2LSH。

      2008年,Andoni 在麻省理工大学将Leech lattice引入文献的LSH方案,将查询时间和内存消耗降到接近于文献中Motwani给出的下界。

      2010年,Andoni在他的博士论文中对LSH相关问题进行了详细描述。

    Jaccard相似度

      判断两个集合是否相等,一般使用称之为Jaccard相似度的算法(后面用Jac(S1,S2)来表示集合S1和S2的Jaccard相似度)。举个列子,集合X = {a,b,c},Y = {b,c,d}。那么Jac(X,Y) = 2 / 3 = 0.67。也就是说,结合X和Y有67%的元素相同。下面是形式的表述Jaccard相似度公式:

    Jac(X,Y) = |X∩Y| / |X∪Y|

      也就是两个结合交集的个数比上两个集合并集的个数。
    范围在[0,1]之间。

    文档的局部敏感哈希算法

      此时如果我们有一种算法,能将两个文档 S 1 S_1 S1, S 2 S_2 S2编码为一个数字 s 1 s_1 s1, s 2 s_2 s2,文档相似的概率为这两个数字相等的概率。

      如果能得到上面的算法,就可对目标文档进行多次哈希处理,得到一个签名向量,所有的签名向量会形成签名矩阵M。再对签名分组进行哈希计算,使得相似项会比不相似项更可能哈西到同一桶中。然后将至少有一次哈希到同一桶中的文档对看成为候选对。我们只会检查这些候选对的相似度。这样哈希到同一个桶中的非相似文档称为伪正例(FP),那些没有映射到相同桶中的真正相似的文档对称为伪反例(FN),我们希望FP和FN的都很小。

      我们将矩阵的行分成b个Band(r行为一个Band)

    图片名称

    行条号策略分析

      假定使用b个行条,每个行条由r行组成,并假定某具体文档之间的jaccard相似度为s,上面已经说明某一种算法得到某个具体行中的两个签名相等的概率为s。接下来我们可以计算这些文档作为候选对的概率,具体计算过程如下:

    1. 在某个具体行条中所有行的两个签名相等的概率为 s r s^r sr

    2. 在某个具体行条中至少有一对签名不相等的概率为 1 − s r 1 - s^r 1sr

    3. 在任何行条中的任意一行的签名都不相等的概率为 ( 1 − s r ) b (1 - s^r)^b (1sr)b

    4. 签名至少在一个行条中全部相等的概率,也就是称为候选对的概率为 1 − ( 1 − s r ) b 1 - (1 - s^r)^b 1(1sr)b

      虽然有可能并不特别明显,但是不论常数b和r的取值如何,上述形式的概率函数图像大致为下图给出的s曲线。曲线中上升最陡的地方对应的相似度就是所谓的阈值,他是b和r的函数。阈值是一个近似估计值为 ( 1 b ) 1 r (\frac{1}{b})^{\frac{1}{r}} (b1)r1,比如b=16, r=4 则阈值的近似度为0.5。

    图片名称

      考虑b=20且r=5的情况,也就是说假定签名的个数为100,分为20个行条,每个行条包含5行。于是两列成为候选对的概率为 1 − ( 1 − s 5 ) 20 1 - (1 - s^5)^{20} 1(1s5)20不同相似度下它的采样值以及曲线如下

    图片名称

      当两篇文档的相似度为0.8时,它们hash到同一个桶而成为候选对的概率是0.9996,而当它们的相似度只有0.3时,它们成为候选对的概率只有0.0475,因此局部敏感hash解决了让相似的对以较高的概率hash到同一个桶,而不相似的项hash到不同的桶的问题。

    集合的矩阵表示

      假定给出全集{a , b, c, d , e}中元素组成的多个集合的矩阵表示,这里 S 1 = { a , d } S_1=\{a , d\} S1={a,d}, S 2 = { c } S_2=\{c\} S2={c}, S 3 = { b , d , e } S_3=\{b , d , e\} S3={b,d,e}, S 4 = { a , c , d } S_4=\{a , c , d\} S4={a,c,d},组成矩阵:

    image

      实际中,数据不会存储为一个矩阵的一个原因是该矩阵往往非常稀疏(0的个数远远多于1的个数),只存储1所在的位置能够大大节省存储的开销,同时又能完整的表示整个矩阵。

    Minhash

      所谓的MinHsah,即进行如下的操作:

      1. 对 S 1 , S 2 , S 3 , S 4 S_1,S_2,S_3,S_4 S1,S2,S3,S4的n个维度,做一个随机排列(即对索引随机 a , b , c , d , e a,b,c,d,e a,b,c,d,e打乱)

      2. 分别取向量 S 1 , S 2 , S 3 , S 4 S_1,S_2,S_3,S_4 S1,S2,S3,S4的第一个非0行的索引值(index),即为MinHash值

      举个例子,对上面的数据进行随机打乱,得到下图

    image

      可以得到 h ( S 1 ) = a , h ( S 2 ) = c , h ( S 3 ) = b , h ( S 4 ) = a h(S_1)=a,h(S_2)=c,h(S_3)=b,h(S_4)=a h(S1)=a,h(S2)=c,h(S3)=b,h(S4)=a

    一个神奇的结论

    P ( M i n h a s h ( S 1 ) = M i n h a s h ( S 2 ) ) = J a c ( S 1 , S 2 ) P(Minhash(S_1) = Minhash(S_2)) = Jac(S_1,S_2) P(Minhash(S1)=Minhash(S2))=Jac(S1,S2)

      在集合的jaccard相似度和最小哈希值之间存在着非同寻常的关联:

      两个集合经随机排列转换之后得到的两个最小哈希值相等的概率等于这两个集合的jaccard相似度。

      为了理解上述结论的原因,必须要对两个集合同一列所有可能的结果进行枚举,假设只考虑集合 S 1 S_1 S1 S 2 S_2 S2所对应的列,那么他们所在的行可以按照所有可能的结果分为3类:

    X类:两列的值均为1

    Y类 : 其中一列值为0,另外一列为1

    Z类:两列的值均为0

      由于特征非常稀疏,因此大部分行都属于Z类,但是X和Y类行的数目的比例决定了 J a c ( S 1 , S 2 ) Jac(S_1,S_2) Jac(S1,S2)和概率 h ( S 1 ) = h ( S 2 ) h(S_1)=h(S_2) h(S1)=h(S2)的大小,假定X类行的数目为x,Y类行的数目为y,则 J a c ( S 1 , S 2 ) = x x + y Jac(S_1,S_2)=\frac{x}{x+y} Jac(S1,S2)=x+yx原因是 S 1 ⋃ S 2 {S_1}\bigcup{S_2} S1S2的大小为x,而 S 1 ⋂ S 2 {S_1}\bigcap{S_2} S1S2的大小为x+y。

      接下来考虑 h ( S 1 ) = h ( S 2 ) h(S_1)=h(S_2) h(S1)=h(S2)的概率。设想所有行进行随机排列转换,然后从上到下进行扫描处理,在碰到Y类行之前碰到X类行的概率是 x x + y \frac{x}{x+y} x+yx,如果从上往下扫描遇到的除Z类之外的第一行属于X类,那么肯定有 h ( S 1 ) = h ( S 2 ) h(S_1)=h(S_2) h(S1)=h(S2)。另一方面,如果首先碰到的是Y类行,而不是Z类行,那么值为1的那个集合的最小哈希值为当前行。但值为0的那个集合必将会进一步扫描下去,因此,如果首先碰到Y类行,那么此时 h ( S 1 ) ≠ h ( S 2 ) h(S_1) \neq h(S_2) h(S1)=h(S2).于是,可以得到最终结论,及 h ( S 1 ) = h ( S 2 ) h(S_1)=h(S_2) h(S1)=h(S2)的概率为 x x + y \frac{x}{x+y} x+yx

    最小哈希签名

      之前用矩阵M表示特征矩阵,我们随机选择n个排列转换用于矩阵M的行处理,其中n一般为几百。对于集合S对应的列,分别调用这些排序转换锁具定的最小哈希函数 h 1 , h 2 , . . . h n h_1,h_2,...h_n h1,h2,...hn则可构建S的最小哈希签名向量 h 1 ( S ) , h 2 ( S ) , . . . h n ( S ) h_1(S),h_2(S),...h_n(S) h1(S),h2(S),...hn(S),该向量通常写为列向量格式,因此,基于矩阵M可以构建一个签名矩阵,其中M的每一列替换为该列所对应的最小哈希签名向量即可。这里的签名矩阵与M的列数相同但行数只有n,通常签名矩阵所需要的空间比矩阵M本身需要的空间要小的多。

      对大规模特征矩阵进行行显示排列转换是不可行的,即使对上百万的行选择一个随机排列转换也及其消耗时间,而对行进行必要的排序则需要花费更多的时间,因此上面的算法理论上很吸引人,但是缺少可操作性。

      这里我们可以通过一个随机哈希函数来模拟随机排列转换的效果,该函数将行号映射到与行数目大致相等数量的桶中。通常,一个将整数0,1,…,k-1映射到桶号0,1,…,k-1的哈希函数会将某些整数映射到一个桶中,而有些桶却没有倍任何整数所映射到。然而,只要k很大且哈希结果冲突不太频繁的话,差异就不是很重要。于是,我们就可以继续假设哈希函数h将原来的第r行放在排列转换后次序中的第h®个位置上。

      因此,我们就可以不对行选择n个随机排列转换,取而代之的是随机选择n个哈希函数 h 1 , h 2 , . . . h n h_1,h_2,...h_n h1,h2,...hn作用于行。在上述处理基础上,就可以根据每行在哈希之后的位置来构建签名矩阵。令SIG(i,c)为签名矩阵中第i个哈希函数在第c列上的元素。一开始,对于所有的i和c,将SIG(i,c)都初始化为 ∞ \infty .然后,对r进行如下处理:

    h 1 ( r ) , h 2 ( r ) , . . . h n ( r ) h_1(r),h_2(r),...h_n(r) h1(r),h2(r),...hn(r)

    对每列c进行如下操作:

    (a)如果c在第r行为0,则什么都不做

    (b)否则,如果c在第r行为1,那么对于每个i=1,2,…,n,将SIG(i,c)置为原来的SIG(i,c)和 h i ( r ) h_i(r) hi(r)中的较小值。

      使用上面的特征矩阵,在前面加上行号,在后面加上哈希函数对行号散列后的值形成下图,这里使用2个哈希函数 $h_1(x)=(x+1)\mod 5 $ , h 1 ( x ) = ( 3 x + 1 ) m o d    5 h_1(x)=(3x+1)\mod5 h1(x)=(3x+1)mod5

    image

      签名计算流程为下图

    image

      通过上面签名矩阵可以看出 J a c ( S 1 , S 4 ) = 1 Jac(S_1,S_4)=1 Jac(S1,S4)=1而真实的 J a c ( S 1 , S 4 ) = 2 3 Jac(S_1,S_4)=\frac{2}{3} Jac(S1,S4)=32,这里签名的相似度只是一个真实相似度的估计值,本例中数据规模太小,不足以说明大规模数据下相似度的估计程度。

    最小函数族总结

      这里给出一个完整的相似项发现算法:首先找出可能的候选对相似文档集合,然后基于该集合发现真正的相似文档。

      这里还是4000万文档,计算机单核计算能力 2 × 1 0 8 2 \times 10^8 2×108,采用20个band,每个band5行,每个文档取20个特征。总的计算量为:

    生成minhash签名: 4 × 1 0 7 × 100 × 20 4 \times 10^7 \times 100 \times 20 4×107×100×20

    哈希到相应的桶中: 4 × 1 0 7 × 20 4 \times 10^7 \times 20 4×107×20

    两两计算相似度: 4 × 1 0 7 × 20 × C 4 \times 10^7 \times 20 \times C 4×107×20×C这里C为常数,假设为100

    总的计算量: 4 × 1 0 7 × 4020 4 \times 10^7 \times 4020 4×107×4020

    单核用时: 800 s 800s 800s

      但是上面最小哈希函数族并不能满足要求,因为jaccard距离是没有权重的,而实际中往往是有特征权重的,接下来就讨论除最小函数族之外的的其它函数族,他们也能非常高效地产生候选对

    局部敏感函数理论

       上面介绍了LSH技术是一个具体函数族(最小哈希函数族)上的应用例子,这些函数可以组合在一起(如上面提到的行条化技术)来更有效的区分低距离对和高距离对。

      这些函数族必须满足以下3个条件:

      (1) 它们必须更可能选择近距离对而不是远距离对作为候选对。

      (2) 函数之间必须在统计上相互独立。

      (3) 他们必须在以下两个方面具有很高的效率:

        (a) 它们必须能在很短的时间内识别候选对,改时间远小于扫描所有对所花费的时间。

        (b) 他们必须能更好的组合在一起来避免伪正例和伪反例,组合后所花费的时间也必须远小于对的数目。

      这里考虑一个判定函数,它判定两个候选项是否为候选对。很多情况下,函数f会对两个输入项求哈希值,最后的判断取决于两个哈希值是否相等。当 f ( x , y ) f(x,y) f(x,y)判定x和y是一个候选对时,用 f ( x ) = f ( y ) f(x)=f(y) f(x)=f(y)来表示。同样的用 f ( x ) ≠ f ( y ) f(x)\ne f(y) f(x)=f(y)来表示 f ( x , y ) f(x,y) f(x,y)判定x,y不是候选对。这种形式的一系列函数集合构成了所谓的函数族。

      令 d 1 < d 2 d_1<d_2 d1<d2是定义在某个距离测度d下的两个距离值。如果一个函数族F中每一个函数f都满足如下条件,则称其为: ( d 1 , d 2 , p 1 , p 2 ) (d_1,d_2,p_1,p_2) (d1,d2,p1,p2)敏感的函数族:

      (1) 如果 d ( x , y ) ≤ d 1 d(x,y)\le d_1 d(x,y)d1,那么 f ( x ) = f ( y ) f(x)=f(y) f(x)=f(y)的概率至少是 p 1 p_1 p1

      (2) 如果 d ( x , y ) ≥ d 2 d(x,y)\ge d_2 d(x,y)d2,那么 f ( x ) ≠ f ( y ) f(x)\ne f(y) f(x)=f(y)的概率至少是 p 2 p_2 p2

      下图给出了一个示意图,该图表示的是 ( d 1 , d 2 , p 1 , p 2 ) (d_1,d_2,p_1,p_2) (d1,d2,p1,p2)敏感的函数族中的一个给定函数对两个输入项判断是否候选对的期望概率情况。我们可以使 d 1 d_1 d1 d 2 d_2 d2尽量靠近。当然这种靠近通常也会使得 p 1 p_1 p1 p 2 p_2 p2相互靠近。后面会提供一种方法,在固定 d 1 d_1 d1 d 2 d_2 d2的情况下尽量分开 p 1 p_1 p1 p 2 p_2 p2

    图片名称

    面向Jaccard距离的局部敏感函数族

      目前为止,我们暂时只有一种找到局部敏感函数族的方法,即采用最小哈希函数族并假设距离测度采用Jaccard距离。

      对任意 d 1 d_1 d1, d 2 d_2 d2, 0 ≤ d 1 ≤ d 2 ≤ 1 0\le d_1 \le d_2 \le 1 0d1d21,最小哈希函数族是 ( d 1 , d 2 , 1 − d 1 , 1 − d 2 ) (d_1,d_2,1-d_1,1-d_2) (d1,d2,1d1,1d2)敏感的。

      证明: ∵ \because 如果x,y的jaccard距离 d ( x , y ) ≤ d 1 d(x,y)\le d_1 d(x,y)d1

    ∴ s i m ( x , y ) = 1 − d ( x , y ) ≥ 1 − d 1 \therefore sim(x,y) = 1- d(x,y) \ge 1- d_1 sim(x,y)=1d(x,y)1d1

    ∵ \because x和y的jaccard相似度等于最小哈希函数对x及y哈希之后结果相等的概率

    ∴ P ( f ( x ) = f ( y ) ) ≥ 1 − d 1 \therefore P(f(x)=f(y)) \ge 1- d_1 P(f(x)=f(y))1d1

    同理证明(2)

    局部敏感函数族的放大处理

      假设给定一个 ( d 1 , d 2 , p 1 , p 2 ) (d_1,d_2,p_1,p_2) (d1,d2,p1,p2)敏感的函数族F.我们可以对F进行与构造得到新的函数族 F ′ F' F F ′ F' F的定义如下: F ′ F' F的每个成员函数由r个F成员函数组成,其中r是一个固定常数。若f在 F ′ F' F中,而f为F的函数成员 f 1 , f 2 , . . . f r f_1,f_2,...f_r f1,f2,...fr中一个,当且仅当所有i都有 f i ( x ) = f i ( y ) f_i(x)=f_i(y) fi(x)=fi(y)时,才有 f ( x ) = f ( y ) f(x)=f(y) f(x)=f(y)

      由于 F ′ F' F的成员函数都是从F的成员函数中独立选出的,因此可以断言 F ′ F' F是一个 ( d 1 , d 2 , ( p 1 ) r , ( p 2 ) r ) (d_1,d_2, (p_1)^r,(p_2)^r) (d1,d2,(p1)r,(p2)r)敏感的函数族。也就是说,对于任意p,如果F的一个成员函数判定(x,y)是候选对的概率为p,那么 F ′ F' F的一个成员函数相同判定的概率为 p r p^r pr

      另外一种构造方式称为或构造,他可以将一个 ( d 1 , d 2 , p 1 , p 2 ) (d_1,d_2,p_1,p_2) (d1,d2,p1,p2)敏感的函数族F转换为
    一个 ( d 1 , d 2 , 1 − ( 1 − p 1 ) r , 1 − ( 1 − p 2 ) r ) (d_1,d_2, 1-(1-p_1)^r,1-(1-p_2)^r) (d1,d2,1(1p1)r,1(1p2)r)敏感的函数族 F ′ F' F。也就是说当且仅当存在一个或者多个i使 f i ( x ) = f i ( y ) f_i(x)=f_i(y) fi(x)=fi(y)时,才有 f ( x ) = f ( y ) f(x)=f(y) f(x)=f(y)

      我们注意到,与构造过程降低了所有的概率。但是如果能够谨慎选择F和r,就能使得小概率 p 2 p_2 p2非常接近于0,同时大概率 p 1 p_1 p1显著偏离0。类似的,或构造过程提升了所有的概率,但是通过谨慎选择F和r,能使得 p 1 p_1 p1接近于1而 p 2 p_2 p2有界远离1。通过任意次序串联与构造与或构造就可以使得 p 2 p_2 p2接近于0,同时 p 1 p_1 p1接近于1。

    image

      不同r和b选择的情况下图像的样子如下

    image

    面向其它函数族的LSH

    面向海明距离的LSH函数族

      假定一个d维向量空间, h ( x , y ) h(x,y) h(x,y)表示向量x和向量y之间的海明距离。选取向量的任意位置(如第i个位置),则定义函数 f i ( x ) f_i(x) fi(x)为向量x的第i个位置上的分量,当且仅当x和y的第i个位置上的分量值相等时有 f i ( x ) = f i ( y ) f_i(x) = f_i(y) fi(x)=fi(y)。对于随机选择的i, f i ( x ) = f i ( y ) f_i(x) = f_i(y) fi(x)=fi(y)的概率为 1 − h ( x , y ) d 1 - \frac{h(x,y)}{d} 1dh(x,y),即向量x和向量y中相等分量所占的比例。

      上述情况和最小哈希遇到的情况几乎完全相同。因此,对任意 d 1 < d 2 d_1<d_2 d1<d2,由{ f 1 , f 2 , . . . , f n f_1,f_2,...,f_n f1,f2,...,fn}构成的函数族是 ( d 1 , d 2 , 1 − d 1 d , 1 − d 2 d ) (d_1,d_2,1-\frac{d_1}{d},1-\frac{d_2}{d}) (d1,d2,1dd1,1dd2)敏感的哈希函数族,它与最小哈希函数族仅有两点不同。

      (1) Jaccard距离的取值范围是0到1之间,而两个d维向量空间的海明距离的取值范围是0到d。因此,必须对海明距离除以d来转换成概率。

      (2) 本质上,最小哈希函数的形式可以有无限可能,而海明距离的函数族F的规模仅为d。

      因为海明距离的函数族F规模有限,所以其会限制S-曲线的陡峭程度。

      举例说明其计算过程:

      数据点集合P由以下6个点构成:A=(1,1) ,B=(2,1),C=(1,2),D=(2,2),E=(4,2),F=(4,3)

    image

      可知坐标出现的最大值是4,则d=4,维度为2,则n=2,显然dC=8,我们进行8位海明编码

    v(A) = 10001000

    v(B) = 11001000

    v© = 10001100

    v(D) = 11001100

    v(E) = 11111100

    v(F) = 11111110

      若采用d=2 , r= 3生成哈希函数

      G由 g 1 g_1 g1, g 2 g_2 g2, g 3 g_3 g3构成,每个g由它对应的 h 1 h_1 h1, h 2 h_2 h2构成,假设有如下结果。

    g 1 g_1 g1分别抽取2,4位。

    g 2 g_2 g2分别抽取1,6位。

    g 3 g_3 g3分别抽取3,8位。

      哈希表的分布如下图所示

    image

      若此时我们查询点q=(4,4),可以计算出 g 1 = [ 1 , 1 ] T g_1=[1,1]^T g1=[1,1]T, g 2 = [ 1 , 1 ] T g_2=[1,1]^T g2=[1,1]T, g 3 = [ 1 , 1 ] T g_3=[1,1]^T g3=[1,1]T。则分别取出表1,2,3的11,11,11号哈希桶的数据点与q比较。依次是C,D,E,F。算出距离q最近的点为F。原始搜索空间为6个点,现在搜索空间为4个点。

    随机超平面和余弦距离

      两个向量的余弦距离是他们的夹角,夹角越小,成为候选对的概率越大。接下来讨论这个夹角和夹角相关的概率如何确定。

      答案就是通过一个哈希函数对这两个向量进行哈希,而这个哈希函数实际上只是一个随机的向量与这两个向量的内积,如果好多个这样的随机向量与他们的内积是同号的,则说明这两个向量的夹角很小,相似性大,否则相似性小。

      如下图,紫色虚线能将x和y哈希到同一位置,黄色的不能。

    image

      对于任意的向量x和y,x和y之间的距离为x和y之间的夹角 θ \theta θ.哈希函数为随机寻找一个向量v,如果 x × v x \times v x×v y × v y \times v y×v同号,则说明f(x) = f(y),很明显,其概率为 1 − θ 180 1-\frac{\theta}{180} 1180θ。所以F具备 ( d 1 , d 2 , 1 − d 1 180 , 1 − d 2 180 ) (d_1,d_2,1-\frac{d_1}{180},1-\frac{d_2}{180}) (d1,d2,1180d1,1180d2)敏感性。

      随机向量v可以只由1和-1组成,因为夹角和向量的模无关,而只选择1或者-1方便计算。

      举例说明其计算过程:

      给定一个四维的向量空间,选出3个随机向量 v 1 = [ + 1 , − 1 , + 1 , + 1 ] v_1=[+1,-1,+1,+1] v1=[+1,1,+1,+1], v 2 = [ − 1 , + 1 , − 1 , + 1 ] v_2=[-1,+1,-1,+1] v2=[1,+1,1,+1], v 3 = [ + 1 , + 1 , − 1 , − 1 ] v_3=[+1,+1,-1,-1] v3=[+1,+1,1,1]。那么对于向量x=[3,4,5,6]。计算过程如下: v 1 × x = 3 − 4 + 5 + 6 = 10 > 0 v_1 \times x = 3-4+5+6=10>0 v1×x=34+5+6=10>0因此其梗概的第一个元素为+1。类似的, v 2 × x = 3 v_2 \times x = 3 v2×x=3 v 3 × x = − 4 v_3 \times x = -4 v3×x=4。因此梗概的第二个元素和第三个元素分别是+1和-1.

      考虑向量y=[4,3,2,1],可以采用上述类似的方法计算其梗概为 [ + 1 , − 1 , + 1 ] [+1,-1,+1] [+1,1,+1]。由于x和y的梗概中相同元素的比例为 1 3 \frac{1}{3} 31,因此可以估计这两个向量间夹角为 1 − θ 180 = 1 3 1-\frac{\theta}{180}=\frac{1}{3} 1180θ=31,其中 θ = 120 \theta = 120 θ=120

      但是上述结论却大错特错。我们可以计算这两个向量的的夹角为 θ = 6 × 1 + 5 × 2 + 4 × 3 + 3 × 4 6 2 + 5 2 + 4 2 + 3 2 × 1 2 + 2 2 + 3 2 + 4 2 = 0.7875 \theta = \frac{6\times 1 + 5\times 2 + 4\times 3 + 3\times 4}{\sqrt{6^2+5^2+4^2+3^2}\times \sqrt{1^2+2^2+3^2 +4^2}}=0.7875 θ=62+52+42+32 ×12+22+32+42 6×1+5×2+4×3+3×4=0.7875大约为38度左右。但是上面结果错误是法向量采样问题,如果选择所有的16个不同的四维+1,-1向量就会发现,其中只有4个向量与x的内积和与y的内积符号相反。因此,如果选择16个向量来构成梗概的话,那么最后的夹角估计结果为 1 − θ 180 = 12 16 1-\frac{\theta}{180}=\frac{12}{16} 1180θ=1612 为45度。

    面向欧式距离的LSH函数族(E2LSH)

      欧式空间中,LSH的hash算法的思路是将n维向量随机射到一个向量,使用向量点乘,由于投射向量不是单位向量,所以严格意义上不能称之为投影。投射hash算法如下:

    h ( v ) = ⌊ a ∙ v + b w ⌋ h(v) = \left\lfloor \frac{a \bullet v + b}{w} \right\rfloor h(v)=wav+b

      其中 b ( ∈ [ 0 , w ] ) b(\in [0,w]) b([0,w])是随机量, a ( ∈ R n , a i ∼ N ( 0 , 1 ) ) a (\in R^n,a_i \sim N(0,1)) a(Rn,aiN(0,1)),是被投射的向量。投射完后,需要设置一个固定长度为w的参数,将向量严格的划分为不同的单位,投射到相同单位的向量就认为比较近。所以,w的设置十分重要,如果设置太大,比较远的对象也设hash到一个单位里,无法做到过滤的效果;如果太小,即使很近的对象也到不了一个桶里面,导致找不到相邻的对象。

    碰撞概率分析

      设向量 p , q ∈ R n p,q \in R^n p,qRn,并且 u = ∥ p − q ∥ 2 u=\lVert p-q\rVert_2 u=pq2,p与q投射到任意向量
    a
    的概率如下,

    p ( u , w ) = P r ( h ( p ) = h ( q ) ) = 2 ∫ 0 w 1 u f ( t u ) ( 1 − t w ) d t p(u,w) = Pr(h(p) = h(q)) = 2\int_{0}^{w}\frac{1}{u}f(\frac{t}{u})(1-\frac{t}{w})dt p(u,w)=Pr(h(p)=h(q))=20wu1f(ut)(1wt)dt

      上面概率公式很突然,先别慌是怎么过来的,后面慢慢道来,我们先直奔主题: p ( u , w ) p(u,w) p(u,w)
    的解析解。观察w,u与概率的关系,控制碰撞概率。 f ( x ) f(x) f(x)
    是稳定分布的概率密度函数,该分布只有在欧式距离和曼哈顿距离才有解析解,否则没有。好在我们关心的是欧式空间,所以可以得到解析解。欧式空间中, f ( x ) = 1 2 π e − x 2 2 f(x)=\frac{1}{\sqrt{2\pi}}e^{-\frac{x^2}{2}} f(x)=2π 1e2x2是标准正在分布的概率密度函数。完整推导如下,

    p ( u ) = 2 ( ∫ 0 w 1 u f ( t u ) d t − ∫ 0 w 1 u f ( t u ) t w d t ) = 2 ( ∫ 0 w f ( t u ) d t u − ∫ 0 w 1 u 2 π e − t 2 2 u 2 t w d t ) = 2 ( ∫ 0 w u f ( x ) d x − − u 2 π w ∫ 0 w e − t 2 2 u 2 d ( − t 2 2 u 2 ) ) = 2 ( 1 2 − F ( − w u ) + u 2 π w e − t 2 2 u 2 ∣ 0 w ) = 2 ( 1 2 − F ( − w u ) + u 2 π w ( e − w 2 2 u 2 − 1 ) ) \begin{array}{l l l} p(u)&= 2(\int_{0}^{w}\frac{1}{u}f(\frac{t}{u})dt - \int_{0}^{w}\frac{1}{u}f(\frac{t}{u})\frac{t}{w}dt) \\ &= 2(\int_{0}^{w}f(\frac{t}{u})d\frac{t}{u} - \int_{0}^{w}\frac{1}{u\sqrt{2\pi}}e^{-\frac{t^2}{2u^2}}\frac{t}{w}dt) \\ &= 2(\int_{0}^{\frac{w}{u}}f(x)dx - \frac{-u}{\sqrt{2\pi}w}\int_{0}^{w}e^{-\frac{t^2}{2u^2}}d(-\frac{t^2}{2u^2})) \\ &= 2(\frac{1}{2} - F(-\frac{w}{u}) + \frac{u}{\sqrt{2\pi}w}e^{-\frac{t^2}{2u^2}}|^w_0) \\ &= 2(\frac{1}{2} - F(-\frac{w}{u}) + \frac{u}{\sqrt{2\pi}w}(e^{-\frac{w^2}{2u^2}}-1)) \\ \end{array} p(u)=2(0wu1f(ut)dt0wu1f(ut)wtdt)=2(0wf(ut)dut0wu2π 1e2u2t2wtdt)=2(0uwf(x)dx2π wu0we2u2t2d(2u2t2))=2(21F(uw)+2π wue2u2t20w)=2(21F(uw)+2π wu(e2u2w21))

      令 c = u w c=\frac{u}{w} c=wu

    g ( c ) = 1 − 2 F ( − 1 c ) + 2 π c ( e − 1 2 c 2 − 1 ) g(c) = 1 - 2F(-\frac{1}{c}) + \sqrt{\frac{2}{\pi}}c(e^{-\frac{1}{2c^2}}-1) g(c)=12F(c1)+π2 c(e2c211)

      上面推导的结果只与c有关,无需考虑数据真实的单位.画出上面函数的图像为下图:

    图片名称

      根据曲线,上述函数是关于c的减函数,也就是比例越大,聚到一起的概率越低,理论与直觉一致。可以通过设定u,然后通过曲线设置一个概率,得到对应w。通过计算导数,也可以很容易发现其值恒为复数,如下:

    p ( u ) = 2 ( ∫ 0 w 1 u f ( t u ) d t − ∫ 0 w 1 u f ( t u ) t w d t ) = 2 ( ∫ 0 w f ( t u ) d t u − ∫ 0 w 1 u 2 π e − t 2 2 u 2 t w d t ) = 2 ( ∫ 0 w u f ( x ) d x − − u 2 π w ∫ 0 w e − t 2 2 u 2 d ( − t 2 2 u 2 ) ) = 2 ( 1 2 − F ( − w u ) + u 2 π w e − t 2 2 u 2 ∣ 0 w ) = 2 ( 1 2 − F ( − w u ) + u 2 π w ( e − w 2 2 u 2 − 1 ) ) \begin{array}{l l l} p(u)&= 2(\int_{0}^{w}\frac{1}{u}f(\frac{t}{u})dt - \int_{0}^{w}\frac{1}{u}f(\frac{t}{u})\frac{t}{w}dt) \\ &= 2(\int_{0}^{w}f(\frac{t}{u})d\frac{t}{u} - \int_{0}^{w}\frac{1}{u\sqrt{2\pi}}e^{-\frac{t^2}{2u^2}}\frac{t}{w}dt) \\ &= 2(\int_{0}^{\frac{w}{u}}f(x)dx - \frac{-u}{\sqrt{2\pi}w}\int_{0}^{w}e^{-\frac{t^2}{2u^2}}d(-\frac{t^2}{2u^2})) \\ &= 2(\frac{1}{2} - F(-\frac{w}{u}) + \frac{u}{\sqrt{2\pi}w}e^{-\frac{t^2}{2u^2}}|^w_0) \\ &= 2(\frac{1}{2} - F(-\frac{w}{u}) + \frac{u}{\sqrt{2\pi}w}(e^{-\frac{w^2}{2u^2}}-1)) \\ \end{array} p(u)=2(0wu1f(ut)dt0wu1f(ut)wtdt)=2(0wf(ut)dut0wu2π 1e2u2t2wtdt)=2(0uwf(x)dx2π wu0we2u2t2d(2u2t2))=2(21F(uw)+2π wue2u2t20w)=2(21F(uw)+2π wu(e2u2w21))

      导数严格小于0,证实了上述曲线确实严格下降。

    碰撞概率推导过程

      本节详细介绍如何得到上面的概率公式。假设 t = a ∙ v 1 − a ∙ v 2 t=a\bullet v_1 - a \bullet v_2 t=av1av2是向量 v 1 v_1 v1 v 2 v_2 v2投影到a上距离,必须 ∣ t ∣ ≤ w |t| \le w tw才有 P r ( h ( v 1 ) = h ( v 2 ) ) > 0 Pr(h(v_1) = h(v_2)) > 0 Pr(h(v1)=h(v2))>0所以当 v 1 v_1 v1 v 2 v_2 v2投影距离为t时,且t>0时碰撞的概率为 1 − t w 1-\frac{t}{w} 1wt。但是t的概率是什么呢?t与投影向量有关,同时与向量 v 1 v_1 v1 v 2 v_2 v2也有关。

      这里需要利用稳定分布。这是一类分布,如果任意分布D是稳定分布,那么任意n个D的独立同分布随机变量 X 1 , X 2 , ⋯   , X n X_1,X_2,\cdots,X_n X1,X2,,Xn,在任意n个实数 v 1 , v 2 , ⋯   , v n v_1,v_2,\cdots,v_n v1,v2,,vn,有 ∑ i = 1 n ( v i X i ) \sum_{i=1}^{n}{(v_iX_i)} i=1n(viXi) ( ∑ ∣ v i ∣ s ) 1 s X (\sum{|v_i|^s})^{\frac{1}{s}}X (vis)s1X(X也是D的一个随机变量)有相同的分布。可以利用这个分布计算t的概率分布。

      对于随机变量 t = a ( v 1 − v 2 ) = ∑ a i ( v 1 i − v 2 i ) t=a(v_1-v_2)=\sum{a_i(v_{1i}-v_{2i})} t=a(v1v2)=ai(v1iv2i),只要 a i a_i ai的概率分布固为D,t的分布与 ( ∑ ∣ v 1 i − v 2 i ∣ s ) 1 s a (\sum{|v_{1i}-v_{2i}|^s})^{\frac{1}{s}}a (v1iv2is)s1a一致。且当 s = 2 s=2 s=2时,分布D是标准正太分布。(只有S等于2或1,D的分布才有解析解,否则没有)。在s=2的情况下,设a的概率密度函数为 f A ( a ) f_A(a) fA(a),且 u = ( ∑ ∣ v 1 i − v 2 i ∣ 2 ) 1 2 u=(\sum{|v_{1i}-v_{2i}|^2})^{\frac{1}{2}} u=(v1iv2i2)21,那么 f T ( t ) = 1 u f A ( t u ) f_T(t)=\frac{1}{u}f_A(\frac{t}{u}) fT(t)=u1fA(ut)有了t的概率密度函数,对于任意t的概率为 1 u f X ( t u ) d t \frac{1}{u}f_X(\frac{t}{u})dt u1fX(ut)dt结合概率积分,得到概率最后的碰撞概率公式: p ( w , u ) = 2 ∫ 0 w 1 u f X ( t u ) ( 1 − t w ) d t p(w,u)=2\int_0^w\frac{1}{u}f_X(\frac{t}{u})(1-\frac{t}{w})dt p(w,u)=20wu1fX(ut)(1wt)dt,当 t ∈ [ − w , 0 ] t \in [-w,0] t[w,0]时,概率与 [ 0 , w ] [0,w] [0,w]一致,所以乘以2;其他范围概率均为0。

    欧式空间中的 ( r 1 , r 2 , p 1 , p 2 ) − s e n s i t i v e (r_1,r_2,p_1,p_2)-sensitive (r1,r2,p1,p2)sensitive

      上面讨论的投射方法和碰撞概率函数,就是分别对应上面定义的h和P。尤其是碰撞函数,可以根据给出的 r 1 r_1 r1 r 2 r_2 r2情况估算 p 1 p_1 p1 p 2 p_2 p2,并且计算最优的w的范围。首先,回顾一下碰撞函数:

    P ( h ( v ) = h ( q ) ) = g ( c ) = 1 − 2 F ( − 1 c ) + 2 π c ( e − 1 2 c 2 − 1 ) P(h(v)=h(q)) = g(c) = 1 - 2F(-\frac{1}{c}) + \sqrt{\frac{2}{\pi}}c(e^{-\frac{1}{2c^2}}-1) P(h(v)=h(q))=g(c)=12F(c1)+π2 c(e2c211)

      其中 c = u w c=\frac{u}{w} c=wu,假设 r 1 = 5 , r 2 = 50 , p 1 = 0.95 , p 2 = 0.1 r_1=5,r_2=50,p_1 = 0.95, p_2 = 0.1 r1=5,r2=50,p1=0.95,p2=0.1。根据碰撞概率的解析公式,可以得到 p 1 p_1 p1, p 2 p_2 p2对应的数值解。即 c 1 = g − 1 ( p 1 ) , c 2 = g − 1 ( p 2 ) c_1 = g^{-1}(p_1),c_2=g^{-1}(p_2) c1=g1(p1),c2=g1(p2)。由于概率公式是减函数,所以

    g ( c 1 ) ≥ p 1 a n d g ( c 2 ) ≤ p 2 ⇒ c 1 ≤ g − 1 ( p 1 ) a n d c 2 ≥ g − 1 ( p 2 ) ⇒ r 1 w ≤ g − 1 ( p 1 ) a n d r 2 w ≥ g − 1 ( p 2 ) ⇒ r 1 g − 1 ( p 1 ) ≤ w ≤ r 2 g − 1 ( p 2 ) ⇒ 5 g − 1 ( 0.95 ) ≤ w ≤ 50 g − 1 ( 0.1 ) \begin{array}{l l l} g(c_1) \ge p_1 and g(c_2) \le p_2 &\Rightarrow c_1 \le g^{-1}(p_1) and c_2 \ge g^{-1}(p_2) \\ &\Rightarrow \frac{r_1}{w} \le g^{-1}(p_1) and \frac{r_2}{w} \ge g^{-1}(p_2) \\ &\Rightarrow \frac{r_1}{g^{-1}(p_1)} \le w \le \frac{r_2}{g^{-1}(p_2)} \\ &\Rightarrow \frac{5}{g^{-1}(0.95)} \le w \le \frac{50}{g^{-1}(0.1)} \\ \end{array} g(c1)p1andg(c2)p2c1g1(p1)andc2g1(p2)wr1g1(p1)andwr2g1(p2)g1(p1)r1wg1(p2)r2g1(0.95)5wg1(0.1)50

       p 1 p_1 p1, p 2 p_2 p2 r 1 r_1 r1, r 2 r_2 r2可以根据应用精度和数据范围,人工估算。通过碰撞公式,完美的解决了w的取值问题,非常具有指导意义。但是,上述不等式不是永远成立的,部分 p 1 p_1 p1, p 2 p_2 p2 r 1 r_1 r1, r 2 r_2 r2的组合可能导致不等式下界大于上界。

    强化LSH函数

      强化LSH函数,可以不改变LSH算法的情况下,通过增加运算量,提高精度。假设有一个 ( r 1 , r 2 , p 1 , p 2 ) − s e n s i t i v e (r_1,r_2,p_1,p_2)-sensitive (r1,r2,p1,p2)sensitive函数族 F F F,可以通过逻辑与的方法构造一个新的函数族 F ′ F' F.

      假设 f ∈ F ′ f \in F' fF并且 f i ∈ F , i = 1 , 2 , ⋯ r f_i \in F, i = 1,2,\cdots r fiF,i=1,2,r f ( x ) = f ( y ) f(x)=f(y) f(x)=f(y) f i ( x ) = f i ( y ) , i = 1 , 2 , ⋯   , r f_i(x)=f_i(y),i = 1,2,\cdots,r fi(x)=fi(y),i=1,2,,r,此时 F ′ F' F ( r 1 , r 2 , p 1 r , p 2 r ) − s e n s i t i v e (r_1, r_2, p_1^r, p_2^r) - sensitive (r1,r2,p1r,p2r)sensitive。如果 p 2 p_2 p2较小 p 1 p_1 p1较大,可以通过此操作将其进一步的缩小,同时有不会将 p 1 p_1 p1变得太小。

      同理,可以使用逻辑或的方法,将其变成一个 ( r 1 , r 2 , 1 − ( 1 − p 1 ) r , 1 − ( 1 − p 2 ) r ) − s e n s i t i v e (r_1, r_2, 1-(1-p_1)^r, 1-(1-p_2)^r) - sensitive (r1,r2,1(1p1)r,1(1p2)r)sensitive。其效果与逻辑和相反,它将概率均变大。

      常用的方法是将逻辑与嵌套到逻辑或中使用,得到 ( r 1 , r 2 , 1 − ( 1 − p 1 k ) L , 1 − ( 1 − p 2 k ) L ) − s e n s i t i v e (r_1, r_2, 1-(1-p_1^k)^L, 1-(1-p_2^k)^L) - sensitive (r1,r2,1(1p1k)L,1(1p2k)L)sensitive函数族(先使用逻辑与,再使用逻辑和也可)。这种组合的意义是去掉那些碰巧hash到一起的情况,如果真的很近,在L组计算中,总有一组k个hash均相等。k和L需要设置合理,L如果设置太大,计算开销会增加。给定k,增强的概率比原来要好,即 ρ 1 ≤ 1 − ( 1 − p 1 k ) L , ρ 2 ≥ 1 − ( 1 − p 2 k ) L \rho_1 \le 1-(1-p_1^k)^L, \rho_2 \ge 1-(1-p_2^k)^L ρ11(1p1k)L,ρ21(1p2k)L,可以得到L的范围:

    ln ⁡ ( 1 − ρ 1 ) ln ⁡ ( 1 − p 1 k ) ≤ L ≤ ln ⁡ ( 1 − ρ 2 ) ln ⁡ ( 1 − p 2 k ) \frac{\ln{(1-\rho_1)}}{\ln{(1-p_1^k)}} \le L \le \frac{\ln{(1-\rho_2)}}{\ln{(1-p_2^k)}} ln(1p1k)ln(1ρ1)Lln(1p2k)ln(1ρ2)

      L取范围内最小的整数,节省空间。桶里,上面不等式不保证永远成立,条件太苛刻时需要调整。

    E2LSH的工作流程

      前面理论讲了很多,现在介绍LSH的工作流程。大体步骤分为两步:1)创建hash表;2)对象聚集。

    1. 创建hash表

      如果不使用增强hash函数,理论上只需要一个hash表即可。但是从实际应用来看,一个hash表要不够精确。最佳实践是创建 k × L k \times L k×L
    个hash表,每一个表的hash函数均是使用标准正太分布随机生成投影向量和均匀分布生成随机偏移量。每k个hash表为一组,称为一个“桶”,共L个桶。生成过程如下:

    图片名称

      由于随机的原因,可能有些比较近的对象在一个桶内会hash到不同的位置,但是我们给了L个桶,即L此碰撞机会。如果他们真的相似,总有可能在其他桶里面hash到同一个位置(该桶内所有k个hash值均相等)。

    1. 对象聚集

      hash表创建完毕后,只是给每个对象一堆 k × l k \times l k×l标记,实际上相似的对象并没有在一起。需要将这些标记,每k个合并为一个id,然后按照id聚合。生成id使用两段hash函数 h 1 h_1 h1, h 2 h_2 h2计算方法如下

    h 1 = a ∙ v   m o d   p   m o d   n h 2 = b ∙ v   m o d   p \begin{array}{l l l} h_1 = a \bullet v \bmod p \bmod n \\ h_2 = b \bullet v \bmod p \end{array} h1=avmodpmodnh2=bvmodp

      其中 a , b ∈ R k a,b \in R^k a,bRk,且 a i , b i a_i,b_i ai,bi是随机整数.p
    是一个很大的质素,通常 p = 2 32 − 5 p=2^{32}-5 p=2325;n是源数据条数。如果只使用1个hash函数,n较大时,冲撞的概率不可以忽略;但是如果使用两个hash函数,冲撞的概率基本可以忽略不计。整个过程示意图如下:

    图片名称

      合并后,同一个key下的所有对象就是比较近的对下。然后根据事先设定的相似度阀值,得到阀值以内的相似对象。

    展开全文
    sun361113244 2021-02-20 17:02:03
  • weixin_42584586 2021-02-04 20:32:26
  • fengdu78 2021-01-27 11:36:44
  • weixin_30550287 2021-02-03 04:47:43
  • weixin_42358173 2021-03-17 12:45:04
  • weixin_42510210 2021-03-07 14:33:43
  • qq_41221322 2021-11-16 19:14:15
  • weixin_39574708 2020-12-18 21:00:26
  • weixin_42397925 2021-02-07 20:29:13
  • weixin_33583401 2021-03-22 13:54:55
  • PaddlePaddle 2021-07-27 00:17:26
  • chenxy02 2021-12-16 16:31:06
  • choose_c 2021-02-22 21:03:00
  • weixin_39793813 2020-12-19 05:32:32
  • weixin_33877981 2021-02-07 03:19:11
  • weixin_30902943 2021-02-26 21:21:36
  • weixin_43758551 2021-02-21 15:24:18
  • weixin_33602725 2021-01-14 01:16:14
  • weixin_42131424 2020-12-28 23:50:02