| 维度 i | vec1[i] (句子1) |
vec2[i] (句子2) |
差值 (Aᵢ - Bᵢ) |
差值平方 (Aᵢ - Bᵢ)² |
|---|
/** * 计算欧氏距离 * * @param vec1 向量1 (768维) * @param vec2 向量2 (768维) * @return 欧氏距离值 (范围: [0, ∞), 0表示完全相同) */ public static double euclideanDistance(float[] vec1, float[] vec2) { if (vec1.length != vec2.length) { throw new IllegalArgumentException("向量维度不匹配"); } double sumSquaredDiff = 0.0; // 差值平方和 // 遍历所有维度(768维) for (int i = 0; i < vec1.length; i++) { double diff = vec1[i] - vec2[i]; // 计算差值 sumSquaredDiff += diff * diff; // 累加差值平方 } // 返回平方根 return Math.sqrt(sumSquaredDiff); } /** * 归一化欧氏距离(范围转换为 [0, 1]) * * @param vec1 向量1 * @param vec2 向量2 * @return 归一化后的距离 (0表示相同, 1表示最远) */ public static double normalizedEuclideanDistance(float[] vec1, float[] vec2) { double distance = euclideanDistance(vec1, vec2); // 对于单位向量,最大距离是 √2 double maxDistance = Math.sqrt(2.0); return distance / maxDistance; } /** * 将欧氏距离转换为相似度 (范围: [0, 1]) * * @param vec1 向量1 * @param vec2 向量2 * @return 相似度 (1表示相同, 0表示最远) */ public static double euclideanSimilarity(float[] vec1, float[] vec2) { double distance = euclideanDistance(vec1, vec2); // 使用公式: similarity = 1 / (1 + distance) return 1.0 / (1.0 + distance); }
| 对比维度 | 欧氏距离 (Euclidean Distance) | 余弦相似度 (Cosine Similarity) |
|---|---|---|
| 计算公式 | √(Σ(Aᵢ - Bᵢ)²) | (A · B) / (||A|| × ||B||) |
| 几何意义 | 两点之间的直线距离 | 两向量之间的夹角 |
| 值域 | [0, ∞),0表示相同 | [-1, 1],1表示相同 |
| 向量长度敏感性 | ❌ 受向量长度影响 | ✅ 不受向量长度影响 |
| 计算复杂度 | O(n),稍快 | O(n),需要额外计算模长 |
| 适用场景 | • 归一化后的向量 • 绝对位置重要的场景 • 图像特征比较 |
• 文本语义检索 • 不关心向量长度 • 只关注方向 |
| 优点 | • 计算简单 • 直观易懂 • 考虑绝对距离 |
• 不受长度影响 • 适合文本检索 • 抗噪声能力强 |
| 缺点 | • 受向量长度影响 • 高维空间受维度诅咒影响 |
• 不考虑向量大小 • 计算稍复杂 |
| Elasticsearch 支持 | ✅ l2_norm | ✅ cosine (默认推荐) |
/** * L2 归一化(将向量归一化为单位向量) * * @param vec 原始向量 * @return 归一化后的向量(长度为1) */ public static float[] normalize(float[] vec) { // 计算向量的模长 double norm = 0.0; for (float v : vec) { norm += v * v; } norm = Math.sqrt(norm); // 归一化 float[] normalized = new float[vec.length]; for (int i = 0; i < vec.length; i++) { normalized[i] = (float) (vec[i] / norm); } return normalized; } // 使用示例 float[] vec1 = getEmbedding("如何学习 Java 编程"); float[] vec2 = getEmbedding("Java 入门教程"); // 归一化后再计算欧氏距离 float[] norm1 = normalize(vec1); float[] norm2 = normalize(vec2); double distance = euclideanDistance(norm1, norm2);
d²(A, B) = 2 - 2×cos(A, B)
// 1. 创建索引时指定 similarity 为 l2_norm PUT /my_index { "mappings": { "properties": { "title_vector": { "type": "dense_vector", "dims": 768, "index": true, "similarity": "l2_norm" ← 使用欧氏距离 } } } } // 2. kNN 搜索(会自动使用 l2_norm 计算距离) POST /my_index/_search { "knn": { "field": "title_vector", "query_vector": [0.1, 0.2, ...], // 768维向量 "k": 10, "num_candidates": 100 } } // 返回的 _score 是基于欧氏距离计算的相似度分数 // 分数越高,距离越小(越相似)
score = 1 / (1 + distance²)cosine 而不是 l2_norm| 场景 | 推荐度量 | 原因 |
|---|---|---|
| 文本语义检索 | ✅ 余弦相似度 | 不受文本长度影响,只关注语义方向 |
| 图像特征匹配 | ✅ 欧氏距离 | 归一化后的图像特征,绝对距离有意义 |
| 推荐系统 | ✅ 余弦相似度 | 用户偏好向量,关注偏好方向而非强度 |
| 聚类分析 | ✅ 欧氏距离 | 归一化后,可以形成球形聚类 |
| 异常检测 | ✅ 欧氏距离 | 绝对距离更能反映异常程度 |
| 时间序列 | ✅ 欧氏距离 | DTW 或欧氏距离测量序列差异 |
cosine,除非有特殊需求