第 4 章:Embedding 选型与向量化
能讲清"文字怎么变成向量、怎么靠向量找相似",会按场景选 Embedding 模型和向量库,并知道何时该微调
📊 学习时长:45-60 分钟 🎯 完成后能力:能讲清"文字怎么变成向量、怎么靠向量找相似",会按场景选 Embedding 模型和向量库,并知道何时该微调 🔗 关联面试题:5 道(覆盖 5 个不同角度,见 Part 3)
这一章你会学到什么
- ✓ 零基础也能跟着做:用一段零依赖代码理解"文字 → 向量 → 算距离",并亲眼看到它的硬伤
- ✓ 看懂 3 个核心概念:Embedding(向量化)、余弦相似度、双塔模型
- ✓ 学会按场景选 Embedding 模型(BGE / E5 / GTE / Jina / OpenAI),并知道向量维度(768/1024/1536)怎么取舍
- ✓ 理解向量库 + ANN 索引,讲清 HNSW vs IVF 的区别和"近似"二字的含义
- ✓ 知道通用模型为什么对行业黑话不准,以及领域微调的思路
本章按三段式组织,不同基础的读者各取所需:
段落 给谁看 内容 占比 🚀 Part 1 · 主线实战 零基础,想先跑起来 概念白话 + 玩具向量化代码 ~50% 🎯 Part 2 · 面试深度 想讲透、备战面试 模型选型 + 向量库索引 + 微调 ~40% 🏆 Part 3 · 验收串题 检验学到位没 关联面试题 + 自检清单 ~10%
🚀 Part 1 · 主线实战 | ~50% 这部分零基础也能跟着做。先用一段不依赖任何库的代码,把"文字变向量、靠向量找相似"跑通,顺便看清它的硬伤。
在开始之前
你需要:
- ✅ 装好 Python(3.9+),能看懂基础 Python(字典、函数)
- ✅ Part 1 的玩具 demo 零依赖;Part 1 末尾的"真实 Embedding"需要
pip install sentence-transformers(可选) - ✅ 最好先读过第 2 章,这章讲的就是第 2 章里
build_index(把文字转向量)那一步的真身
3 个核心概念(先白话,再术语)
概念 1:什么是 Embedding(向量化)?
Embedding(向量化),就是把一段文字变成一串数字(向量),比如 [0.12, -0.4, 0.88, …](可能几百上千个数)。
关键不在"变成数字",而在怎么变:好的 Embedding 模型会让意思相近的文字,数字也相近。于是"孩子摔伤"和"未成年人意外伤害"虽然一个字都不一样,它俩的向量却挨得很近。
概念 2:什么是余弦相似度?
把两段文字都变成向量后,怎么判断它俩像不像?最常用的是余弦相似度,可以理解成看两个向量"指向"的方向有多接近,值在 0~1 之间,1 表示方向完全一致(最像),0 表示毫不相关。
检索时就是:把用户问题变成向量,去库里找余弦相似度最高的那几个 chunk。
为什么用"方向"而不用"距离"?因为一段长文本和一段短文本,向量的长度可能差很多,但只要讲的是一回事,方向就一致。余弦只看方向、不看长短,正好抓住"意思像不像"。
概念 3:什么是双塔模型?
Embedding 模型通常是**双塔(Bi-Encoder)**结构:query 和 document 各自独立编码成向量,最后才算它俩的相似度。
好处是:document 的向量可以离线提前算好、存进库里;线上只需要现算 query 的向量,再去库里比较,因此能减少在线编码开销。实际延迟受数据规模、硬件、索引、过滤条件和并发影响,需要测量。(这点和第 6 章的"交叉编码"正好相反,到时再对比。)
记住这张图,第 6 章会用它做对比:双塔是"各编各的、能预存",所以快;交叉编码是"两段拼一起喂进去一起算",所以准但慢。一个负责海里捞,一个负责精挑,这正是后面"召回 → 重排"两阶段的由来。
🛠️ 跟着做:用玩具向量,看懂"向量化"也看清它的坑
Step 1:写一个最朴素的"向量化"
我们先不用任何模型,用最朴素的"数每个字出现几次"当向量(字级词袋)。新建 toy_embedding.py(完整文件见 code-snippets/toy_embedding.py):
import math
def to_vector(text): # 把一段文字变成"向量":这里用最朴素的"数每个字出现几次"
v = {} # 用字典当向量:键=某个字,值=这个字出现的次数
for ch in text: # 逐个字扫过去
v[ch] = v.get(ch, 0) + 1 # 这个字计数 +1(没出现过就从 0 开始)
return v # 返回 {字: 次数},就是这段文字的"玩具向量"
def cosine(a, b): # 余弦相似度:两个向量方向有多接近,1 最像、0 不相关
dot = sum(cnt * b.get(ch, 0) for ch, cnt in a.items()) # 点积:两边都出现的字,次数相乘再求和
na = math.sqrt(sum(c * c for c in a.values())) # 向量 a 的长度(模)
nb = math.sqrt(sum(c * c for c in b.values())) # 向量 b 的长度(模)
return dot / (na * nb) if na and nb else 0.0 # 点积 ÷(两个长度相乘)= 余弦值
query = "保险理赔" # 用户的问题
for d in ["理赔流程说明", "索赔方法介绍", "等待期是多久"]: # 三条候选文档
print(f" cos={cosine(to_vector(query), to_vector(d)):.2f} {d}") # 打印每条和问题的相似度
Step 2:运行
python toy_embedding.py
你应该看到(纯字符统计,数字是固定的):
cos=0.41 理赔流程说明
cos=0.20 索赔方法介绍
cos=0.00 等待期是多久
🐛 跑不通看这里
ModuleNotFoundError?这段零依赖,只用了标准库math,报这个多半是文件名或运行路径不对,确认你就在toy_embedding.py所在目录里python toy_embedding.py。- 三个数字全是
0.00?检查 query 和文档是不是写成了全英文/全空:字级词袋靠"共享汉字"算分,一个相同字都没有,自然就是 0。- 数字和上面对不上?确认你没动 query 或文档文本;改了字分数当然变(这恰好是下一步要你做的事)。
Step 3:发现硬伤
- "理赔流程说明"和 query 共享"理赔"两个字 → 0.41,排第一,合理;
- "等待期"完全不相关 → 0.00,也合理;
- 但"索赔方法介绍"只拿到 0.20,而"索赔"和"理赔"明明是一个意思!只因为字面只共享一个"赔"字,就被判成几乎不相关。
这就是玩具向量(以及所有"数词重合"方法)的硬伤:只懂字面,不懂意思。 真正的 Embedding 模型是在海量语料上学出来的,它知道"索赔""理赔"经常出现在相似语境,于是把它俩的向量拉到一起。
Step 4:🛠️ 动手实验,把硬伤"逼"出来
改一行,亲手验证"字级词袋只看字面":
- 把
query = "保险理赔"改成query = "索赔",再跑一次。你会看到"索赔方法介绍"这次分数飙到最高,而"理赔流程说明"反而掉下去,因为现在共享的字变成了"索赔"。同一批文档,只因为问法换了个近义词,排序就全反了。 - 再加一条文档
"出事故后能领多少钱"(和"理赔"是一个意思,但和保险理赔一个相同字都没有),你会看到它拿0.00。意思最近,字面零重合,玩具向量直接判它"毫不相关"。
这两个改动,就是 Embedding 模型存在的全部理由:让"意思近"压过"字面像"。
🧭 你刚才做的,等于真实系统的什么?
- 你写的
to_vector()= 真实系统里的 Embedding 模型(只不过它输出的是几百维稠密向量,且"懂意思");- 你写的
cosine()= 向量库检索时算相似度的那一步,一模一样;- 你这个
for循环逐条比 = 第 2 章search()在做的事,只是真实库里有几百万条、且用 ANN 索引加速(本章 Part 2 会讲)。 换句话说,你已经用 20 行手搓了一个"检索内核",剩下的工程都是在给它换更聪明的向量和更快的查法。
Step 5(可选):换成真实 Embedding
装上库就能看到"索赔方法"也拿高分(完整文件见 code-snippets/real_embedding.py):
# pip install sentence-transformers (首次运行会下载模型权重,需联网)
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer("BAAI/bge-base-zh-v1.5") # 中文常用开源模型
q = model.encode("保险理赔", normalize_embeddings=True)
docs = ["理赔流程说明", "索赔方法介绍", "等待期是多久"]
d = model.encode(docs, normalize_embeddings=True)
for doc, score in sorted(zip(docs, util.cos_sim(q, d)[0].tolist()), key=lambda x: -x[1]):
print(f" cos={score:.2f} {doc}")
这次"索赔方法"也会拿到高分,因为模型懂"意思"。这,就是你为什么要选一个好的 Embedding 模型。
这一节你掌握了什么?
- ✅ Embedding 是什么、余弦相似度是什么、双塔怎样减少在线计算以及如何实测延迟
- ✅ 亲手验证了"数词重合"不懂近义词,而学习得到的 Embedding 懂
下面 Part 2,我们解决三个落地问题:选哪个模型?用哪个向量库?什么时候得自己微调?
🎯 Part 2 · 面试深度 | ~40% 这部分讲选型与工程落地:Embedding 模型怎么挑、向量库和索引怎么选、什么时候该微调。
2.1 Embedding 模型怎么选
开源生态很成熟,不用自己造,按场景挑一个即可:
选型口诀:中文/中英混合 → 无脑 BGE;多语言 → GTE-multilingual;省事不想本地部署 → OpenAI API(有成本和延迟);长文(≥8K token)→ Jina v2;资源紧张/边缘设备 → E5-small / MiniLM。
业内行话:别一上来就纠结"哪个模型榜单第一"。MTEB / C-MTEB 榜单只是起点,务必在你自己的业务数据上实测:榜单是在通用语料上评的,换到你的保险/法律/医疗语料,排名经常会变。先用 BGE 这类稳的中文模型跑通,再拿你的评估集(第 3 章那套)横向比几个,用数据定。
选型演练:比较云端 Embedding 与本地模型时,不要只看公开榜单。先列出目标数据是否允许外发、索引是否频繁重建、请求量、硬件、延迟和预算,再在同一评估集上测召回质量。正文不预设查询规模、账单或"切换后一定提升"的结果;这些都要由你的真实约束和实测决定。
2.2 向量维度怎么选(768?1024?还是 1536?)
选模型时绕不开一个参数:向量维度(模型输出向量有多少个数)。常见有 384 / 768 / 1024 / 1536 几档。新手容易觉得"维度越高越准",其实是个典型的取舍:
- 精度:维度高确实能装下更细的语义,但过了一定点,精度只是微涨;
- 存储/成本:维度翻倍,向量库占用和内存几乎线性翻倍:上百万条 chunk 时这是真金白银;
- 检索速度:每条向量更长,算距离更慢,索引也更占内存。
业内行话:向量维度没有通用的"性价比甜点"。应把模型质量、向量维度、存储、构建时间和查询延迟放在同一评估里比较;部分支持 MRL(可截断表示) 的模型允许尝试较短向量,但截断后的质量损失与资源收益仍需在自己的数据和负载上实测。
2.3 向量库 + ANN 索引
文字变成向量后,得有地方存、还得能快速查,这就是向量库(Milvus / Faiss / Qdrant 等)。
但"快速查"有讲究:随着向量规模增长,query 逐个算精确距离的开销会增大,因此常会评估 ANN(近似最近邻)索引(如 HNSW、IVF):
业内行话:
ANN里的 A 是 approximate(近似)。它通过减少需要精确比较的候选,用可接受的召回损失换查询加速;提升幅度取决于数据规模、分布、硬件、索引与参数,必须和精确检索在同一评估集、同一负载下实测。
面试常问"HNSW 和 IVF 区别",一张图讲清两者怎么"少算":
- HNSW:在向量间建多层跳表图,从顶层稀疏入口逐层下钻、顺着邻边走到最近点。查询快、召回高,但建图占内存大、写入较慢,适合"读多写少、追求低延迟"。
- IVF:先把向量聚类成若干桶,query 来了只在最近的几个桶里精算,跳过其余。省内存、建得快,但召回受"探查几个桶(
nprobe)"影响大,适合"超大规模、能接受调参换召回"。
面试提示:别只背"HNSW 是图、IVF 是桶"。可以说明它们要在内存、召回、写入和查询延迟之间取舍,并给出验证步骤:按自己的数据规模选候选方案,再用同一评估集扫描
efSearch/nprobe等参数,记录召回与延迟。只有实际跑过,才能在简历里写成个人调参经历。
2.4 什么时候该微调 Embedding
通用 Embedding 对常见语义很准,但对行业黑话经常翻车:第 1 章那个"现金价值""退保费"召回不准的坑,根因就是通用模型没见过这些词,在向量空间里把它们放错了位置。
微调的思路(不一定从零训):准备领域的"问题 — 正确 chunk"配对当正样本,配上不相关的 chunk 当负样本,用对比学习(如 MultipleNegativesRankingLoss)在通用 BGE 上继续训练,把相关的拉近、不相关的推远。
业内行话:微调不是"必选项",而是"召回实在上不去时的杀手锏"。先把切分、混合检索、重排都做好,这些通常比微调见效快、成本低;等这些都到位、召回还卡在专业术语上,再上微调。顺序搞反了,你会花大力气微调却收效甚微。
🏆 Part 3 · 验收串题 | ~10% 学到这一步,做几道题验证一下,知道自己学到位没。
关联面试题(5 道,覆盖 5 个角度)
- 【向量化与语义检索原理】 如何用嵌入模型把 query 和文档映射到向量空间,并用相似度做检索?
- 【模型架构与维度】 常用文本嵌入模型架构(BERT / Sentence-BERT 等)及向量维度的影响?
- 【模型选型】 如何根据任务需求选择合适的文本嵌入模型?
- 【向量库选型】 选择向量数据库要综合考虑哪些关键因素?
- 【ANN 索引】 向量匹配的实现原理,以及 HNSW / IVF 的精度与效率权衡?
学完这一章你应该能干嘛
跟着做(Part 1):
- 能跑通玩具向量化,讲清余弦相似度
- 能说出"数词重合"为什么不如学习得到的 Embedding
讲深度(Part 2):
- 能按场景选一个 Embedding 模型,并说明为什么不能只看榜单
- 能讲清向量维度怎么取舍,为什么不是越高越好
- 能讲清向量库为什么要用 ANN 索引,"近似"是什么意思,以及 HNSW vs IVF 的区别
- 能讲清什么时候该微调 Embedding,以及为什么它通常不是第一优先
3 项以下 → 回去 Part 1 重做;4–5 项 → Part 2 再看一遍;6 项以上 → 进入下一章。
完整代码 + 数据集
本书每章的完整可运行代码 + 测试样本,都在配套 GitHub 仓库:
🔗 github.com/MisterBooo/rag-from-zero
- 跟着教程 clone 下来就能跑
- 本章的玩具向量化(零依赖)和真实 Embedding 示例都在里面
- 欢迎 Star ⭐ / Issue 反馈
导航