2.2 检索算法优化 — 提升RAG系统的检索效率 本节导读:深入掌握向量检索算法的原理和优化策略,从基础的暴力搜索到高效的索引结构,全面提升RAG系统的检索性能。 学习目标 理解向量检索的基本原理和算法类型 掌握主流向量索引结构的优缺点 学习检索算法的性能优化方法 实现高效的检索系统架构 核心概念 向量检索的基本原理 向量检索是在高维空间中快速找到与查询向量最相似的向量的过程。核心概念包括: 相似度度量:余弦相似度、欧氏距离、内积等 索引结构:树结构、哈希表、图结构等 搜索策略:暴力搜索、近似搜索、分层搜索 检索算法的分类 基于不同的技术路线,检索算法可以分为: 精确检索:保证找到最相似的结果,但性能较低 近似检索:在保证质量的前提下提高搜索速度 分层检索:通过层次结构减少搜索范围
本节导读:深入掌握向量检索算法的原理和优化策略,从基础的暴力搜索到高效的索引结构,全面提升RAG系统的检索性能。
向量检索是在高维空间中快速找到与查询向量最相似的向量的过程。核心概念包括:
基于不同的技术路线,检索算法可以分为:
# 向量检索库 pip install faiss-cpu pip install scikit-learn pip install matplotlib seaborn
import numpy as np import faiss import time from typing import List, Dict, Any, Optional from sklearn.metrics.pairwise import cosine_similarity import logging # 设置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 配置参数 class Config: DIMENSION = 768 # 向量维度 TOP_K = 10 # 检索数量 INDEX_TYPE = "HNSW" # 索引类型 config = Config()
class BruteForceRetrieval: """暴力检索实现""" def __init__(self, dimension: int = 768): self.dimension = dimension self.embeddings = None self.documents = [] def build_index(self, embeddings: np.ndarray, documents: List[Dict[str, Any]]): """构建索引""" self.embeddings = embeddings self.documents = documents logger.info(f"暴力检索索引构建完成,包含 {len(documents)} 个文档") def search(self, query_embedding: np.ndarray, top_k: int = 10) -> List[Dict[str, Any]]: """暴力搜索""" if self.embeddings is None: raise ValueError("索引未构建") # 计算余弦相似度 query_norm = np.linalg.norm(query_embedding) query_normalized = query_embedding / query_norm embeddings_norm = np.linalg.norm(self.embeddings, axis=1) embeddings_normalized = self.embeddings / embeddings_norm.reshape(-1, 1) similarities = np.dot(embeddings_normalized, query_normalized) # 获取top-k结果 top_indices = np.argsort(-similarities)[:top_k] results = [] for i, idx in enumerate(top_indices): if idx < len(self.documents): result = { 'document': self.documents[idx], 'score': float(similarities[idx]), 'rank': i + 1, 'distance': float(1 - similarities[idx]) } results.append(result) return results # 使用示例 dimension = 768 n_docs = 1000 # 生成测试数据 np.random.seed(42) embeddings = np.random.randn(n_docs, dimension).astype('float32') documents = [{'id': i, 'content': f'文档{i}的内容'} for i in range(n_docs)] # 构建暴力检索 brute_force = BruteForceRetrieval(dimension) brute_force.build_index(embeddings, documents) # 查询测试 query_embedding = np.random.randn(dimension).astype('float32') results = brute_force.search(query_embedding, top_k=5) print("暴力检索结果:") for result in results[:3]: print(f" 文档{result['document']['id']}: 分数={result['score']:.4f}")
class FaissRetrievalSystem: """FAISS检索系统""" def __init__(self, dimension: int = 768, index_type: str = "HNSW"): self.dimension = dimension self.index_type = index_type self.index = None self.documents = [] self.embeddings = None self.index_stats = { 'total_documents': 0, 'index_build_time': 0, 'avg_search_time': 0, 'total_searches': 0 } def _create_index(self) -> faiss.Index: """创建索引""" if self.index_type == "Flat": return faiss.IndexFlatIP(self.dimension) elif self.index_type == "HNSW": return faiss.IndexHNSWFlat(self.dimension, 32, faiss.METRIC_INNER_PRODUCT) else: raise ValueError(f"不支持的索引类型: {self.index_type}") def build_index(self, embeddings: np.ndarray, documents: List[Dict[str, Any]]): """构建索引""" start_time = time.time() self.embeddings = embeddings.astype('float32') self.documents = documents # 创建索引 self.index = self._create_index() # 添加向量 self.index.add(self.embeddings) # 更新统计 build_time = time.time() - start_time self.index_stats['total_documents'] = len(documents) self.index_stats['index_build_time'] = build_time logger.info(f"{self.index_type}索引构建完成,耗时 {build_time:.2f}秒," f"包含 {len(documents)} 个文档") def search(self, query_embedding: np.ndarray, top_k: int = 10) -> List[Dict[str, Any]]: """搜索""" start_time = time.time() if self.index is None: raise ValueError("索引未构建") query_embedding = query_embedding.astype('float32').reshape(1, -1) # 搜索 distances, indices = self.index.search(query_embedding, top_k) # 处理结果 results = [] for i, (dist, idx) in enumerate(zip(distances[0], indices[0])): if idx < len(self.documents): result = { 'document': self.documents[idx], 'score': float(1 - dist), 'rank': i + 1, 'distance': float(dist) } results.append(result) # 更新统计 search_time = time.time() - start_time self._update_search_stats(search_time) return results def _update_search_stats(self, search_time: float): """更新搜索统计""" self.index_stats['total_searches'] += 1 current_avg = self.index_stats['avg_search_time'] total_searches = self.index_stats['total_searches'] self.index_stats['avg_search_time'] = ( (current_avg * (total_searches - 1) + search_time) / total_searches ) # 使用示例 faiss_system = FaissRetrievalSystem(dimension=dimension, index_type="HNSW") faiss_system.build_index(embeddings, documents) # 测试搜索 query_embedding = np.random.randn(dimension).astype('float32') results = faiss_system.search(query_embedding, top_k=5) print("FAISS检索结果:") for result in results[:3]: print(f" 文档{result['document']['id']}: 分数={result['score']:.4f}") # 获取统计信息 stats = faiss_system.get_stats() print(f"平均搜索时间: {stats['avg_search_time']:.4f}秒")
class MultiStageRetrieval: """多级检索系统""" def __init__(self, dimension: int = 768): self.dimension = dimension self.stage1_index = None # 粗粒度索引 self.stage2_index = None # 精细索引 self.documents = [] self.stage1_size = 100 # 第一级返回数量 def build_index(self, embeddings: np.ndarray, documents: List[Dict[str, Any]]): """构建多级索引""" self.embeddings = embeddings self.documents = documents # 第一级:使用HNSW进行粗检索 self.stage1_index = faiss.IndexHNSWFlat(self.dimension, 16) self.stage1_index.add(embeddings) # 第二级:使用Flat进行精检索 self.stage2_index = faiss.IndexFlatIP(self.dimension) self.stage2_index.add(embeddings) logger.info(f"多级检索索引构建完成,包含 {len(documents)} 个文档") def search(self, query_embedding: np.ndarray, top_k: int = 10) -> List[Dict[str, Any]]: """多级搜索""" # 第一级:粗检索 stage1_distances, stage1_indices = self.stage1_index.search( query_embedding.reshape(1, -1), self.stage1_size ) # 第二级:在候选集中精检索 candidate_indices = stage1_indices[0] candidate_embeddings = self.embeddings[candidate_indices] stage2_distances, stage2_indices = self.stage2_index.search( query_embedding.reshape(1, -1), top_k ) # 处理结果 results = [] for i, (dist, idx) in enumerate(zip(stage2_distances[0], stage2_indices[0])): if idx < len(self.documents): result = { 'document': self.documents[idx], 'score': float(1 - dist), 'rank': i + 1, 'distance': float(dist) } results.append(result) return results # 使用示例 multi_stage = MultiStageRetrieval(dimension=dimension) multi_stage.build_index(embeddings, documents) # 测试多级检索 query_embedding = np.random.randn(dimension).astype('float32') results = multi_stage.search(query_embedding, top_k=5) print("多级检索结果:") for result in results[:3]: print(f" 文档{result['document']['id']}: 分数={result['score']:.4f}")
import threading from concurrent.futures import ThreadPoolExecutor from typing import List, Dict, Any, Optional import time class OptimizedRetrievalSystem: """优化的检索系统""" def __init__(self, dimension: int = 768, max_workers: int = 4): self.dimension = dimension self.max_workers = max_workers self.index = None self.documents = [] self.embeddings = None self.executor = ThreadPoolExecutor(max_workers=max_workers) # 缓存 self.query_cache = {} self.cache_lock = threading.Lock() # 统计 self.stats = { 'total_queries': 0, 'cache_hits': 0, 'avg_response_time': 0.0 } def build_index(self, embeddings: np.ndarray, documents: List[Dict[str, Any]]): """构建优化的索引""" self.embeddings = embeddings.astype('float32') self.documents = documents # 使用HNSW索引 self.index = faiss.IndexHNSWFlat(self.dimension, 32) self.index.add(self.embeddings) logger.info(f"优化检索系统构建完成,包含 {len(documents)} 个文档") def search(self, query_embedding: np.ndarray, top_k: int = 10, use_cache: bool = True) -> List[Dict[str, Any]]: """带缓存的搜索""" start_time = time.time() # 检查缓存 cache_key = hash(query_embedding.tobytes()) if use_cache and cache_key in self.query_cache: self.stats['cache_hits'] += 1 return self.query_cache[cache_key] # 执行搜索 query_embedding = query_embedding.astype('float32').reshape(1, -1) distances, indices = self.index.search(query_embedding, top_k) # 处理结果 results = [] for i, (dist, idx) in enumerate(zip(distances[0], indices[0])): if idx < len(self.documents): result = { 'document': self.documents[idx], 'score': float(1 - dist), 'rank': i + 1, 'distance': float(dist) } results.append(result) # 缓存结果 if use_cache: with self.cache_lock: self.query_cache[cache_key] = results # 更新统计 response_time = time.time() - start_time self._update_stats(response_time) return results def _update_stats(self, response_time: float): """更新统计信息""" self.stats['total_queries'] += 1 current_avg = self.stats['avg_response_time'] total_queries = self.stats['total_queries'] self.stats['avg_response_time'] = ( (current_avg * (total_queries - 1) + response_time) / total_queries ) # 使用示例 optimized_system = OptimizedRetrievalSystem(dimension=dimension, max_workers=2) optimized_system.build_index(embeddings, documents) # 测试带缓存的搜索 query_embedding = np.random.randn(dimension).astype('float32') # 第一次搜索(会计算) start_time = time.time() results1 = optimized_system.search(query_embedding, top_k=5) first_search_time = time.time() - start_time # 第二次搜索(使用缓存) start_time = time.time() results2 = optimized_system.search(query_embedding, top_k=5) second_search_time = time.time() - start_time print(f"第一次搜索时间: {first_search_time:.4f}秒") print(f"第二次搜索时间: {second_search_time:.4f}秒") print(f"速度提升: {first_search_time/second_search_time:.2f}倍") # 获取系统统计 stats = optimized_system.get_stats() print("系统统计:", stats)
A:选择索引类型时需要考虑以下因素:
A:优化搜索性能的方法包括:
A:处理动态数据更新的策略:
A:处理高并发查询的方法:
通过本节的学习,我们掌握了向量检索算法的优化技术:
高效的检索系统是RAG性能的关键。下一节我们将探讨提示词优化技术,进一步提升RAG系统的生成质量。
关键词:RAG高级优化, 向量检索, 检索算法, FAISS索引, 性能优化, 教程, 实战, 最佳实践
难度:进阶
预计阅读:20 分钟