FAISS(Facebook AI Similarity Search)是 Facebook AI 研究院开发的一款高效的向量相似性搜索库,专门用于优化大规模高维向量的近邻搜索任务,广泛应用于语义检索、图像检索、推荐系统等场景。
本文将详细介绍如何使用 FAISS 实现语义检索。具体内容包括:
- 将内容矢量化并保存为索引
- 在保存索引的同时,保存每条信息的相关信息metadata
- 通过矢量库进行语义检索
语义检索是一种基于内容含义(语义)而非表面关键词的信息检索技术,核心目标是让计算机 “理解” 用户查询的真实意图,从而返回含义相关的结果,而非仅匹配字面关键词的内容。
传统检索(如早期搜索引擎)依赖关键词匹配,例如用户搜索 “如何缓解头痛”,仅会返回包含 “缓解”“头痛” 等字面词汇的内容,若某篇文章写 “应对偏头痛的方法”(未出现 “缓解”“头痛”),则会被遗漏。 语义检索则通过语义嵌入(Embedding) 技术解决这一问题:
- 将文本转化为 “语义向量”:用 AI 模型(如 BERT、BGE-M3 等)把用户的查询、待检索的文档(句子 / 段落)都转化为高维向量(类似 “数字指纹”),向量的相似度直接对应内容语义的相似度。
- 通过向量相似度匹配结果:计算 “查询向量” 与 “文档向量” 的相似度(如余弦相似度),排序后返回相似度最高的内容,实现 “含义相关即匹配”。
数据结构设计
数据项中包含metadata,可以存储任何重要内容。
documents = [
{
"text": "人工智能是计算机科学的一个分支,研究如何使机器具有智能",
"metadata": {
"id": "doc_001",
"category": "人工智能基础",
"source": "教科书",
"publish_date": "2023-01-15"
}
},
{
"text": "机器学习是人工智能的核心技术,让计算机能从数据中学习",
"metadata": {
"id": "doc_002",
"category": "机器学习",
"source": "技术博客",
"publish_date": "2023-03-20"
}
},
{
"text": "深度学习是机器学习的一个子领域,基于人工神经网络",
"metadata": {
"id": "doc_003",
"category": "深度学习",
"source": "论文",
"publish_date": "2023-02-10"
}
},
{
"text": "自然语言处理专注于让计算机理解和生成人类语言",
"metadata": {
"id": "doc_004",
"category": "自然语言处理",
"source": "技术文档",
"publish_date": "2023-04-05"
}
},
{
"text": "计算机视觉是人工智能的一个重要方向,处理图像和视频",
"metadata": {
"id": "doc_005",
"category": "计算机视觉",
"source": "教程",
"publish_date": "2023-01-28"
}
}
]
初始化模型和存储路径
model = SentenceTransformer(
'BAAI/bge-m3',
cache_folder=Path("./model") # 模型会下载到这个目录,第一次执行会下载模型,比较慢。
)
prefix = "为这个句子生成表示以用于检索相关句子:"
# 定义存储路径
data_folder_path = Path("./data")
if not data_folder_path.exists():
data_folder_path.mkdir(parents=True, exist_ok=True)
index_path = data_folder_path / "faiss_index_with_metadata.index"
metadata_path = data_folder_path / "metadata.json"
上面代码中有两个要点:
使用bge-m3 :因为在 中文检索/相似度匹配 场景中,bge-m3 远超 all-MiniLM-L6-v2
使用前缀 prefix:
在文本嵌入(Embedding)过程中添加特定前缀是指令微调(Instruction Tuning) 思想在嵌入模型中的应用,主要作用是引导模型生成更符合特定任务需求的向量表示,从而提升下游任务(如语义检索)的效果。
当一个模型需要同时支持多种任务时,前缀可以作为 “任务标识符”:
- 检索任务:用"为检索生成表示:…"
- 聚类任务:用"为聚类生成表示:…"
- 问答匹配:用"为匹配问题和答案生成表示:…"
前缀的本质是通过自然语言指令引导模型聚焦任务需求,这是基于大语言模型时代的典型优化手段。对于 BGE-M3 这类支持指令微调的模型,使用官方推荐的前缀能显著提升检索效果;而对于早期不支持指令的模型(如原始 BERT),前缀可能无效甚至产生干扰。
生成数据库
下面使用 FAISS 生成嵌入数据库,并将其保存在本地文件夹中。
def process_documents(docs):
# 提取文本并添加前缀
texts = [prefix + doc["text"] for doc in docs]
# 生成向量
embeddings = model.encode(
texts,
normalize_embeddings=True
).astype(np.float32)
# 提取metadata列表(保持与向量顺序一致)
metadatas = [doc["metadata"] for doc in docs]
return embeddings, metadatas
def create_db():
"""生成矢量库并保存"""
if index_path.exists():
print("数据库已创建")
return
# 处理文档生成向量和metadata
embeddings, metadatas = process_documents(documents)
print(f"向量形状: {embeddings.shape}")
print(f"metadata数量: {len(metadatas)}")
# 创建内积索引
index = faiss.IndexFlatIP(embeddings.shape[1])
index.add(embeddings)
print(f"索引向量数量: {index.ntotal}")
# 保存FAISS索引
faiss.write_index(index, str(index_path))
# 保存metadata(使用JSON格式)
with open(metadata_path, "w", encoding="utf-8") as f:
json.dump(metadatas, f, ensure_ascii=False, indent=2)
print("\n数据保存完成:")
print(f" - 索引: {index_path}")
print(f" - Metadata: {metadata_path}")
create_db()
保存json格式的metadata时,其顺序要与与向量顺序一致,这样在语义检索的时候,可以根据检索出来的矢量位置找到对应的 metadata 。
加载矢量库
与 metadata 一同加载。
def load_data():
# 加载索引
index = faiss.read_index(str(index_path))
# 加载metadata
with open(metadata_path, "r", encoding="utf-8") as f:
metadatas = json.load(f)
print("\n数据加载完成:")
print(f" - 索引向量数量: {index.ntotal}")
print(f" - Metadata数量: {len(metadatas)}")
return index, metadatas
index, metadatas = load_data()
语义检索
下面实现 FAISS语义检索 功能,在检索到最匹配文本的同时,也会找到关联的 metadata 。
def search_with_metadata(query, top_k=2):
# 处理查询
query_text = prefix + query
query_embedding = model.encode(
[query_text],
normalize_embeddings=True
).astype(np.float32)
# 搜索相似向量
scores, indices = index.search(query_embedding, top_k)
# 关联metadata并返回结果
results = []
for i in range(top_k):
idx = indices[0][i]
results.append({
"score": float(scores[0][i]),
"metadata": metadatas[idx],
"text": documents[idx]["text"] # 也可以从metadata中存储text
})
return results
我们用下面的代码做一下语义检索测试:
if __name__ == '__main__':
query = "什么是深度学习"
results = search_with_metadata(query, top_k=2)
print(f"\n查询: {query}")
print("检索结果:")
for i, result in enumerate(results, 1):
print(f"\n结果 {i}:")
print(f"相似度得分: {result['score']:.4f}")
print(f"文本内容: {result['text']}")
print(f"Metadata: {json.dumps(result['metadata'], ensure_ascii=False, indent=2)}")
执行上述代码后,将按照相似度高低返回两个结果:
结果 1:
相似度得分: 0.8673
文本内容: 深度学习是机器学习的一个子领域,基于人工神经网络
Metadata: {
"id": "doc_003",
"category": "深度学习",
"source": "论文",
"publish_date": "2023-02-10"
}
结果 2:
相似度得分: 0.7643
文本内容: 机器学习是人工智能的核心技术,让计算机能从数据中学习
Metadata: {
"id": "doc_002",
"category": "机器学习",
"source": "技术博客",
"publish_date": "2023-03-20"
}
靠谱,哈哈:)
总结
通过上面的演示,我们发现使用 FAISS 实现 语义检索 并不复杂,在 CPU 中运行速度也很快。
代码
本文涉及的所有代码以及相关资源都已经共享,参见:
为便于找到代码,程序文件名称最前面的编号与本系列文章的文档编号相同。
🪐感谢您观看,祝好运🪐