你的向量数据库是个垃圾场

我们花了数小时辩论 GPT-4 与 Claude 3。我们为完美的温度设置而苦恼。我们将提示调整到完美。

然后我们将一个 5000 页的原始 PDF 扔进分块器,将其切成 512 字符的小块,并想知道为什么 AI 会产生幻觉。

AI 的脏秘密是,80% 的性能提升来自于处理数据清理的 0% 代码。

您的向量数据库不是图书馆;它目前是一个垃圾场,充满了 HTML 表格、页面标题(“第 1 页,共 50 页”)、导航栏和版权声明。每次您搜索答案时,模型都必须穿过这些噪音来找到信号。

停止优化模型。开始清理数据。

1、核心知识:信噪比

在信息论中,信道的容量受噪音限制。

其中:

无

  • C 是容量(有用信息)。
  • S 是信号(实际文本)。
  • N 是噪音(页眉、页脚、HTML 伪影)。
    如果您取一个干净的句子并用"机密——请勿分发"和"第 45 页"包围它,向量嵌入会偏移。块的几何中心会从它所代表的概念移开,向格式噪音的方向移动。

当用户搜索"如何重置密码?"时,您不希望最接近"第 45 页:机密请勿分发"的向量。但这正是天真分块创建的内容。

2、流程:"脏"与"净"管道

大多数教程向您展示"脏"路径。这就是生产 RAG 失败的原因。

"脏"路径(朴素 RAG)

无

"净"路径(生产级 RAG)

无

注意差异。我们不只是切片数据;我们是在重构它。

3、代码:不要切片,要解析

RAG 中最大的罪过是在原始 PDF 文本上使用 RecursiveCharacterTextSplitter。您不可避免地会将句子切成两半。

问题(朴素切片):

from langchain.text_splitter import RecursiveCharacterTextSplitter

text = """
重要通知:本文件是机密的。
密码重置程序需要管理员访问权限。
第 1 页,共 5 页
"""

# 这盲目地按字符数拆分
splitter = RecursiveCharacterTextSplitter(chunk_size=50, chunk_overlap=0)
docs = splitter.split_text(text)

# 结果:
# 块 1:"重要通知:本文件是 conf"
# 块 2:"idential. 密码重置程序 r"
# 块 3:"equires admin access. 第 1 页,共 5 页"

您刚刚创建了三个毫无意义的块。“机密"的向量并不意味着"密码重置”。

解决方案(启发式清理):

在嵌入之前,您必须运行清理管道。

import re

def clean_text(text):
    # 1. 移除页眉/页脚(启发式)
    lines = text.split('\n')
    cleaned_lines = []
    for line in lines:
        # 删除常见的页眉/页脚的短行(根据您的文档自定义)
        if len(line) < 5 and "Page" in line:
            continue
        # 如果"机密"警告杂乱视野,则移除它们
        if "CONFIDENTIAL" in line or "Copyright" in line:
            continue
        cleaned_lines.append(line)

    text = "\n".join(cleaned_lines)

    # 2. 移除电子邮件地址/电话号码(个人身份信息)
    text = re.sub(r'\S+@\S+', '', text)

    # 3. 折叠空白
    text = re.sub(r'\s+', ' ', text).strip()

    return text

# 现在我们拆分清洁后的文本
# 理想情况下,使用语义分块器或至少使用句子分词器
import nltk
sentences = nltk.sent_tokenize(clean_text(raw_pdf))

# 现在我们将句子分组为块,确保不将句子切成两半
chunk_size = 3 # 每块 3 个句子
chunks = []
for i in range(0, len(sentences), chunk_size):
    chunk = " ".join(sentences[i:i+chunk_size])
    chunks.append(chunk)

# 现在的块是:
# "密码重置程序需要管理员访问权限。"
# (干净、有意义、语义单元)

4、高级秘密:元数据预过滤

这是您可以实现的最有效的优化。

向量搜索是 O(1),具有较大的常数因子(它很快,但不是免费的)。SQL 过滤是瞬时的。

在您要求向量数据库找到"最近邻居"之前,使用元数据过滤器移除 90% 的垃圾。

错误的方式:

“在整个 100 万个块的数据库中搜索’2023 年财务报告’。” 向量数据库搜索 100 万个向量。-> 慢。

正确的方式:

"使用 SQL 过滤 year == 2023 AND category == 'finance' (返回 500 个块)。" 向量数据库搜索 500 个向量。-> 快 1000 倍且更准确。

代码(SQL + 向量):

# 我们假设存储了元数据:{'year': 2022, 'topic': 'marketing'}

def hybrid_search(query, year, topic):
    # 1. 用 SQL 预过滤(廉价过滤器)
    candidates = db.query("SELECT id, content FROM docs WHERE year = ? AND topic = ?", [year, topic])

    if not candidates:
        return []

    # 2. 仅在候选上进行向量搜索(昂贵的精确度)
    query_embedding = embed(query)

    best_match = None
    highest_score = -1

    for doc in candidates:
        doc_embedding = db.get_embedding(doc['id'])
        score = cosine_similarity(query_embedding, doc_embedding)
        if score > highest_score:
            highest_score = score
            best_match = doc

    return best_match

这会自动移除"第 1 页,共 50 页"的噪音,因为如果那些页面没有正确的元数据标签,它们会在数学开始之前被排除。

5、结束语

您无法在泥浆基础上建造摩天大楼。

下次您想要调整 temperaturetop_p 时,停下来。去查看您的数据摄取管道。

  • 您是否在剥离 HTML?
  • 您是否在移除页眉?
  • 您是否按日期预过滤?
  • 您是否按句子而不是字符拆分?
    如果您输入的数据是垃圾,世界上最好的 LLM 也是无用的。

做清洁工作。其余的会自己解决。


原文链接:你的向量数据库是个垃圾场 - 汇智网

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐