📏 欧氏距离(Euclidean Distance)完整可视化

📌 学习目标: 深入理解欧氏距离的计算原理、几何意义,以及与余弦相似度的区别。

1欧氏距离的基本概念

数学定义

公式:
euclidean_distance(A, B) = √(Σ(Aᵢ - Bᵢ)²)

展开形式:
d(A, B) = √[(A₀-B₀)² + (A₁-B₁)² + (A₂-B₂)² + ... + (A₇₆₇-B₇₆₇)²]

对于 768 维向量:
d = √[Σᵢ₌₀⁷⁶⁷(Aᵢ - Bᵢ)²]
几何意义:
  • 欧氏距离是向量在空间中的直线距离
  • 类似于我们在地图上测量两点之间的直线距离
  • 距离越小,两个向量越相似
  • 值域:[0, ∞),其中 0 表示两个向量完全相同

22D 空间直观理解

说明:在二维平面上,欧氏距离就是两点之间的直线距离(勾股定理)

2D 示例计算

点 A = (0.8, 0.6) "如何学习 Java 编程"
点 B = (0.85, 0.53) "Java 入门教程"
点 C = (0.1, 0.99) "今天天气真好"

d(A, B) = √[(0.85-0.8)² + (0.53-0.6)²]
        = √[0.05² + (-0.07)²]
        = √[0.0025 + 0.0049]
        = √0.0074
        ≈ 0.086 (距离近,相似度高)

d(A, C) = √[(0.1-0.8)² + (0.99-0.6)²]
        = √[(-0.7)² + 0.39²]
        = √[0.49 + 0.1521]
        = √0.6421
        ≈ 0.801 (距离远,相似度低)

33D 空间欧氏距离

说明:在三维空间中,欧氏距离是两点之间的空间直线距离

3D 示例计算

点 A = (0.8, 0.6, 0.3)
点 B = (0.85, 0.53, 0.35)

d(A, B) = √[(0.85-0.8)² + (0.53-0.6)² + (0.35-0.3)²]
        = √[0.05² + (-0.07)² + 0.05²]
        = √[0.0025 + 0.0049 + 0.0025]
        = √0.0099
        ≈ 0.099

4768 维向量的欧氏距离计算

以 GTE 模型生成的 768 维向量为例,展示前 5 个维度的计算过程:
维度 i vec1[i]
(句子1)
vec2[i]
(句子2)
差值
(Aᵢ - Bᵢ)
差值平方
(Aᵢ - Bᵢ)²
完整计算(768 维):

5Java 代码实现

/**
 * 计算欧氏距离
 *
 * @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);
}

6距离矩阵热力图

说明:计算所有句子两两之间的欧氏距离,颜色越深距离越大(相似度越低)
热力图解读:
  • 颜色越,距离越小,相似度越高
  • 颜色越,距离越大,相似度越低
  • 对角线为 0.0(自己与自己的距离为0)
⚡ 欧氏距离 VS 余弦相似度 ⚡

7欧氏距离 vs 余弦相似度对比

对比维度 欧氏距离 (Euclidean Distance) 余弦相似度 (Cosine Similarity)
计算公式 √(Σ(Aᵢ - Bᵢ)²) (A · B) / (||A|| × ||B||)
几何意义 两点之间的直线距离 两向量之间的夹角
值域 [0, ∞),0表示相同 [-1, 1],1表示相同
向量长度敏感性 受向量长度影响 不受向量长度影响
计算复杂度 O(n),稍快 O(n),需要额外计算模长
适用场景 • 归一化后的向量
• 绝对位置重要的场景
• 图像特征比较
• 文本语义检索
• 不关心向量长度
• 只关注方向
优点 • 计算简单
• 直观易懂
• 考虑绝对距离
• 不受长度影响
• 适合文本检索
• 抗噪声能力强
缺点 • 受向量长度影响
• 高维空间受维度诅咒影响
• 不考虑向量大小
• 计算稍复杂
Elasticsearch 支持 ✅ l2_norm ✅ cosine (默认推荐)

8关键差异:向量长度的影响

⚠️ 重要区别:欧氏距离会受到向量长度(模长)的影响,而余弦相似度不会。

示例:向量长度的影响

假设有三个向量:
A = (3, 4) 长度 ||A|| = 5
B = (3.3, 4.4) 长度 ||B|| = 5.5 (与A方向相同,只是更长)
C = (4, 3) 长度 ||C|| = 5 (与A方向不同,但长度相同)

余弦相似度:
cos(A, B) = 1.000 (方向完全相同)
cos(A, C) = 0.960 (方向略有不同)
→ 余弦相似度认为 A 和 B 最相似

欧氏距离:
d(A, B) = √[(3.3-3)² + (4.4-4)²] = √[0.09 + 0.16] = 0.5
d(A, C) = √[(4-3)² + (3-4)²] = √[1 + 1] = 1.414
→ 欧氏距离也认为 A 和 B 更相似

但如果 B 变成 (6, 8)(方向相同但长度加倍):
cos(A, (6,8)) = 1.000 (方向仍然相同!)
d(A, (6,8)) = √[(6-3)² + (8-4)²] = √[9 + 16] = 5.0 (距离变大!)
余弦相似度不变,但欧氏距离显著增加

9为什么需要归一化?

✅ 最佳实践:在使用欧氏距离时,通常需要对向量进行归一化(L2 normalization)
/**
 * 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);
归一化后的效果:
  • 所有向量长度变为 1(单位向量)
  • 欧氏距离只反映方向差异,不受长度影响
  • 归一化后的欧氏距离与余弦相似度有数学关系:
    d²(A, B) = 2 - 2×cos(A, B)

10在 Elasticsearch 中使用欧氏距离

// 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 是基于欧氏距离计算的相似度分数
// 分数越高,距离越小(越相似)
⚠️ 注意:
  • Elasticsearch 会自动将欧氏距离转换为评分(score)
  • 评分公式:score = 1 / (1 + distance²)
  • 对于大多数文本语义检索,推荐使用 cosine 而不是 l2_norm

11何时使用欧氏距离?

场景 推荐度量 原因
文本语义检索 ✅ 余弦相似度 不受文本长度影响,只关注语义方向
图像特征匹配 ✅ 欧氏距离 归一化后的图像特征,绝对距离有意义
推荐系统 ✅ 余弦相似度 用户偏好向量,关注偏好方向而非强度
聚类分析 ✅ 欧氏距离 归一化后,可以形成球形聚类
异常检测 ✅ 欧氏距离 绝对距离更能反映异常程度
时间序列 ✅ 欧氏距离 DTW 或欧氏距离测量序列差异
💡 最佳实践总结:
  1. 文本检索:优先使用余弦相似度
  2. 使用欧氏距离前:务必对向量进行 L2 归一化
  3. 归一化后:欧氏距离和余弦相似度等价
  4. Elasticsearch:默认使用 cosine,除非有特殊需求