Skip to content

26. Embedding 与向量数据库

本章深入讲解 Embedding 原理、向量数据库核心技术、索引算法和检索优化。Embedding 和向量数据库是 RAG 系统的基础设施。


Embedding 基础

Q: 什么是 Embedding?为什么需要向量化表示?⭐⭐

答: Embedding 是将离散的、高维的数据(文本、图片、音频)映射到连续的、低维的向量空间的过程。

为什么需要 Embedding?

  • 文本是离散符号("猫"和"猫咪"是完全不同的字符串)
  • 向量可以表示语义相似度("猫"和"猫咪"的向量很近)
  • 向量支持高效检索(最近邻搜索,O(log N) 复杂度)
文本 → Embedding Model → [0.12, -0.34, 0.56, ..., 0.78] (1536维)

                         语义空间中的一个点
                         与相似文本距离近

Q: 常见的 Embedding 模型有哪些?如何选型?⭐⭐⭐

答:

模型维度中文价格推荐场景
text-embedding-3-small1536$0.02/M tokens通用,性价比最高
text-embedding-3-large3072$0.13/M tokens高精度需求
BGE-large-zh-v1.51024✅✅免费本地中文最佳,本地部署
BGE-M31024✅✅免费本地多语言+多粒度
Jina-embeddings-v31024$0.02/M tokens长文本(8K)
Cohere embed-v31024$0.1/M tokens多语言最佳
GTE-Qwen21536✅✅免费本地阿里开源,中文强

选型决策树:

需要本地部署?
  ├─ 是 → 中文为主? → BGE-large-zh / GTE-Qwen2
  │       多语言? → BGE-M3
  └─ 否 → 预算敏感? → text-embedding-3-small
           精度优先? → text-embedding-3-large / Cohere
           长文本? → Jina-embeddings-v3
python
# OpenAI Embedding API
import httpx

async def get_embedding(text: str, model: str = "text-embedding-3-small") -> list[float]:
    async with httpx.AsyncClient(timeout=30) as client:
        resp = await client.post(
            "https://api.openai.com/v1/embeddings",
            headers={"Authorization": f"Bearer {API_KEY}"},
            json={"model": model, "input": text}
        )
        return resp.json()["data"][0]["embedding"]

# 本地 BGE 模型
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("BAAI/bge-large-zh-v1.5")
embeddings = model.encode(["什么是RAG?", "检索增强生成是什么?"])
# 两句话的余弦相似度 > 0.9

Q: 余弦相似度 vs 点积 vs 欧氏距离有什么区别?⭐⭐

答:

度量公式范围特点适用场景
余弦相似度cos(θ) = A·B / (‖A‖·‖B‖)[-1, 1]只看方向,不看长度文本相似度(最常用)
点积A·B = Σai·bi[-∞, +∞]方向+长度已归一化的向量
欧氏距离‖A-B‖₂[0, +∞]空间直线距离聚类、异常检测
python
import numpy as np

def cosine_similarity(a, b):
    """余弦相似度:最常用,不受向量长度影响"""
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

def dot_product(a, b):
    """点积:向量已归一化时等价于余弦相似度"""
    return np.dot(a, b)

def euclidean_distance(a, b):
    """欧氏距离:越小越相似"""
    return np.linalg.norm(a - b)

# 文本 Embedding 通常已归一化,所以余弦相似度 ≈ 点积
a = get_embedding("什么是RAG?")
b = get_embedding("检索增强生成的原理")
print(f"余弦相似度: {cosine_similarity(a, b):.4f}")  # 0.85+

向量数据库

Q: 向量数据库的核心原理是什么?⭐⭐⭐

答: 向量数据库的核心是 近似最近邻搜索(ANN)——在百万级向量中快速找到与查询向量最相似的 Top-K 个。

为什么不直接暴力搜索?

  • 暴力搜索:O(N×D),100 万 × 1536 维 = 15 亿次计算
  • ANN 索引:O(log N × D),100 万只需要几千次计算
  • 代价:牺牲少量精度(召回率 95-99%)

主流 ANN 索引算法:

算法原理优点缺点代表
HNSW分层可导航小世界图精度高、查询快内存大Milvus, Qdrant
IVF倒排索引 + 聚类内存小精度稍低Milvus, Faiss
PQ乘积量化压缩超省内存精度损失Faiss
ScaNNGoogle 的各向异性量化速度快生态小Google

Q: HNSW 索引算法的原理是什么?⭐⭐⭐

答: HNSW(Hierarchical Navigable Small World)是目前最流行的 ANN 算法。

核心思想: 多层跳表 + 小世界图

Layer 3 (最稀疏):  A ←→ D           ← 全局导航,快速定位区域

Layer 2:           A ←→ B ←→ D      ← 中间层

Layer 1 (最密集):  A ←→ B ←→ C ←→ D ←→ E  ← 精确搜索

搜索过程:

  1. 从最高层的入口点开始
  2. 在当前层贪心搜索最近邻
  3. 向下一层移动,继续搜索
  4. 在最底层返回 Top-K 结果

关键参数:

  • M:每个节点的最大邻居数(越大精度越高,内存越大)
  • ef_construction:建索引时的搜索宽度(越大索引质量越好)
  • ef_search:查询时的搜索宽度(越大精度越高,速度越慢)
python
# Milvus HNSW 索引配置
from pymilvus import Collection, FieldSchema, CollectionSchema, DataType

index_params = {
    "index_type": "HNSW",
    "metric_type": "COSINE",
    "params": {
        "M": 16,              # 邻居数,推荐 16-64
        "efConstruction": 200  # 建索引搜索宽度,推荐 100-500
    }
}

# 查询时
search_params = {
    "metric_type": "COSINE",
    "params": {"ef": 64}  # 查询搜索宽度,越大越精确
}

Q: Milvus vs Pinecone vs Chroma vs Weaviate 如何选型?⭐⭐⭐

答:

特性MilvusPineconeChromaWeaviateQdrant
部署自托管/云纯云本地/嵌入自托管/云自托管/云
规模十亿级十亿级百万级十亿级十亿级
索引HNSW/IVF/DiskANN专有HNSWHNSWHNSW
混合检索
多租户
开源
学习曲线
推荐场景大规模生产快速上线原型验证全功能轻量生产

选型建议:

  • 原型开发 → Chroma(最简单,pip install)
  • 中小规模生产 → Qdrant 或 Weaviate
  • 大规模生产 → Milvus(分布式,十亿级)
  • 不想运维 → Pinecone(全托管)
python
# Chroma(最简单的向量数据库)
import chromadb

client = chromadb.Client()
collection = client.create_collection("docs")

collection.add(
    documents=["什么是RAG?", "什么是Agent?"],
    ids=["doc1", "doc2"]
)

results = collection.query(query_texts=["检索增强"], n_results=2)

# Milvus(生产级)
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType

connections.connect("default", host="localhost", port="19530")

fields = [
    FieldSchema("id", DataType.INT64, is_primary=True, auto_id=True),
    FieldSchema("embedding", DataType.FLOAT_VECTOR, dim=1536),
    FieldSchema("text", DataType.VARCHAR, max_length=65535),
]
schema = CollectionSchema(fields)
collection = Collection("documents", schema)

检索优化

Q: 如何优化向量检索的精度和速度?⭐⭐⭐

答:

优化维度方法效果
索引参数增大 M/ef_search精度 ↑,速度 ↓
量化压缩PQ/SQ8 量化内存 ↓ 75%,精度 ↓ 2-5%
过滤检索先过滤再搜索减少搜索空间
Rerank粗检索 + 精排序精度 ↑↑,延迟 +50ms
分片策略按时间/类别分片减少扫描范围
缓存热点查询缓存延迟 ↓ 90%
python
class OptimizedRetriever:
    """粗检索 + Rerank 两阶段方案"""

    def __init__(self):
        self.vector_db = MilvusClient()
        self.reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")

    async def search(self, query: str, top_k: int = 5) -> list[dict]:
        # 阶段 1:向量粗检索(取 20 个候选)
        query_emb = get_embedding(query)
        candidates = self.vector_db.search(
            collection="docs",
            query_vector=query_emb,
            top_k=20,
            search_params={"ef": 128}
        )

        # 阶段 2:Cross-Encoder 精排序
        pairs = [(query, c["text"]) for c in candidates]
        scores = self.reranker.predict(pairs)

        # 合并排序
        for c, score in zip(candidates, scores):
            c["rerank_score"] = float(score)

        results = sorted(candidates, key=lambda x: x["rerank_score"], reverse=True)
        return results[:top_k]

Q: 混合检索(向量+关键词)如何实现?⭐⭐⭐

答: 单独用向量检索会丢失精确匹配能力(比如搜"ERR-404"错误码),混合检索结合两者优势。

RRF(Reciprocal Rank Fusion)融合公式:

RRF_score(d) = Σ 1 / (k + rank_i(d))
其中 k=60(常数),rank_i 是第 i 个检索器的排名
python
from collections import defaultdict

class HybridRetriever:
    """向量 + BM25 混合检索"""

    def __init__(self):
        self.vector_db = MilvusClient()
        self.bm25 = BM25Index()

    async def search(self, query: str, top_k: int = 5, k: int = 60) -> list[dict]:
        # 向量检索 Top-20
        query_emb = get_embedding(query)
        vector_results = self.vector_db.search("docs", query_emb, top_k=20)

        # BM25 关键词检索 Top-20
        bm25_results = self.bm25.search(query, top_k=20)

        # RRF 融合
        scores = defaultdict(float)
        for rank, doc in enumerate(vector_results):
            scores[doc["id"]] += 1 / (k + rank + 1)
        for rank, doc in enumerate(bm25_results):
            scores[doc["id"]] += 1 / (k + rank + 1)

        # 按融合分数排序
        sorted_ids = sorted(scores, key=scores.get, reverse=True)[:top_k]

        # 返回结果
        all_docs = {d["id"]: d for d in vector_results + bm25_results}
        return [all_docs[doc_id] for doc_id in sorted_ids]

Embedding 微调

Q: Embedding 模型的微调方法?⭐⭐

答: 当通用 Embedding 在特定领域效果不好时(如医疗、法律),需要微调。

微调方式:

方式数据需求效果复杂度
对比学习微调正负样本对最好
LoRA 微调少量标注
提示工程一般
python
from sentence_transformers import SentenceTransformer, InputExample, losses
from torch.utils.data import DataLoader

# 加载预训练模型
model = SentenceTransformer("BAAI/bge-large-zh-v1.5")

# 准备训练数据(正样本对)
train_examples = [
    InputExample(texts=["什么是RAG?", "检索增强生成的原理介绍"], label=0.9),
    InputExample(texts=["什么是RAG?", "今天天气怎么样"], label=0.1),
]

train_dataloader = DataLoader(train_examples, shuffle=True, batch_size=16)
train_loss = losses.CosineSimilarityLoss(model)

# 微调
model.fit(
    train_objectives=[(train_dataloader, train_loss)],
    epochs=3,
    warmup_steps=100,
    output_path="./bge-finetuned-medical"
)

面试速查

问题关键点
Embedding 是什么?离散数据→连续向量,支持语义相似度计算
余弦 vs 点积?余弦看方向、点积看方向+长度,归一化后等价
HNSW 原理?分层可导航小世界图,贪心搜索逐层下降
向量数据库选型?原型用 Chroma,生产用 Milvus/Qdrant,托管用 Pinecone
混合检索?向量 + BM25 + RRF 融合,兼顾语义和精确匹配
精度优化?Rerank(Cross-Encoder)、增大 ef_search、混合检索
成本优化?量化压缩、分片、缓存、选小维度模型

LLM 应用 & Agent 开发面试准备