向量数据库与检索
一、向量基础
Q1: 什么是向量/Embedding?
Embedding(嵌入/向量化) = 将文本、图像等非结构化数据映射为高维空间中的一个向量(一串数字)。语义相似的内容,向量距离也近。
示例(简化,实际是几百到几千维):
"人工智能" → [0.23, -0.15, 0.67, 0.01, ...]
"机器学习" → [0.19, -0.12, 0.58, -0.03, ...] ← 与"人工智能"距离近
"天气变化" → [-0.45, 0.81, 0.02, 0.33, ...] ← 距离远
"气候变化" → [-0.42, 0.78, 0.05, 0.29, ...] ← 与"天气变化"距离近直觉理解:
- 每一个维度代表一个语义特征(可能是"是否和科技相关"、"是否积极情感"等)
- 模型学习到的维度是隐式的,我们不需要明确知道每个维度含义
- 只要保证"相似内容→相似向量"这个映射,就可以用向量做相似度计算
Q2: 相似度计算方法
方法1: 余弦相似度(Cosine Similarity)⭐最常用
cos(θ) = A · B / (|A| × |B|)
范围: [-1, 1]
1 = 完全相同
0 = 完全无关
-1 = 完全相反
优点:不受向量长度影响,只关心方向(语义)
适用:文本、图像等语义匹配
方法2: 欧式距离(Euclidean Distance / L2)
d = √[(a1-b1)² + (a2-b2)² + ... + (an-bn)²]
范围: [0, ∞),越小越相似
适用:特征向量(如人脸特征、图像特征)
方法3: 点积/内积(Dot Product / Inner Product)
A · B = Σ ai × bi
如果向量已归一化(长度=1),点积等价于余弦相似度
范围: [-1, 1](归一化后)
优点:计算最快实际选择建议:
| 场景 | 推荐方法 | Milvus配置 |
|---|---|---|
| 文本Embedding | 余弦相似度 (COSINE) | metric_type="COSINE" |
| 图像特征 | 欧式距离 (L2) | metric_type="L2" |
| 已归一化向量 | 内积 (IP) | metric_type="IP"(速度最快) |
Q3: 向量维度的选择
| 模型 | 维度 | 说明 |
|---|---|---|
| OpenAI text-embedding-3-small | 1536 | 性价比高,通用 |
| OpenAI text-embedding-3-large | 3072 | 效果更好,更慢更贵 |
| BGE-small-zh | 512 | 中文优化,轻量级 |
| BGE-base-zh | 768 | 平衡选择 |
| BGE-large-zh | 1024 | 中文效果好 |
| sentence-transformers | 768 | 英文/通用场景 |
维度与性能的权衡:
维度 ↑ → 表达能力 ↑ 但 存储成本 ↑、检索速度 ↓
维度 ↓ → 检索速度 ↑ 但 可能损失精度
推荐做法:
1. 先用1536维(或512/768维的开源模型)
2. 如果精度不够,再尝试更大维度
3. 如果存储/速度不够,用PCA降维或切分成更小维度二、主流向量数据库对比
Q4: 向量数据库选型指南
┌──────────────────────────────────────────────────────────────┐
│ 向量数据库选型矩阵 │
├────────────┬──────────┬──────────┬──────────┬─────────────────┤
│ 产品 │ Milvus │ Chroma │ Pinecone │ Weaviate │
├────────────┼──────────┼──────────┼──────────┼─────────────────┤
│ 类型 │ 自建服务 │ 本地/服务 │ SaaS云 │ 自建服务 │
│ 开源协议 │ Apache 2│ Apache 2│ 商业 │ BSD │
│ 规模 │ 大规模 │ 中小规模 │ 弹性 │ 中大规模 │
│ 易用性 │ ⭐⭐⭐⭐ │ ⭐⭐⭐⭐⭐ │ ⭐⭐⭐⭐⭐ │ ⭐⭐⭐ │
│ 性能 │ ⭐⭐⭐⭐⭐│ ⭐⭐⭐ │ ⭐⭐⭐⭐⭐ │ ⭐⭐⭐⭐ │
│ 标量过滤 │ ✅强 │ ✅基础 │ ✅强 │ ✅原生GraphQL │
│ 多租户 │ ✅ │ ✅ │ ✅ | ✅ │
│ GPU加速 | ✅ | ❌ | ✅ | ✅ │
└────────────┴──────────┴──────────┴──────────┴─────────────────┘具体建议:
| 场景 | 推荐产品 | 理由 |
|---|---|---|
| 快速POC/原型 | Chroma | 零配置、Python友好、本地即可 |
| 企业生产 | Milvus | 功能全、性能强、社区活跃、可扩展 |
| 不想运维 | Pinecone | 全托管、SaaS模式、按量计费 |
| 需要GraphQL+向量 | Weaviate | 原生支持混合查询、模块丰富 |
| 简单/研究场景 | FAISS | Facebook开源、纯向量、最灵活 |
Q5: Milvus快速上手
python
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType
# 1. 连接
connections.connect(host="localhost", port="19530")
# 2. 定义Schema(类似建表)
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=2000),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1536),
FieldSchema(name="category", dtype=DataType.VARCHAR, max_length=64),
]
schema = CollectionSchema(fields, "文档知识库")
collection = Collection("documents", schema)
# 3. 创建索引(HNSW是当前主流)
index_params = {
"index_type": "HNSW", # 索引类型:Hierarchical Navigable Small World
"metric_type": "COSINE", # 相似度度量
"params": {"M": 32, "efConstruction": 256} # 索引构建参数
}
collection.create_index(field_name="embedding", index_params=index_params)
# 4. 插入数据
embeddings = model.encode(["文档1的内容", "文档2的内容", ...])
data = [
["文档1的内容", "文档2的内容", ...], # content
embeddings, # embedding
["技术", "技术", ...], # category
]
collection.insert(data)
# 5. 加载到内存 + 检索
collection.load()
search_params = {"metric_type": "COSINE", "params": {"ef": 64}}
results = collection.search(
data=[query_embedding], # 查询向量
anns_field="embedding", # 在哪个字段上搜索
param=search_params,
limit=5, # 返回Top-5
expr="category == '技术'", # 额外的标量过滤
output_fields=["content"] # 返回内容字段
)
# 6. 查看结果
for hit in results[0]:
print(f"ID: {hit.id}, Score: {hit.score}, Content: {hit.entity.get('content')}")Q6: Chroma快速上手(更简单)
python
import chromadb
# 1. 创建Client
client = chromadb.Client()
# 或持久化到磁盘: client = chromadb.PersistentClient(path="./chroma_db")
# 2. 创建Collection(相当于表)
collection = client.create_collection(
name="documents",
metadata={"hnsw:space": "cosine"} # l2 / ip / cosine
)
# 3. 添加文档(Chroma自动调用Embedding)
collection.add(
documents=["文档1内容", "文档2内容", "文档3内容"],
metadatas=[{"source": "guide"}, {"source": "api"}, {"source": "faq"}],
ids=["doc1", "doc2", "doc3"]
)
# 4. 查询
results = collection.query(
query_texts=["如何安装Python?"],
n_results=2,
where={"source": "guide"} # 元数据过滤
)
# 5. 删除
collection.delete(ids=["doc3"])三、索引类型与参数
Q7: HNSW索引详解(最常用)
HNSW (Hierarchical Navigable Small World) = 基于多层小世界图的近似最近邻搜索
搜索原理(简化理解):
Layer 2 (最上层,稀疏节点): ○ ○
╱ ╲
Layer 1: ○──○──○ ○──○
╱ ╲ ╱
Layer 0 (最下层,全量节点): ●●●●●●●●●●●●●●
搜索过程:
1. 从Layer 2的随机入口点开始
2. 在当前层贪心找到最近邻
3. 下到下一层,从刚才的最近邻继续搜索
4. 重复直到Layer 0
5. 得到最终的Top-K近邻HNSW参数调优:
| 参数 | 推荐范围 | 影响 |
|---|---|---|
| M | 8-64 | 每层节点的连接数。M越大,召回率越高、内存越大、构建越慢 |
| efConstruction | 100-500 | 构建时的搜索广度。越大质量越高、构建越慢 |
| ef (搜索时) | 10-2000 | 搜索时的广度。越大召回率越高、搜索越慢 |
经验配置:
内存敏感 → M=16, ef=50 (速度快,精度略低)
平衡场景 → M=32, ef=64 (⭐推荐默认)
精度优先 → M=64, ef=128 (高精度,速度/内存开销大)Q8: 其他索引类型
| 索引 | 全称 | 特点 | 适用场景 |
|---|---|---|---|
| FLAT | 暴力搜索 | 100%召回、最慢 | 小数据集(<1M)、需要绝对精度 |
| IVF_FLAT | 倒排文件 | 先聚类后搜索 | 中大数据集、内存充足 |
| IVF_SQ8 | 倒排+标量量化 | 向量压缩到8bit,内存1/4 | 大数据集、内存受限 |
| IVF_PQ | 倒排+乘积量化 | 向量压缩到更小 | 超大数据集、可容忍精度损失 |
| HNSW | 多层小世界图 | 高维、高性能、内存较大 | ⭐ 大多数生产场景 |
| DISKANN | 磁盘友好 | 内存占用极低,依赖SSD | 超大规模、内存受限 |
Q9: 索引构建 vs 搜索时间
索引类型 构建时间 搜索延迟 内存占用 召回率
FLAT 最快 最慢 中 100%
IVF_FLAT 快 快 中 95-99%
IVF_SQ8 中 快 低 90-95%
HNSW 中 极快 偏高 95-99%
DiskANN 慢 中 极低 90-98%四、向量检索实战技巧
Q10: 混合检索(关键词 + 向量)
纯向量检索的问题:关键词匹配能力弱(如"iPhone 15 Pro Max"可能被"Apple手机"匹配,但精确型号不匹配)。用BM25+向量的混合检索可以兼顾。
python
# 1. 向量检索(语义相关)
vector_results = collection.search(
data=[query_embedding],
limit=20, # 多取一些,后面重排
...
)
# 2. 关键词检索(精确匹配)
keyword_results = bm25_search(query, documents, top_k=20)
# 3. 融合(RRF - Reciprocal Rank Fusion)
def rrf_score(rank, k=60):
return 1 / (k + rank)
scores = {}
for rank, doc_id in enumerate(vector_results):
scores[doc_id] = scores.get(doc_id, 0) + rrf_score(rank)
for rank, doc_id in enumerate(keyword_results):
scores[doc_id] = scores.get(doc_id, 0) + rrf_score(rank)
# 4. 返回融合后的Top-K
top_docs = sorted(scores.items(), key=lambda x: x[1], reverse=True)[:5]Q11: 文档切分策略与Chunk大小
Chunk大小选择指南:
小 Chunk (100-200 tokens)
✓ 更精确的匹配
✓ 检索速度快
✗ 丢失上下文
✗ 需要更多Chunks
➜ 适用:问答系统、关键词密集的内容
中 Chunk (300-500 tokens) ⭐推荐
✓ 平衡精度与上下文
✓ 通用性好
➜ 适用:大多数文档、通用RAG场景
大 Chunk (800-1500 tokens)
✓ 包含完整上下文
✗ 可能引入噪声
✗ 检索变慢(向量更长)
➜ 适用:长文章、需要完整段落理解
切分策略:
1. 按段落切分(优先)
2. 按句子切分(补充,处理长段落)
3. 固定长度切分(最通用的fallback)
4. 按标题层级切分(Markdown/HTML,效果最佳)
重叠大小(Overlap):
· 建议为chunk_size的10-20%
· 例:chunk=500,overlap=50-100
· 保证相邻chunk有上下文连续性Q12: 标量过滤与混合查询
向量搜索的同时,可能需要按时间、分类、来源等做过滤
python
# Milvus示例:先过滤再搜索
results = collection.search(
data=[query_vec],
anns_field="embedding",
param={"metric_type": "COSINE", "params": {"ef": 64}},
limit=5,
# 标量过滤表达式(类似SQL WHERE)
expr="""
category == '技术文档'
AND created_at > '2024-01-01'
AND view_count > 100
""",
output_fields=["title", "content", "category"]
)
# Chroma示例:用where过滤
results = collection.query(
query_texts=[query],
n_results=5,
# where过滤
where={"category": {"$eq": "技术文档"}},
# 或更复杂的过滤
where_document={"$contains": "Python"} # 包含特定关键词
)Q13: 多向量策略(提高召回的高级技巧)
策略1: 一个文档生成多个向量
文档:"Python 3.12新特性解析"
向量1: [基于标题生成]
向量2: [基于摘要生成]
向量3: [基于关键词生成]
↓
分别存储,查询时取并集
优点:召回率更高
缺点:存储成本×N,检索需要去重
策略2: HyDE(Hypothetical Document Embedding)
用户查询:"Python asyncio如何使用?"
↓
LLM生成"假设性文档"(一段看起来像答案的文本)
↓
用这段文档做向量检索(比直接用问题检索更准确)
↓
返回真实文档
策略3: 查询改写
原始查询 → LLM改写为3种不同表达方式 → 3个查询并行检索
↓
结果合并去重后返回
(类似搜索引擎的"相关搜索"扩展)五、性能优化
Q14: 大规模场景优化
┌────────────────────────────────────────────────────────────┐
│ 向量检索性能优化 │
├────────────────────────────────────────────────────────────┤
│ │
│ 📊 数据规模:10万以下 → Chroma / FAISS │
│ 10万-1亿 → Milvus / Weaviate │
│ 1亿以上 → Milvus集群 + GPU加速 │
│ │
│ 🚀 检索优化 │
│ · 合理设置ef搜索参数(64-256,越大越准越慢) │
│ · 用分区(Partition)按时间/类别分片 │
│ · 启用标量索引(如category字段建B树索引) │
│ · 预热:定期加载热门collection到内存 │
│ │
│ 💾 内存优化 │
│ · 使用IVF_SQ8/PQ等压缩索引(内存减到1/4-1/8) │
│ · 合理设置M值(HNSW) │
│ · 冷数据可以降维到更小维度 │
│ │
│ 🔄 缓存策略 │
│ · 热门Query的Embedding缓存 │
│ · 热门Query的检索结果缓存 │
│ · LRU淘汰策略 │
│ │
│ 📈 监控指标 │
│ · 检索延迟(P50/P95/P99) │
│ · 召回率(抽样对比ground truth) │
│ · 内存/CPU/GPU利用率 │
│ · 索引构建时间与大小 │
│ │
└────────────────────────────────────────────────────────────┘Q15: 常见问题排查
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 搜索结果不相关 | Embedding模型不匹配任务 | 换更大的Embedding模型/微调 |
| 搜索太慢(>1s) | 数据量大 + 索引参数不合理 | 减小ef / 建分区 / 加缓存 |
| 内存不够 | 维度太高 + 数据太多 | 量化/降维 / IVF_PQ索引 |
| 搜索波动大 | 索引不稳定 | 重新构建索引 / 调整ef |
| 中文效果差 | 用了英文Embedding模型 | 换BGE/M3E等中文专用模型 |
向量检索高频问题速查
| 问题 | 一句话答案 |
|---|---|
| 用什么向量数据库? | 原型Chroma,生产Milvus,不想运维Pinecone |
| 用什么相似度? | 文本用COSINE,特征用L2,已归一化用IP |
| Chunk大小? | 300-500 tokens + 10-20% overlap(通用) |
| Embedding模型选什么? | 中文BGE/M3E,通用text-embedding-3 |
| 搜索不准怎么办? | 换模型/增大召回top-k/混合检索/重排序 |
| 数据量1亿+怎么办? | Milvus集群 + IVF索引 + 合理分区 |
| 如何提高搜索速度? | 减小ef/用GPU索引/缓存热门结果 |
| 如何保证搜索准确性? | 用Cross-Encoder重排序 / RRF融合 |