向量检索的数学之美余弦相似度、欧氏距离和内积的使用场景辨析向量检索有三种主流的相似度度量但你有没有想过为什么有的选余弦、有的选欧氏距离、有的选内积这三种数学工具背后的语义差异是什么很多 RAG 系统的默认配置就用余弦相似度——因为它是默认的。但实际场景中选错相似度度量会让你的检索质量差 20% 以上。这三个公式不是通用的它们各自假设了不同的数据分布和语义空间。一、深度引言与场景痛点余弦相似度cos(θ) (a·b) / (|a| * |b|)只看方向不看长度。两个向量指向同一方向相似度为 1正交为 0相反为 -1。欧氏距离d sqrt(Σ(ai - bi)²)计算空间中的直线距离。两个向量越近距离越小。内积a·b Σ(ai * bi) |a| * |b| * cos(θ)方向相同且长度越大值越大。二、底层机制与原理深度剖析现代 Text Embedding 模型OpenAI text-embedding-3、BGE、E5在训练时通常会做 L2 归一化让所有向量的长度都约等于 1。当|a| |b| 1时余弦相似度等于内积。但这不是你可以随便替换的理由。如果你自训练的 Embedding 模型没有做归一化用余弦和用内积的结果就不一样。余弦相似度适合 Embedding 的三个原因不受向量长度影响。同一个文档的不同长度 Embedding不同模型余弦相似度稳定内积会波动。语义空间里方向比大小更有意义。两篇关于 Python 的文章Embedding 方向相近但长度不同一篇长一篇短余弦相似度仍然高。数学上简洁范围在 [-1, 1] 之间容易设定阈值。三、生产级代码实现欧氏距离在以下场景优于余弦相似度图像检索。图像 Embedding 的长度通常编码了图像的信息量复杂度、细节丰富度。用余弦会丢失长度信息用欧氏距离更准确。向量聚类。K-Means 基于欧氏距离。如果你用余弦相似度做聚类中心和成员的关系就没那么直观。数值特征向量。不是 Embedding 模型输出的人工特征如 [价格, 评分, 销量]欧氏距离更能反映综合差距。四、边界分析与架构权衡import numpy as np from enum import Enum from typing import Optional import logging logger logging.getLogger(__name__) class SimilarityMetric(Enum): COSINE cosine EUCLIDEAN euclidean DOT_PRODUCT dot_product class SimilarityComputer: def __init__(self, metric: SimilarityMetric SimilarityMetric.COSINE): self.metric metric def compute(self, a: np.ndarray, b: np.ndarray) - float: if self.metric SimilarityMetric.COSINE: return self._cosine(a, b) elif self.metric SimilarityMetric.EUCLIDEAN: return self._euclidean(a, b) else: return self._dot_product(a, b) staticmethod def _cosine(a: np.ndarray, b: np.ndarray) - float: norm_a np.linalg.norm(a) norm_b np.linalg.norm(b) if norm_a 0 or norm_b 0: logger.warning(Zero vector detected in cosine similarity) return 0.0 sim np.dot(a, b) / (norm_a * norm_b) return max(-1.0, min(1.0, float(sim))) staticmethod def _euclidean(a: np.ndarray, b: np.ndarray) - float: distance np.linalg.norm(a - b) # 转换为相似度距离越小相似度越高 return float(1.0 / (1.0 distance)) staticmethod def _dot_product(a: np.ndarray, b: np.ndarray) - float: return float(np.dot(a, b)) class AutoMetricSelector: 自动选择合适的相似度度量 1. 检查 Embedding 是否归一化 → 余弦和内积等价 → 用余弦 2. 检查向量维度中的特征类型 → 数值特征用欧氏距离 3. 否则默认余弦 staticmethod def select( sample_vectors: list[np.ndarray], is_embedding: bool True, is_normalized: Optional[bool] None, ) - SimilarityMetric: if is_normalized is None and sample_vectors: norms [np.linalg.norm(v) for v in sample_vectors] avg_norm np.mean(norms) std_norm np.std(norms) # 如果所有向量的模长接近 1方差极小认为已归一化 is_normalized ( abs(avg_norm - 1.0) 0.01 and std_norm 0.01 ) if is_normalized: return SimilarityMetric.COSINE if not is_embedding: return SimilarityMetric.EUCLIDEAN return SimilarityMetric.COSINE # 使用示例 vectors [np.array([0.1, 0.3, 0.5]), np.array([0.2, 0.4, 0.6])] metric AutoMetricSelector.select(vectors) print(f推荐度量: {metric.value}) computer SimilarityComputer(metric) sim computer.compute(vectors[0], vectors[1]) print(f相似度: {sim:.4f})五、总结场景推荐度量原因RAG 文本检索余弦相似度Embedding 已归一化图像相似搜索欧氏距离图像特征长度编码信息量推荐系统召回内积协同过滤矩阵分解文档聚类余弦相似度归一化后更稳定异常检测欧氏距离偏离正常模式的距离人脸识别余弦相似度人脸特征向量的方向比长度重要商品特征匹配欧氏距离数值特征的绝对差异有意义六、常见错误与纠正错误一用欧氏距离不加归一化如果向量 A 的模长是 10向量 B 的模长是 1。即使它们方向完全相同欧氏距离也约等于 9。这在语义上毫无意义。用欧氏距离之前先做 L2 归一化。错误二认为余弦相似度 0.8 就是好余弦相似度的阈值需要根据你的数据分布来定。在某些 Embedding 空间里0.8 可能非常普遍所有文档都相似0.95 才算是相关。做任何阈值设定前先看你的相似度分布直方图。错误三混合使用不同度量同一个系统里检索用余弦、重排用欧氏、融合用内积三种度量不可比。统一成一种或者在融合前归一化。七、总结三种相似度度量的选择不是数学偏好是场景匹配余弦相似度比较方向。适用于归一化的 Text Embedding 检索。欧氏距离比较空间距离。适用于数值特征向量和图像检索。内积比较投影长度。适用于需要越大越好的推荐场景。RAG 系统 95% 的场景用余弦相似度就够了。但如果你的 Embedding 不是标准模型输出、或者你的数据是人工特征花十分钟验证一下度量选择可能让你少走很多弯路。资料说明本文中的协议、版本、性能、成本和行业趋势应以可核验的一手资料为准。未标注统计口径的比例、时间表和预测仅作工程讨论不应视为行业事实。可参考 0730 资料来源索引并在发布前将具体来源贴到对应断言之后。