你在文档里搜"退款",结果一个都没有。但文档里明明写着"我想退钱"。
先说结论
传统搜索靠关键词匹配,搜"退款"找不到"退钱",因为机器只认字不认意思。
向量检索把文字变成数字(向量),按语义相似度搜索。搜"退款"能命中"我想退钱",因为它俩意思一样。
Sentence-Transformers 负责编码,FAISS 负责搜索。
20 行 Python,搭一个比 MySQL LIKE 强 100 倍的语义搜索引擎。数据量从一千条到十亿条,都有对应的方案。
下面我从原理到代码,一步步带你跑通。
第一章:向量检索是什么(3 分钟搞懂原理)
文本变向量:让机器理解"意思"
embedding(嵌入)把一段文字编码成一串数字,比如 768 维的浮点数组。
这串数字就是文字的"语义指纹"。意思相近的文字,指纹也相近;意思无关的文字,指纹差很远。
“退款"和"退钱"的关键词不同,但编码出来的向量非常接近。“退款"和"天气预报"的向量则差了十万八千里。
相似度怎么算:余弦 vs L2
算两个向量有多接近,常用两种方式:
| 方式 | 原理 | 特点 |
|---|---|---|
| 余弦相似度 | 算向量夹角的余弦值 | 值域 [-1,1],越大越相似 |
| L2 距离 | 算欧氏距离 | 值域 [0,∞),越小越相似 |
大多数语义搜索场景用余弦相似度。配合向量归一化(让每个向量的长度变成 1),余弦相似度等价于向量内积,计算更快。
为什么不用 MySQL LIKE
LIKE 做的是字面匹配。搜"退款”,数据库遍历每一条记录,看有没有"退"和"款"这两个字挨在一起。
- “如何申请退款” → 命中 ✓
- “我想把钱退回来” → 不命中 ✗
- “退订服务” → 不命中 ✗
但这三条说的是同一件事。LIKE 的本质缺陷:它不理解语义,只匹配字符。
向量检索的思路完全不同:先把所有文本编码成向量,搜索时把查询也编码成向量,然后找最近的几个。它匹配的是"意思”,不是"字面"。
第二章:Sentence-Transformers — 让文本变成向量
安装和一行代码上手
| |
加载模型、编码文本,三行搞定:
| |
输出 (3, 512) 表示 3 条文本各编码成了 512 维的向量。
中文模型怎么选
这是新手最纠结的问题。我直接给你结论:
| 模型 | 维度 | 大小 | 适用场景 |
|---|---|---|---|
| bge-small-zh-v1.5 | 512 | ~100MB | 入门首选,速度快 |
| m3e-base | 768 | ~210MB | 社区流行,中文适配好 |
| bge-large-zh-v1.5 | 1024 | ~1.3GB | 精度最高,生产推荐 |
| BGE-m3 | 1024 | ~2.3GB | 多语言+多功能,最强 |
我的建议:学习阶段用 bge-small-zh-v1.5,小快够用。上生产了换 bge-large-zh-v1.5 或 BGE-m3。
避坑:维度不一致不能混用
这是 90% 的新手会踩的坑。
bge-small 输出 512 维,m3e-base 输出 768 维。
如果你用 bge-small 建了索引,后来换成 m3e-base 编码查询,FAISS 直接报错——维度对不上。
规则:编码模型和建索引模型必须是同一个。换模型 = 重建索引,没有例外。
第三章:FAISS — 让向量搜起来飞快
安装
| |
3 分钟跑通第一个索引
| |
四种索引类型决策树
这是这篇文章的核心价值。FAISS 有十几种索引,但 99% 的场景只需要搞懂这四种:
| 索引类型 | 数据量级 | 速度 | 精度 | 典型场景 |
|---|---|---|---|---|
| IndexFlatL2 | <10万 | 慢 | 100%精确 | 原型开发、小数据 |
| IndexIVFFlat | 百万级 | 快 | 近似(可调) | 中等规模生产 |
| IndexHNSWFlat | 千万级 | 最快 | 高召回率 | 低延迟在线搜索 |
| IndexIVFPQ | 十亿级 | 快 | 有损压缩 | 超大规模+省内存 |
怎么选:别想太多,从 IndexFlatL2 开始。数据量到 10 万条以后再考虑换。
我把四种索引的适用场景画个决策路径:
| |
索引的保存和加载
90% 的新手忘的一步。程序退出,内存里的索引就没了。
| |
注意:FAISS 只保存向量和索引结构,不保存原始文本。你需要自己维护一个"索引ID → 原始文本"的映射(比如存一个 JSON 或列表)。
第四章:实战 — 搭一个能用的中文语义搜索引擎
完整可运行代码
下面这段代码复制就能跑。我准备了 10 条模拟 FAQ 数据,展示语义搜索的效果。
| |
运行效果
搜"退款"命中"如何申请退款"和"我想把钱退回来"——关键词完全不同,但语义命中。
搜"取消购买"命中"怎么取消已经付款的购买"和"取消订单的步骤"——“取消"和"购买"拆开了照样匹配。
这就是语义搜索的威力:它理解意思,不依赖关键词重合。
性能数据
10 条数据的搜索时间几乎为 0(亚毫秒级)。扩展到 1000 条,纯索引搜索(不含编码)仍在 1ms 以内。
对比 MySQL LIKE 查询 1000 条记录,需要全表扫描 + 逐行字符匹配,通常在 5-50ms。向量检索在语义理解上有质的飞跃,速度也不逊色。
第五章:数据量增长后怎么办
索引迁移路径
数据量增长到一定程度,IndexFlatL2 的暴力搜索会变慢。这时候该换索引了:
| |
切换时机:当你感觉搜索延迟不可接受(比如超过 100ms)的时候,就该考虑换索引了。不要过早优化。
FAISS vs 其他方案
FAISS 是一个搜索库,不是完整的数据库。它没有增删改查 API,没有持久化层,需要你自己管理。
如果不想自己管这些,可以考虑封装好的向量数据库:
| 方案 | 类型 | 适用规模 | 特点 |
|---|---|---|---|
| FAISS | 嵌入式库 | 任意 | 轻量无依赖,需自己管持久化 |
| ChromaDB | 轻量数据库 | <100万 | 开箱即用,内置持久化 |
| Qdrant | 数据库 | 千万级 | Rust 写的,高性能,API 友好 |
| Milvus | 分布式数据库 | 十亿级 | 生产级,支持分布式 |
我的建议:原型阶段用 FAISS,够轻够快。产品化了再按需升级。别一上来就上 Milvus,运维成本会吃掉你。
核心建议
别过早优化。从 IndexFlatL2 开始,能跑就先跑着。
等数据量真到了瓶颈再换索引,迁移成本远低于你从第一天就过度设计的成本。这句话适用于 99% 的项目。
结论
向量检索不是黑魔法。
Sentence-Transformers 把文本编码成向量,FAISS 把向量搜得飞快。两个库,20 行代码,你就拥有了一个能理解语义的搜索引擎。
搜"退款"能找到"退钱”,搜"取消购买"能命中"取消订单"。这就是语义搜索和关键词搜索的本质区别。
记住三个要点:从 IndexFlatL2 开始、编码模型和索引维度必须一致、别忘了保存索引到磁盘。
剩下的,动手跑一遍代码就懂了。
关注 varkm,一起学习,一起成长