MinHash入门指南

发布时间:2023-05-22

一、什么是MinHash

MinHash是一种用于近似相等度量的技术,它被广泛应用于文本比较、网页去重、音乐相似度分析等领域。 在使用MinHash之前,先来了解一下Jaccard相似度。 Jaccard相似度是用来衡量两个集合相似度的指标,计算公式为:

J(A,B) = |A∩B| / |A∪B|

其中,A和B为两个集合,|A∩B|为两个集合的交集的元素个数,|A∪B|为两个集合的并集的元素个数。

二、MinHash的原理

MinHash是基于Locality Sensitive Hashing(局部敏感哈希LSH)的一种技术,它可以近似计算出两个集合的Jaccard相似度,且时间和空间复杂度都较小。 MinHash需要将集合中元素的哈希值分成多个小组,每组取一个最小的哈希值,将这些哈希值组成一个签名(签名的长度一般是一个固定值k),两个集合的相似度就可以通过它们的签名的相似度来近似计算。 通俗来讲,就是将两个集合的哈希值进行比较,如果两个哈希值相同,那么就把这个位置上的1替换成0,并记录这个位置,那么最后的结果就是两个集合在所有位置上相同的数量,除以签名的长度k即为它们的相似度近似值。

三、如何实现MinHash

Step1:生成哈希函数

MinHash需要用到多个哈希函数,每个哈希函数都可以将一个元素映射到0到1之间的一个随机值,可以采用MurMurHash、SHA-1等哈希函数。在这里,我们使用Python的hashlib库来生成哈希函数。

import hashlib
class MinHash(object):
    def __init__(self, k):
        self.k = k
        self.hashes = self._create_hashes()
    def _create_hashes(self):
        hashes = []
        for i in range(self.k):
            hashes.append(hashlib.sha1(str(i).encode('utf-8')))
        return hashes

Step2:生成签名

生成签名需要先将集合里的元素用哈希函数生成一个哈希值,然后选取一个最小的哈希值作为这个元素的签名,将所有元素的签名组成一个签名矩阵,每行代表一个元素的签名,每列代表一个哈希函数的结果。

def _sig(self, element):
    # 将元素用哈希函数映射为k个随机值,选取一个最小的值作为这个元素的签名
    sig = []
    for h in self.hashes:
        h.update(str(element).encode('utf-8'))
        sig.append(h.hexdigest())
    return min(sig)
def generate_signature_matrix(self, elements):
    # 生成元素的签名矩阵
    signature_matrix = []
    for element in elements:
        signature = [int(bit, 16) for bit in self._sig(element)]
        signature_matrix.append(signature)
    return signature_matrix

Step3:计算相似度

计算相似度需要先比较两个集合每个元素的签名,然后计算它们相同的元素数量,最后除以签名的长度即可得到相似度的近似值。

def jaccard_similarity(self, signature1, signature2):
    # 计算签名的Jaccard相似度
    count_same = sum([1 for i in range(len(signature1)) if signature1[i] == signature2[i]])
    count_all = len(signature1)
    return float(count_same) / float(count_all)
def minhash_similarity(self, set1, set2):
    # 计算集合的MinHash相似度
    signature_matrix1 = self.generate_signature_matrix(set1)
    signature_matrix2 = self.generate_signature_matrix(set2)
    similarities = []
    for i in range(len(signature_matrix1)):
        for j in range(len(signature_matrix2)):
            similarity = self.jaccard_similarity(signature_matrix1[i], signature_matrix2[j])
            similarities.append(similarity)
    return sum(similarities) / len(similarities)

四、应用案例

MinHash最初是用于文本去重的,现在被广泛应用于音乐相似度分析、推荐系统、网页去重等领域。

Case1:文本去重

文本去重是指在一定的数据范围内,将相似的文本取出来,一般是用于搜索引擎等领域。 假设有10000篇文章需要去重,要求相同文章不超过100篇,不同的文章要被归为不同的组别。那么可以使用MinHash对文章进行去重。首先,将每篇文章转换成set格式,然后利用MinHash计算集合的相似度,找出相似度大于等于0.8的文章,它们就归为同一组别。

import time
class Article(object):
    def __init__(self, id, title, content):
        self.id = id
        self.title = title
        self.content = content
        self.words = set(self.title.split() + self.content.split())
def read_data():
    # 读取数据,返回文章列表
    articles = []
    with open('articles.txt', 'r') as f:
        for line in f:
            id, title, content = line.strip().split('\t')
            article = Article(id, title, content)
            articles.append(article)
    return articles
def deduplication(articles):
    # 对文章进行去重
    minhash = MinHash(10)
    doc_signatures = []
    for article in articles:
        doc_signature = minhash.generate_signature_matrix(article.words)
        doc_signatures.append((article.id, doc_signature))
    groups = {}
    for i in range(len(doc_signatures)):
        id1, sig1 = doc_signatures[i]
        for j in range(i+1, len(doc_signatures)):
            id2, sig2 = doc_signatures[j]
            similarity = minhash.minhash_similarity(sig1, sig2)
            if similarity >= 0.8:
                group_i = groups.get(id1, set([id1]))
                group_i.add(id2)
                groups[id1] = group_i
                group_j = groups.get(id2, set([id2]))
                group_j.add(id1)
                groups[id2] = group_j
    deduplicated_articles = []
    for group in groups.values():
        if len(group) <= 100:
            articles_in_group = [article for article in articles if article.id in group]
            deduplicated_articles.extend(articles_in_group)
        else:
            # 如果一个组别超过100篇文章,将它们分成多个子组别
            sub_groups = []
            sub_group_i = set([list(group)[0]])
            group.remove(list(group)[0])
            for id in group:
                is_in_sub_group = False
                for sub_group in sub_groups:
                    if minhash.minhash_similarity(doc_signatures[sub_group[0]][1], 
                                                   doc_signatures[doc_signatures.index((id, doc_signatures[id][1]))][1]) >= 0.8:
                        sub_group.add(id)
                        is_in_sub_group = True
                        break
                if not is_in_sub_group:
                    sub_groups.append(set([id]))
            for sub_group in sub_groups:
                articles_in_group = [article for article in articles if article.id in sub_group]
                deduplicated_articles.extend(articles_in_group)
    return deduplicated_articles
if __name__ == '__main__':
    start_time = time.time()
    articles = read_data()
    deduplicated_articles = deduplication(articles)
    print('文章数量:{}'.format(len(deduplicated_articles)))
    print('用时:{}s'.format(time.time() - start_time))

Case2:音乐相似度分析

音乐相似度分析是指在海量音乐数据中,根据用户的听歌记录或者上传音乐的音频特征提取,找出相似的音乐,用于音乐推荐、音乐搜索等领域。 首先,使用spectral clustering或者kmeans等聚类算法将相似的音乐聚到一起,然后针对每个聚类,使用MinHash计算相似度,将相似度大于等于0.8的音乐归为同一组别。

五、总结

MinHash是一种高效的近似相等度量技术,它可以广泛用于文本去重、音乐相似度分析、推荐系统等领域。在使用MinHash时,需要先生成哈希函数、计算元素的签名、比较签名、计算相似度。通过应用案例,可以看到MinHash在实际场景中有着广泛的应用。