One-Hot编码 + 相似度计算:余弦相似度 vs. 欧氏距离
·
One-Hot编码 + 相似度计算:余弦相似度 vs. 欧氏距离
1. 核心概念
| 方法 | 数学公式 | 直观解释 |
|---|---|---|
| One-Hot编码 | 词wiw_iwi的向量:vi=[0,...,1,...,0]\mathbf{v}_i = [0,...,1,...,0]vi=[0,...,1,...,0](第iii维为1,其余为0) | 每个词对应一个唯一的二进制向量,文本向量为词向量的逻辑"或"或求和。 |
| 余弦相似度 | sim(A,B)=A⋅B∣A∣⋅∣B∣\text{sim}(\mathbf{A}, \mathbf{B}) = \frac{\mathbf{A} \cdot \mathbf{B}}{\lvert{\mathbf{A}}\rvert \cdot \lvert{\mathbf{B}}\rvert}sim(A,B)=∣A∣⋅∣B∣A⋅B | 衡量向量方向的相似性,与向量长度无关。 |
| 欧氏距离 | d(A,B)=∑i=1n(Ai−Bi)2d(\mathbf{A}, \mathbf{B}) = \sqrt{\sum_{i=1}^n (A_i - B_i)^2}d(A,B)=∑i=1n(Ai−Bi)2 | 衡量向量空间中的绝对距离,距离越小越相似。 |
注意:simsimsim就是余弦cos(θ)cos(θ)cos(θ),simsimsim是cosine similarity的缩写,sklearn中相关方法也叫这个名字,如下所示
from sklearn.metrics.pairwise import cosine_similarity
sim = cosine_similarity([A], [B]) # 直接使用函数名
2. 具体对比(以文本"苹果 香蕉" vs "苹果 橘子"为例)
- One-Hot 编码:将每个词表示为长度为词汇表大小的二进制向量(词对应的位置为1,其余为0)。
- 示例:词汇表
["苹果", "香蕉", "橘子"]- “苹果” →
[1, 0, 0] - “香蕉” →
[0, 1, 0]
- “苹果” →
- 示例:词汇表
- 文本向量化:将文本中所有词的 One-Hot 向量相加(或取逻辑“或”),得到文本的二进制向量。
- 示例:文本 “苹果 香蕉” →
[1, 1, 0]
- 示例:文本 “苹果 香蕉” →
| 对比维度 | 余弦相似度 | 欧氏距离 |
|---|---|---|
| 向量表示 | A=[1,1,0]\mathbf{A}=[1,1,0]A=[1,1,0], B=[1,0,1]\mathbf{B}=[1,0,1]B=[1,0,1] | A=[1,1,0]\mathbf{A}=[1,1,0]A=[1,1,0], B=[1,0,1]\mathbf{B}=[1,0,1]B=[1,0,1] |
| 计算结果 | 1⋅1+1⋅0+0⋅12⋅2=0.5\frac{1 \cdot 1 + 1 \cdot 0 + 0 \cdot 1}{\sqrt{2} \cdot \sqrt{2}} = 0.52⋅21⋅1+1⋅0+0⋅1=0.5 | (1−1)2+(1−0)2+(0−1)2=2≈1.414\sqrt{(1-1)^2 + (1-0)^2 + (0-1)^2} = \sqrt{2} \approx 1.414(1−1)2+(1−0)2+(0−1)2=2≈1.414 |
| 相似度归一化 | 直接输出[0,1][0,1][0,1](0=无关,1=完全相同) | 需手动转换(如1/(1+d)1/(1+d)1/(1+d)),输出范围依赖词汇表大小。注:d就是欧氏距离 |
| 语义敏感性 | 对共现词敏感("苹果"共享,相似度=0.5) | 无法区分共现和非共现词("香蕉 vs 橘子"和"苹果 香蕉 vs 苹果 橘子"距离相同)。 |
| 高维稀疏性 | 适合高维稀疏向量(文本特征) | 高维时计算效率低,且距离值无直观意义。 |
3. 关键问题:为什么选择余弦相似度?
- 方向优于距离
- 文本相似度应关注词的重叠程度(方向),而非绝对坐标差异。
- 例:[1,1,0][1,1,0][1,1,0]和[1,0,1][1,0,1][1,0,1]的欧氏距离与[0,1,0][0,1,0][0,1,0]和[0,0,1][0,0,1][0,0,1]相同,但前者因共享"苹果"更相似。
- 归一化友好
- 余弦相似度天然归一化到[0,1][0,1][0,1],而欧氏距离需人工调整(如1/(1+d)1/(1+d)1/(1+d))。
- 稀疏向量适配性
- One-Hot向量维度=词汇表大小(通常数万维),余弦相似度忽略0值维度,计算更高效。
4. 改进方案总结
| 需求场景 | 推荐方法 | 原因 |
|---|---|---|
| 简单关键词匹配 | One-Hot + 余弦相似度 | 快速、易实现,适合小词汇表。 |
| 保留词频信息 | TF-IDF + 余弦相似度 | 削弱高频词影响,提升区分度。 |
| 捕捉语义相似性 | BERT/Sentence-Transformer | 解决一词多义和同义词问题(如"苹果"水果 vs "苹果"公司)。 |
| 表面字符相似度(如拼写检查) | 编辑距离(Levenshtein) | 直接计算字符级差异。 |
最终总结
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| One-Hot + 余弦相似度 | 小词汇表、关键词匹配 | 简单快速,方向敏感 | 忽略语义,高维稀疏 |
| One-Hot + 欧氏距离 | 几乎不推荐 | 理论可行 | 无法区分共现词,高维失效 |
| TF-IDF + 余弦相似度 | 通用文本匹配 | 平衡效率与效果 | 仍无法解决语义问题 |
| 词嵌入模型(BERT) | 高精度语义匹配 | 捕捉深层语义 | 计算资源消耗大 |
结论:在文本相似度任务中,余弦相似度因其方向敏感性和归一化特性,几乎总是优于欧氏距离。优先选择 TF-IDF + 余弦相似度 或 预训练词嵌入模型。
附录:完整代码
# 导入必要库
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity, euclidean_distances
import numpy as np
# 示例文本
texts = ["苹果 香蕉", "苹果 橘子"]
# ======================
# 方法1:One-Hot + 余弦相似度
# ======================
print("\n=== One-Hot + 余弦相似度 ===")
onehot_vectorizer = CountVectorizer(binary=True)
onehot_vectors = onehot_vectorizer.fit_transform(texts).toarray()
print("词汇表:", onehot_vectorizer.get_feature_names_out())
print("文本1向量:", onehot_vectors[0])
print("文本2向量:", onehot_vectors[1])
cos_sim = cosine_similarity(onehot_vectors[0:1], onehot_vectors[1:2])[0][0]
print("余弦相似度:", round(cos_sim, 4))
# ======================
# 方法2:One-Hot + 欧氏距离
# ======================
print("\n=== One-Hot + 欧氏距离 ===")
euclidean_dist = euclidean_distances(onehot_vectors[0:1], onehot_vectors[1:2])[0][0]
normalized_sim = 1 / (1 + euclidean_dist) # 距离转相似度
print("欧氏距离:", round(euclidean_dist, 4))
print("归一化相似度:", round(normalized_sim, 4))
# ======================
# 方法3:TF-IDF + 余弦相似度(推荐)
# ======================
print("\n=== TF-IDF + 余弦相似度 ===")
tfidf_vectorizer = TfidfVectorizer()
tfidf_vectors = tfidf_vectorizer.fit_transform(texts)
print("词汇表:", tfidf_vectorizer.get_feature_names_out())
print("文本1向量:", tfidf_vectors[0].toarray())
print("文本2向量:", tfidf_vectors[1].toarray())
tfidf_cos_sim = cosine_similarity(tfidf_vectors[0:1], tfidf_vectors[1:2])[0][0]
print("余弦相似度:", round(tfidf_cos_sim, 4))
# ======================
# 方法4:BERT语义相似度(需安装sentence-transformers)
# ======================
try:
from sentence_transformers import SentenceTransformer
print("\n=== BERT语义相似度 ===")
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
embeddings = model.encode(texts)
bert_sim = cosine_similarity(embeddings[0:1], embeddings[1:2])[0][0]
print("BERT相似度:", round(bert_sim, 4))
except ImportError:
print("\n(如需运行BERT示例,请安装sentence-transformers库)")
更多推荐



所有评论(0)