如何用嵌入适配器转换查询嵌入以产生更好的检索结果

发布时间:2026/9/13 18:39:18
如何用嵌入适配器转换查询嵌入以产生更好的检索结果 如何用嵌入适配器转换查询嵌入以产生更好的检索结果【免费下载链接】llm-cookbook面向开发者的 LLM 入门教程吴恩达大模型系列课程中文版项目地址: https://gitcode.com/GitHub_Trending/ll/llm-cookbook在基于嵌入的检索RAG系统中嵌入模型会把查询映射成向量再做近邻检索但检索回来的文本往往只与查询主题相近而不一定包含想要的答案。《基于 Chroma 的高级检索技术》llm-cookbook 中由 Chroma 联合创始人 Anton Troynikov 与 DeepLearning.ai 联合开发的课程第六章给出的对策是在嵌入模型之后、得到最终检索结果之前插入一个嵌入适配器Embedding Adaptors阶段——学习一个适配器矩阵用它转换查询嵌入使查询向量更靠近真正相关的文档。本文跟随 6. 嵌入适配器 Embedding Adaptors.ipynb 走通完整流程建立 Chroma 集合 → 生成查询并检索候选文档 → 用 LLM 标注查询-文档对的相关性 → 用 PyTorch 训练适配器矩阵 → 用最佳矩阵转换查询嵌入并通过投影散点图验证效果。Embedding Adapter 工作原理查询经嵌入模型生成嵌入后由嵌入适配器转换再用于检索文档按课程文档的说明这条链路是用户通过应用提交一个查询例如 Revenue Growth嵌入模型根据查询生成嵌入向量嵌入适配器对这些嵌入做进一步处理或转换系统基于嵌入向量检索相关文档例如 Annual Income...最后 LLM 使用这些信息生成答案。准备条件代码需要放在课程目录content/选修-Advanced Retrieval for AI with Chroma/下运行Jupyter kernel 的工作目录即该目录这样代码中的./data/...相对路径和helper_utils导入才能解析。该目录已包含helper_utils.py提供load_chroma、word_wrap、project_embeddings三个函数data/2024年北京市政府工作报告.pdf本章节的语料文件。依赖方面helper_utils.py的导入需要chromadb、langchain、pypdf、tqdm、numpy本章代码还直接用到torch、umap、matplotlib、openai、python-dotenv。其中 UMAP 的安装命令在课程第三章中给出需要注意包名pip install umap-learn3. 检索陷阱 原文提示注意是安装umap-learn而不是umap。python-dotenv和openai的安装命令在 docs/环境配置.md 中pip install -q python-dotenv pip install -q openai此外还需要配置 OpenAI API key在 OpenAI 官网创建 key 后按 环境配置文档 的说明以OPENAI_API_KEYsk-...的形式写入项目根目录下的.env文件代码通过load_dotenv读取。建立 Chroma 集合并投影数据集嵌入加载辅助函数和嵌入模型把 PDF 加载进 Chroma 集合。load_chroma定义于helper_utils.py会读取 PDF 文本、按语言分块、创建内存版chromadb.Client()集合并写入分块。注意中文文档要把langcode设为zh此时按中文标点做句子级分割、不再做 token 级切分from helper_utils import load_chroma, word_wrap, project_embeddings from chromadb.utils.embedding_functions import SentenceTransformerEmbeddingFunction import numpy as np import umap from tqdm import tqdm import torch embedding_function SentenceTransformerEmbeddingFunction() chroma_collection load_chroma(filename./data/2024年北京市政府工作报告.pdf, collection_namebeijing_annual_report_2024, embedding_functionembedding_function, langcodezh) # 注意中文文档将langcode改为zh chroma_collection.count()文档示例中count()返回1028即集合中的分块数量该数值为文档示例取决于语料与分块方式。随后取出集合中全部嵌入用 UMAP 拟合出投影变换并把数据集嵌入投影到二维备用project_embeddings在helper_utils.py中逐条调用umap_transform.transformembeddings chroma_collection.get(include[embeddings])[embeddings] umap_transform umap.UMAP(random_state0, transform_seed0).fit(embeddings) projected_dataset_embeddings project_embeddings(embeddings, umap_transform)配置 OpenAI 客户端import os import openai from openai import OpenAI from dotenv import load_dotenv, find_dotenv _ load_dotenv(find_dotenv()) openai.api_key os.environ[OPENAI_API_KEY] openai_client OpenAI()生成查询并检索候选文档用 LLM 生成一批分析年度报告时值得问的短问题。generate_queries的系统提示要求模型扮演政府年度报告研究助手输出 10 到 15 个短问题、不用复合问题、每行一个、使用中文def generate_queries(modelgpt-3.5-turbo): messages [ { role: system, content: You are a helpful and expert financial research assistant. You help users analyze government work reports to better understand the government. Suggest 10 to 15 short questions that are important to ask when analyzing an annual report. Do not output any compound questions (questions with multiple sentences or conjunctions). Output each question on a separate line divided by a newline. Use Chinese }, ] response openai_client.chat.completions.create( modelmodel, messagesmessages, ) content response.choices[0].message.content content content.split(\n) return content文档示例输出为 15 个中文问题节选政府在过去一年中主要实施了哪些经济政策政府在年度报告中提及的财政支出重点是什么 …共 15 个完整列表见文档示例对每个查询取前 10 个检索结果并要求同时返回相关文档与嵌入向量——这两份数据是后面构造训练集的核心results chroma_collection.query(query_textsgenerated_queries, n_results10, include[documents, embeddings]) retrieved_documents results[documents] retrieved_embeddings results[embeddings] query_embeddings embedding_function(generated_queries)用 LLM 标注相关性构造训练集evaluate_results让 LLM 判断给定陈述是否与给定查询相关只输出yes/no因此max_tokens1映射为相关记1、不相关记-1def evaluate_results(query, statement, modelgpt-3.5-turbo): messages [ { role: system, content: You are a helpful expert financial research assistant. You help users analyze financial statements to better understand companies. For the given query, evaluate whether the following satement is relevant. Output only yes or no. }, { role: user, content: fQuery: {query}, Statement: {statement} } ] response openai_client.chat.completions.create( modelmodel, messagesmessages, max_tokens1 ) content response.choices[0].message.content if content yes: return 1 return -1准备三个列表存放适配器训练数据然后遍历每个查询及其 10 个检索文档逐对调用evaluate_results打标签adapter_query_embeddings [] adapter_doc_embeddings [] adapter_labels [] import time from tqdm import tqdm for q, query in enumerate(tqdm(generated_queries)): for d, document in enumerate(retrieved_documents[q]): adapter_query_embeddings.append(query_embeddings[q]) adapter_doc_embeddings.append(retrieved_embeddings[q][d]) adapter_labels.append(evaluate_results(query, document)) time.sleep(30)注意time.sleep(30)文档说明是因为使用免费 OpenAI API 会有调用限制用 sleep 减缓请求速度以避免被限流文档同时说明这里也可以使用 OpenAI 以外的 API 服务。这一步是全流程中最耗时的文档示例的进度条显示 15 个查询共 150 对总耗时超过 2 小时。标注完成后检查数据集长度len(adapter_labels)文档示例返回150即 15 个查询 × 10 个检索结果每一对都标有相关性标签。训练适配器矩阵先把三个列表转成 torch 张量并封装成TensorDataset。这是一个回归式的分类模型预测给定查询-文档对是否相关标签1相关、-1不相关adapter_query_embeddings torch.Tensor(np.array(adapter_query_embeddings)) adapter_doc_embeddings torch.Tensor(np.array(adapter_doc_embeddings)) adapter_labels torch.Tensor(np.expand_dims(np.array(adapter_labels),1)) dataset torch.utils.data.TensorDataset(adapter_query_embeddings, adapter_doc_embeddings, adapter_labels)模型本身很薄用适配器矩阵左乘查询嵌入得到更新后的查询嵌入再与文档嵌入计算余弦相似度dim0表示沿嵌入维度归约def model(query_embedding, document_embedding, adaptor_matrix): updated_query_embedding torch.matmul(adaptor_matrix, query_embedding) return torch.cosine_similarity(updated_query_embedding, document_embedding, dim0) def mse_loss(query_embedding, document_embedding, adaptor_matrix, label): return torch.nn.MSELoss()(model(query_embedding, document_embedding, adaptor_matrix), label)适配器矩阵按嵌入维度初始化mat_size即单个嵌入向量的维度requires_gradTrue使其可训练mat_size len(adapter_query_embeddings[0]) adapter_matrix torch.randn(mat_size, mat_size, requires_gradTrue)训练循环固定为 100 个 epoch每个 epoch 遍历全部样本计算损失、记录损失最小时的矩阵副本再以步长 0.01 做手动梯度下降min_loss float(inf) best_matrix None for epoch in tqdm(range(100)): for query_embedding, document_embedding, label in dataset: loss mse_loss(query_embedding, document_embedding, adapter_matrix, label) if loss min_loss: min_loss loss best_matrix adapter_matrix.clone().detach().numpy() loss.backward() with torch.no_grad(): adapter_matrix - 0.01 * adapter_matrix.grad adapter_matrix.grad.zero_() print(fBest loss: {min_loss.detach().numpy()})文档示例输出为Best loss: 0.3013603091239929文档称最佳损失值约为 0.30意味着我们有了很大的进步。另外笔记本示例的 stderr 中出现过一条torch的UserWarning提示 MSELoss 的 target 尺寸[1]与 input 标量尺寸不同、会按广播处理属于示例运行中出现的告警示例进度条显示 100 个 epoch 仍正常跑完。训练结束后可以先直观地看一下适配器矩阵对向量的作用用全 1 的测试向量乘以best_matrix并画条形图文档代码如下。文档的解释是每个维度仅由 1 组成的测试向量被拉伸和挤压有些维度被拉长了很多、另一些几乎为零说明适配器矩阵已经确定了哪些维度是它认为与相关性有关的关键维度test_vector torch.ones((mat_size,1)) scaled_vector np.matmul(best_matrix, test_vector).numpy() import matplotlib.pyplot as plt plt.bar(range(len(scaled_vector)), scaled_vector.flatten()) plt.show()用最佳矩阵转换查询嵌入并验证效果真正使用适配器的动作只有一行矩阵乘法用训练得到的best_matrix左乘查询嵌入矩阵嵌入按行存储所以先转置、乘完再转回来得到adapted_query_embeddings然后原始与转换后的查询嵌入分别用同一个umap_transform投影query_embeddings embedding_function(generated_queries) adapted_query_embeddings np.matmul(best_matrix, np.array(query_embeddings).T).T projected_query_embeddings project_embeddings(query_embeddings, umap_transform) projected_adapted_query_embeddings project_embeddings(adapted_query_embeddings, umap_transform)最后把三者画在同一张散点图上灰色小点是数据集文档嵌入红色 X 是原始查询嵌入绿色 X 是转换后的查询嵌入import matplotlib.pyplot as plt 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_embeddings[:, 0], projected_query_embeddings[:, 1], s150, markerX, colorr, labeloriginal) plt.scatter(projected_adapted_query_embeddings[:, 0], projected_adapted_query_embeddings[:, 1], s150, markerX, colorgreen, labeladapted) plt.gca().set_aspect(equal, datalim) plt.title(Adapted Queries) plt.axis(off) plt.legend()这就是本章文档给出的效果判断方式对比红绿两组 X 相对灰色文档点的落位。文档的结论是——原始查询非常分散而调整后的查询更接近某些特定的文档嵌入向量即适配器把查询嵌入推向了文档空间中有内容的区域从而产生更好的检索结果。限制与说明耗时LLM 标注阶段每对查询-文档调用一次 API 并 sleep 30 秒文档示例中 150 对总耗时超过 2 小时如 API 限流情况不同可按文档提示换用其他 API 服务。语言load_chroma的langcode只有zh和en两种helper_utils.py中有断言中文语料必须传zh。示例数值文中的1028集合分块数、150标签数、0.3013603091239929最佳损失以及示例问题列表均为笔记本文档中的示例输出换语料、换查询数量后数值会不同。适用边界本章只演示了转换查询嵌入这一条增强路径课程并未宣称它适用于所有检索失败场景。章节小结6.4只有两句通过本次使用 Embedding Adaptors 对查询嵌入向量做自定义处理从而达到查询结果的增强效果。课程目录中本章之后是 7. 其他技术 Other techniques.md可以作为下一步阅读。【免费下载链接】llm-cookbook面向开发者的 LLM 入门教程吴恩达大模型系列课程中文版项目地址: https://gitcode.com/GitHub_Trending/ll/llm-cookbook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询