用户提交搜索请求后,我们的系统需要理解其语义,而不仅仅是使用的字面词语。这包括将原始请求(通常是文本,但在更先进的系统中也可能是其他数据类型)转换为向量表示,使其与表示我们已索引文档的向量处于相同的高维空间。这个查询向量充当我们用来在向量数据库中查找相关项目的“探针”。这个过程直观,但需要仔细执行,尤其是在与索引阶段保持一致性方面。从原始查询到搜索向量当用户在搜索栏中输入查询或通过API接口提交查询时,该过程就开始了。让我们分解一下典型步骤:接收原始查询: 系统捕获用户的输入。这可能是一个简单的字符串,如“电池技术最新进展”,也可能是一个更复杂的自然语言问题。预处理(可选): 根据索引阶段(第1章)选择的向量化模型,可能会应用一些基本的文本预处理。这可能包括:将文本转换为小写。删除过多的标点符号或特殊字符。处理拼写错误(尽管这通常由向量化模型本身或专门的查询理解组件更好地处理)。然而,许多现代基于Transformer的向量化模型(如Sentence-BERT变体)被设计为有效处理原始或最少处理的文本。它们通常能捕获大小写和标点符号带来的细微含义。过度激进的预处理有时会去除有用的语义信息。指导原则是,对查询进行预处理的方式必须与在生成源文档向量化表示之前对源文档进行预处理的方式完全一致。 如果源文档未进行预处理,则查询也不应进行预处理。应用向量化模型: 这是最重要的一步。预处理(或原始)的查询文本被输入到用于为数据库中存储的文档创建向量的相同向量化模型中。此处的一致性是必不可少的。使用不同的模型,甚至是相同模型的不同版本或配置,都将产生一个可能位于不兼容向量空间中的查询向量,导致无意义的相似性比较和糟糕的搜索结果。from sentence_transformers import SentenceTransformer # 加载用于索引数据的*完全相同*的模型 # 示例:使用一个常见的Sentence-BERT模型 model_name = 'all-MiniLM-L6-v2' model = SentenceTransformer(model_name) # 用户查询 user_query = "What are the best practices for distributed system monitoring?" # --- 预处理步骤(示例 - 如果在索引期间使用则应用) --- # processed_query = user_query.lower() # 应用与索引相同的步骤 processed_query = user_query # 假设此模型仅需最少预处理 # --- 生成向量化表示 --- query_vector = model.encode(processed_query) # query_vector 现在是一个NumPy数组,表示向量空间中的查询 print(f"查询: {user_query}") print(f"生成的向量维度: {query_vector.shape}") # 示例输出: Generated Vector Dimension: (384,) # print(f"向量片段: {query_vector[:5]}") # 显示前几个维度向量归一化(推荐): 许多相似性度量,特别是余弦相似度(在第1章中讨论),对向量的幅度敏感。尽管Sentence-BERT模型通常默认生成归一化向量,但如果不太确定或使用其他模型,明确将查询向量归一化到单位长度($L_2$范数)仍是好的做法。这确保了比较纯粹关注向量空间中的方向,而非幅度。在使用余弦相似度时,在摄取数据时对已索引的向量进行归一化也是标准做法。$$ \vec{v}_{normalized} = \frac{\vec{v}}{|\vec{v}|_2} $$其中 $\vec{v}$ 的 $|\vec{v}|_2$ 是欧几里得范数(向量分量平方和的平方根)。import numpy as np # 假设 query_vector 是您生成的向量化表示 norm = np.linalg.norm(query_vector) if norm > 0: # 避免除以零 normalized_query_vector = query_vector / norm else: normalized_query_vector = query_vector # 必要时处理零向量情况 # 使用 normalized_query_vector 进行搜索 # print(f"归一化向量片段: {normalized_query_vector[:5]}")查询流程示意图将原始查询转换为可搜索向量的过程可以作为整个搜索流程中的一个独立阶段进行描绘:digraph QueryProcessing { rankdir=LR; node [shape=box, style=rounded, fontname="Arial", fontsize=10, margin=0.2]; edge [fontname="Arial", fontsize=9]; raw_query [label="原始用户查询\n(例如,文本)", shape=parallelogram, style=filled, fillcolor="#a5d8ff"]; preprocess [label="预处理\n(可选,必须与索引匹配)", style=filled, fillcolor="#bac8ff"]; embedding_model [label="向量化模型\n(与索引模型相同)", style=filled, fillcolor="#91a7ff"]; normalize [label="向量归一化\n(推荐)", style=filled, fillcolor="#bac8ff"]; query_vector [label="查询向量", shape=ellipse, style=filled, fillcolor="#748ffc"]; ann_search [label="ANN搜索\n(向量数据库输入)", shape=cds, style=filled, fillcolor="#ffe066"]; raw_query -> preprocess [label=" 输入 "]; preprocess -> embedding_model [label=" 处理后的文本 "]; embedding_model -> normalize [label=" 原始向量 "]; normalize -> query_vector [label=" 归一化向量 "]; query_vector -> ann_search [label=" 搜索输入 "]; // Make flow clear { rank=same; raw_query; } { rank=same; ann_search; } }此图描绘了将用户的原始查询转换为适用于搜索向量数据库的向量的典型阶段。与索引过程的一致性,特别是在模型选择和预处理方面,非常重要。为何重要生成准确的查询向量是语义搜索的核心。这个向量在已学习的高维空间中封装了用户请求的含义。当我们使用这个向量查询数据库时(如第3章关于ANN搜索的详细说明),数据库的算法会高效地查找与此查询向量在语义空间中最接近(最相似)的已存储文档向量。如果没有这种转换,数据库将不知道在哪里“查找”。查询向量作为搜索操作的坐标,根据语义接近性而非简单的关键词重叠,引导搜索找到可能相关的结果。准备好查询向量后,下一步是将其传递给向量数据库的搜索接口,启动近似最近邻搜索,以检索语义最相似的文档向量及其相关元数据。这有助于对结果进行排名并向用户呈现。