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)=ABAB 衡量向量方向的相似性,与向量长度无关。
欧氏距离 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(AiBi)2 衡量向量空间中的绝对距离,距离越小越相似。

注意:simsimsim就是余弦cos(θ)cos(θ)cos(θ)simsimsimcosine 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 2 11+10+01=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(11)2+(10)2+(01)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,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]相同,但前者因共享"苹果"更相似。
  2. 归一化友好
    • 余弦相似度天然归一化到[0,1][0,1][0,1],而欧氏距离需人工调整(如1/(1+d)1/(1+d)1/(1+d))。
  3. 稀疏向量适配性
    • 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库)")
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐