本节目标:理解如何把文字变成数字(Embedding),以及如何高效地存储和搜索这些数字(向量数据库)。这是构建 RAG 系统的基础。
一、什么是 Embedding?
1.1 通俗理解
Embedding 就是把文字变成一组数字(向量),让计算机能理解文字之间的语义关系。
人类理解语义: "猫" 和 "狗" 很相似(都是宠物)
"猫" 和 "汽车" 很不同
计算机怎么理解? → 把文字变成数字!
"猫" → [0.2, 0.8, 0.1, 0.9, ...] ← 一组数字(向量)
"狗" → [0.3, 0.7, 0.2, 0.8, ...] ← 和"猫"的数字很接近
"汽车" → [0.9, 0.1, 0.8, 0.2, ...] ← 和"猫"的数字差很远
1.2 一个直观的比喻
想象一个二维地图(实际的 Embedding 是几百维,但原理一样):
↑ "动物属性"
│
1.0 │ 🐱猫 🐶狗
│
0.8 │ 🐰兔子
│
0.5 │
│ ✈️飞机
0.2 │ 🚗汽车
│ 🚂火车
0.0 ├───────────────────→ "交通工具属性"
0.0 0.2 0.5 0.8 1.0
"猫"和"狗"在地图上离得很近 → 语义相似
"猫"和"汽车"离得很远 → 语义不同
1.3 Embedding 的维度
实际的 Embedding 不是 2 维,而是几百到几千维:
┌─────────────────────┬──────────────────────┐
│ Embedding 模型 │ 向量维度 │
├─────────────────────┼──────────────────────┤
│ OpenAI text-ada-002│ 1536 维 │
│ OpenAI text-3-small│ 1536 维 │
│ OpenAI text-3-large│ 3072 维 │
│ BGE-large-zh │ 1024 维 │
│ Jina Embeddings v3 │ 1024 维 │
│ Cohere Embed v3 │ 1024 维 │
└─────────────────────┴──────────────────────┘
维度越高 → 能表达更细微的语义差异 → 但计算量更大
二、如何生成 Embedding?
2.1 使用 OpenAI Embedding API
from openai import OpenAI
client = OpenAI(api_key="sk-xxx")
# 生成单个文本的 Embedding
response = client.embeddings.create(
model="text-embedding-3-small",
input="今天天气真好"
)
vector = response.data[0].embedding
print(f"维度:{len(vector)}") # 1536
print(f"前5个数字:{vector[:5]}") # [0.012, -0.034, 0.056, ...]
2.2 使用开源 Embedding 模型
# 使用 sentence-transformers 库(免费、可本地运行)
# pip install sentence-transformers
from sentence_transformers import SentenceTransformer
# 加载模型(首次运行会自动下载)
model = SentenceTransformer('BAAI/bge-large-zh-v1.5') # 中文最佳
# 生成 Embedding
sentences = ["今天天气真好", "天气不错适合出门", "我要买一辆汽车"]
embeddings = model.encode(sentences)
print(f"形状:{embeddings.shape}") # (3, 1024) → 3个句子,每个1024维
三、向量相似度计算
3.1 余弦相似度(最常用)
衡量两个向量的方向是否一致,不关心长度。
余弦相似度的直觉理解:
两个向量方向完全相同 → 相似度 = 1(完全相似)
两个向量方向垂直 → 相似度 = 0(不相关)
两个向量方向相反 → 相似度 = -1(完全相反)
↑ B
╱
╱ θ = 小角度 → cos(θ) 接近 1 → 很相似
╱
──────→ A
import numpy as np
def cosine_similarity(a, b):
"""计算两个向量的余弦相似度"""
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
# 示例
emb_cat = model.encode("一只可爱的猫咪")
emb_dog = model.encode("一只可爱的狗狗")
emb_car = model.encode("一辆红色的汽车")
print(f"猫 vs 狗:{cosine_similarity(emb_cat, emb_dog):.3f}") # ~0.85 高相似
print(f"猫 vs 车:{cosine_similarity(emb_cat, emb_car):.3f}") # ~0.25 低相似
3.2 其他距离度量
┌──────────────────┬──────────────────────────────────────────┐
│ 度量方式 │ 适用场景 │
├──────────────────┼──────────────────────────────────────────┤
│ 余弦相似度 │ 最通用,推荐默认使用 │
│ (Cosine) │ 不受向量长度影响 │
├──────────────────┼──────────────────────────────────────────┤
│ 欧氏距离 │ 需要考虑"绝对距离"时 │
│ (Euclidean) │ 值越小越相似 │
├──────────────────┼──────────────────────────────────────────┤
│ 内积 │ 已归一化的向量 │
│ (Inner Product) │ 等价于余弦相似度 │
└──────────────────┴──────────────────────────────────────────┘
四、向量数据库
4.1 为什么需要向量数据库?
场景:你有 100 万篇文档的 Embedding,用户问了一个问题。
需要快速找到最相关的 10 篇文档。
暴力搜索(逐一比较):
比较 100 万次 → 太慢了!(几分钟)
向量数据库(用索引加速):
用特殊的数据结构 → 毫秒级返回结果!
4.2 主流向量数据库对比
┌──────────────┬──────────┬──────────┬──────────┬─────────────────┐
│ 数据库 │ 类型 │ 特点 │ 难度 │ 适合场景 │
├──────────────┼──────────┼──────────┼──────────┼─────────────────┤
│ Chroma │ 嵌入式 │ 最简单 │ ★☆☆☆☆ │ 学习、原型验证 │
│ FAISS │ 库(Meta)│ 最快 │ ★★★☆☆ │ 研究、单机大规模 │
│ Milvus │ 分布式 │ 功能全 │ ★★★★☆ │ 企业级生产环境 │
│ Qdrant │ 服务端 │ Rust写的 │ ★★★☆☆ │ 性能敏感场景 │
│ Weaviate │ 服务端 │ 多模态 │ ★★★☆☆ │ 图+文混合搜索 │
│ Pinecone │ 全托管 │ 免运维 │ ★★☆☆☆ │ 不想管运维 │
│ PgVector │ PG扩展 │ 复用PG │ ★★☆☆☆ │ 已有PostgreSQL │
└──────────────┴──────────┴──────────┴──────────┴─────────────────┘
4.3 Chroma 快速上手(最简单)
# pip install chromadb
import chromadb
# 1. 创建客户端和集合
client = chromadb.Client()
collection = client.create_collection("my_docs")
# 2. 添加文档(Chroma 自动生成 Embedding!)
collection.add(
documents=[
"Python 是一种简单易学的编程语言",
"Java 是一种面向对象的编程语言",
"今天股市大涨,上证指数突破3500点",
"机器学习是人工智能的一个子领域",
],
ids=["doc1", "doc2", "doc3", "doc4"]
)
# 3. 搜索最相关的文档
results = collection.query(
query_texts=["什么编程语言适合初学者?"],
n_results=2 # 返回最相关的2条
)
print(results["documents"])
# [['Python 是一种简单易学的编程语言',
# 'Java 是一种面向对象的编程语言']]
# → 准确找到了编程相关的文档,没有返回股市新闻!
4.4 Milvus 生产级使用
# pip install pymilvus
from pymilvus import MilvusClient
# 1. 连接 Milvus
client = MilvusClient("http://localhost:19530")
# 2. 创建集合
client.create_collection(
collection_name="articles",
dimension=1024, # 向量维度,需要与 Embedding 模型匹配
)
# 3. 插入数据
data = [
{"id": 1, "vector": [0.1, 0.2, ...], "text": "Python 入门教程"},
{"id": 2, "vector": [0.3, 0.4, ...], "text": "Java 设计模式"},
# ...
]
client.insert(collection_name="articles", data=data)
# 4. 搜索
results = client.search(
collection_name="articles",
data=[query_embedding], # 查询向量
limit=5, # 返回 top 5
output_fields=["text"] # 同时返回文本
)
五、向量索引类型
5.1 为什么需要索引?
没有索引(暴力搜索 Flat):
┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ... ┌──┐
│ │ │ │ │ │ │ │ │ │ │ │
└──┘ └──┘ └──┘ └──┘ └──┘ └──┘
逐一比较所有向量 → 准确但慢
有索引(近似搜索):
先把向量分组/建树,搜索时只看相关的组 → 快但可能遗漏
5.2 常见索引类型
┌───────────┬──────────────────────────────────────────────┐
│ 索引类型 │ 通俗解释 │
├───────────┼──────────────────────────────────────────────┤
│ Flat │ 暴力搜索,逐一比较 │
│ (平坦) │ 最准确但最慢。数据少时可以用 │
│ │ │
│ IVF │ 先把向量分成若干个"簇" │
│ (倒排) │ 搜索时只在最相近的几个簇中找 │
│ │ 类比:先确定在哪个书架,再在书架上找书 │
│ │ │
│ HNSW │ 建一个多层图结构(像跳表) │
│ (层次图) │ 从粗到细逐层搜索 │
│ │ 类比:先看世界地图,再看国家地图,再看城市地图 │
│ │ 目前最流行的索引类型 │
│ │ │
│ PQ │ 把向量压缩(量化),用更少的空间存储 │
│ (乘积量化)│ 牺牲一点精度换取大幅减少内存占用 │
└───────────┴──────────────────────────────────────────────┘
HNSW 索引示意图:
Layer 2 (最顶层,最稀疏): A ─────────── D
│ │
Layer 1 (中间层): A ── B ──── C ── D
│ │ │ │
Layer 0 (最底层,最密集): A─B─E─F─C─G─H─D─I
搜索过程:
1. 从顶层开始,找到大致方向(A → D)
2. 下一层细化(A → B → C)
3. 底层精确搜索(找到最近邻)
六、混合检索
6.1 向量检索的局限
问题:用户搜索 "Python 3.12 新特性"
纯向量搜索可能返回:
1. "Python 最新版本的功能介绍" ← 相关但不精确
2. "编程语言的新特性总结" ← 相关但太宽泛
✗ 可能没有精确匹配 "3.12" 这个版本号
纯关键词搜索(BM25)可能返回:
1. "Python 3.12 发布说明" ← 精确匹配 ✓
2. "Python 3.12 变更日志" ← 精确匹配 ✓
✗ 但如果用户搜 "最新Python有啥变化",关键词匹配不到
6.2 混合检索 = 向量 + 关键词
┌────────────────────────────────────────────────────────┐
│ 混合检索流程 │
│ │
│ 用户查询:"Python 3.12 新特性" │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ 向量检索 BM25 关键词检索 │
│ (语义理解) (精确匹配) │
│ │ │ │
│ ▼ ▼ │
│ 结果集A 结果集B │
│ │ │ │
│ └────┬────┘ │
│ ▼ │
│ 融合排序(RRF / 加权融合) │
│ │ │
│ ▼ │
│ 最终排序结果 │
└────────────────────────────────────────────────────────┘
# 混合检索的简化实现
def hybrid_search(query, documents, alpha=0.7):
"""
alpha: 向量检索的权重(0-1)
1-alpha: 关键词检索的权重
"""
# 向量检索得分
vector_scores = vector_search(query, documents)
# BM25 关键词检索得分
bm25_scores = bm25_search(query, documents)
# 融合得分
final_scores = {}
for doc_id in set(vector_scores) | set(bm25_scores):
v_score = vector_scores.get(doc_id, 0)
b_score = bm25_scores.get(doc_id, 0)
final_scores[doc_id] = alpha * v_score + (1 - alpha) * b_score
return sorted(final_scores.items(), key=lambda x: x[1], reverse=True)
七、Reranker 重排序
7.1 为什么需要重排序?
向量检索是"粗筛",可能前 10 名的排序不够精准。Reranker 是"精排",对粗筛结果进行更精确的重新排序。
检索流程:
百万文档 ──向量检索──→ Top 20 候选 ──Reranker──→ Top 5 精排结果
(粗筛:快但粗略) (精排:慢但精准)
7.2 使用 Reranker
# 使用 Cohere Reranker(API 方式)
import cohere
co = cohere.Client(api_key="xxx")
results = co.rerank(
query="什么是向量数据库?",
documents=[
"向量数据库用于存储和检索高维向量数据",
"关系数据库使用SQL进行查询",
"向量搜索引擎可以进行语义搜索",
"NoSQL数据库包括MongoDB等"
],
top_n=2
)
for r in results.results:
print(f"排名 {r.index}: 分数 {r.relevance_score:.3f}")
# 排名 0: 分数 0.95 → "向量数据库用于存储..."
# 排名 2: 分数 0.82 → "向量搜索引擎可以..."
# 使用开源 Reranker(本地运行,免费)
from sentence_transformers import CrossEncoder
reranker = CrossEncoder('BAAI/bge-reranker-v2-m3')
pairs = [
["什么是向量数据库?", "向量数据库用于存储和检索高维向量数据"],
["什么是向量数据库?", "关系数据库使用SQL进行查询"],
]
scores = reranker.predict(pairs)
# [0.95, 0.12] → 第一个文档和查询更相关
八、Embedding 模型选择指南
┌──────────────────────────────────────────────────────────────┐
│ Embedding 模型选择决策树 │
│ │
│ 你的数据是什么语言? │
│ │ │
│ ├─ 中文为主 → BGE-large-zh / Jina Embeddings v3 │
│ │ │
│ ├─ 英文为主 → OpenAI text-embedding-3-small │
│ │ (付费但效果好) │
│ │ 或 BGE-large-en (免费开源) │
│ │ │
│ └─ 多语言 → Jina Embeddings v3 / Cohere Embed v3 │
│ │
│ 数据能否发送到云端? │
│ │ │
│ ├─ 可以 → OpenAI / Cohere API(简单省事) │
│ └─ 不行 → BGE / Jina(本地部署) │
│ │
│ 数据规模多大? │
│ │ │
│ ├─ < 10万条 → 维度大一点没关系(1024-3072) │
│ └─ > 100万条 → 考虑较小维度(512-768)节省存储 │
└──────────────────────────────────────────────────────────────┘
九、实战练习
完整示例:构建一个简易语义搜索引擎
"""
用 Chroma + Sentence Transformers 构建语义搜索引擎
无需 API Key,完全本地运行
"""
import chromadb
from sentence_transformers import SentenceTransformer
# 1. 初始化
embedding_model = SentenceTransformer('BAAI/bge-small-zh-v1.5')
chroma_client = chromadb.Client()
# 2. 创建集合,使用自定义 Embedding 函数
collection = chroma_client.create_collection(
name="knowledge_base",
metadata={"hnsw:space": "cosine"} # 使用余弦相似度
)
# 3. 准备文档
documents = [
"Python 是一种解释型、面向对象的高级编程语言",
"Java 是一种广泛使用的编程语言,具有跨平台特性",
"JavaScript 是网页开发的核心语言,可以在浏览器中运行",
"机器学习是人工智能的一个分支,通过数据来学习规律",
"深度学习使用多层神经网络来处理复杂的模式识别任务",
"Docker 是一种容器化技术,可以将应用打包成轻量级容器",
"Kubernetes 用于自动化部署、扩展和管理容器化应用",
"Git 是一种分布式版本控制系统,用于跟踪代码变更",
"REST API 是一种基于 HTTP 协议的接口设计风格",
"微服务架构将应用拆分为多个独立的小服务",
]
# 4. 生成 Embedding 并存入数据库
embeddings = embedding_model.encode(documents).tolist()
collection.add(
documents=documents,
embeddings=embeddings,
ids=[f"doc_{i}" for i in range(len(documents))]
)
# 5. 搜索
def search(query: str, top_k: int = 3):
query_embedding = embedding_model.encode([query]).tolist()
results = collection.query(
query_embeddings=query_embedding,
n_results=top_k
)
print(f"\n查询:{query}")
print("=" * 50)
for i, (doc, distance) in enumerate(
zip(results["documents"][0], results["distances"][0])
):
similarity = 1 - distance # Chroma 返回的是距离,转为相似度
print(f" {i+1}. [{similarity:.2f}] {doc}")
# 测试
search("什么编程语言好学")
# 1. [0.78] Python 是一种解释型、面向对象的高级编程语言
# 2. [0.65] Java 是一种广泛使用的编程语言...
# 3. [0.61] JavaScript 是网页开发的核心语言...
search("如何部署应用")
# 1. [0.82] Docker 是一种容器化技术...
# 2. [0.75] Kubernetes 用于自动化部署...
# 3. [0.52] 微服务架构将应用拆分为...
search("怎么管理代码")
# 1. [0.80] Git 是一种分布式版本控制系统...
# 2. [0.45] ...
十、本章小结
┌────────────────────────────────────────────────────────┐
│ 本章知识地图 │
│ │
│ Embedding:把文字变成数字向量 │
│ ├── 语义相似的文字 → 向量距离近 │
│ ├── 余弦相似度 = 最常用的相似度计算方式 │
│ └── 主流模型:OpenAI / BGE / Jina │
│ │
│ 向量数据库:高效存储和搜索向量 │
│ ├── 入门首选:Chroma(嵌入式,零配置) │
│ ├── 生产首选:Milvus / Qdrant │
│ └── 索引类型:Flat / IVF / HNSW / PQ │
│ │
│ 检索增强: │
│ ├── 混合检索 = 向量 + 关键词(互补) │
│ └── Reranker = 对初步结果精排 │
└────────────────────────────────────────────────────────┘
十一、扩展学习资源
必读
- Chroma 文档 —— 最简单的向量数据库入门
- Sentence Transformers 文档 —— 开源 Embedding 模型库
- MTEB 排行榜 —— Embedding 模型评测排行
推荐
- Milvus 文档 —— 生产级向量数据库
- Pinecone 学习中心 —— 优秀的向量数据库教程
- What Are Embeddings?(Vicki Boykis) —— 深入理解 Embedding 的好文章
动手实践
- 用 Chroma 构建一个简单的文档搜索系统
- 对比不同 Embedding 模型在中文搜索上的效果
- 试试在同一数据集上,纯向量检索 vs 混合检索的效果差异
下一篇章预告:将讲解 RAG(检索增强生成)——把 Embedding 和向量数据库用起来,让大模型能够基于你的私有文档来回答问题!
觉得有用的话,点个关注吧!大模型方面你想看什么?留言区说,我来写。
声明:本博客内容素材来源于网络,文章由AI技术辅助生成。如有侵权或不当引用,请联系作者进行下架或删除处理。