向量数据库实战(Milvus/Chroma)
从 Embedding 的第一个维度到 RAG 系统的最后一公里——手把手带你玩转向量数据库。
2. Embedding——把万物变成向量
上一章我们知道了:向量数据库靠"计算向量之间的距离"来做语义搜索。但有一个关键前提——你得先把文本变成向量。
这个"变"的过程,就是 Embedding。
它是整个向量数据库工作流的第一步,也是决定搜索质量的最关键一步——如果向量生成得不好,后面的索引、检索、RAG 全都白搭。
2.1 Embedding 的直觉理解:语义的坐标系
什么是 Embedding
Embedding 的本质是一个映射函数——把非结构化数据(文本、图片、音频)映射成一组固定长度的浮点数:
Embedding 函数的输入输出:
输入(人类可读) 输出(机器可计算)
───────────────── ─────────────────────────
"Python 很好用" → [0.82, -0.15, 0.93, ..., 0.41]
"Java 性能很强" → [0.71, -0.22, 0.85, ..., 0.38]
"今天天气真好" → [-0.12, 0.88, -0.05, ..., 0.67]
关键特性:
• 输出长度固定(如 1536 维)
• 语义相近 → 向量相近
• 语义无关 → 向量远离为什么叫"嵌入"
"Embedding"翻译成"嵌入"——把高维的、复杂的语义信息嵌入到一个连续的数值空间中:
从离散文字到连续空间:
人类的文字世界(离散、模糊、多义)
┌──────────────────────────────────┐
│ "快" │
│ → 速度快?心情愉快?快递? │
│ → 传统搜索无法区分 │
└──────────────────────────────────┘
│
│ Embedding 模型
│ (神经网络)
▼
连续数值空间(精确、可计算、消歧义)
┌──────────────────────────────────┐
│ "这个算法运行很快" │
│ → [0.82, 0.15, ...] │
│ 靠近"性能优化"、"加速" │
│ │
│ "今天心情很快乐" │
│ → [-0.31, 0.77, ...] │
│ 靠近"开心"、"愉悦" │
│ │
│ → 同一个"快"字,不同语境 │
│ → 被映射到空间中完全不同的位置 │
└──────────────────────────────────┘核心价值:Embedding 把人类语言中的"模糊语义"变成了计算机能精确计算的"数字坐标"。有了坐标,就能算距离;能算距离,就能做搜索。
Embedding 模型是怎么训练出来的
你不需要自己训练 Embedding 模型(直接用现成的就好),但理解训练原理有助于做出更好的选型决策:
Embedding 模型的训练思路(简化版):
训练目标:让语义相近的文本对,向量也相近
─────────────────────────────────────────
正样本对(语义相近):
"怎么提升代码性能" ←→ "如何让程序跑得更快"
→ 训练目标:让这两个文本的向量距离 → 0
负样本对(语义无关):
"怎么提升代码性能" ←→ "今天午饭吃什么"
→ 训练目标:让这两个文本的向量距离 → 1
训练数据来源:
• 搜索引擎的 query-document 点击日志
• 问答网站的 question-answer 配对
• 论文的 title-abstract 配对
• 人工标注的语义相似度数据集
模型架构:
┌───────────┐ ┌───────────┐
│ 文本 A │ │ 文本 B │
└─────┬─────┘ └─────┬─────┘
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ Transformer│ │ Transformer│ ← 共享参数
│ Encoder │ │ Encoder │
└─────┬─────┘ └─────┬─────┘
│ │
▼ ▼
向量 A 向量 B
│ │
└───── 对比 ──────┘
│
是否相似?→ 调整参数实用结论:训练数据决定了 Embedding 模型擅长什么。用英文数据训练的模型做中文搜索效果差,用通用数据训练的模型做代码搜索也不够好。所以选对模型很重要。
2.2 主流 Embedding 模型选型
Embedding 模型的选择直接决定了搜索质量。选错了模型,后面的向量数据库再怎么调优也救不回来。
三大阵营
Embedding 模型的生态格局:
┌─────────────────────────────────────────────────┐
│ │
│ 1️⃣ 商业 API(开箱即用,按量付费) │
│ • OpenAI text-embedding-3-small/large │
│ • Google text-embedding-005 │
│ • Cohere embed-v4 │
│ │
│ 2️⃣ 开源通用模型(免费,本地部署) │
│ • BGE 系列(智源,中文最强) │
│ • GTE 系列(阿里) │
│ • Sentence-Transformers(HuggingFace 生态) │
│ • E5 系列(微软) │
│ │
│ 3️⃣ 特定领域模型(针对性场景) │
│ • CodeBERT / UniXcoder(代码搜索) │
│ • BiomedCLIP(医学图文) │
│ • 各行业微调模型 │
│ │
└─────────────────────────────────────────────────┘主流模型对比
| 模型 | 维度 | 中文能力 | 价格 | 速度 | 适合场景 |
|---|---|---|---|---|---|
| OpenAI text-embedding-3-small | 1536 | ★★★★ | $0.02/100万 token | 极快(API) | 通用场景、快速原型 |
| OpenAI text-embedding-3-large | 3072 | ★★★★ | $0.13/100万 token | 快(API) | 需要更高精度 |
| BGE-large-zh-v1.5 | 1024 | ★★★★★ | 免费 | 中等(本地) | 中文场景首选 |
| BGE-M3 | 1024 | ★★★★★ | 免费 | 中等(本地) | 多语言 + 混合检索 |
| GTE-large-zh | 1024 | ★★★★★ | 免费 | 中等(本地) | 中文场景备选 |
| all-MiniLM-L6-v2 | 384 | ★★ | 免费 | 极快(本地) | 英文轻量场景 |
| E5-large-v2 | 1024 | ★★★ | 免费 | 中等(本地) | 英文高质量 |
选型决策指南
你的场景是什么?
需要支持中文?
├── 是
│ ├── 追求省事,有预算? → OpenAI text-embedding-3-small
│ ├── 数据不能出境? → BGE-large-zh-v1.5(本地部署)
│ ├── 需要多语言混合? → BGE-M3
│ └── 数据量巨大,要求性价比? → BGE-M3(免费 + 混合检索)
│
└── 否(纯英文)
├── 快速原型? → OpenAI text-embedding-3-small
├── 资源受限? → all-MiniLM-L6-v2(384 维,极快)
└── 高精度要求? → E5-large-v2 或 OpenAI large关于维度的一个常见误解
"维度越高 = 效果越好?"
❌ 错误理解:
384 维 < 1024 维 < 1536 维 < 3072 维
→ 所以 3072 维一定最好?
✅ 正确理解:
维度只是向量的"信息容量"
更重要的是模型的训练质量和数据
实际表现(MTEB 中文基准测试):
BGE-large-zh 1024 维 → 得分 67.3
OpenAI small 1536 维 → 得分 65.1
all-MiniLM 384 维 → 得分 41.2(中文很差)
结论:
→ 中文场景下,1024 维的 BGE 比 1536 维的 OpenAI 更好
→ 维度不是决定因素,训练数据和模型架构才是
→ 但维度越高,存储和计算成本越高
实际建议:
• 1024 维是目前的甜点(平衡精度和成本)
• 384 维适合资源极度受限的场景
• 3072 维通常性价比不高,除非有特殊精度需求经验之谈:如果你的应用主要面向中文用户,BGE 系列是当前的最优选。免费、本地部署、在中文基准上持续领先。只有在追求开发效率(不想管模型部署)时,才考虑 OpenAI API。
2.3 实操:用 Python 生成文本向量
理论讲完,动手。我们分别用 OpenAI API 和开源模型生成向量,然后亲手验证"语义相近 → 向量相近"。
方案一:OpenAI Embedding API(最快上手)
# 安装依赖
# pip install openai numpy
from openai import OpenAI
import numpy as np
client = OpenAI() # 需要设置 OPENAI_API_KEY 环境变量
# --- 生成单条文本的向量 ---
def get_embedding(text: str, model: str = "text-embedding-3-small") -> list[float]:
"""调用 OpenAI API 生成文本向量"""
response = client.embeddings.create(
input=text,
model=model
)
return response.data[0].embedding
# 试一下
embedding = get_embedding("Python 性能优化指南")
print(f"向量维度:{len(embedding)}") # 1536
print(f"前 5 个值:{embedding[:5]}") # [0.023, -0.041, ...]
# --- 批量生成(更高效) ---
def get_embeddings(texts: list[str], model: str = "text-embedding-3-small") -> list[list[float]]:
"""批量生成向量,一次 API 调用处理多条文本"""
response = client.embeddings.create(
input=texts,
model=model
)
return [d.embedding for d in response.data]
# 批量处理 100 条文本,只需 1 次 API 调用
texts = ["文本1", "文本2", "文本3"] # 实际可放 100+ 条
embeddings = get_embeddings(texts)成本估算:text-embedding-3-small 的价格是 $0.02 / 100 万 token。一篇 1000 字的中文文档 ≈ 500-800 token,也就是说 100 万字的文档库,向量化成本不到 1 块钱。
方案二:开源模型 BGE(本地部署、免费)
# 安装依赖
# pip install sentence-transformers
from sentence_transformers import SentenceTransformer
# 加载 BGE 中文模型(首次运行会下载约 1.2GB)
model = SentenceTransformer("BAAI/bge-large-zh-v1.5")
# --- 生成向量 ---
texts = [
"Python 性能优化指南",
"怎么让 Python 程序跑得更快",
"今天天气真好"
]
# encode() 自动处理批量
embeddings = model.encode(texts, normalize_embeddings=True)
print(f"向量维度:{embeddings.shape}") # (3, 1024)
print(f"第1条向量前5个值:{embeddings[0][:5]}")注意:BGE 模型推荐设置
normalize_embeddings=True,这会对向量做 L2 归一化,使得余弦相似度计算更高效(归一化后的向量,内积 = 余弦相似度)。
亲手验证:语义相近 = 向量相近
import numpy as np
def cosine_similarity(a, b):
"""计算两个向量的余弦相似度"""
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 准备 3 组文本
texts = [
"Python 性能优化指南", # A: 基准
"怎么让 Python 程序跑得更快", # B: 语义相近,措辞不同
"今天午饭吃什么好呢", # C: 完全无关
]
# 生成向量(用 BGE 或 OpenAI 都行)
embeddings = model.encode(texts, normalize_embeddings=True)
# 计算相似度
sim_ab = cosine_similarity(embeddings[0], embeddings[1])
sim_ac = cosine_similarity(embeddings[0], embeddings[2])
print(f"A ↔ B 相似度:{sim_ab:.4f}") # ≈ 0.85 (高度相似)
print(f"A ↔ C 相似度:{sim_ac:.4f}") # ≈ 0.12 (几乎无关)运行结果解读:
"Python 性能优化指南" ↔ "怎么让程序跑得更快"
→ 相似度 ≈ 0.85
→ ✅ 模型理解了"性能优化" ≈ "跑得更快"
"Python 性能优化指南" ↔ "今天午饭吃什么"
→ 相似度 ≈ 0.12
→ ✅ 模型知道两者毫无关系
对比关键词搜索:
→ "性能优化" 和 "跑得更快" 没有一个字重叠
→ 关键词搜索会认为它们完全无关
→ 但 Embedding 知道它们在说同一件事关键收获:这就是为什么向量数据库能做到"关键词搜索做不到的事"——因为 Embedding 模型在训练过程中已经学会了人类语言的语义关系。
Embedding 使用的注意事项
实际项目中容易踩的坑:
1. 模型不能混用
─────────────────────────────────────
❌ 文档用 OpenAI 向量化,查询用 BGE 向量化
→ 两个模型的向量空间完全不同,无法比较
✅ 必须用同一个模型生成所有向量
2. 文本长度有上限
─────────────────────────────────────
• OpenAI small:最多 8191 token(≈ 4000 中文字)
• BGE-large-zh:最多 512 token(≈ 250 中文字)
→ 超长文本必须先分块(Chunking),再逐块向量化
3. 不同类型的内容需要不同策略
─────────────────────────────────────
• 短文本(标题、问题)→ 直接向量化
• 长文本(文章、文档)→ 分块后向量化
• 代码 → 考虑用代码专用 Embedding 模型
• 多模态 → 考虑 CLIP 等跨模态模型本章小结
| 知识点 | 要点 |
|---|---|
| Embedding 本质 | 把非结构化数据映射为固定长度的浮点数向量 |
| 核心特性 | 语义相近的文本 → 向量距离近;语义无关 → 向量距离远 |
| 训练原理 | 对比学习:正样本对距离 → 0,负样本对距离 → 1 |
| 模型选型 | 中文优先 BGE 系列,省事选 OpenAI API |
| 维度迷思 | 维度 ≠ 质量;1024 维是当前甜点 |
| OpenAI API | 简单、快速、便宜($0.02/百万 token),但数据要出境 |
| BGE 本地部署 | 免费、中文强、数据不出境,但需要 GPU 加速 |
| 核心注意点 | 模型不能混用、长文本要分块、选对场景匹配的模型 |
下一章预告:相似度搜索——向量数据库的核心能力。我们会深入余弦相似度、欧氏距离、内积三种度量方式的原理与对比,理解为什么在百万级数据上"暴力搜索"不可行,以及 ANN 近似搜索如何解决这个问题。