用 20 行代码理解 Embedding 为什么能做语义搜索

知识库

关键词搜索认字面,语义搜索认意思,中间的魔法就是 Embedding。本文用一段极简代码,把「文字变成向量、向量比距离、距离即相似」这条链路跑一遍,讲清语义搜索的原理、边界和常见误解。

「报销流程」和「费用核销办法」是两组完全不同的字,但意思几乎一样。关键词搜索会漏掉后者,语义搜索却能把它们连起来。这中间的关键,就是 Embedding(嵌入)。很多讲解把它说得很玄,其实它的核心思想一句话就能概括:把文字变成一串数字(向量),让意思相近的文字,数字也相近。 一旦文字变成了空间里的点,「搜索」就变成了「找最近的点」——一个纯数学问题。

这篇文章不堆概念,而是用一段极简代码把这条链路跑一遍:文字 → 向量 → 比距离 → 排序。看完你会明白语义搜索为什么有效、它的能力边界在哪,以及几个常见的误解。

向量空间中的语义距离
向量空间中的语义距离

Embedding 把文字映射成空间里的点,意思越近的点离得越近,搜索就变成了「找最近的点」。

核心直觉:把意思变成坐标

想象一张地图,每个词是地图上的一个点。「猫」和「狗」离得很近(都是宠物),「猫」和「银行」离得很远。如果这张地图不是二维而是几百上千维,每个维度都编码了某种语义特征,那么任意两段文字的「意思有多像」,就可以用它们两个点之间的距离来衡量。

Embedding 模型干的就是这件事:输入一段文字,输出一个高维向量(比如 768 或 1536 个数字),这个向量就是这段文字在「语义地图」上的坐标。模型是怎么学会这套坐标的?简单说,它在海量文本上训练,让经常出现在相似上下文里的词获得相似的向量——这背后是深度学习,但用起来你完全不需要关心内部细节,只需要知道:同一个模型输出的向量,可以互相比较距离。

20 行代码:把原理跑起来

下面这段伪代码(Python 风格)就是一个最小语义搜索引擎的全部逻辑:

# 1. 准备一个 embedding 模型(任意一家的 API 或本地模型都行)
from some_embedding_lib import embed   # embed(text) -> 向量(list[float])

# 2. 待搜索的文档库
docs = [
    "员工报销流程与审批步骤",
    "公司年假与调休规定",
    "费用核销办法说明",
    "食堂菜单每周更新",
]

# 3. 预先把每篇文档变成向量(入库时做一次)
doc_vecs = [embed(d) for d in docs]

# 4. 计算两个向量的余弦相似度:越接近 1 越像
def cosine(a, b):
    dot = sum(x*y for x, y in zip(a, b))
    na = sum(x*x for x in a) ** 0.5
    nb = sum(y*y for y in b) ** 0.5
    return dot / (na * nb)

# 5. 来一个查询,变成向量,和每篇文档比距离,按相似度排序
query = "怎么报销差旅费"
qv = embed(query)
ranked = sorted(docs, key=lambda d: cosine(qv, doc_vecs[docs.index(d)]), reverse=True)
print(ranked[:2])
# 期望输出:'员工报销流程与审批步骤'、'费用核销办法说明'

去掉注释真的就是二十来行。注意查询里没有「核销」两个字,但「费用核销办法」依然会排到前面——因为它和「报销差旅费」在语义空间里离得近。这就是语义搜索的全部魔法:没有匹配任何关键词,靠的是意思相近。

三个关键点值得停下来看:

  • 入库时算一次,查询时算一次:文档向量提前算好存起来,查询来了只需算查询向量,再和库里的向量比距离。这是它能规模化的基础。
  • 余弦相似度衡量方向而非长度:它比较的是两个向量「指向的方向」有多一致,是文本相似度最常用的度量。
  • 必须用同一个模型:文档和查询要用同一个 embedding 模型编码,不同模型的向量空间不通用,混用等于拿两张不同的地图找路。
代码编辑器中运行向量计算
代码编辑器中运行向量计算

语义搜索的核心逻辑二十行就能写完:文档预先向量化,查询实时向量化,比余弦距离再排序。

从玩具到生产:向量数据库补上的那一环

上面代码有个致命问题:第 5 步用 cosine 挨个和每篇文档比。四篇文档没问题,四千万篇呢?每次查询都全量算一遍距离,慢到不可用。

向量数据库(Milvus、Qdrant、Weaviate、pgvector 等)解决的就是这件事:它用专门的近似最近邻(ANN)索引,能在亿级向量里毫秒级找出「最近的几个」,不必和每一个都比。所以生产级语义搜索的真实结构是:

入库:文档 → 切分 → embed → 存进向量数据库(带索引)
查询:问题 → embed → 向量数据库找 Top-K 最近邻 → 返回

这也正是 RAG 检索环节的底层原理——RAG 的「检索」,本质就是这套语义搜索。理解了 embedding,你就理解了 RAG 为什么能「找到相关内容」。

能力边界:语义搜索不是万能的

跑通原理后,更重要的是知道它在哪失手,这能帮你避开一大半坑:

  • 专有名词、编号、型号容易翻车:「ERR-4032」和「ERR-4033」在语义空间里几乎重合,但对用户是两个问题。这类精确匹配是 embedding 的短板,要靠关键词检索(BM25)补上——具体怎么组合见《向量检索、BM25、Hybrid Search 应该怎么选》
  • 域外黑话不认识:公司内部的项目代号、行业黑话,模型训练时没见过,向量表达接近随机。
  • 相似不等于相关:两段文字主题相近,不代表其中一段能回答另一段的问题。「怎么退款」和「怎么开发票」都属售后,向量很近,答案却不对——这需要重排序(Rerank)来纠正。
  • 永远有「最近的点」:哪怕库里根本没有相关内容,最近邻也总是存在的。所以要配相似度阈值,低于阈值就明确说「没找到」,否则就是幻觉的温床。
工程师分析检索结果的相关性
工程师分析检索结果的相关性

理解 embedding 的边界,比理解它的原理更能帮你做出好用的检索系统。

动手小实验:亲手感受语义距离

想真正建立直觉,花十分钟做个小实验:任选一家 embedding 模型(云 API 或本地都行),把下面几组词各自 embed,两两算余弦相似度,观察数字:

  1. 「猫」vs「狗」vs「银行」——前两个应明显更近;
  2. 「苹果手机」vs「iPhone」vs「苹果水果」——看模型能不能区分同名的不同含义;
  3. 「今天天气很好」vs「Nice weather today」——试试跨语言,多语言模型下这两个会很近。

你会直观看到「意思相近 = 数字相近」是真的在发生。这种亲手验证,比读十篇原理文章都管用——这也是「原理 + 实验」这类学习方式的价值:别只记结论,用最小实验把它复现出来。

适用人群与常见误解

适合想搞懂语义搜索、RAG、向量数据库底层是怎么回事的开发者和产品经理。澄清几个常见误解:

  • 「Embedding 是大模型独有的新东西」:不是。词向量(Word2Vec)十年前就有了,如今的 embedding 模型只是更强、能编码整段话的语义。
  • 「向量维度越高越准」:不一定。维度是模型设计决定的,更高维度意味着更大的存储和计算成本,和「准不准」没有简单正相关。
  • 「有了语义搜索就不需要关键词搜索了」:恰恰相反,两者互补,生产系统通常两个都要(Hybrid Search)。

常见问题

Q:我该选哪个 embedding 模型? A:中文场景可重点评测 BGE、M3E 等开源模型,或各家的 embedding API。选型要点:语言支持、向量维度(影响存储)、最大输入长度、以及在你自己数据上的检索命中率——用评估集实测,别只看榜单。

Q:一段很长的文档能直接 embed 吗? A:不建议。模型有最大输入长度,且长文本 embed 出的向量是「平均语义」,什么都沾边、什么都不准。要先切分成小块再分别 embed,切分策略见《RAG 文档切分怎么做》

Q:Embedding 只能用于文本搜索吗? A:不。同样的思想可以嵌入图片、音频、代码,做跨模态检索(用文字搜图)、推荐系统、聚类分析、去重等。「把对象变成可比距离的向量」是一个通用范式,语义搜索只是它最直观的应用。

小结

Embedding 的原理一点都不玄:把文字变成高维空间的坐标,让意思相近的文字坐标也相近,于是「搜意思」变成了「找最近的点」。二十行代码就能跑通这条链路,向量数据库让它规模化,RAG 让它派上大用场。但别忘了它的边界——精确匹配靠关键词、相关性靠重排、拒答靠阈值。理解了这颗螺丝钉,你就看懂了整台语义搜索机器。