NLP学习笔记07:文本相似度计算------从 TF-IDF 到 BERT
作者:Ye Shun
日期:2026-04-17
一、前言
文本相似度计算是自然语言处理(NLP)中的一项基础任务,它的目标是量化两个文本片段之间有多相似。这里的"相似"既可以指表面词汇上的重叠,也可以指语义层面的接近程度。
例如,下面两句话:
- "苹果发布新款 iPhone 手机"
- "苹果公司推出最新智能手机"
虽然它们的措辞并不完全相同,但表达的主题高度接近,因此可以认为它们具有较高的文本相似度。
文本相似度在很多系统里都是基础能力,例如:
- 搜索排序
- 问答匹配
- 抄袭检测
- 推荐系统
- 文本去重
- 相似新闻聚合
这篇笔记将围绕以下几个问题展开:
- 什么是文本相似度,它有哪些常见类型
- 常见的相似度计算方法有哪些
- 余弦相似度、欧氏距离等指标分别适合什么场景
- 如何通过示例理解短文本相似度计算
- 在实际应用中应该如何选择方法
二、核心概念
在讨论方法之前,先要分清文本相似度中几个容易混淆的概念。
1. 字面相似度
字面相似度关注文本在表层词汇上的重叠程度。例如两个句子用了很多相同的词,那么它们的字面相似度通常较高。
这种方法的优点是简单、计算快,但缺点也很明显:它往往无法真正理解语义。
2. 语义相似度
语义相似度关注文本表达的含义是否接近,而不只是看词是不是一样。
例如:
- "我喜欢自然语言处理"
- "我很爱学习 NLP 技术"
从词面看,两句话不完全相同;但从语义上看,它们都表达了对 NLP 学习的积极态度,因此语义相似度较高。
3. 向量空间模型
很多文本相似度方法的本质,都是先把文本转换成向量,再在向量空间中计算"距离"或"相似度"。
也就是说,文本相似度通常分两步:
- 文本表示
- 向量之间的相似度计算
4. 距离与相似度
需要注意,"距离"和"相似度"并不完全一样:
- 距离越小,通常表示越接近
- 相似度越大,通常表示越接近
两者可以互相转换,但语义上方向不同。
三、常用文本相似度计算方法
从方法演进上看,文本相似度大致可以分为三类:
- 基于词频的传统方法
- 基于词向量的方法
- 基于预训练语言模型的方法
1. 基于词频的方法
这类方法的核心特点是:先统计词在文本中的出现情况,再据此构造向量。
词袋模型(Bag of Words)
词袋模型把文本看成词的集合,不考虑顺序,只统计词出现与否或出现次数。
示例:
python
from sklearn.feature_extraction.text import CountVectorizer
corpus = [
"我 喜欢 自然语言处理",
"我 爱 学习 NLP 技术",
"文本 相似度 计算 很 有趣"
]
vectorizer = CountVectorizer(token_pattern=r"(?u)\b\w+\b")
X = vectorizer.fit_transform(corpus)
print(X.toarray())
这种方法简单直观,但它的问题也很明显:
- 忽略词序
- 忽略语义
- 向量维度高且稀疏
TF-IDF 方法
TF-IDF 是词袋模型的改进版。它不仅考虑词频,还考虑一个词在整个语料中的区分能力。
如果一个词在所有文本里都频繁出现,那么它对区分文本的帮助就较小;而某些只在少数文本中出现的词,往往更能反映文本特点。
示例:
python
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(token_pattern=r"(?u)\b\w+\b")
tfidf_matrix = tfidf.fit_transform(corpus)
print(tfidf_matrix.toarray())
TF-IDF 在信息检索、长文档相似度计算、文本去重等任务中仍然非常常见,因为它简单、高效,而且在很多场景下效果并不差。
2. 基于词向量的方法
传统词频方法能反映词重叠,但难以表示真正的语义相似。词向量方法的目标,就是让语义相近的词在向量空间中也更接近。
Word2Vec
Word2Vec 是经典的词向量方法,它通过上下文学习词语的分布式表示。
示例:
python
from gensim.models import Word2Vec
sentences = [
["我", "喜欢", "自然语言处理"],
["我", "爱", "学习", "NLP", "技术"],
["文本", "相似度", "计算", "很", "有趣"]
]
model = Word2Vec(sentences, vector_size=100, window=5, min_count=1, workers=4)
vector = model.wv["自然语言处理"]
相比词袋模型,词向量的优势在于:
- 维度更低
- 向量更稠密
- 能表达一定的语义接近性
句子向量
如果要比较两句话,仅有词向量还不够,通常还需要把整句话表示成一个向量。最简单的方法就是对句中所有词向量取平均。
示例:
python
import numpy as np
def sentence_vector(sentence, model):
vectors = [model.wv[word] for word in sentence if word in model.wv]
return np.mean(vectors, axis=0) if vectors else np.zeros(model.vector_size)
这种方式实现简单,但问题在于:
- 它把词的重要性看得差不多
- 无法很好建模词序
- 对一词多义处理有限
3. 基于预训练模型的方法
随着预训练语言模型的发展,文本相似度计算已经越来越多地依赖 BERT、Sentence-BERT 等模型。
BERT 相似度
BERT 的核心优势在于它能利用上下文生成动态表示,因此更适合处理语义相似度,而不只是词面重叠。
例如:
python
from transformers import BertTokenizer, BertModel
import torch
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
model = BertModel.from_pretrained("bert-base-chinese")
inputs = tokenizer("这是一个示例句子", return_tensors="pt")
outputs = model(**inputs)
last_hidden_states = outputs.last_hidden_state
不过,严格来说,直接拿 BERT 的输出做句子相似度并不是最优实践。现在更常见的做法是使用专门做句向量优化的模型,例如 Sentence-BERT。
为什么预训练模型更强
预训练模型的优势在于:
- 能更好处理同义表达
- 能缓解一词多义问题
- 对短文本语义匹配更有效
但代价是:
- 计算成本更高
- 部署复杂度更高
四、常见相似度度量指标
当文本已经被表示成向量之后,下一步就是计算两个向量之间的接近程度。常见指标如下:
| 方法名称 | 公式思路 | 特点 |
|---|---|---|
| 余弦相似度 | 比较向量方向夹角 | 忽略长度,最常用 |
| 欧氏距离 | 比较两点的直线距离 | 关注绝对位置差异 |
| 曼哈顿距离 | 各维绝对差之和 | 对异常值相对稳定 |
| Jaccard 相似度 | 交集 / 并集 | 适合集合或关键词重叠 |
1. 余弦相似度
余弦相似度是文本相似度中最常见的指标。它比较的是两个向量的方向,而不是绝对长度。
python
from sklearn.metrics.pairwise import cosine_similarity
similarity = cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:2])
print(f"文本相似度: {similarity[0][0]:.4f}")
之所以常用,是因为很多文本向量本身长度差异较大,而我们更关心它们表达方向是否一致。
2. 欧氏距离与曼哈顿距离
这两类指标更偏"距离"思路,适合在某些嵌入空间或数值特征空间中做比较。但在文本任务里,相比余弦相似度,它们通常没那么常用。
3. Jaccard 相似度
Jaccard 相似度更适合比较两个集合之间的重叠程度,比如两个文本分词后的词集合有多少交集。
它的优点是简单直观,但缺点也很明显:对语义理解能力弱。
五、实践应用示例:新闻标题相似度检测
假设有一组新闻标题:
python
titles = [
"苹果发布新款 iPhone 手机",
"苹果公司推出最新智能手机",
"微软公布季度财报",
"谷歌宣布新的人工智能计划"
]
如果使用 TF-IDF + 余弦相似度计算相似度矩阵,就可能得到类似结果:
| 标题 | 苹果发布新款 iPhone 手机 | 苹果公司推出最新智能手机 | 微软公布季度财报 | 谷歌宣布新的人工智能计划 |
|---|---|---|---|---|
| 苹果发布新款 iPhone 手机 | 1.000000 | 0.723417 | 0.000000 | 0.000000 |
| 苹果公司推出最新智能手机 | 0.723417 | 1.000000 | 0.000000 | 0.000000 |
| 微软公布季度财报 | 0.000000 | 0.000000 | 1.000000 | 0.204598 |
| 谷歌宣布新的人工智能计划 | 0.000000 | 0.000000 | 0.204598 | 1.000000 |
从这个例子可以看出:
- 前两条标题主题接近,因此相似度高
- 与微软和谷歌相关的标题主题差异较大,因此相似度低
这类方法非常适合:
- 相似新闻聚类
- 标题去重
- 内容推荐初筛
六、进阶技术与挑战
文本相似度看起来像一个简单任务,但真正做好并不容易。
1. 语义相似但词汇不同
例如:
- "我喜欢猫"
- "我讨厌狗"
表面上它们都涉及动物,因此某些浅层方法可能误判为相似;但从情感和语义角度看,它们表达的意思并不接近。
2. 一词多义问题
例如:
- "苹果很甜"
- "苹果市值创新高"
这里"苹果"一个表示水果,一个表示公司。传统词频方法很难处理这种情况,而上下文模型会更有优势。
3. 长文本相似度
对于长文档,相似度计算会面临:
- 信息冗余
- 局部主题差异
- 句间重要性不同
这时往往不能只靠简单平均,还需要更复杂的段落表示、句向量聚合或检索式匹配方法。
4. 计算效率问题
在大规模系统中,如果要比较成千上万甚至上百万条文本,两两计算相似度会非常耗时。因此往往需要:
- 向量索引
- 近似最近邻(ANN)
- Faiss 等高效相似度搜索工具
七、最佳实践建议
1. 先做好数据预处理
很多相似度效果问题,最后都能追溯到预处理不到位。例如:
- 大小写不统一
- 停用词影响过大
- 中文没有合理分词
- 文本噪声过多
2. 根据场景选择方法
可以做一个大致的经验判断:
- 短文本语义匹配:更适合 BERT / Sentence-BERT
- 长文档初筛:TF-IDF + 余弦相似度 仍然很好用
- 实时轻量系统:Word2Vec 或更轻量的句向量方法更合适
3. 同时考虑效果和效率
实际系统中,不一定一上来就用最重的模型。很多时候会采用两阶段策略:
- 先用 TF-IDF 或轻量向量做粗筛
- 再用更强的语义模型做精排
4. 建立评估集并持续优化
文本相似度的好坏往往和具体场景强相关,因此最好建立人工标注的相似度评估集,定期检查模型效果。
八、学习资源建议
如果想继续深入,比较值得看的资源包括:
- Gensim 官方文档
- Hugging Face Transformers 文档
- Scikit-learn 文本处理教程
- Transformer 论文《Attention Is All You Need》
- BERT 论文《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》
这里需要顺手纠正一个常见误区:Attention Is All You Need 是 Transformer 的原始论文,不是 BERT 论文;BERT 对应的是另一篇专门论文。
九、总结
文本相似度计算是 NLP 中非常基础但又极其实用的能力。它的核心流程可以概括为:
- 把文本表示成向量
- 选择合适的相似度度量方式
- 根据具体场景平衡效果和效率
从方法演进来看:
- 词袋模型和 TF-IDF 适合做字面层面的相似度计算
- Word2Vec 等词向量方法开始引入语义信息
- BERT 和 Sentence-BERT 等预训练模型则把文本相似度推进到更强的语义匹配阶段
在真正的工程实践中,没有哪种方法能一招通吃。理解不同方法的能力边界,并根据任务场景做组合,往往比单纯追求"最新模型"更重要。