1. 引言:什么是向量搜索?

最初关注到向量搜索,是因为在学习 RAG(Retrieval-Augmented Generation,检索增强生成)时遇到了一个核心问题:大模型虽然能生成自然语言答案,但它并不天然知道我本地文档、企业知识库、项目代码和业务资料里的内容。如果想让模型基于这些资料回答问题,就必须先从知识库中检索出相关内容,再把这些内容交给模型生成答案。

传统搜索更多依赖关键词。例如用户搜索“怎么提高召回率”,如果文档中写的是“提升检索覆盖面”,关键词搜索可能无法很好匹配。向量搜索则不同,它试图理解“意思是否接近”。也就是说,关键词搜索问的是“有没有出现这些词”,而向量搜索问的是“表达的含义是不是相似”。

用一个小例子来理解它:

用户查询 文档内容 传统关键词匹配 向量搜索理解
怎么用 Python 打开表格文件 Python 如何读取 CSV 文件 可能匹配 相关
怎么用 Python 打开表格文件 使用 pandas 加载表格数据 可能漏掉部分关键词 相关
怎么用 Python 打开表格文件 明天天气怎么样 不相关 不相关

向量搜索的价值就在这里:它可以跨越字面词语的差异,捕捉更深层的语义关系。

用户查询
怎么用 Python 打开表格文件

Embedding 模型

文档 1
Python 如何读取 CSV 文件

Embedding 模型

文档 2
使用 pandas 加载表格数据

Embedding 模型

文档 3
明天天气怎么样

Embedding 模型

查询向量

文档向量 1

文档向量 2

文档向量 3

计算相似度

返回最相似结果

这里我们简单用一段代码演示一下


from sentence_transformers import SentenceTransformer
import numpy as np

# =========================
# 1. 原始文档(模拟知识库)
# =========================
documents = [
    "Python 如何读取 CSV 文件",
    "使用 pandas 加载表格数据",
    "明天天气怎么样"
]

# =========================
# 2. 用户查询
# =========================
query = "怎么用 Python 打开表格文件"

# =========================
# 3. 加载向量模型
# =========================
model = SentenceTransformer("all-MiniLM-L6-v2")

# =========================
# 4. 向量化(Embedding)
# =========================
doc_embeddings = model.encode(documents)
query_embedding = model.encode([query])[0]

# =========================
# 5. 相似度计算(余弦相似度)
# =========================
def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

scores = []
for i, doc_vec in enumerate(doc_embeddings):
    score = cosine_similarity(query_embedding, doc_vec)
    scores.append((documents[i], score))

# =========================
# 6. 排序(TopK)
# =========================
scores = sorted(scores, key=lambda x: x[1], reverse=True)

# =========================
# 7. 输出结果
# =========================
print("查询:", query)
print("\n检索结果(按相似度排序):\n")

for doc, score in scores:
    print(f"相似度: {score:.4f} | 文档: {doc}")

输出结果

检索结果(按相似度排序):

相似度: 0.8261 | 文档: Python 如何读取 CSV 文件
相似度: 0.5214 | 文档: 使用 pandas 加载表格数据
相似度: 0.3241 | 文档: 明天天气怎么样

到这里后,大家应该意识到向量搜索不是单个函数,而是一整条工程链路:数据清洗、文本切分、向量化、索引构建、相似度计算、Top K 排序、结果评估,每个环节都会影响最终效果。

2. 什么是向量表示?

向量表示(Vector Representation)就是把文本、图像、音频等原本不方便直接计算的数据,转换成一组数字。例如一句话可以被转换成一个固定维度的浮点数组:

"向量搜索适合用于文档检索"

=> [0.021, -0.184, 0.337, ..., 0.092]

这些数字不是人工随便指定的,而是由向量化模型生成的。一个好的向量化模型,会让语义相近的内容在向量空间中距离更近,让语义差异较大的内容距离更远。

2.1 稀疏向量与稠密向量

所有“向量化”并非混为一谈,一般可以分成稀疏向量和稠密向量来理解。

稀疏向量常见于 Bag of Words 和 TF-IDF。它们通常以词表为维度,如果词表有 50,000 个词,那么每篇文档可能就是一个 50,000 维向量。大部分位置都是 0,因为一篇文档只会出现词表中的少量词。

稠密向量常见于 Word2Vec、FastText、BERT、Sentence-BERT 以及现代 Embedding 模型。稠密向量的维度通常固定,例如 384、768、1024 或 1536 维。每一维大多都有值,但单个维度往往很难解释,它们共同编码文本语义。

方法 向量类型 优点 局限
Bag of Words 稀疏向量 简单直观 不理解语序和语义
TF-IDF 稀疏向量 能突出重要词 对同义词和上下文不敏感
Word2Vec 稠密向量 能表达词语关系 主要偏词级别
FastText 稠密向量 考虑子词,适合处理未登录词 对复杂上下文表达有限
BERT 类模型 稠密向量 能结合上下文理解语义 计算成本更高

简单理解是:TF-IDF 更像是在统计“哪些词重要”,而现代 embedding 更像是在建模“这段话是什么意思”。

2.2 向量空间模型

向量空间模型(Vector Space Model)的核心思想是:把对象放到同一个数学空间中,然后通过距离或相似度判断它们之间的关系。

真实的文本向量通常是几百维甚至几千维,无法直接画出来。为了帮助理解,可以用二维空间做一个简化示意:

编程 + 数据处理 数据处理相关 弱相关 编程相关 明天是否下雨 JavaScript DOM 操作 pandas 加载表格 Python 读取 CSV 编程弱相关 编程强相关 数据处理弱相关 数据处理强相关 文本在向量空间中的相对位置示意

在这个示意图中,Python 读取 CSVpandas 加载表格 离得比较近,因此更可能被认为语义相似。真实场景虽然维度更高,但原理一致:查询和文档都被映射到向量空间,然后寻找离查询最近的文档向量。

如果可以,我们可以使用sentence-transformers库生成文本向量,然后使用PCA将这些向量降到二维,最后使用matplotlib进行可视化。

import matplotlib.pyplot as plt
from sentence_transformers import SentenceTransformer
from sklearn.decomposition import PCA
import numpy as np


# 设置 matplotlib 中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号


# 示例文本
documents = [
    "Python 读取 CSV",
    "pandas 加载表格",
    "JavaScript DOM 操作",
    "明天是否下雨"
]

# 加载预训练的模型
model = SentenceTransformer('all-MiniLM-L6-v2')

# 将文本转化为向量
document_embeddings = model.encode(documents)

# 使用PCA将高维向量降到二维
pca = PCA(n_components=2)
reduced_embeddings = pca.fit_transform(document_embeddings)

# 可视化文本向量
plt.figure(figsize=(8, 6))
plt.scatter(reduced_embeddings[:, 0], reduced_embeddings[:, 1])

# 给每个点加上文本标签
for i, doc in enumerate(documents):
    plt.text(reduced_embeddings[i, 0], reduced_embeddings[i, 1], doc, fontsize=12)

plt.title('文本向量在二维空间中的分布')
plt.xlabel('PCA 维度 1')
plt.ylabel('PCA 维度 2')
plt.grid(True)
plt.show()

文本向量在二维空间中的分布

3. 相似度度量:如何判断两个向量像不像?

将文本变成向量之后,还需要一个度量方法来判断两个向量是否相似。常见方式包括余弦相似度、欧氏距离、曼哈顿距离和 Jaccard 相似度。

3.1 余弦相似度

余弦相似度关注两个向量方向是否一致,而不是长度是否完全相同。公式如下:

cosine_similarity(A, B) = (A · B) / (||A|| * ||B||)

其中 A · B 表示向量点积,||A||||B|| 表示向量长度。两个向量方向越接近,余弦相似度越接近 1;方向越不一致,分数越低。

文本 A

向量 A

文本 B

向量 B

计算点积 A·B

计算向量 A 长度

计算向量 B 长度

除以长度乘积

得到余弦相似度

在文本语义搜索中,余弦相似度非常常见。很多实践中会先把向量归一化,使每个向量长度约为 1,这样点积就可以近似等价于余弦相似度。

3.2 欧氏距离和曼哈顿距离

欧几里得距离可以理解为两点之间的直线距离:

euclidean_distance(A, B) = sqrt(sum((A_i - B_i)^2))

曼哈顿距离通常称为出租车距离或城市街区距离( Taxicab distance or City Block distance),计算实值向量之间的距离。想象描述均匀网格(如棋盘)上物体的向量。曼哈顿距离是指两个矢量之间的距离,如果它们只能移动直角。在计算距离时不涉及对角线移动。:

manhattan_distance(A, B) = sum(abs(A_i - B_i))

实际项目中使用哪种距离,需要看 embedding 模型的训练方式和向量数据库的配置。常见选项包括 cosinedot_productl2。如果不确定,优先参考模型文档或数据库索引推荐。

3.3 Jaccard 相似度

Jaccard 相似度通常用于集合之间的比较,是一个用于计算样本集的相似性和多样性的度量。它是交集的大小除以样本集的并集的大小。:

Jaccard(A, B) = |A ∩ B| / |A ∪ B|

例如:

A = {Python, CSV, pandas}
B = {Python, Excel, pandas}

A ∩ B = {Python, pandas}
A ∪ B = {Python, CSV, pandas, Excel}

Jaccard(A, B) = 2 / 4 = 0.5

它不是典型的稠密向量相似度,但在标签匹配、集合去重、推荐系统特征匹配中依然有用。

当然,相似计算的算法还有很多,这里我们就不一一列举,如果大家感兴趣可以看下这篇文章,详细了解一下 一图看遍9种距离度量

下面是梳理后的段落,你可以直接加入到你的博客文章中,帮助读者理解暴力搜索和近似最近邻搜索的区别,以及为什么大规模数据时需要使用近似搜索:


4. 近似最近邻搜索:为什么不能一直暴力搜索?

在处理小规模数据时,我们可以通过暴力搜索(Brute Force Search)计算查询向量与所有文档向量的相似度,然后排序返回最相似的文档。这个方法简单直观,能够保证100%的准确性。但当数据量急剧增加到百万、千万甚至更大的规模时,每次查询都要遍历所有向量进行计算,这会导致计算速度变慢,内存消耗增加,甚至使系统变得无法承载。

为什么暴力搜索不适合大规模数据?

暴力搜索的核心思想是逐一计算每个查询向量与所有文档向量的相似度,并返回最相似的文档。这种方法对小规模数据没有问题,但当文档数量达到千万级时,计算和内存压力会显著增加。例如,如果有1,000万条向量,每条向量有1536维,暴力搜索将逐一计算每个查询向量与每条文档向量的相似度,所需的计算资源非常庞大,速度也会变得非常慢。

近似最近邻搜索(ANN)如何解决?

为了解决暴力搜索带来的性能瓶颈,我们需要使用近似最近邻搜索(ANN,Approximate Nearest Neighbor)。与暴力搜索不同,ANN的目标不是每次都计算绝对准确的最近邻,而是在可接受的精度损失范围内,快速找到一个近似的最相似文档。

ANN的工作原理是通过提前构建索引结构,将文档向量存储成特定的形式,查询时只访问相关的候选区域。这样,大多数情况下我们可以通过快速检索候选区域来找到一个非常接近的结果,虽然这个结果可能不是精确的最近邻,但通常足够满足实际需求。

优势:
  • 查询速度大幅提升:通过预构建索引,查询时能够快速定位到最相关的候选区域。
  • 处理海量数据:适用于百万级、千万级甚至更多的数据集,能够显著减少计算和内存开销。
缺点:
  • 牺牲少量精度:为了提高查询速度,ANN在某些情况下可能无法找到最精确的结果,但精度损失通常在可接受范围内。

下面的图示展示了精确最近邻搜索与近似最近邻搜索的区别:

精确最近邻搜索

逐个比较所有向量

准确率高

大规模数据下速度慢

近似最近邻搜索 ANN

提前构建索引结构

只访问可能相关的候选区域

查询速度快

可能牺牲少量精度

  • 精确最近邻搜索:逐个比较所有向量,确保返回最精确的结果,但随着数据量的增加,速度会变得非常慢。
  • 近似最近邻搜索(ANN):通过构建索引,只访问可能相关的候选区域,查询速度显著提高,但可能会牺牲少量精度。

在实际应用中,近似最近邻搜索(ANN)通过构建索引和预计算候选区域,快速定位相似的文档或项,但这通常会牺牲少量的精度。举个例子,假设你正在使用电影推荐系统。当你查询喜欢的电影时,ANN会快速找到与查询最相似的电影,虽然返回的电影可能不是绝对最相似的(比如电影A),而是一个稍微差一些的电影(比如电影C),但它们依然是符合你兴趣的推荐。这样做虽然牺牲了精度,但能大幅提高查询速度,尤其是在大规模数据中,用户通常能接受这种精度的轻微损失。

4.1 维度灾难

高维空间中有一个经典问题叫“维度灾难”。随着维度升高,点与点之间的距离分布会变得不直观,一些低维空间中有效的索引结构在高维下效果会下降。

常见的 ANN 技术包括:

技术 核心思想 学习理解
LSH 相似对象更可能落入同一个桶 像给相似内容分组
KD-Tree 按维度递归切分空间 低维场景更有效
Ball Tree 用超球体组织空间 适合部分距离度量
IVF 先聚类,再到相关簇中搜索 先粗召回,再细搜索
HNSW 构建多层小世界图 工业界常用,速度和召回表现好
PQ 对向量进行量化压缩 节省内存,适合大规模数据

ANN 的它不是“偷懒搜索”,而是在召回率、速度和内存之间做工程权衡。

5. 向量数据库:把向量搜索工程化

学完基础理论后,我们需要理解为什么还需要向量数据库。真实项目里,问题不只是“存一堆向量然后查一下”,还包括:

  • 如何持久化存储向量。
  • 如何维护向量索引。
  • 如何保存文档元数据。
  • 如何做新增、删除和更新。
  • 如何根据时间、分类、权限等过滤。
  • 如何支撑高并发查询。
  • 如何处理扩容、备份和监控。

传统关系型数据库擅长结构化数据,例如订单、用户和商品表;向量数据库则专门优化向量相似度检索。

向量数据库

向量化层

应用层

用户查询

检索 API

Embedding 模型

向量索引

元数据

原文或文档 ID

5.1 FAISS、Pinecone、Weaviate 的定位

工具 类型 适合场景 学习印象
FAISS 向量搜索库 本地实验、离线检索、自建索引 性能强,但元数据和服务化要自己处理
Pinecone 托管向量数据库 快速上线、减少运维 使用简单,适合云端应用
Weaviate 开源向量数据库 自部署、对象模型、混合搜索 功能完整,适合知识库服务

如果是学习原理,我更推荐先从 FAISS 入手,因为它比较直接,可以帮助理解索引、距离和 Top K 检索。如果是快速搭建线上 Demo,可以考虑 Pinecone 或 Weaviate。

6. 实战一:不用向量库,先手写一个最小语义搜索

进入 FAISS 之前,我先做一个最小实验:使用 sentence-transformers 生成向量,再用 NumPy 手写相似度计算。

6.1 安装依赖

pip install sentence-transformers numpy

6.2 代码实现

from sentence_transformers import SentenceTransformer
import numpy as np


docs = [
    "Python 如何读取 CSV 文件?",
    "使用 pandas 加载表格数据的方法",
    "向量数据库可以用于语义搜索",
    "今天天气怎么样?",
    "推荐系统可以通过向量召回相似商品",
]

query = "怎么用 Python 打开表格文件?"

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

doc_embeddings = model.encode(docs, normalize_embeddings=True)
query_embedding = model.encode([query], normalize_embeddings=True)[0]

scores = np.dot(doc_embeddings, query_embedding)
top_indices = np.argsort(scores)[::-1][:3]

for rank, idx in enumerate(top_indices, start=1):
    print(f"Top {rank}: score={scores[idx]:.4f}, text={docs[idx]}")

这里设置了 normalize_embeddings=True。向量归一化之后,每个向量长度接近 1,此时点积可以近似看作余弦相似度。

一次可能的输出如下:

Top 1: score=0.78, text=Python 如何读取 CSV 文件?
Top 2: score=0.72, text=使用 pandas 加载表格数据的方法
Top 3: score=0.31, text=向量数据库可以用于语义搜索

这个实验让我第一次把“语义相似”变成了一个可计算的分数。虽然分数不是绝对真理,但它已经比单纯关键词匹配更接近人的直觉。

6.3 最小版本的不足

这个版本适合理解原理,但不适合大规模应用:

  • 每次查询都要和所有文档向量计算相似度。
  • 文档向量只存在内存中,程序退出后需要重算。
  • 没有索引优化,数据量大时速度会下降。
  • 没有元数据过滤,无法按分类、时间、权限等筛选。

这些不足正好引出下一步:使用 FAISS 构建向量索引。

7. 实战二:使用 FAISS 构建本地向量搜索

FAISS(Facebook AI Similarity Search)是一个常用的向量相似度搜索库,适合本地实验,也可以用于大规模向量索引。

7.1 安装依赖

pip install faiss-cpu sentence-transformers numpy

学习阶段使用 faiss-cpu 就够了。如果后续数据量更大、机器有 GPU,再考虑 GPU 版本。

7.2 使用 IndexFlatIP 做精确检索

import faiss
import numpy as np
from sentence_transformers import SentenceTransformer


docs = [
    {"id": 1, "title": "CSV 读取", "text": "Python 可以使用 pandas.read_csv 读取 CSV 文件。"},
    {"id": 2, "title": "Excel 读取", "text": "pandas 的 read_excel 可以加载 Excel 表格。"},
    {"id": 3, "title": "向量数据库", "text": "向量数据库可以存储 embedding 并进行相似度搜索。"},
    {"id": 4, "title": "天气查询", "text": "天气预报可以告诉我们明天是否下雨。"},
    {"id": 5, "title": "推荐系统", "text": "推荐系统可以通过用户和商品向量召回相似内容。"},
]

model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

texts = [item["text"] for item in docs]
embeddings = model.encode(texts, normalize_embeddings=True)
embeddings = np.array(embeddings).astype("float32")

dimension = embeddings.shape[1]
index = faiss.IndexFlatIP(dimension)
index.add(embeddings)

query = "如何用 Python 加载表格数据?"
query_vector = model.encode([query], normalize_embeddings=True)
query_vector = np.array(query_vector).astype("float32")

scores, indices = index.search(query_vector, k=3)

for score, idx in zip(scores[0], indices[0]):
    item = docs[idx]
    print(f"score={score:.4f}, id={item['id']}, title={item['title']}, text={item['text']}")

这里使用了 IndexFlatIP

  • Flat 表示精确搜索,不做近似索引。
  • IP 表示 Inner Product,也就是内积。
  • 因为向量已经归一化,所以内积可以近似当作余弦相似度。

7.3 FAISS 检索流程

准备文档

文本清洗与切分

Embedding 模型生成文档向量

转换为 float32

创建 FAISS Index

添加向量到 Index

用户输入查询

生成查询向量

FAISS search topK

返回向量索引位置

映射回原始文档

展示搜索结果

7.4 保存和加载索引

实际应用中,索引构建可能比较耗时,因此需要持久化。

import faiss


faiss.write_index(index, "docs.index")

loaded_index = faiss.read_index("docs.index")
scores, indices = loaded_index.search(query_vector, k=3)

需要注意的是,FAISS 只保存向量索引,不会自动保存原始文档内容。通常还需要额外保存一份 id -> 文档内容 的映射,例如 JSON、SQLite、PostgreSQL 或对象存储。

import json


with open("docs_metadata.json", "w", encoding="utf-8") as f:
    json.dump(docs, f, ensure_ascii=False, indent=2)

with open("docs_metadata.json", "r", encoding="utf-8") as f:
    loaded_docs = json.load(f)

8. 实战三:从零实现一个小型文本检索应用

为了把前面的知识串起来,我可以实战一个最小项目:给定一组学习笔记,用户输入问题后,系统返回最相关的笔记片段。

8.1 项目目标

这个小项目覆盖向量搜索的关键步骤:

  • 准备数据。
  • 文本切分。
  • 生成 embedding。
  • 构建索引。
  • 查询召回。
  • 展示结果。

8.2 准备学习笔记数据

raw_notes = [
    """
    FAISS 是一个向量相似度搜索库,适合本地构建高性能向量索引。
    IndexFlatIP 可以使用内积进行精确检索。
    如果向量已经归一化,内积结果可以近似看作余弦相似度。
    """,
    """
    RAG 的核心流程包括文档加载、文本切分、向量化、检索和生成。
    检索阶段会从知识库中找出和用户问题最相关的内容。
    """,
    """
    推荐系统可以将用户和商品表示为向量。
    通过向量相似度可以召回用户可能感兴趣的商品。
    """,
]

8.3 文本切分

真实文档通常不能直接整篇向量化,原因包括:

  • 太长的文本可能超过模型输入限制。
  • 大段文本语义过宽,检索结果不够精确。
  • 切成小块后,更适合交给大模型生成答案。

先实现一个简单的固定长度切分:

def split_text(text, chunk_size=80, overlap=20):
    chunks = []
    start = 0

    while start < len(text):
        end = start + chunk_size
        chunk = text[start:end].strip()

        if chunk:
            chunks.append(chunk)

        start += chunk_size - overlap

    return chunks


chunks = []
for note_id, note in enumerate(raw_notes):
    for chunk_id, chunk in enumerate(split_text(note)):
        chunks.append({
            "note_id": note_id,
            "chunk_id": chunk_id,
            "text": chunk,
        })

真实项目里可以按标题、段落、Markdown 结构、语义边界进行更精细的切分。

8.4 构建索引与查询函数

import faiss
import numpy as np
from sentence_transformers import SentenceTransformer


model = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

texts = [chunk["text"] for chunk in chunks]
vectors = model.encode(texts, normalize_embeddings=True)
vectors = np.array(vectors).astype("float32")

index = faiss.IndexFlatIP(vectors.shape[1])
index.add(vectors)


def search(query, top_k=3):
    query_vector = model.encode([query], normalize_embeddings=True)
    query_vector = np.array(query_vector).astype("float32")

    scores, indices = index.search(query_vector, top_k)

    results = []
    for score, idx in zip(scores[0], indices[0]):
        item = chunks[idx]
        results.append({
            "score": float(score),
            "note_id": item["note_id"],
            "chunk_id": item["chunk_id"],
            "text": item["text"],
        })

    return results


for result in search("RAG 检索阶段是做什么的?"):
    print(result)

一个返回结果可能是:

{
  "score": 0.81,
  "note_id": 1,
  "chunk_id": 0,
  "text": "RAG 的核心流程包括文档加载、文本切分、向量化、检索和生成..."
}

这个结果让我更清楚地理解了 RAG 中的“检索”:它不是直接让模型凭空回答,而是先用向量搜索找到证据。

8.5 小型 RAG 检索链路

大语言模型 向量索引 Embedding 模型 应用服务 用户 大语言模型 向量索引 Embedding 模型 应用服务 用户 提问 将问题向量化 查询向量 Top K 相似度检索 返回相关片段 问题 + 检索片段 生成答案 返回带上下文的回答

9. Pinecone:托管向量数据库的实践思路

FAISS 更像一个底层库,适合本地和自建场景。Pinecone 则是托管向量数据库服务,适合希望快速上线、减少运维成本的项目。

Pinecone 的典型流程是:

创建 Index

指定维度和距离度量

Upsert 向量和元数据

Query 查询 Top K

返回 ID、分数、元数据

示意代码:

from pinecone import Pinecone, ServerlessSpec


pc = Pinecone(api_key="YOUR_API_KEY")

index_name = "learning-notes"

pc.create_index(
    name=index_name,
    dimension=384,
    metric="cosine",
    spec=ServerlessSpec(
        cloud="aws",
        region="us-east-1",
    ),
)

index = pc.Index(index_name)

index.upsert(
    vectors=[
        {
            "id": "note-1",
            "values": [0.01, 0.02, 0.03],
            "metadata": {"title": "FAISS 学习笔记"},
        }
    ]
)

result = index.query(
    vector=[0.01, 0.02, 0.03],
    top_k=3,
    include_metadata=True,
)

print(result)

这段代码里的向量只是示意。真实项目中,values 应该来自 embedding 模型,而且向量维度必须和创建索引时的 dimension 一致。

10. Weaviate:开源向量数据库的实践思路

Weaviate 是一个开源向量数据库,它除了向量检索,还强调对象模型、Schema、GraphQL 查询和混合搜索能力。它适合需要自部署,同时又希望数据库层提供较完整功能的场景。

概念流程如下:

定义 Collection 或 Class

写入对象

对象包含文本字段和元数据

生成或写入向量

nearVector 或 nearText 查询

返回相似对象

示意代码:

import weaviate
from weaviate.classes.config import Configure


client = weaviate.connect_to_local()

client.collections.create(
    name="LearningNote",
    vectorizer_config=Configure.Vectorizer.none(),
)

collection = client.collections.get("LearningNote")

collection.data.insert(
    properties={
        "title": "向量搜索学习笔记",
        "content": "向量搜索通过 embedding 表示语义相似度。",
    },
    vector=[0.01, 0.02, 0.03],
)

result = collection.query.near_vector(
    near_vector=[0.01, 0.02, 0.03],
    limit=3,
)

for obj in result.objects:
    print(obj.properties)

从学习角度看,Weaviate 让我意识到向量数据库并不是“数组 + search 函数”。当数据需要结构化管理、过滤和业务建模时,数据库能力会变得很重要。

11. 向量搜索的典型应用

11.1 NLP 文本检索

语义搜索是最容易理解的应用。用户输入自然语言问题,系统返回语义最相关的文档。

自然语言问题

问题向量

文档库向量

相似度搜索

相关文档片段

常见场景包括企业知识库、API 文档问答、论文检索、代码片段搜索和客服问答。实际项目中,关键词搜索仍然很有价值,因此经常会采用 BM25 + 向量检索的混合搜索。

11.2 推荐系统

推荐系统也可以看成“向量之间找相似”。用户可以表示为向量,商品也可以表示为向量。用户向量代表兴趣,商品向量代表内容或行为特征。

用户历史行为

用户兴趣向量

商品标题/描述/图片/类目

商品向量

相似度匹配

推荐 Top K 商品

如果用户经常浏览“机械键盘”“程序员桌搭”“人体工学椅”,系统就可以召回向量空间中相近的商品。

11.3 图像搜索

图像也可以通过 CNN、Vision Transformer 或多模态模型转换成向量。相似图像搜索的流程和文本类似,只是 embedding 模型不同。

图像搜索描述

常见场景包括以图搜图、商品图片去重、相册相似照片聚类和内容审核中的相似样本检索。

11.4 多模态搜索

更进一步,多模态模型可以把文本和图片映射到同一个向量空间。例如用户输入“红色运动鞋”,系统可以直接返回相关图片。

多模态向量搜索

这让我意识到,向量搜索的核心不局限于文本,而是“把不同类型的数据变成可比较的向量”。

12. 优化与挑战

把 Demo 跑起来只是第一步。真实项目中,向量搜索的难点通常在数据规模、检索效果、更新成本和评估体系。

12.1 检索效率优化

常见优化方向包括:

  • 使用 ANN 索引,例如 HNSW、IVF、PQ。
  • 对向量做归一化,简化相似度计算。
  • 控制 Top K,避免返回过多候选。
  • 对长文档合理切分,减少无效召回。
  • 使用两阶段检索:先向量召回,再重排序。

用户查询

Embedding

向量召回 Top 50

Reranker 重排序

最终 Top 5

向量召回追求“不要漏掉可能相关的内容”,重排序追求“把最相关的内容排在最前面”。

12.2 大规模数据处理

当数据规模变大时,需要考虑:

  • 向量存储成本。
  • 索引构建时间。
  • 新增、删除、更新文档的同步策略。
  • 单机存不下时的分片策略。
  • 高频数据和低频数据的冷热分层。

可以用一个简单公式估算向量内存:

内存占用 ≈ 向量数量 × 向量维度 × 每个 float32 的字节数

例如 1,000,000 条 768 维向量:

1,000,000 × 768 × 4 bytes ≈ 2.86 GB

这还只是原始向量本身,不包括索引结构、元数据和系统运行开销。

12.3 检索效果优化

效果优化不只是换更大的模型,还包括整个链路:

  • 数据清洗:去除模板文本、乱码和重复内容。
  • 文档切分:避免 chunk 太长或太短。
  • 查询改写:让用户问题更适合检索。
  • 混合搜索:结合 BM25 和向量检索。
  • 元数据过滤:根据时间、分类、权限缩小范围。
  • 重排序:使用 reranker 提升最终排序质量。
  • 评估集:构建问题和标准答案,持续评估召回率和准确率。

我认为最容易被忽视的是文档切分。如果 chunk 切得不好,再好的 embedding 模型也可能召回不准。

13. 学习过程中的关键理解

13.1 向量不是给人看的,而是给机器比较的

刚开始看到 embedding 数组时,我会下意识想问:“第 1 维是什么意思?第 2 维是什么意思?”后来发现,大多数稠密向量的单个维度很难独立解释。更重要的是理解向量整体如何用于比较、聚类、召回和排序。

13.2 相似度分数不是绝对标准

同一个分数在不同模型、不同数据集、不同距离度量下意义都不同。比如 0.75 在某些模型中可能很高,在另一些模型中可能只是中等。因此真实项目里应该结合评估集、人工标注和业务反馈确定阈值。

13.3 向量搜索解决的是召回问题,不等于完整问答系统

在 RAG 中,向量搜索负责找相关材料,但最终答案质量还取决于文档质量、检索排序、提示词设计、生成模型能力和幻觉控制。向量搜索是核心环节之一,但不是全部。

14. 学习总结

学习路线可以按照下面的路径推进:

理解关键词搜索的局限

学习向量表示

理解余弦相似度和距离度量

用 Numpy 手写最小语义搜索

使用 FAISS 构建本地索引

加入文档切分和元数据映射

尝试 Pinecone 或 Weaviate

学习 ANN、HNSW、IVF、PQ

做 RAG 或推荐系统 Demo

构建评估集并持续优化

这个路线的好处是,每一步都能看见前一步为什么不够用。手写 NumPy 搜索能理解原理,但性能有限;FAISS 能提升本地检索能力,但服务化和元数据需要自己处理;向量数据库解决工程化问题,但仍然需要理解索引和相似度;RAG 应用能把检索和生成结合起来,但需要评估与优化。

向量搜索它不是一个神秘黑盒,而是一套把非结构化数据转换成向量,并在向量空间中寻找相似对象的技术体系。

从理论上看,关键概念包括向量表示、向量空间模型、相似度度量和近似最近邻搜索。从实践上看,需要掌握 embedding 生成、索引构建、Top K 查询、元数据管理和结果评估。

向量搜索的价值并不只是“搜得更准”,而是让机器能够在海量非结构化数据中找到语义相关的信息。对于知识库、智能问答、推荐系统和多模态应用来说,这是一项非常值得投入时间学习的基础能力。

参考文档

Embedding嵌入模型是什么?为什么需要 Embedding?
Transformer | 一文带你了解Embedding(从传统嵌入方法到大模型Embedding)
一图看遍9种距离度量,图文并茂,详述应用场景!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐