嵌入与检索:相似度就是一行数学
手写一遍 20 行的检索引擎,再决定要不要上向量数据库
"嵌入模型""向量数据库""余弦相似度"——这几个词吓退了很多人,以为要先学一学期线性代数。
这一节我们手写一遍:把文本变成向量、算相似度、排序取前三。全程不装任何库,核心代码不到 20 行。写完你会发现:检索的原理简单到令人失望——真正难的是后面的工程取舍。
把每段文字变成一串数字(向量),就像给每个人量三围。"找相似的文本" = "找三围最接近的人"——用的就是初中学过的距离公式。
嵌入模型只是一台更高级的量尺(量的是"语义"而不是"字面"),但比对方式完全一样。
三步,手写一个检索引擎
第 1 步:分词(把文本拆成"特征")
def tokenize(s):
s = s.lower()
toks = re.findall(r'[a-z]+|\d+\.?\d*', s) # 英文和数字按词
zh = re.sub(r'[^一-鿿]', '', s)
toks += [zh[i:i+2] for i in range(len(zh)-1)] # 中文按相邻两字
return toks
第 2 步:向量化(TF-IDF:一个词在本块里越常见、在全库里越罕见,权重越高)
idf = {t: math.log((N+1)/(df[t]+1)) + 1 for t in df} # 罕见词权重高
v = {t: (1+math.log(freq)) * idf[t] for t, freq in counts.items()}
norm = math.sqrt(sum(x*x for x in v.values()))
v = {t: x/norm for t, x in v.items()} # 归一化
第 3 步:算相似度(归一化之后,余弦相似度就是点积)
def cosine(a, b):
return sum(x * b.get(t, 0.0) for t, x in a.items()) # 就这一行
检索 = 把问题也变成向量 → 和每个块算一次 cosine → 排序取前 k。完了。
手写版的能力边界(重要)
本节的 TF-IDF 版在实测里表现不错(按结构切时 10/10),但它有个根本局限:只能匹配字面,不懂意思。
| 问题 | 文档里的写法 | TF-IDF | 嵌入模型 |
|---|---|---|---|
| "退货要几天" | "退换货时限 7 个自然日" | ⚠️ 靠"退"字勉强命中 | ✅ 理解同义 |
| "运费怎么算" | "配送费收取标准" | ❌ 字面无重合 | ✅ 理解同义 |
| "会员划算吗" | "年费 199 元享 95 折" | ❌ 完全不匹配 | ✅ 理解意图 |
所以生产环境用嵌入模型:它把文本映射到"语义空间",意思相近的向量就相近——比对方式和你手写的完全一样(还是余弦),区别只在于向量是怎么来的。
那要不要上向量数据库
| 你的规模 | 建议 |
|---|---|
| 几百块以内 | 内存里算就行(本节代码的做法),一次遍历几毫秒 |
| 几千到几万块 | 内存 + 简单索引仍可行;或用轻量向量库 |
| 十万块以上 / 需要过滤+并发 | 上专业向量数据库 |
别一上来就装向量数据库——很多真实知识库(公司制度、产品文档、FAQ)总共就几百到几千块,内存里暴力算完全够用,还省掉一整套运维。先跑通,再优化。
选嵌入模型的三个考量
- 中文能力:优先选中文或多语言训练充分的(纯英文模型处理中文会明显掉分);
- 向量维度与成本:维度越高一般越准但存储和计算更贵;嵌入调用也要计费(通常远低于生成);
- 能否本地跑:敏感数据不能出内网时,选可本地部署的开源嵌入模型。
⚠️ 具体选哪个模型,以你实测为准——用 4.4 的检索评测跑一遍你自己的数据,比看任何榜单都靠谱。
🔧 动手做:手写一遍,再想升级(15 分钟)
- 跑本节代码
rag_pipeline.py,重点读build_index和cosine那 20 行——确认你能看懂每一步。 - 做个"字面 vs 语义"的对比实验:给你的评测集加 3 个"问题用词和文档完全不同"的问题(比如文档写"配送费",你问"运费"),看手写版能不能命中。
- 如果掉分了——恭喜,你亲手证明了为什么需要嵌入模型。
- 可选升级:接一个嵌入 API(把
build_index换成调嵌入模型、cosine保持不变),重跑评测,看提升多少。 - 记下你的决策:我的场景用字面检索够吗?还是必须上嵌入?——依据是那 3 个同义问题的命中情况。
✅ 做到这里你应该有:对检索原理的"祛魅",以及一个用数据支撑的"要不要上嵌入模型"的判断。
为什么:很多人在 RAG 上花了大钱(向量数据库、昂贵嵌入模型),却从没验证过"我的场景真的需要吗"。手写一遍让你有能力判断:哪些是必需的,哪些是被卖给你的。
一个容易踩的坑:你用模型 A 建好了 10 万块的索引,后来换成模型 B(或 A 的新版本)——旧向量和新查询向量不在同一个空间,比对结果是垃圾。
规矩:① 索引里存好"用哪个模型、哪个版本建的";② 换模型 = 全量重建 + 重跑评测(别假设新模型一定更好);③ 重建期间的双写/灰度方案要提前想(4.7 的知识更新会讲)。
我的知识库:【文档类型和总量】,用户问法特点:【是否用词统一/口语化】,数据敏感度:【能否出内网】。请帮我判断:1) 字面检索(BM25/TF-IDF)是否够用,还是必须上嵌入模型;2) 如果上嵌入,选云端 API 还是本地部署,推荐哪类模型;3) 我这个规模要不要向量数据库;4) 怎么用评测集验证这个选择。
检索祛魅三句话:原理就是"文本变向量 + 比余弦"(20 行手写)、TF-IDF 只懂字面、生产用嵌入模型但比对方式一样;几百块的知识库内存里算就够,别急着上向量数据库。
下一节回答 4.2 留下的问题:回答准确率会掩盖检索问题——所以必须把检索单独拎出来测。
🔎 来源与核验· 2 条,点开核对
