跳转至

第3章:核心原理 - 向量表示、距离计算与索引结构

3.1 向量表示

什么是向量

向量(Vector) 是数学中的基本概念,可以理解为一个"有方向和大小"的数值数组。在向量数据库中,每个数据都被表示为一个高维数值数组。

通俗解释:想象你在描述一篇文章的特征: - 第一个数表示"科技含量"(0.8表示很高) - 第二个数表示"娱乐性"(0.2表示不太娱乐) - 第三个数表示"政治敏感度"(-0.3表示略微负面) - ...

这篇文章就可以用 [0.8, 0.2, -0.3, ...] 这样一个向量来表示。

# 向量示例:不同文本的向量表示
text_vectors = {
    "今天天气真好": [0.1, 0.9, 0.3, -0.1],
    "人工智能将改变世界": [0.8, 0.2, 0.7, 0.5],
    "看了一场精彩的电影": [0.2, 0.8, 0.4, 0.3],
}

# 维度:每个向量有4个数值,就是4维向量
# [0.1, 0.9, 0.3, -0.1]  # 4维向量

向量的直观理解

graph TD
    subgraph 低维到高维的映射
        A[1维: 点在线上] --> B[2维: 点在平面上]
        B --> C[3维: 点在空间中]
        C --> D[高维: 无法直观想象
但数学上完全一致] end subgraph 向量表示示例 E["文本: '苹果'"] --> F["向量: [0.8, 0.1, 0.3, ...]"] G["图片: 猫的照片"] --> H["向量: [0.2, 0.7, 0.4, ...]"] H --> J["所有数据都可以
表示为数值向量"] end style D fill:#e1f5fe style J fill:#c8e6c9

向量的数学性质

性质 说明 示例
方向 向量有方向,不同方向代表不同的语义 [1,0][0,1] 方向完全不同
距离 方向越接近的向量,语义越相似 [0.9, 0.1][0.8, 0.2] 很接近
可计算 向量之间可以做加减运算 著名的 king - man + woman ≈ queen

3.2 距离计算

什么是向量距离

向量距离(Distance)是衡量两个向量之间"相似程度"的指标。距离越小,两个向量越相似。

常见距离度量方法

1. 欧氏距离(L2 Distance)

定义:两点之间的直线距离,即各维度差值平方和的平方根。

通俗理解:就像你在地图上计算两点之间的直线距离。

import numpy as np

def euclidean_distance(v1, v2):
    """计算欧氏距离"""
    return np.sqrt(np.sum((v1 - v2) ** 2))

# 示例
vector_a = np.array([1, 2, 3])
vector_b = np.array([4, 6, 3])

distance = euclidean_distance(vector_a, vector_b)
print(f"欧氏距离: {distance:.2f}")  # 输出: 4.47

几何解释

graph TD
    subgraph 二维空间中的欧氏距离
        A["A点 (1,2)"] --> B["B点 (4,6)"]
        A --> C["(4,2)"]
        C --> B

        style A fill:#e1f5fe
        style B fill:#ffccbc
        style C fill:#b3e5fc
    end

    NOTE["欧氏距离 = √[(4-1)² + (6-2)²] = √[9 + 16] = √25 = 5"]

适用场景: - 图像/音频特征向量 - 当各维度重要性相同时 - 需要考虑向量大小(magnitude)的场景

2. 余弦相似度(Cosine Similarity)

定义:两个向量夹角的余弦值,取值范围 [-1, 1]。

通俗理解:比较两个向量的方向是否一致,而不考虑它们的长度。

import numpy as np

def cosine_similarity(v1, v2):
    """计算余弦相似度"""
    dot_product = np.dot(v1, v2)
    norm_v1 = np.linalg.norm(v1)
    norm_v2 = np.linalg.norm(v2)
    return dot_product / (norm_v1 * norm_v2)

# 示例
vector_a = np.array([1, 1, 0])
vector_b = np.array([1, 1, 1])
vector_c = np.array([-1, -1, 0])

print(f"a与b相似度: {cosine_similarity(vector_a, vector_b):.3f}")  # 0.816
print(f"a与c相似度: {cosine_similarity(vector_a, vector_c):.3f}")  # -1.000

几何解释

graph TD
    subgraph 余弦相似度示意
        O["原点"]
        A["A [1,1]"]
        B["B [1,1,1]"]
        C["C [-1,-1]"]

        O --> A
        O --> B
        O --> C

        AA["夹角45°
相似度0.816"] CC["夹角180°
相似度-1.0"] end style A fill:#b3e5fc style B fill:#c8e6c9 style C fill:#ffccbc

适用场景: - 文本语义相似度(最常用!) - 文档主题相似度 - 忽略向量大小,只关心方向

3. 点积相似度(Dot Product)

定义:两个向量对应元素相乘后求和。

def dot_product_similarity(v1, v2):
    """计算点积"""
    return np.dot(v1, v2)

# 示例
vector_a = np.array([1, 2, 3])
vector_b = np.array([4, 5, 6])

dot = dot_product_similarity(vector_a, vector_b)
print(f"点积: {dot}")  # 输出: 32 (1*4 + 2*5 + 3*6)

距离度量方法对比

graph LR
    subgraph 距离度量对比
        A[向量A] -->|相同方向| B[向量B]
        A -->|相反方向| C[向量C]
        A -->|垂直| D[向量D]
    end

    subgraph 相似度值
        B --> E["余弦: 0.95 (很相似)"]
        C --> F["余弦: -0.95 (相反)"]
        D --> G["余弦: 0 (正交)"]
    end

    subgraph 距离值
        B --> H["欧氏: 0.3 (很近)"]
        C --> I["欧氏: 2.5 (很远)"]
        D --> J["欧氏: 1.4 (中等)"]
    end

    style E fill:#c8e6c9
    style F fill:#ffccbc
    style G fill:#fff3e0

如何选择距离度量

场景 推荐方法 原因
文本语义搜索 余弦相似度 文本向量方向比长度更重要
图像特征匹配 欧氏距离 需要考虑特征强度
推荐系统 点积 综合考虑用户偏好强度和物品属性
人脸识别 余弦相似度 关注特征方向,不受光照影响

3.3 索引结构

为什么需要索引

如果没有索引,向量数据库在查询时需要与所有向量逐一计算距离,时间复杂度是 O(N),这在海量数据下是不可接受的。

# 无索引的线性搜索
def linear_search(query_vector, all_vectors, top_k=10):
    distances = []
    for vector in all_vectors:
        dist = euclidean_distance(query_vector, vector)
        distances.append((dist, vector))

    distances.sort(key=lambda x: x[0])
    return distances[:top_k]

# 问题:100万条数据 = 100万次距离计算 = 几秒钟
# 解决:建立索引,将计算次数降低到几百次

近似最近邻搜索(ANN)

近似最近邻搜索(Approximate Nearest Neighbor, ANN) 是向量数据库的核心技术。它通过建立索引结构,在"可接受的精度损失范围内",大幅提升搜索速度。

flowchart LR
    subgraph 精确 vs 近似
        A[精确搜索] -->|100%准确| B["时间: O(N)
N=10亿时不可用"] C[近似搜索] -->|99%准确| D["时间: O(log N)
毫秒级响应"] end style A fill:#ffccbc style C fill:#c8e6c9 style B fill:#ffccbc style D fill:#c8e6c9

主流索引算法

1. HNSW(分层导航小世界图)

HNSW(Hierarchical Navigable Small World) 是目前最流行的高性能向量索引算法。

核心思想: 1. 构建多层图结构,上层稀疏、下层密集 2. 查询时从最上层开始,通过"贪心搜索"快速定位 3. 利用"小世界"特性,确保搜索路径很短

HNSW索引结构

graph TB
    subgraph Layer 2 [L2层 - 顶层导航]
        L2_1(( )) --> L2_2(( ))
        L2_2(( )) --> L2_3(( ))
        L2_3(( )) --> L2_1(( ))
    end

    subgraph Layer 1 [L1层 - 中层]
        L1_1(( )) --> L1_2(( ))
        L1_2(( )) --> L1_3(( ))
        L1_3(( )) --> L1_4(( ))
        L1_4(( )) --> L1_1(( ))
        L1_2(( )) --> L1_5(( ))
    end

    subgraph Layer 0 [L0层 - 底层数据]
        L0_1(( )) --> L0_2(( ))
        L0_2(( )) --> L0_3(( ))
        L0_3(( )) --> L0_4(( ))
        L0_4(( )) --> L0_5(( ))
        L0_5(( )) --> L0_6(( ))
        L0_6(( )) --> L0_7(( ))
        L0_7(( )) --> L0_8(( ))
        L0_1(( )) --> L0_4(( ))
        L0_3(( )) --> L0_6(( ))
        L0_5(( )) --> L0_8(( ))
    end

    subgraph 查询路径
        Q((Query)) -->|1. 进入L2| L2_2
        L2_2 -->|2. 下降到L1| L1_3
        L1_3 -->|3. 下降到L0| L0_4
        L0_4 -->|4. 局部搜索| L0_5
    end

    style L2_1 fill:#90caf9
    style L2_2 fill:#90caf9
    style L2_3 fill:#90caf9

    style Q fill:#ff5722,color:#fff

HNSW搜索过程示例

# HNSW搜索伪代码
def hnsw_search(query_vector, index, top_k=10):
    # 1. 从最高层开始
    current_layer = index.max_layer

    # 2. 在当前层贪心搜索,找到最近的入口点
    entry_point = index.get_entry_point(current_layer)
    while current_layer > 0:
        neighbors = index.get_neighbors(entry_point, current_layer)
        best_next = find_nearest(query_vector, neighbors)
        if distance(query_vector, best_next) < distance(query_vector, entry_point):
            entry_point = best_next
        current_layer -= 1

    # 3. 在底层进行精细搜索
    results = layer0_search(query_vector, entry_point, top_k)
    return results

HNSW的参数说明

参数 说明 推荐值
M 每个节点的最大连接数 16-64
efConstruction 构建时的动态列表大小 100-400
efSearch 搜索时的动态列表大小 100-500
max_level 最大层数 log(N)

2. IVF(倒排索引)

IVF(Inverted File Index) 是一种基于聚类的索引方法。

核心思想: 1. 对所有向量进行聚类(如K-Means) 2. 每个聚类维护一个倒排列表 3. 查询时只搜索目标向量所在的聚类及其邻近聚类

graph TD
    subgraph 聚类空间
        C1["聚类中心1"]
        C2["聚类中心2"]
        C3["聚类中心3"]

        C1 -.->|倒排列表| I1["向量1,5,8,12..."]
        C2 -.->|倒排列表| I2["向量2,3,7,15..."]
        C3 -.->|倒排列表| I3["向量4,6,9,11..."]
    end

    subgraph 查询过程
        Q["查询向量"] -->|找到最近中心| C2
        C2 -->|搜索该聚类| I2
        I2 --> R["候选向量"]
    end

    style C1 fill:#e1f5fe
    style C2 fill:#c8e6c9
    style C3 fill:#fff3e0
    style I2 fill:#c8e6c9

3. PQ(乘积量化)- 有损压缩

PQ(Product Quantization) 将高维向量分割成多个子向量,分别进行聚类压缩。

graph TD
    subgraph 原始向量
        O["原始向量 [0.23, -0.45, 0.89, -0.12, 0.56, -0.78]"]
    end

    subgraph 分段
        S1["段1: [0.23, -0.45]"]
        S2["段2: [0.89, -0.12]"]
        S3["段3: [0.56, -0.78]"]
    end

    subgraph 量化
        Q1["→ 聚类中心ID: 42"]
        Q2["→ 聚类中心ID: 17"]
        Q3["→ 聚类中心ID: 89"]
    end

    O --> S1
    O --> S2
    O --> S3
    S1 --> Q1
    S2 --> Q2
    S3 --> Q3

    Q1 --> R["压缩后: [42, 17, 89]"]
    Q2 --> R
    Q3 --> R

    style O fill:#e1f5fe
    style R fill:#c8e6c9

索引算法对比

索引算法 查询速度 精度 内存占用 适用场景
HNSW 极快 中等 通用场景,推荐首选
IVF 中高 大规模数据
PQ 很低 内存受限场景

3.4 最近邻搜索

最近邻搜索示意图

graph TD
    subgraph 二维向量空间示意
        Q["查询向量 Q"]
        A["A [0.2, 0.3]"]
        B["B [0.8, 0.7]"]
        C["C [0.5, 0.9]"]
        D["D [0.9, 0.2]"]
        E["E [0.3, 0.8]"]
    end

    subgraph 距离计算
        Q -->|与A距离| DA["0.36"]
        Q -->|与B距离| DB["0.79"]
        Q -->|与C距离| DC["0.28"]
        Q -->|与D距离| DD["0.70"]
        Q -->|与E距离| DE["0.22"]
    end

    subgraph 排序结果
        DE -->|最近| R1["1. E (0.22)"]
        DC -->|次近| R2["2. C (0.28)"]
        DA -->|第三| R3["3. A (0.36)"]
    end

    style Q fill:#ff5722,color:#fff
    style E fill:#4caf50
    style C fill:#4caf50

ANN vs KNN

graph LR
    subgraph KNN精确搜索
        A[K=5] -->|找到| B["5个精确最近邻
100%准确
但很慢"] end subgraph ANN近似搜索 C[K=5] -->|找到| D["5个近似最近邻
99%准确
但很快"] end style A fill:#e1f5fe style C fill:#c8e6c9 style B fill:#ffccbc style D fill:#c8e6c9
对比项 KNN ANN
精确度 100% 95%-99%
速度
适用数据量 <10万 10万-10亿+
召回率 1.0 0.95-0.99

3.5 过滤与混合搜索

什么是过滤搜索

在实际应用中,我们常常需要在搜索时加入额外的过滤条件,如"只搜索2024年发布的文章"。

# 过滤搜索示例
results = vector_db.search(
    query_vector=query_embedding,
    top_k=10,
    filter={
        "category": "technology",  # 只搜索科技类
        "year": {"$gte": 2024},     # 发布时间>=2024
        "is_published": True        # 已发布的
    }
)

混合搜索架构

flowchart TD
    subgraph 查询处理
        Q["查询向量"] -->|1| FE[过滤器表达式]
        Q -->|2| ANN[ANN索引搜索]
    end

    subgraph 过滤阶段
        FE -->|构建| FM["过滤掩码
[T,F,T,T,F...]"] ANN -->|返回| CR["候选结果"] end subgraph 最终筛选 CR -->|应用过滤| FR["过滤后的结果"] end style ANN fill:#e1f5fe style FE fill:#fff3e0 style FR fill:#c8e6c9

本章小结

本章要点:

  1. 向量表示:所有数据(文本、图像、音频)都可以转换为高维数值向量
  2. 距离计算
  3. 欧氏距离:适合图像/音频,需要考虑大小
  4. 余弦相似度:适合文本语义,最常用
  5. 点积:适合推荐系统
  6. 索引结构
  7. HNSW:目前最主流,多层图结构
  8. IVF:基于聚类的倒排索引
  9. PQ:有损压缩,适合内存受限场景
  10. ANN搜索:用少量精度损失换取大规模提速

下一章预告第4章:基础概念解析 - 深入理解嵌入、维度、相似度度量等核心术语 →