文本字面相似度算法回顾整理

概述: 通常大家说的文本相似度多指文本语义的相似度,但是对于部分场景却会非常关注文本字面上的相似度,比如对于内容投放平台需要去判重,这就涉及看文本是否字面大量重复,需要去比较文本的字面相似度。 

梳理了下常用的字面相似度算法: 

文本字面相似度包括:

  • Simhash
  • 序列相似度(Sequence Similarity)
  • Jaccard相似度
  • 余弦相似度(Cosine Similarity)
  • Levenshtein距离
  • N-gram相似度

各种算法的概述: 

1、Simhash相似度
  • 主要思想: Simhash是一种局部敏感哈希算法,旨在快速检测近似重复的文档。它将长文本或文档转换为固定长度的指纹(通常是64位整数),使得相似的文档会产生相似的指纹。SimHash算法是Google在2007年发表的论文《Detecting Near-Duplicates for Web Crawling》中提到的一种指纹生成算法,被应用在Google搜索引擎网页去重的工作之中。

实现步骤:

  • 将文档分割成特征(如词或n-gram)
  • 对每个特征计算哈希值
  • 对每个哈希值的每一位进行加权
  • 合并所有特征的加权结果
  • 对合并结果进行阈值化,得到最终的指纹

应用场景: 

  • 网页去重
  • 文档相似度检测
  • 抄袭检测

对于文本去重这个问题,常见的解决办法有余弦算法、欧式距离、Jaccard相似度、最长公共子串等方法。但是这些方法并不能对海量数据高效的处理。
比如说,在搜索引擎中,会有很多相似的关键词,用户所需要获取的内容是相似的,但是搜索的关键词却是不同的,如“北京好吃的火锅“和”哪家北京的火锅好吃“,是两个可以等价的关键词,然而通过普通的hash计算,会产生两个相差甚远的hash串。而通过SimHash计算得到的Hash串会非常的相近,从而可以判断两个文本的相似程度。

通过对不同文本的SimHash值进而比较海明距离,从而判断两个文本的相似度。海明距离越小,相似度越低(根据 Detecting Near-Duplicates for Web Crawling 论文中所说),一般海明距离为3就代表两篇文章相同。

什么是海明距离呢?

简单的说,海明距离(Hamming distance)可以理解为,两个二进制串之间相同位置不同的个数。

举个例子,[1,1,1,0,0,0]和[1,1,1,1,1,1]的海明距离就是3。

在处理大规模数据的时候,我们一般使用64位的SimHash,正好可以被一个long型存储。这种时候,海明距离在3以内就可以认为两个文本是相似的。

实现代码: 

from simhash import Simhash
def simhash_similarity(text1, text2):
    """计算两个文本的Simhash相似度"""
    hash1 = Simhash(get_features(text1))
    hash2 = Simhash(get_features(text2))
    distance = hash1.distance(hash2)
    return 1 - (distance / 64)  # 64

2、序列相似度(基于最长公共子序列LCS)

主要思想: 通过找到两个序列中最长的公共子序列来衡量它们的相似度。子序列不需要连续,但必须保持原始顺序。

实现步骤:

  1. 构建一个矩阵来存储两个序列的所有子问题的解
  2. 填充矩阵,每个单元格表示到当前位置的LCS长度
  3. 回溯矩阵以构建最长公共子序列
  4. 计算相似度:LCS长度 / max(序列1长度, 序列2长度)

应用场景:

  • 文本比较和差异检测
  • 生物信息学中的DNA序列比对
  • 版本控制系统中的文件比较

实现代码: 

from difflib import SequenceMatcher
def sequence_similarity(text1, text2):
    """计算两个文本的序列相似度"""
    matcher = SequenceMatcher(None, text1, text2)
    return matcher.ratio()

3、Jaccard相似度

主要思想: 衡量两个集合的相似度,通过计算它们交集的大小除以并集的大小。

实现步骤:

  1. 将文本转换为集合(如单词集或字符集)
  2. 计算两个集合的交集
  3. 计算两个集合的并集
  4. 相似度 = 交集大小 / 并集大小

应用场景:

  • 文档相似度计算
  • 推荐系统
  • 聚类分析

实现代码

def jaccard_similarity(text1, text2):
    """计算两个文本的Jaccard相似度"""
    set1 = set(text1)
    set2 = set(text2)
    intersection = set1.intersection(set2)
    union = set1.union(set2)
    return len(intersection) / len(union)
4、余弦相似度

主要思想: 将文本表示为向量空间中的点,然后计算这些向量之间的夹角余弦值来衡量相似度。

实现步骤:

  1. 将文本转换为词频向量
  2. 计算两个向量的点积
  3. 计算每个向量的模长
  4. 相似度 = 点积 / (向量1模长 * 向量2模长)

应用场景:

  • 信息检索
  • 文本分类
  • 推荐系统

代码: 

from collections import Counter
def cosine_similarity(text1, text2):
    """计算两个文本的余弦相似度"""
    vec1 = Counter(text1)
    vec2 = Counter(text2)
    intersection = set(vec1.keys()) & set(vec2.keys())
    numerator = sum([vec1[x] * vec2[x] for x in intersection])
    sum1 = sum([vec1[x]**2 for x in vec1.keys()])
    sum2 = sum([vec2[x]**2 for x in vec2.keys()])
    denominator = np.sqrt(sum1) * np.sqrt(sum2)
    if not denominator:
        return 0.0
    return numerator / denominator

5、Levenshtein距离

主要思想: 计算将一个字符串转换为另一个字符串所需的最小编辑操作(插入、删除、替换)次数。

实现步骤:

  1. 创建一个矩阵来存储子问题的解
  2. 初始化第一行和第一列
  3. 填充矩阵,每个单元格表示到当前位置的最小编辑距离
  4. 矩阵右下角的值即为Levenshtein距离

应用场景:

  • 拼写检查
  • DNA序列比对
  • 模糊字符串匹配

代码实现: 

def levenshtein_distance(s1, s2):
    """计算两个字符串的Levenshtein距离"""
    if len(s1) < len(s2):
        return levenshtein_distance(s2, s1)
    if len(s2) == 0:
        return len(s1)
    previous_row = range(len(s2) + 1)
    for i, c1 in enumerate(s1):
        current_row = [i + 1]
        for j, c2 in enumerate(s2):
            insertions = previous_row[j + 1] + 1
            deletions = current_row[j] + 1
            substitutions = previous_row[j] + (c1 != c2)
            current_row.append(min(insertions, deletions, substitutions))
        previous_row = current_row
    return previous_row[-1]

def levenshtein_similarity(text1, text2):
    """基于Levenshtein距离计算相似度"""
    distance = levenshtein_distance(text1, text2)
    max_length = max(len(text1), len(text2))
    return 1 - (distance / max_length)

def sliding_window_similarity(text1, text2):
    """使用滑动窗口计算子集相似度"""
    try:
        # 动态选择window_size为较短文本的长度
        window_size = min(len(text1), len(text2))
        
        if len(text1) < len(text2):
            text1, text2 = text2, text1
        
        best_similarity = 0
        for i in range(len(text1) - window_size + 1):
            window = text1[i:i+window_size]
            similarity = 1 - levenshtein_distance(window, text2) / max(len(window), len(text2))
            best_similarity = max(best_similarity, similarity)
        
        return best_similarity
    except Exception as e:
        logger.error(f"Error calculating sliding_window_similarity Levenshtein similarity: {str(e)}")
        raise

6、N-gram相似度

主要思想: 将文本分割成连续的n个字符(或词)的序列,然后比较这些序列的重合程度。

实现步骤:

  1. 将文本分割成n-gram
  2. 计算两个文本的n-gram集合的交集
  3. 计算两个文本的n-gram集合的并集
  4. 相似度 = 交集大小 / 并集大小

应用场景:

  • 语言识别
  • 拼写检查
  • 文本分类

代码实现: 

def ngram_similarity(text1, text2, n=3):
    """计算n-gram相似度"""
    def get_ngrams(text, n):
        return [text[i:i+n] for i in range(len(text)-n+1)]
    ngrams1 = get_ngrams(text1, n)
    ngrams2 = get_ngrams(text2, n)
    common = set(ngrams1) & set(ngrams2)
    unique = set(ngrams1) | set(ngrams2)
    return len(common) / len(unique)

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值