一、为什么不能直接用文本计算相似度?

在传统的信息检索中,比如搜索引擎,我们通常使用关键词匹配。例如,搜索“苹果手机”,系统会查找包含“苹果”和“手机”这两个词的文档。这种方法简单直接,但存在一个根本性的缺陷:它无法理解词语背后的语义。同义词(如“手机”和“移动电话”)、一词多义(如“苹果”是水果还是公司)以及上下文关系,都可能被忽略。

计算机本质上只擅长处理数字。要让机器“理解”一段文字,我们必须找到一种方法,将人类语言的离散符号(如单词、句子)转换成计算机可以高效处理的连续数值表示。这个过程,就是Embedding(嵌入)。它产出的结果——一个由浮点数构成的向量——就是Embedding 向量。

提示:可以把Embedding向量想象成文本的“数字指纹”。两段语义相近的文本,它们的“指纹”在向量空间中的距离就会很近;反之,则相距很远。这是实现语义检索的基石。

二、Embedding 是如何将文本变成向量的?

Embedding 的核心思想是 “上下文决定语义”。一个词的含义,取决于它在不同语境中与其他词的共现关系。现代主流的Embedding技术,无论是经典的Word2Vec、GloVe,还是基于Transformer的BERT、Sentence-BERT,其训练目标都围绕这一点。

以词嵌入为例,模型在大规模文本语料上进行训练,学习为每个词分配一个固定长度的向量(例如128维、300维)。训练过程中,模型会调整向量,使得在语料中上下文相似的词,其向量也相似。例如,“国王”和“王后”的向量在空间中会很接近,而它们与“苹果”的向量则距离较远。更神奇的是,向量之间甚至能进行“语义算术”,比如 vector(‘国王’) - vector(‘男人’) + vector(‘女人’) 的结果会非常接近 vector(‘王后’)。

对于句子或段落级别的文本,通常的做法是将句中所有词的词向量进行聚合(如平均、加权平均或使用专门的模型如BERT的[CLS] token输出),来获得整个句子的向量表示。

三、语义检索:从“字面匹配”到“心领神会”

理解了Embedding,语义检索就水到渠成了。它的流程与传统检索截然不同:

  1. 离线索引阶段:将文档库中的所有文档(或文档切片)通过Embedding模型转换为向量,并存储在专门的向量数据库中。
  2. 在线查询阶段:将用户的查询文本(如“如何快速学习一门新语言?”)也转换为向量。
  3. 相似度匹配:使用余弦相似度等度量方法,在向量数据库中查找与查询向量最相似的K个文档向量,返回对应的原始文档。

这个过程的优势是革命性的:即使用户的查询词没有出现在目标文档中,只要语义相关(例如文档讲的是“高效外语学习方法”),也能被检索出来。这彻底超越了关键词匹配的局限。

四、向量数据库:语义检索的基础设施

当文档数量达到百万、千万甚至亿级时,线性扫描计算相似度(逐一计算查询向量与所有库中向量的距离)在时间上是不可接受的。这就催生了专门优化的向量数据库(如FAISS, Milvus, Weaviate, Pinecone)。

它们的核心技术是近似最近邻搜索。通过构建特殊的索引结构(如倒排文件、乘积量化、层级可导航小世界图),以牺牲微小的精度为代价,将搜索复杂度从线性降低到对数级别,实现毫秒级的响应。它们为大规模向量数据的存储、索引和快速检索提供了开箱即用的解决方案。

五、动手体验:一个简单的语义检索流程

下面我们用一个最简化的代码示例,感受整个流程。这里我们使用sentence-transformers库来生成高质量句子向量,并用numpy计算余弦相似度。

from sentence_transformers import SentenceTransformer
import numpy as np

# 1. 加载预训练的Embedding模型
model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')

# 2. 准备文档库和查询
documents = [
    "深度学习是人工智能的一个子领域,它使用多层神经网络来模拟人脑的学习过程。",
    "卷积神经网络在图像识别任务中取得了突破性的进展。",
    "自然语言处理让机器能够理解、解释和生成人类语言。",
    "烹饪一顿美味的晚餐需要新鲜的食材和耐心。"
]
query = "怎么教电脑理解文字?"

# 3. 将文本转换为向量
doc_embeddings = model.encode(documents)
query_embedding = model.encode(query)

# 4. 计算查询向量与所有文档向量的余弦相似度
# 余弦相似度 = (A·B) / (||A|| * ||B||)
cosine_similarities = np.dot(doc_embeddings, query_embedding) / (np.linalg.norm(doc_embeddings, axis=1) * np.linalg.norm(query_embedding))

# 5. 按相似度降序排列并输出
sorted_indices = np.argsort(cosine_similarities)[::-1]
print("查询:", query)
for idx in sorted_indices:
    print(f"相似度:{cosine_similarities[idx]:.4f} - 文档:{documents[idx]}")

这段代码的输出会显示,与查询“怎么教电脑理解文字?”最相似的文档,毫无疑问是关于“自然语言处理”的那一句,尽管它没有包含“教”、“电脑”、“文字”这些精确词。

六、挑战与进阶方向

尽管语义检索强大,但并非万能。它面临一些挑战:

提示:在实际生产中,一个强大的检索系统通常是混合检索:同时考虑关键词的精确匹配(BM25算法)和语义的模糊匹配(Embedding相似度),并融合两者的分数进行排序,以兼顾准确性和语义泛化能力。