Python相似度函数:从文本到向量,全面解析与实践指南260

 

 

在数据驱动的时代,我们经常需要衡量不同数据点之间的“相似”或“不相似”程度。无论是推荐系统中的用户兴趣匹配,自然语言处理中的文本抄袭检测,图像识别中的特征比对,还是基因序列分析中的序列对齐,相似度计算都是其核心基石。Python作为一门功能强大且生态系统丰富的编程语言,提供了多种库和方法来实现各类相似度函数。

本文将作为一份详尽的指南,深入探讨Python中常用的相似度计算方法,涵盖从字符串、文本到数值向量和集合的多种数据类型,并提供具体的代码示例,帮助您理解其原理并应用于实际项目中。

 

一、相似度函数的重要性与应用场景

 

相似度函数的核心在于将“相似性”这一抽象概念量化,通常以一个介于0到1(或0到正无穷)的数值表示。值越大通常代表越相似(如余弦相似度),或值越小代表越相似(如距离度量)。

其应用场景无处不在:
自然语言处理 (NLP):文本查重、语义相似度分析、信息检索、问答系统、机器翻译评估。
推荐系统:用户-商品相似度、商品-商品相似度,实现个性化推荐。
计算机视觉:图像检索、人脸识别、物体识别中的特征匹配。
数据挖掘与机器学习:聚类分析、异常检测、分类器中的近邻算法(K-NN)。
生物信息学:DNA/RNA序列比对、蛋白质结构相似性分析。
数据清洗与质量:去重、实体匹配、拼写检查。

 

二、文本相似度函数

 

文本相似度是应用最广泛的领域之一。根据对比的粒度(字符、词语、语义),文本相似度函数可以分为多种。

2.1 基于字符/字符串的相似度


 

这类方法主要关注字符串本身的结构和编辑距离。

2.1.1 编辑距离 (Edit Distance / Levenshtein Distance)


 

编辑距离是指将一个字符串转换为另一个字符串所需的最少单字符编辑操作(插入、删除、替换)次数。距离越小,字符串越相似。它非常适合拼写检查、模糊匹配等场景。

Python实现:`fuzzywuzzy`库(基于`python-Levenshtein`)提供了高效的实现。from fuzzywuzzy import fuzz
s1 = "kitten"
s2 = "sitting"
s3 = "sittin"
# 纯编辑距离的相似度分数(0-100)
print(f"Levenshtein相似度 ('{s1}', '{s2}'): {(s1, s2)}") # 输出:74
print(f"Levenshtein相似度 ('{s2}', '{s3}'): {(s2, s3)}") # 输出:92
# 局部匹配:找到两个字符串中最相似的子串
s4 = "The quick brown fox"
s5 = "quick brown dog"
print(f"局部相似度 ('{s4}', '{s5}'): {fuzz.partial_ratio(s4, s5)}") # 输出:89
# 词语排序匹配:忽略词序,考虑词语集合的相似度
s6 = "this is a test"
s7 = "test is this a"
print(f"词序无关相似度 ('{s6}', '{s7}'): {fuzz.token_sort_ratio(s6, s7)}") # 输出:100
# 词语集合匹配:考虑共同词语和不共同词语,更适用于包含噪音的情况
s8 = "fuzzy wuzzy was a bear"
s9 = "wuzzy fuzzy bear"
print(f"词集相似度 ('{s8}', '{s9}'): {fuzz.token_set_ratio(s8, s9)}") # 输出:100

2.1.2 汉明距离 (Hamming Distance)


 

汉明距离用于衡量两个等长字符串之间对应位置上不同字符的个数。常用于二进制编码或等长序列的错误检测。def hamming_distance(s1, s2):
if len(s1) != len(s2):
raise ValueError("Undefined for strings of unequal length")
return sum(c1 != c2 for c1, c2 in zip(s1, s2))
print(f"汉明距离 ('karolin', 'kathrin'): {hamming_distance('karolin', 'kathrin')}") # 输出:3
print(f"汉明距离 ('1011101', '1001001'): {hamming_distance('1011101', '1001001')}") # 输出:2

2.1.3 最长公共子序列 (Longest Common Subsequence, LCS)


 

LCS是两个序列中,最长的、以相同相对顺序出现但不必连续的子序列。LCS的长度或基于LCS的相似度(如LCS长度 / 两个字符串平均长度)可以用来衡量字符串相似性。

Python实现:`difflib`模块提供了`SequenceMatcher`来计算LCS相关度。from difflib import SequenceMatcher
s1 = "AGGTAB"
s2 = "GXTXAYB"
matcher = SequenceMatcher(None, s1, s2)
# ratio() 返回相似度分数,范围在0到1之间
print(f"LCS相似度 ('{s1}', '{s2}'): {()}") # 输出:0.571428...

2.2 基于词语/词袋模型 (Bag-of-Words, BoW) 的相似度


 

这类方法通常将文本分解成词语,然后将文本表示为词频向量,再计算这些向量之间的相似度。

2.2.1 Jaccard 相似度 (Jaccard Index)


 

Jaccard相似度是衡量两个集合之间相似性的指标,定义为两个集合交集的大小除以并集的大小。对于文本,我们需要先将文本分词,得到词语集合。def jaccard_similarity(text1, text2):
set1 = set(().split()) # 转换为小写并分词
set2 = set(().split())

intersection = len((set2))
union = len((set2))

if union == 0: # 避免除以零
return 0.0
return intersection / union
t1 = "Python is a powerful programming language"
t2 = "Python is an easy and powerful language"
t3 = "Java is a programming language"
print(f"Jaccard相似度 ('{t1}', '{t2}'): {jaccard_similarity(t1, t2)}") # 输出:0.625
print(f"Jaccard相似度 ('{t1}', '{t3}'): {jaccard_similarity(t1, t3)}") # 输出:0.375

2.2.2 余弦相似度 (Cosine Similarity)


 

余弦相似度通过计算两个向量在多维空间中的夹角余弦值来衡量它们的相似性。夹角越小,余弦值越大,表示越相似。在文本分析中,文本通常通过TF-IDF(词频-逆文档频率)或其他向量化方法转换为向量。

Python实现:`scikit-learn`库提供了强大的文本向量化和余弦相似度计算功能。from import TfidfVectorizer
from import cosine_similarity
documents = [
"Python is a popular programming language",
"Python is widely used for data science",
"Java is another popular programming language",
"Machine learning with Python is exciting"
]
# 1. 文本向量化 (TF-IDF)
vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(documents)
# print(vectorizer.get_feature_names_out()) # 查看词汇表
# print(()) # 查看TF-IDF矩阵
# 2. 计算余弦相似度
# 计算所有文档两两之间的相似度
cosine_sim_matrix = cosine_similarity(tfidf_matrix, tfidf_matrix)
print("文档之间的余弦相似度矩阵:", cosine_sim_matrix)
# 示例:第一个文档与所有文档的相似度
doc0_similarities = cosine_sim_matrix[0]
print(f"文档'{documents[0]}'与所有文档的相似度: {doc0_similarities}")
# 比较自定义新文本与现有文档的相似度
new_doc = ["I love programming in Python"]
new_doc_tfidf = (new_doc)
new_doc_similarities = cosine_similarity(new_doc_tfidf, tfidf_matrix)
print(f"新文档'{new_doc[0]}'与现有文档的相似度: {new_doc_similarities}")

2.3 语义相似度


 

前述方法主要关注词法和语法层面,而语义相似度旨在捕捉词语或文本的深层含义。这通常通过词嵌入(Word Embeddings)或预训练语言模型(如BERT、GPT)来实现。

2.3.1 词嵌入 (Word Embeddings) + 余弦相似度


 

词嵌入将每个词映射到一个低维连续向量空间,使得语义相似的词在向量空间中距离也近。Word2Vec、GloVe、FastText是常见的词嵌入模型。对于句子或文档,可以将其词向量平均或加权平均得到句向量,再计算句向量之间的余弦相似度。

Python实现:`gensim`库用于Word2Vec等,或使用`spaCy`、`transformers`加载预训练模型。# 示例:使用预训练的spaCy模型计算语义相似度
import spacy
# 需要先下载英文模型:python -m spacy download en_core_web_md
# 或中文模型:python -m spacy download zh_core_web_md
nlp = ("en_core_web_md")
doc1 = nlp("apple is a fruit")
doc2 = nlp("orange is a fruit")
doc3 = nlp("apple is a company")
doc4 = nlp("banana is a yellow fruit")
# 使用()直接计算,其内部通常是向量余弦相似度
print(f"语义相似度 ('{}', '{}'): {(doc2)}") # 较高
print(f"语义相似度 ('{}', '{}'): {(doc3)}") # 较低
print(f"语义相似度 ('{}', '{}'): {(doc4)}") # 较高

对于更复杂的语义匹配,如句子或段落级别的相似度,可以利用Sentence-BERT或更先进的Transformer模型获取高质量的句子嵌入,再计算余弦相似度。

 

三、数值向量相似度/距离函数

 

在许多机器学习任务中,数据被表示为数值向量。衡量这些向量之间的相似性(或距离)至关重要。

3.1 欧几里得距离 (Euclidean Distance)


 

欧几里得距离是多维空间中两点之间直线距离的泛化。距离越小,点越相似。在数据未标准化时,受量纲影响较大。

公式:$d(P, Q) = \sqrt{\sum_{i=1}^{n}(P_i - Q_i)^2}$

Python实现:``或``。import numpy as np
from import euclidean
from import euclidean_distances
v1 = ([1, 2, 3])
v2 = ([4, 5, 6])
v3 = ([1.1, 2.1, 3.1])
print(f"欧几里得距离 ('{v1}', '{v2}'): {euclidean(v1, v2)}") # 输出:5.196...
print(f"欧几里得距离 ('{v1}', '{v3}'): {euclidean(v1, v3)}") # 输出:0.173...
# sklearn计算矩阵形式的距离
vectors = ([[1, 2, 3], [4, 5, 6], [1.1, 2.1, 3.1]])
print("欧几里得距离矩阵:", euclidean_distances(vectors))

3.2 曼哈顿距离 (Manhattan Distance / City Block Distance)


 

曼哈顿距离是两点在各坐标轴上距离的绝对值之和。想象在城市中沿着街区行走,不能斜穿。同样,距离越小越相似。

公式:$d(P, Q) = \sum_{i=1}^{n}|P_i - Q_i|$

Python实现:``。from import cityblock
v1 = ([1, 2, 3])
v2 = ([4, 5, 6])
print(f"曼哈顿距离 ('{v1}', '{v2}'): {cityblock(v1, v2)}") # 输出:9.0 (即 (4-1) + (5-2) + (6-3) = 3+3+3)

3.3 余弦相似度 (Cosine Similarity)


 

对于数值向量,余弦相似度衡量的是向量方向的相似性,而非大小。它对向量的长度不敏感,通常在处理高维稀疏数据(如TF-IDF向量)时表现良好。

公式:$similarity(A, B) = \frac{A \cdot B}{||A|| \cdot ||B||}$

Python实现:``(返回距离,1-相似度)或`.cosine_similarity`(返回相似度)。from import cosine
from import cosine_similarity
v1 = ([1, 2, 3])
v2 = ([4, 5, 6])
v4 = ([2, 4, 6]) # 与v1方向相同,但长度是两倍
# 返回余弦距离 (1 - 相似度)
print(f"余弦距离 ('{v1}', '{v2}'): {cosine(v1, v2)}") # 输出:0.027...
print(f"余弦距离 ('{v1}', '{v4}'): {cosine(v1, v4)}") # 输出:0.0 (因为方向完全相同)
# .cosine_similarity 返回余弦相似度
print(f"余弦相似度 ('{v1}', '{v2}'): {cosine_similarity((1, -1), (1, -1))[0][0]}") # 输出:0.972...
print(f"余弦相似度 ('{v1}', '{v4}'): {cosine_similarity((1, -1), (1, -1))[0][0]}") # 输出:1.0

3.4 闵可夫斯基距离 (Minkowski Distance)


 

闵可夫斯基距离是欧几里得距离和曼哈顿距离的推广。

公式:$d(P, Q) = (\sum_{i=1}^{n}|P_i - Q_i|^p)^{\frac{1}{p}}$
当 $p=1$ 时,为曼哈顿距离。
当 $p=2$ 时,为欧几里得距离。
当 $p \to \infty$ 时,为切比雪夫距离 (Chebyshev Distance),即各维度最大差值。

Python实现:``。from import minkowski
v1 = ([1, 2, 3])
v2 = ([4, 5, 6])
print(f"闵可夫斯基距离 (p=1, 曼哈顿) ('{v1}', '{v2}'): {minkowski(v1, v2, p=1)}") # 输出:9.0
print(f"闵可夫斯基距离 (p=2, 欧几里得) ('{v1}', '{v2}'): {minkowski(v1, v2, p=2)}") # 输出:5.196...
print(f"闵可夫斯基距离 (p=inf, 切比雪夫) ('{v1}', '{v2}'): {minkowski(v1, v2, p=)}") # 输出:3.0 (max(|4-1|, |5-2|, |6-3|))

 

四、集合相似度函数

 

集合相似度用于比较两个集合中元素的重叠程度。

4.1 Jaccard 相似度 (Jaccard Index)


 

如前文所述,Jaccard相似度是交集大小除以并集大小。它在推荐系统(用户共同喜好)、基因分析(基因共同表达)等场景中非常有用。def jaccard_similarity_sets(set1, set2):
intersection = len((set2))
union = len((set2))
if union == 0:
return 0.0
return intersection / union
set_a = {'apple', 'banana', 'orange', 'grape'}
set_b = {'apple', 'orange', 'kiwi', 'mango'}
set_c = {'strawberry', 'blueberry'}
print(f"Jaccard相似度 ('{set_a}', '{set_b}'): {jaccard_similarity_sets(set_a, set_b)}") # 输出:0.333... (交集:{'apple', 'orange'}, 并集:{'apple', 'banana', 'orange', 'grape', 'kiwi', 'mango'})
print(f"Jaccard相似度 ('{set_a}', '{set_c}'): {jaccard_similarity_sets(set_a, set_c)}") # 输出:0.0

4.2 Sørensen-Dice 系数 (Dice Coefficient)


 

Sørensen-Dice系数(通常简称为Dice系数)与Jaccard相似,但其分母是两个集合大小之和,对交集给予了双倍权重。它在图像分割的评估中很常见。

公式:$similarity(A, B) = \frac{2 |A \cap B|}{|A| + |B|}$def dice_similarity(set1, set2):
intersection = len((set2))
sum_cardinalities = len(set1) + len(set2)
if sum_cardinalities == 0:
return 0.0
return (2 * intersection) / sum_cardinalities
set_a = {'apple', 'banana', 'orange', 'grape'}
set_b = {'apple', 'orange', 'kiwi', 'mango'}
print(f"Dice相似度 ('{set_a}', '{set_b}'): {dice_similarity(set_a, set_b)}") # 输出:0.5 (交集:{'apple', 'orange'}, len(set_a)=4, len(set_b)=4)

 

五、选择合适的相似度函数

 

没有哪个相似度函数是万能的,选择合适的函数取决于您的数据类型、问题背景和对“相似”的定义:
数据类型:是文本、数值向量还是集合?
数据特点:

文本长度/结构:字符串相似度(编辑距离、LCS)适用于短字符串、拼写纠错;基于词袋/TF-IDF的余弦相似度适用于长文档、语义匹配;语义嵌入适用于更深层次的含义捕捉。
数值向量的量纲和分布:欧几里得距离对数值大小敏感,需要标准化;余弦相似度对向量长度不敏感,更关注方向,适合高维稀疏数据。
集合大小和重叠:Jaccard和Dice系数衡量集合元素的重叠程度。


对“相似”的定义:

是想找出拼写相近的词(编辑距离)?
是想找到主题相似的文档(余弦相似度 with TF-IDF/embeddings)?
是想比较两个用户兴趣的重叠度(Jaccard)?
是想衡量两个数据点在几何空间中的物理距离(欧几里得距离)?


计算效率:对于大规模数据集,性能是一个重要考量。某些复杂的语义模型计算成本较高。

 

六、总结

 

Python凭借其丰富的科学计算库,为相似度计算提供了强大的支持。从基础的字符串编辑距离到复杂的语义嵌入,各种工具和方法应有尽有。理解不同相似度函数的原理、适用场景及其在Python中的实现方式,是成为一名优秀数据科学家或AI工程师的关键技能。

在实际项目中,往往需要结合数据预处理(如文本分词、停用词去除、词形还原、数值标准化)、特征工程和多次实验,才能找到最适合您特定需求的相似度计算方案。不断探索和实践,将使您在处理各种数据相似性问题时游刃有余。

2026-03-12


下一篇:GVim高效运行Python代码:从入门到进阶实践与配置