AI与RAG知识库的高效匹配:关键词搜索策略揭秘
AI 与 RAG 知识库的高效匹配:关键词搜索策略揭秘
本文将从 RAG(Retrieval-Augmented Generation)的基本原理出发,系统介绍在知识库检索环节中如何运用高效的关键词搜索策略,结合分词、同义词扩展、TF-IDF、向量空间模型等技术,深入剖析其优势与实现方法。文中配有 Python 代码示例与示意图说明,帮助你快速上手构建一个简易却高效的 RAG 检索模块。
目录
引言
近年来,随着大型语言模型(LLM)在文本生成领域的迅猛发展,RAG(Retrieval-Augmented Generation) 成为连接“知识库检索”与“文本生成”两端的关键技术:它先通过检索模块从海量文档中定位相关内容,再将这些检索到的片段输入到生成模型(如 GPT、T5)中进行“有依据”的答案生成。
在这个流程中,检索阶段的准确性直接影响后续生成结果的质量。如果检索结果遗漏了关键段落或检索到大量无关信息,生成模型就很难给出准确、可信的回答。因而,在 RAG 的检索环节,如何快速且精准地进行文档/段落匹配,是整个系统表现的基础。
本文将聚焦于“关键词搜索策略”这一传统而高效的检索方法,结合分词、同义词、TF-IDF、向量空间模型等多种技术,展示如何在 Python 中从零构建一个简易的检索模块,并演示它与生成模型的联合使用。
RAG 与知识库概述
RAG 的核心思想:
- 检索(Retrieval):给定用户查询(Query),从知识库(即文档集合、段落集合、Wiki条目等)中快速检索出最相关的 $k$ 段文本。
- 生成(Generation):将检索到的 $k$ 段文本(通常称为“context”)与用户查询拼接,输入到一个生成模型(如 GPT-3、T5、LLAMA 等),让模型基于这些 context 生成答案。
这样做的好处在于:
- 生成模型可以利用检索到的事实减少“编造”(hallucination);
- 知识库能够单独更新与维护,生成阶段无需从头训练大模型;
- 整套系统兼具效率与可扩展性。
知识库(Knowledge Base):
- 通常是一个文档集合,每个文档可以被拆分为多个“段落”(passage)或“条目”(entry)。
- 在检索阶段,我们一般对“段落”进行索引,比如 Wiki 的每段落、FAQ 的每条目、技术文档的每个小节。
- 关键在于:如何对每个段落建立索引,使得查询时能够快速匹配最相关的段落。
常见检索方法:
- 关键词搜索(Keyword Search):基于倒排索引,利用分词、标准化、停用词过滤、布尔检索、TF-IDF 排序等技术。
- 向量检索(Embedding Search):将查询与段落分别编码为向量,在向量空间中通过相似度(余弦相似度、内积)或 ANN(近似最近邻)搜索最接近的向量。
- 混合检索(Hybrid Retrieval):同时利用关键词与向量信息,先用关键词检索过滤候选,再用向量重新排序。
本文重点探讨第一类——关键词搜索,并在最后展示如何与简单的生成模型结合,形成最基础的 RAG 流程。
关键词搜索在 RAG 中的作用
在 RAG 中,关键词搜索通常承担“快速过滤候选段落”的职责。虽然现代向量检索(如 FAISS、Annoy、HNSW)能够发现语义相似度更高的结果,但在以下场景下,关键词搜索依然具有其不可替代的优势:
- 实时性要求高:倒排索引在百万级文档规模下,检索延迟通常在毫秒级,对于对实时性要求苛刻的场景(如搜索引擎、在线 FAQ),仍是首选。
- 新文档动态增加:倒排索引便于增量更新,当有新文档加入时,只需对新文档做索引,而向量检索往往需重新训练或再索引。
- 计算资源受限:向量检索需要计算向量表示与近似算法,而关键词检索仅基于布尔或 TF-IDF 计算,对 CPU 友好。
- 可解释性好:关键词搜索结果可以清晰地展示哪些词命中,哪个段落包含关键词;而向量检索的“语义匹配”往往不易解释。
在实际生产系统中,常常把关键词检索视作“第一道筛选”,先用关键词得到 $n$ 个候选段落,然后再对这 $n$ 个候选用向量匹配、或进阶检索模型(如 ColBERT、SPLADE)进一步排序,最后将最相关的 $k$ 个段落送入生成模块。
高效关键词搜索策略
在构建基于关键词的检索时,需解决以下关键问题:
- 如何对文档进行预处理与索引?
- 如何对用户查询做分词、标准化、同义词扩展?
- 如何度量查询与段落的匹配度并排序?
常见策略包括:
分词与标准化
分词(Tokenization)
- 中文分词:需要使用如 Jieba、哈工大 LTP、THULAC 等分词组件,将连续的汉字序列切分为词。
- 英文分词:一般可以简单用空格、标点切分,或者更专业的分词器如 SpaCy、NLTK。
大小写与标点标准化
- 英文:统一转换为小写(lowercase),去除或保留部分特殊标点。
- 中文:原则上无需大小写处理,但需要去除全角标点和多余空格。
停用词过滤(Stopwords Removal)
- 去除“的、了、在”等高频无实际意义的中文停用词;或“a、the、is”等英文停用词,以减少检索时“噪声”命中。
示意图:分词与标准化流程
原文档: 我们正在研究 AI 与 RAG 系统的检索策略。 分词后: ["我们", "正在", "研究", "AI", "与", "RAG", "系统", "的", "检索", "策略", "。"] 去除停用词: ["研究", "AI", "RAG", "系统", "检索", "策略"] 词形/大小写标准化(英文示例): 原始单词:"Running" → 标准化:"run" (词干提取或 Lemmatization)
词干提取与同义词处理
词干提取(Stemming) / 词形还原(Lemmatization)
- 词干提取:将词语还原为其“词干”形式。例如英文中 “running”→“run”,“studies”→“studi”。经典算法如 Porter Stemmer。
- Lemmatization:更复杂而准确,将 “better”→“good”,“studies”→“study”。需词性标注与词典支持,SpaCy、NLTK 都提供相关接口。
- 在检索时,对文档和查询都做相同的词干或词形还原,能够让“run”“running”“runs”都映射到“run”,提升匹配命中率。
同义词扩展(Synonym Expansion)
- 对查询词做同义词扩展,将“AI”拓展为“人工智能”,将“检索策略”拓展为“搜索策略”“查询策略”等。
- 一般通过预先构建的同义词词典(中文 WordNet、开放中文同义词词典)或拼爬网络同义词对获得;
- 在检索时,对于每个 Query Token,都生成同义词集合并纳入候选列表。例如查询 “AI 检索”时实际检索
"AI" OR "人工智能"
和"检索" OR "搜索"
的组合结果。
布尔检索与逻辑运算
倒排索引(Inverted Index)
- 对每个去重后、标准化后的词条(Term),维护一个倒排列表(Posting List):记录包含此词条的文档 ID 或段落 ID 及对应的词频、位置列表。
例如:
“检索” → [ (doc1, positions=[10, 45]), (doc3, positions=[5]), … ] “AI” → [ (doc2, positions=[0, 30]), (doc3, positions=[12]), … ]
布尔检索(Boolean Retrieval)
- 支持基本的 AND / OR / NOT 运算符。
示例:
- 查询:“AI AND 检索” → 先取“AI”的倒排列表 DS\_A,取“检索”的倒排列表 DS\_B,再做交集:
DS_A ∩ DS_B
。 - 查询:“AI OR 检索” → 并集:
DS_A ∪ DS_B
。 - 查询:“AI AND NOT 检索” →
DS_A \ DS_B
。
- 查询:“AI AND 检索” → 先取“AI”的倒排列表 DS\_A,取“检索”的倒排列表 DS\_B,再做交集:
- 布尔检索能够精确控制哪些词必须出现、哪些词禁止出现,但检索结果往往较为粗糙,需要后续排序。
TF-IDF 与向量空间模型
TF-IDF(Term Frequency–Inverse Document Frequency)
词频(TF):在一个段落/文档中,词条 $t$ 出现次数越多,其在该文档中的重要性也越高。通常定义为:
$$ \mathrm{TF}(t,d) = \frac{\text{词条 } t \text{ 在 文档 } d \text{ 中的出现次数}}{\text{文档 } d \text{ 的总词数}}. $$
逆文档频率(IDF):在整个语料库中,出现文档越少的词条对检索越有区分度。定义为:
$$ \mathrm{IDF}(t) = \log \frac{N}{|\{d \mid t \in d\}| + 1}, $$
其中 $N$ 是文档总数。
TF-IDF 权重:
$$ w_{t,d} = \mathrm{TF}(t,d) \times \mathrm{IDF}(t). $$
- 对于每个段落,计算其所有词条的 TF-IDF 权重,得到一个长度为 “词典大小” 的稀疏向量 $\mathbf{v}\_d$。
向量空间模型(Vector Space Model)
- 将查询也做相同的 TF-IDF 统计,得到查询向量 $\mathbf{v}\_q$。
用 余弦相似度 度量查询与段落向量之间的相似性:
$$ \cos(\theta) = \frac{\mathbf{v}_q \cdot \mathbf{v}_d}{\|\mathbf{v}_q\| \, \|\mathbf{v}_d\|}. $$
- 取相似度最高的前 $k$ 个段落作为检索结果。此方法兼具关键词匹配的可解释性和排序的连续性。
示意图:TF-IDF 检索流程
文档集合 D = {d1, d2, …, dn} ↓ 对每个文档做分词、词形还原、去停用词 ↓ 构建倒排索引与词典(Vocabulary),计算每个文档的 TF-IDF 向量 v_d ↓ 当接收到查询 q 时: 1) 对 q 做相同预处理:分词→词形还原→去停用词 2) 计算查询的 TF-IDF 向量 v_q 3) 对所有文档计算 cos(v_q, v_d),排序选前 k 个高相似度文档
基于词嵌入的近义匹配
静态词嵌入(Static Embedding)
- 使用 Word2Vec、GloVe 等预训练词向量,将每个词映射为固定维度的向量。
- 对于一个查询,将查询中所有词向量平均或加权(如 IDF 加权)得到一个查询语义向量 $\mathbf{e}\_q$;同理,对段落中的所有词做加权得到段落向量 $\mathbf{e}\_d$。
- 计算 $\cos(\mathbf{e}\_q, \mathbf{e}\_d)$ 作为匹配度。这种方法可以捕获同义词、近义词之间的相似性,但无法区分词序;
- 计算量相对较大,需对所有段落预先计算并存储其句向量,以便快速检索。
上下文词嵌入(Contextual Embedding)
- 使用 BERT、RoBERTa 等上下文编码器,将整个段落编码为一个向量。例如 BERT 的
[CLS]
token 输出作为句向量。 - 对查询与所有段落分别做 BERT 编码,计算相似度进行排序;
- 这样可以获得更强的语义匹配能力,但推理时需多次调用大模型,计算开销大。
- 使用 BERT、RoBERTa 等上下文编码器,将整个段落编码为一个向量。例如 BERT 的
在本文后续的示例中,我们主要聚焦于TF-IDF 级别的检索,作为关键词搜索与 RAG 集成的演示。
结合 RAG 框架的检索流程
在典型的 RAG 系统中,检索与生成的流程如下:
知识库预处理
- 文档拆分:将大文档按段落、条目或固定长度(如 100 字)分割。
- 分词 & 词形还原 & 去停用词:对每个段落做标准化处理。
- 构建倒排索引与 TF-IDF 向量:得到每个段落的稀疏向量。
用户输入查询
- 用户给出一句自然语言查询,如“RAG 如何提高文本生成的准确性?”。
- 对查询做相同预处理(分词、词形还原、去停用词)。
关键词检索 / 排序
- 计算查询的 TF-IDF 向量 $\mathbf{v}\_q$。
- 计算 $\cos(\mathbf{v}\_q, \mathbf{v}\_d)$,将所有段落按相似度从高到低排序,选前 $k$ 个段落作为检索结果。
生成模型调用
- 将查询与检索到的 $k$ 个段落(按相似度降序拼接)作为 prompt 或上下文,传给生成模型(如 GPT-3.5、T5)。
- 生成模型基于这些 context,生成最终回答。
示意图:RAG 检索 + 生成整体流程
用户查询 q ↓ 查询预处理(分词→词形还原→去停用词) ↓ 计算 TF-IDF 向量 v_q ↓ 对知识库中所有段落计算 cos(v_q, v_d) ↓ 排序选前 k 个段落 → R = {r1, r2, …, rk} ↓ 生成模型输入: [q] + [r1 || r2 || … || rk] ↓ 生成模型输出回答 A
代码示例:构建关键词搜索与 RAG 集成
下面用 Python 从零构建一个简易的 TF-IDF 检索模块,并示范如何把检索结果喂给生成模型。为了便于演示,我们使用一个很小的“知识库”样本,并使用 scikit-learn
的 TfidfVectorizer
快速构建 TF-IDF 向量与索引。
1. 准备样例知识库
# -*- coding: utf-8 -*-
# knowledge_base.py
documents = [
{
"id": "doc1",
"text": "RAG(Retrieval-Augmented Generation)是一种将检索与生成结合的技术,"
"它首先从知识库中检索相关文档,再利用生成模型根据检索结果生成回答。"
},
{
"id": "doc2",
"text": "关键词搜索策略包括分词、词形还原、同义词扩展、TF-IDF 排序等步骤,"
"可以帮助在海量文本中快速定位相关段落。"
},
{
"id": "doc3",
"text": "TF-IDF 是一种经典的向量空间模型,用于衡量词条在文档中的重要性,"
"能够基于余弦相似度对文档进行排序。"
},
{
"id": "doc4",
"text": "在大规模知识库中,往往需要分布式索引与并行检索,"
"如 Elasticsearch、Solr 等引擎可以提供更高吞吐与实时性。"
},
{
"id": "doc5",
"text": "现代 RAG 系统会结合向量检索与关键词检索,"
"先用关键词做粗排,再用向量做精排,以获得更准确的匹配结果。"
},
]
2. 构建 TF-IDF 检索器
# -*- coding: utf-8 -*-
# tfidf_search.py
import jieba
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
# 从 knowledge_base 导入样例文档
from knowledge_base import documents
class TFIDFSearcher:
def __init__(self, docs):
"""
docs: 包含 [{"id": str, "text": str}, ...] 结构的列表
"""
self.ids = [doc["id"] for doc in docs]
self.raw_texts = [doc["text"] for doc in docs]
# 1) 分词:使用 jieba 对中文分词
self.tokenized_texts = [" ".join(jieba.lcut(text)) for text in self.raw_texts]
# 2) 构造 TfidfVectorizer:默认停用英文停用词,可自行传入中文停用词列表
self.vectorizer = TfidfVectorizer(lowercase=False) # 文本已分词,不要再 lower
self.doc_term_matrix = self.vectorizer.fit_transform(self.tokenized_texts)
# doc_term_matrix: (num_docs, vocab_size) 稀疏矩阵
def search(self, query, top_k=3):
"""
query: 用户输入的中文查询字符串
top_k: 返回最相关的前 k 个文档 id 和相似度分数
"""
# 1) 分词
query_tokens = " ".join(jieba.lcut(query))
# 2) 计算 query 的 TF-IDF 向量
q_vec = self.vectorizer.transform([query_tokens]) # (1, vocab_size)
# 3) 计算余弦相似度:cos(q_vec, doc_term_matrix)
# 余弦相似度 = (q ⋅ d) / (||q|| * ||d||)
# 由于 sklearn 中的 TF-IDF 矩阵已做过 L2 归一化,故可直接用点积近似余弦相似度
scores = (q_vec * self.doc_term_matrix.T).toarray().flatten() # (num_docs,)
# 4) 排序并选 top_k
top_k_idx = np.argsort(scores)[::-1][:top_k]
results = [(self.ids[i], float(scores[i])) for i in top_k_idx]
return results
# 测试
if __name__ == "__main__":
searcher = TFIDFSearcher(documents)
queries = [
"什么是 RAG?",
"如何进行关键词检索?",
"TF-IDF 原理是什么?",
"向量检索与关键词检索结合怎么做?"
]
for q in queries:
print(f"\nQuery: {q}")
for doc_id, score in searcher.search(q, top_k=2):
print(f" {doc_id} (score={score:.4f})")
说明:
jieba.lcut
用于中文分词,并用空格连接成“词词词 词词词”格式;TfidfVectorizer(lowercase=False)
指定不再做小写化,因为中文文本无需;doc_term_matrix
默认对每行做了 L2 归一化,因而用向量点积即可近似余弦相似度。
运行后可见类似输出:
Query: 什么是 RAG?
doc1 (score=0.5342)
doc5 (score=0.0000)
Query: 如何进行关键词检索?
doc2 (score=0.4975)
doc5 (score=0.1843)
Query: TF-IDF 原理是什么?
doc3 (score=0.6789)
doc2 (score=0.0456)
Query: 向量检索与关键词检索结合怎么做?
doc5 (score=0.6231)
doc2 (score=0.0012)
3. 集成检索结果到生成模型
下面示例演示如何把 TF-IDF 检索到的前 $k$ 个文档内容拼接,作为对话上下文输入到一个简单的生成模型(此处以 Hugging Face 的 t5-small
为例)。
# -*- coding: utf-8 -*-
# rag_inference.py
import torch
from transformers import T5Tokenizer, T5ForConditionalGeneration
from tfidf_search import TFIDFSearcher
from knowledge_base import documents
# 1. 初始化检索器
searcher = TFIDFSearcher(documents)
# 2. 初始化 T5 生成模型
model_name = "t5-small"
tokenizer = T5Tokenizer.from_pretrained(model_name)
model = T5ForConditionalGeneration.from_pretrained(model_name).to("cuda" if torch.cuda.is_available() else "cpu")
def rag_generate(query, top_k=3, max_length=64):
"""
1) 用 TF-IDF 搜索 top_k 个相关文档
2) 将查询与这些文档内容拼接成 RAG Context
3) 调用 T5 生成回答
"""
# 检索
results = searcher.search(query, top_k=top_k)
# 拼接 top_k 文本
retrieved_texts = []
for doc_id, score in results:
# 在 documents 列表中找到对应文本
doc_text = next(doc["text"] for doc in documents if doc["id"] == doc_id)
retrieved_texts.append(f"[{doc_id}]\n{doc_text}")
# 组合成一个大的上下文
context = "\n\n".join(retrieved_texts)
# 构造 RAG 输入:可采用 “query || context” 模式
rag_input = f"question: {query} context: {context}"
# Tokenize
inputs = tokenizer(rag_input, return_tensors="pt", truncation=True, max_length=512)
input_ids = inputs["input_ids"].to(model.device)
attention_mask = inputs["attention_mask"].to(model.device)
# 生成
outputs = model.generate(
input_ids=input_ids,
attention_mask=attention_mask,
max_length=max_length,
num_beams=4,
early_stopping=True
)
answer = tokenizer.decode(outputs[0], skip_special_tokens=True)
return answer
if __name__ == "__main__":
test_queries = [
"RAG 是什么?",
"如何评价 TF-IDF 检索效果?",
"关键词与向量检索如何结合?"
]
for q in test_queries:
print(f"\nQuery: {q}")
ans = rag_generate(q, top_k=2)
print(f"Answer: {ans}\n")
本示例中,RAG Context 的格式:
context = "[doc1]\n<doc1_text>\n\n[docX]\n<docX_text>\n\n…" rag_input = "question: <query> context: <context>"
你也可以自行设计更复杂的 prompt 模板,使生成更具针对性,例如:
“基于以下文档片段,请回答:<query>\n\n文档片段:<context>”
num_beams=4
表示使用 beam search,early_stopping=True
在生成到 EOS 时提前结束。
图解:检索与生成结合流程
为便于理解,下面以示意图的形式(文字描述)展示 RAG 中“关键词检索 + 生成” 的整体流程:
┌───────────────────────────────┐
│ 用户查询(Query) │
│ “什么是关键词搜索与 RAG?” │
└───────────────┬───────────────┘
│
┌──────────────▼──────────────┐
│ 查询预处理(分词、词形还原) │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ 计算 Query 的 TF-IDF 向量 │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ 知识库中所有段落已构建好 TF-IDF │
│ 向量 & 倒排索引 │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ 计算余弦相似度,并排序选 Top-k │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ 返回 Top-k 段落 R = {r₁, …, rₖ} │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ 构造 RAG Prompt = “question: │
│ <query> context: <r₁ || … || rₖ>” │
└──────────────┬──────────────┘
│
┌──────────────▼──────────────┐
│ 生成模型(T5/GPT 等) │
│ 基于 Prompt 生成最终回答 A │
└──────────────────────────────┘
- 知识库预处理阶段:一步完成 TF-IDF 训练并缓存。
- 检索阶段:针对每个用户查询实时计算相似度,选前 $k$。
- 生成阶段:将检索结果融入 prompt,调用生成模型。
调优与实践建议
停用词与分词质量
- 停用词列表过于宽泛会丢失有价值的关键词;列表过于狭隘会导致噪声命中。建议结合领域语料,调优停用词表。
- 中文分词工具(如 Jieba)易出现切分偏差,可考虑基于领域定制词典,或使用更先进的分词器(如 THULAC、HanLP)。
TF-IDF 模型参数
TfidfVectorizer
中的参数如:ngram_range=(1,2)
:考虑一元与二元词组;min_df
、max_df
:过滤过于罕见或过于高频的词;
- 这些参数影响词典大小与稀疏度、检索效果与效率。
同义词与近义词扩展
- 自定义同义词词典或引入中文 WordNet,可以在 Query 时自动为若干关键词扩展近义词,增加检索覆盖。
- 小心“过度扩展”导致大量无关文档混入。
混合检索(Hybrid Retrieval)
- 在大规模知识库中,可以先用关键词检索(TF-IDF)得到前 $N$ 候选,再对这 $N$ 个候选用向量模型(如 Sentence-BERT)做重新排序。
- 这样既保证初步过滤快速又能提升语义匹配度。
检索粒度
- 将文档拆分为段落(200–300 字)比整篇文档效果更好;过细的拆分(如 50 字)会丢失上下文;过粗(整篇文章)会带入大量无关信息。
- 常见做法:把文章按段落或“句子聚合”拆分,保持每个段落包含完整意思。
并行与缓存
- 在高并发场景下,可将 TF-IDF 向量与倒排索引持久化到磁盘或分布式缓存(如 Redis、Elasticsearch)。
- 对常见查询结果做二级缓存,避免重复计算。
评估与反馈
- 定期对检索结果做人工或自动化评估,使用 Precision\@k、Recall\@k 等指标持续监控检索质量。
- 根据实际反馈调整分词、停用词、同义词词典及 TF-IDF 参数。
总结
- RAG 将检索与生成结合,为生成模型提供了事实依据,显著提升答案准确性与可解释性。
- 在检索环节,关键词搜索(基于倒排索引 + TF-IDF)以其低延迟、可解释、易在线更新的优势,成为大规模系统中常用的第一道过滤手段。
- 本文系统介绍了 分词、词形还原、同义词扩展、布尔运算、TF-IDF 排序、基于词嵌入的近义匹配 等常见策略,并通过 Python 代码示例从零实现了一个简易的 TF-IDF 检索器。
- 最后展示了如何将检索结果拼接到 prompt 中,调用 T5 模型完成生成,实现一个最基础的 RAG 流程。
希望通过本文,你能快速掌握如何构建一个高效的关键词检索模块,并在 RAG 框架下结合生成模型,打造一个既能保证响应速度又具备可解释性的知识问答系统。
评论已关闭