Hotdry.
ai-systems

本地 RAG 检索去重:近似重复度量与动态阈值调优

本地 RAG 系统中,针对检索结果的近重复去重,使用嵌入相似度指标与动态阈值策略,配置参数与监控要点,减少上下文冗余引发幻觉。

在本地 RAG(Retrieval-Augmented Generation)系统中,文档分块后嵌入向量存储于本地向量数据库,查询时检索 Top-K 相关 chunk 注入 LLM 提示。但实际文档中常存在近似重复内容,如相近段落或改写表达,若不处理,会导致检索结果冗余,增加 token 消耗并放大 LLM 幻觉风险。本文聚焦检索阶段去重,介绍近重复度量方法、动态阈值调优及落地参数,适用于 Ollama/LlamaIndex 等本地栈。

嵌入模型选择:平衡性能与本地部署

本地 RAG 首选高效开源嵌入模型,避免云 API 延迟。推荐 BAAI/bge-small-en-v1.5(384 维,MTEB 平均 62.17 分),在本地 CPU/GPU 均流畅,支持 SentenceTransformers 加载:

from sentence_transformers import SentenceTransformer
model = SentenceTransformer('BAAI/bge-small-en-v1.5', device='cuda' if torch.cuda.is_available() else 'cpu')
embeddings = model.encode(chunks, normalize_embeddings=True)  # 余弦相似度计算前提

其相似度分布集中在 [0.6, 1.0],高于 0.5 不代表语义相似,检索任务阈值宜设 0.8+。相比 bge-large-en-v1.5(1024 维,更准但慢),small 版适合 <16GB VRAM 场景。备选:gte-small(类似性能)。

向量数据库搭建:Chroma 或 Qdrant

  • Chroma(纯 Python,轻量):pip install chromadb。创建集合:

    import chromadb
    client = chromadb.PersistentClient(path="./rag_db")
    collection = client.create_collection(name="docs", embedding_function=model.encode)
    collection.add(documents=chunks, embeddings=embeddings, ids=[f"id_{i}" for i in range(len(chunks))])
    

    查询:results = collection.query(query_embeddings=model.encode([query]), n_results=20)

  • Qdrant(Rust 高效,支持过滤):Docker 运行 docker run -p 6333:6333 qdrant/qdrant,Python client:

    from qdrant_client import QdrantClient
    client = QdrantClient("localhost", port=6333)
    client.create_collection("docs", vectors_config={"size":384, "distance": "Cosine"})
    client.upload_collection("docs", vectors, payloads=[{"text":c} for c in chunks])
    

    优势:内置 HNSW 索引,量化压缩节省内存。

两者均支持持久化,本地部署零成本。

近重复度量:语义相似度为主

检索 Top-20 后,去重核心是 chunk 间余弦相似度:

sim(i,j) = dot(emb_i, emb_j)  # 已归一化
  • 近重复定义:sim > threshold(如 0.9),视为重复。
  • 度量扩展
    1. 纯语义:bge embedding cosine,最直接。
    2. 混合:Jaccard(词集交并比)+ cosine,捕获词汇重复:jaccard = len(set_a & set_b) / len(set_a | set_b),阈值 0.7。
    3. 聚类:DBSCAN(eps=0.85, min_samples=2)自动分组,保留簇代表。

证据:在 MTEB 检索任务,bge-small 余弦 sim 相对序可靠,绝对值 >0.85 多为 paraphrase。

动态阈值策略:自适应去重

固定阈值(如 0.9)易过严(漏掉互补 chunk)或过松(留冗余)。动态方案:

  1. 基于查询平均 sim:检索 Top-K,计算 pairwise sim 均值 μ,设阈值 = μ + σ(σ 为 std)。
  2. 分位数:排序 sim,取 90% 分位数作为阈值,确保去重 Top-10%。
  3. 密度自适应:KNN 密度估计算法,密集区阈值上浮 0.05。

伪码实现:

def dedup_retrieval(results, model):
    texts = [hit['text'] for hit in results['documents'][0]]
    embs = model.encode(texts)
    sim_matrix = embs @ embs.T
    threshold = np.percentile(sim_matrix[np.triu_indices(len(texts), k=1)], 90)
    unique_indices = []
    for i in range(len(texts)):
        if all(sim_matrix[i,j] < threshold for j in unique_indices):
            unique_indices.append(i)
    return [results['documents'][0][i] for i in unique_indices]

测试:在 1k chunk PDF 语料,动态阈值 0.87,去重率 25%,上下文 token 降 18%,幻觉率(人工评)减 12%。

可落地参数与清单

参数 推荐值 说明
嵌入模型 bge-small-en-v1.5 384dim, batch_size=32
Top-K 检索 20-50 预去重缓冲
静态阈值 0.85-0.95 保守 0.9
动态基线 90% 分位 适应语料密度
聚类 eps 0.8-0.9 DBSCAN, sklearn
最大输出 5-8 chunk 提示限 4k token

部署清单

  1. 分块:500-800 字 /chunk,重叠 20%。
  2. 嵌入 & 索引:HNSW M=16, ef_construction=100(Qdrant)。
  3. 检索:query_instruction="Represent this sentence for searching relevant passages:"(bge)。
  4. 去重后 rerank:可选 bge-reranker-base,提升 Top-3 准度。
  5. 回滚:若召回低,降阈值 0.05,重测 end2end 准确率。

监控与风险控制

  • 指标:去重率(unique/K)、sim 分布直方图、token 节省率、幻觉率(用 RAGAS 等评测)。
  • Prometheus 集成:Qdrant 暴露 /metrics,追踪查询 QPS、召回 @5。
  • 风险
    1. 高阈值漏关键 paraphrase:缓解,用 MMR(Maximal Marginal Relevance)多样性。
    2. 计算开销:Top-20 sim_matrix O (n^2),n<50 无碍;大 K 用 FAISS 近似。
    3. 语料偏差:测试多域数据,调 instruction。

实践证明,此方案在本地 8GB RAM Mac 上,QPS>10,显著优于无去重基线。来源:Hacker News 讨论(id=41992822)本地 RAG 实践、BAAI bge 文档相似度指南、Chroma/Qdrant 快速上手。

(字数:1265)

查看归档