向量检索返回主题相似但不含答案的干扰文本时如何定位与观察 向量检索返回主题相似但不含答案的干扰文本时如何定位与观察【免费下载链接】llm-cookbook面向开发者的 LLM 入门教程吴恩达大模型系列课程中文版项目地址: https://gitcode.com/GitHub_Trending/ll/llm-cookbook在 llm-cookbook 项目的《基于 Chroma 的高级检索》课程里搭建 RAG检索增强生成系统时会遇到这样一种故障现象向量检索返回的文本与查询主题相似但不包含答案。课程把这类文本称为“干扰项”——它们会被送进大语言模型LLM并分散其注意力导致输出次优的结果而且这种不良行为对用户和开发者来说都很难诊断和调试。本文按照课程 第三章 检索陷阱 的 Notebook给出一条可复现的观察路径先复现检索、在文本层面确认干扰项再用 UMAP 把高维嵌入投影到二维平面通过散点图看到查询与每条检索结果在嵌入空间中的实际位置从而判断问题来自查询本身还是来自检索机制。先确认现象什么是“干扰项”文档把简单向量检索的失效分成两种干扰项简单的向量检索可能会返回和查询主题相似的文本但不包含答案此时这些检索结果反而会对 LLM 的回复形成干扰无关结果用一个和文档主题毫不相关的查询去检索嵌入模型仍然会返回检索结果LLM 可能被检索结果误导导致回复“牛头不对马嘴”。文档示例中这两种现象都真实出现检索“城镇新增就业人数多少”时第一条结果“城镇新增就业28.1万人”答到了具体数字后面几条结果却是干扰项检索与文档完全无关的“迈克尔·乔丹最近做了什么”时同样返回 5 条结果但没有一条提到乔丹。出现这类现象时就可以按下面的步骤定位与观察。准备环境与数据代码在课程目录content/选修-Advanced Retrieval for AI with Chroma/下运行其中 helper_utils.py 提供load_chroma、word_wrap、project_embeddings等函数data/目录内置两份 PDF 数据中文 2024年北京市政府工作报告.pdf 和英文 microsoft_annual_report_2022.pdf。代码里的./data/...是相对路径以课程目录为工作目录。Python 环境本章 Notebook 使用 Python 3.9内核名为chroma。OpenAI API key 的获取与.env配置方式见 docs/环境配置.md。安装降维库文档要求pip install umap-learn注意是安装umap-learn而不是umap。本章代码与 helper_utils.py 还会用到 chromadb、langchain、pypdf、tqdm、numpy、matplotlib、python-dotenv需在环境中备好。可选代理文档注明如果运行时报SSLErrorSOCKSHTTPConnectionPoolhosthuggingface.co错误需挂上代理并把 7890 改成自己的代理端口os.environ[HTTPS_PROXY] http://127.0.0.1:7890 os.environ[HTTP_PROXY] http://127.0.0.1:7890第一步建立集合并复现检索加载数据并初始化 Chroma 集合文档以中文文档为例import os import chromadb.utils.embedding_functions as embedding_functions from helper_utils import load_chroma, word_wrap from dotenv import load_dotenv, find_dotenv _ load_dotenv(find_dotenv()) openai_api_key os.environ[OPENAI_API_KEY] embedding_function embedding_functions.OpenAIEmbeddingFunction( api_keyopenai_api_key, model_nametext-embedding-ada-002 ) # 中文文档langcode 为 zh chroma_collection load_chroma(filename./data/2024年北京市政府工作报告.pdf, collection_namebeijing_annual_report_2024, embedding_functionembedding_function, langcodezh) chroma_collection.count() # 文档示例1028load_chroma内部依次完成“读取 PDF → 按语言分句分块 → 创建 Chroma 集合并写入分块”具体实现可查 helper_utils.py。count()返回集合中的分块数文档示例为 1028得到正整数即说明数据加载成功。如果要改用英文文档文档给出的对应调用是原 Notebook 中为注释掉的备选分支chroma_collection load_chroma(filename./data/microsoft_annual_report_2022.pdf, collection_namemicrosoft_annual_report_2022, embedding_functionembedding_function, langcodeen)然后执行查询取出文本与嵌入嵌入供后一步可视化使用query 城镇新增就业人数多少 results chroma_collection.query(query_textsquery, n_results5, include[documents, embeddings]) for document in results[documents][0]: print(word_wrap(document)) print()n_results5表示返回 5 条最相关的结果。文档示例中文文档的输出是城镇新增就业28.1万人 实现城镇新增就业不 少于26万人 城镇调查失业 率4.4% 城镇调 查失业率控制在5%以内 持续增加城乡居民收入判断方式逐条对照查询意图——哪条真正包含答案、哪条只是主题相似。上述示例中第一条答到了新增就业的具体数字其余属于干扰项。文档还给了两个对照查询“地区生产总值是多少”5 条中只有 1 条是想要的答案其余不是和“一般公共预算收入是多少”前 2 条与问题较为相关后 3 条相关性逐渐减弱。第二步用 UMAP 把嵌入投影到二维平面先对全数据集做 UMAP 降维import umap import numpy as np from tqdm import tqdm # 获取整个数据集的嵌入 embeddings chroma_collection.get(include[embeddings])[embeddings] # 定义 UMAP 模型 umap_transform umap.UMAP(random_state0, transform_seed0).fit(embeddings) len(embeddings), len(embeddings[0]) # 文档示例(1028, 1536)文档说明embeddings矩阵维度高达 1536文档示例是典型的高维空间非常复杂、难以观察因此用 UMAP 投影到二维平面。random_state0、transform_seed0是文档为保证可复现而设定的参数。再写一个投影函数文档在 Notebook 中内联定义helper_utils.py 也提供了同名函数二选一即可def project_embeddings(embeddings, umap_transform): 用 UMAP 将高维嵌入向量投影到二维向量空间。 umap_embeddings np.empty((len(embeddings), 2)) # 为保证结果可复现逐个进行 umap 转换 for i, embedding in enumerate(tqdm(embeddings)): umap_embeddings[i] umap_transform.transform([embedding]) return umap_embeddings projected_dataset_embeddings project_embeddings(embeddings, umap_transform) print(fBefore UMAP: {np.array(embeddings).shape}) print(fAfter UMAP: {projected_dataset_embeddings.shape})文档示例输出Before UMAP: (1028, 1536) After UMAP: (1028, 2)注意两点耗时预期均为文档示例全量 1028 个点的投影耗时约 14 分钟进度条显示 1.23it/s实际取决于机器UMAPfit阶段可能出现n_jobs value -1 overridden to 1 by setting random_state的 UserWarning不影响后续步骤。第三步绘制散点图观察查询与检索结果的位置import matplotlib.pyplot as plt def plot(query, projected_dataset_embeddings, projected_query_embedding, projected_retrieved_embeddings): plt.figure() plt.rcParams[font.sans-serif] [Microsoft YaHei] # 设置全局字体为微软雅黑显示中文 plt.scatter(projected_dataset_embeddings[:, 0], projected_dataset_embeddings[:, 1], s10, colorgray) # 整个数据集的散点图 plt.scatter(projected_query_embedding[:, 0], projected_query_embedding[:, 1], s150, markerX, colorr) # 查询的散点图 plt.scatter(projected_retrieved_embeddings[:, 0], projected_retrieved_embeddings[:, 1], s80, facecolorsnone, edgecolorsg) # 检索的散点图 plt.gca().set_aspect(equal, datalim) plt.title(f{query}) plt.axis(off) plt.show() query_embedding embedding_function([query])[0] # 查询的嵌入 retrieved_embeddings results[embeddings][0] # 检索的嵌入 # UMAP 降维投影到二维平面 projected_query_embedding project_embeddings([query_embedding], umap_transform) projected_retrieved_embeddings project_embeddings(retrieved_embeddings, umap_transform) plot(query, projected_dataset_embeddings, projected_query_embedding, projected_retrieved_embeddings)文档对这张图的读法是红色的“×”是查询query绿色圆圈圈住的点是检索结果灰色点是整个数据集。文档指出“可以看到一些检索结果都围绕在查询四周但也有一些点离得较远。这些离得远的点就是问题所在它没有答到点子上。”——这些远离查询点的绿圈点就是第一步在文本里定位到的干扰项在嵌入空间中的位置。第四步判断成因——查询落在点云内还是点云外文档把检索结果分散的几何原因解释为所有数据是高维空间中的一个点云。落在点云内部的查询最近邻在点云内部密集而紧密地靠在一起检索结果彼此接近。文档示例“地区生产总值是多少”的散点图中部分结果围绕查询、部分离得较远文档给出的原因是“我们问的问题很泛没有提供足够的背景信息嵌入模型很难理解我们的意图难以去完成我们内心期待的特定任务”——即干扰项来自查询表述过泛。落在点云外部的查询最近邻来自点云许多不同部分因此更加分散。文档示例“迈克尔·乔丹最近做了什么”与数据集毫无关联属于点云外部的查询其检索结果“分散在点云四周”。一个边界说明文档明确提示把高维空间压缩到二维空间的可视化结果“并不总是完美的”散点图是观察工具用于判断“结果是否围绕查询聚集”而不是精确度量。下一步文档建议“换一个查询并可视化一下检索结果来更好地理解简单向量检索的运行机制和缺点。”如果散点图确认了干扰项问题、需要改善检索质量课程的后续章节给出了对应技术下一章是 查询扩展Query Expansion。【免费下载链接】llm-cookbook面向开发者的 LLM 入门教程吴恩达大模型系列课程中文版项目地址: https://gitcode.com/GitHub_Trending/ll/llm-cookbook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考