5种Python文本相似度实战:从基础算法到深度语义匹配
文本相似度计算是自然语言处理中最实用的技术之一。想象一下,你正在开发一个智能客服系统,需要自动识别用户问题与知识库中已有问题的匹配程度;或者你正在构建一个内容推荐引擎,希望找到与用户刚阅读文章最相关的其他内容。这些场景的核心都是文本相似度计算。
1. 环境准备与数据集
在开始之前,我们需要准备一个标准数据集来评估不同算法的效果。对于中文文本相似度计算,LCQMC(Large-scale Chinese Question Matching Corpus)是一个不错的选择,它包含超过26万对中文问题对,每对都有是否相似的标注。
# 安装必要库 !pip install scikit-learn python-Levenshtein gensim sentence-transformers # 下载LCQMC数据集示例 import pandas as pd url = "https://raw.githubusercontent.com/liuhuanyong/ChineseSemanticKB/master/data/LCQMC_train.csv" data = pd.read_csv(url, sep='\t', names=['text1', 'text2', 'label']) sample_data = data.sample(5) # 随机选取5对示例 print(sample_data)常用评估指标:
- 准确率(Accuracy)
- F1分数
- 召回率(Recall)
- 计算时间(对于实时系统很重要)
2. 传统文本相似度方法
2.1 TF-IDF与余弦相似度
TF-IDF是文本处理中最经典的向量化方法,它考虑了词频(TF)和逆文档频率(IDF),能够有效降低常见词的权重。
from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity texts = ["我喜欢吃苹果", "苹果公司发布了新产品", "香蕉和苹果都是水果"] # 创建TF-IDF向量器 vectorizer = TfidfVectorizer() tfidf_matrix = vectorizer.fit_transform(texts) # 计算相似度矩阵 similarity_matrix = cosine_similarity(tfidf_matrix) print("TF-IDF余弦相似度矩阵:\n", similarity_matrix)适用场景:短文本快速匹配,如搜索引擎查询、文档去重等。
2.2 编辑距离(Levenshtein Distance)
编辑距离衡量两个字符串之间的最小编辑操作次数,适用于拼写检查、OCR纠错等场景。
from Levenshtein import distance str1 = "kitten" str2 = "sitting" edit_dist = distance(str1, str2) max_len = max(len(str1), len(str2)) similarity = 1 - (edit_dist / max_len) print(f"编辑距离: {edit_dist}") print(f"标准化相似度: {similarity:.2f}")注意:编辑距离对字符顺序敏感,"苹果手机"和"手机苹果"会被认为差异很大。
3. 词向量方法
3.1 Word2Vec平均词向量
Word2Vec能够捕捉词语的语义信息,我们可以通过平均词向量来表示整个句子。
import gensim.downloader as api import numpy as np # 加载预训练的中文Word2Vec模型 wv = api.load('word2vec-google-news-300') # 示例使用英文模型 def sentence_similarity(s1, s2): vec1 = np.mean([wv[word] for word in s1.split() if word in wv], axis=0) vec2 = np.mean([wv[word] for word in s2.split() if word in wv], axis=0) return np.dot(vec1, vec2) / (np.linalg.norm(vec1) * np.linalg.norm(vec2)) text1 = "king queen palace" text2 = "monarch castle" print(f"Word2Vec相似度: {sentence_similarity(text1, text2):.4f}")局限性:
- 无法处理OOV(词表外)词语
- 简单平均会丢失词序信息
- 对多义词处理不佳
3.2 使用Sentence-BERT进行语义匹配
Sentence-BERT(SBERT)通过孪生网络结构直接生成句子级别的嵌入表示,在语义相似度任务上表现优异。
from sentence_transformers import SentenceTransformer, util # 加载预训练的中文SBERT模型 model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2') sentences = [ "如何重置我的密码", "忘记密码怎么办", "今天的天气真好" ] # 编码所有句子 embeddings = model.encode(sentences) # 计算相似度 sim_matrix = util.cos_sim(embeddings, embeddings) print("SBERT相似度矩阵:\n", sim_matrix)性能对比:
| 方法 | 准确率 | 计算速度 | 内存占用 |
|---|---|---|---|
| TF-IDF | 中等 | 快 | 低 |
| 编辑距离 | 低 | 很快 | 很低 |
| Word2Vec | 中高 | 中等 | 中 |
| SBERT | 高 | 较慢 | 高 |
4. 实战:构建文本相似度系统
让我们整合这些方法,构建一个完整的文本相似度分析流程。
class TextSimilarityAnalyzer: def __init__(self, method='sbert'): self.method = method if method == 'sbert': self.model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2') elif method == 'tfidf': self.vectorizer = TfidfVectorizer() def fit(self, texts): if self.method == 'tfidf': self.vectorizer.fit(texts) def similarity(self, text1, text2): if self.method == 'sbert': emb1 = self.model.encode(text1) emb2 = self.model.encode(text2) return util.cos_sim(emb1, emb2).item() elif self.method == 'tfidf': vecs = self.vectorizer.transform([text1, text2]) return cosine_similarity(vecs[0], vecs[1])[0][0] elif self.method == 'edit': max_len = max(len(text1), len(text2)) return 1 - (distance(text1, text2) / max_len) # 使用示例 analyzer = TextSimilarityAnalyzer(method='sbert') text_a = "如何开通网上银行" text_b = "网上银行怎么申请" print(f"相似度得分: {analyzer.similarity(text_a, text_b):.2f}")优化技巧:
- 对于大规模数据,可以先使用TF-IDF快速筛选候选集,再用SBERT精细匹配
- 可以组合多种相似度得分进行加权融合
- 对于特定领域,可以微调SBERT模型
5. 高级应用与挑战
5.1 处理长文档相似度
对于长文档,直接使用上述方法效果可能不佳。常见的解决方案包括:
- 分块处理:将文档分成段落或句子,分别计算相似度后聚合
- 关键信息提取:先抽取关键词、主题或摘要,再计算相似度
- 层次化方法:先粗粒度匹配整体结构,再细粒度匹配具体内容
# 长文档分块示例 from nltk.tokenize import sent_tokenize def long_doc_similarity(doc1, doc2, model): # 分句 sents1 = sent_tokenize(doc1) sents2 = sent_tokenize(doc2) # 编码所有句子 emb1 = model.encode(sents1) emb2 = model.encode(sents2) # 计算所有句子对之间的相似度 sim_matrix = util.cos_sim(emb1, emb2) # 取最大相似度作为段落间相似度 max_sims = torch.max(sim_matrix, dim=1)[0] return torch.mean(max_sims).item()5.2 跨语言相似度计算
使用多语言模型如mBERT或XLM-R可以直接计算不同语言文本之间的相似度。
# 加载多语言模型 multi_model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2') chinese_text = "我喜欢编程" english_text = "I enjoy coding" # 计算跨语言相似度 emb1 = multi_model.encode(chinese_text) emb2 = multi_model.encode(english_text) similarity = util.cos_sim(emb1, emb2) print(f"中英文本相似度: {similarity.item():.2f}")在实际项目中,文本相似度的应用远不止于此。我曾经在一个电商项目中,使用SBERT结合用户行为数据来优化商品推荐系统,将点击率提升了15%。关键在于理解不同算法的特性,并根据具体场景选择合适的方案。