网站建设与管理大学生职业规划acca少女网课视频2023
网站建设与管理大学生职业规划,acca少女网课视频2023,h5制作的网站,商城网站建设找谁做01. RAPTOR 递归文档树策略
在传统的 RAG 中#xff0c;我们通常依靠检索短的连续文本块来进行检索。但是#xff0c;当我们处理的是长上下文时#xff0c;我们就不能仅仅将文档分块嵌入到其中#xff0c;或者仅仅使用上下文填充所有文档。相反#xff0c;我们希望为 LLM…01. RAPTOR 递归文档树策略在传统的 RAG 中我们通常依靠检索短的连续文本块来进行检索。但是当我们处理的是长上下文时我们就不能仅仅将文档分块嵌入到其中或者仅仅使用上下文填充所有文档。相反我们希望为 LLM 的长下文找到一种好的最小化分块方法这就是 RAPTOR 的用武之地在 RAPTOR 中均衡了多文档、超长上下文、高准确性、超低成本 等特性。RAPTOR 其实是一种用树状组织检索的递归抽象处理技术它采用了一种自下而上的方法通过对文本片段块进行聚类和归纳来形成一种分层结构该技术在https://arxiv.org/pdf/2401.18059论文中被首次提出。RAPTOR 的运行流程其实很简单主要步骤如下对原始文本进行分块拆分成合适的大小对拆分的文档块进行嵌入/向量化向量目前处于高维并将数据存储到向量数据库将高维向量进行降维降低运算成本例如降低成 2 维或者 3 维对降维向量进行聚类找出同一类的文档组合并文档组的文本使用 LLM 对合并文档进行摘要汇总得到新的文本重复 ②-⑤ 的步骤直到最后只剩下一个 文档 并且该文档的长度符合大小时结束整个流程可视化其运行流程后如下在执行检索的时候RAPTOR 模型采用了两种主要的检索策略——树遍历 和 折叠树对比两种策略论文中更推荐 折叠树 检索策略。树遍历 方法从树的根节点开始逐层向下进行检索在每一层它根据查询向量与节点嵌入的余弦相似性选择最相关的前 k 个节点然后将这些节点的子节点作为下一层的候选节点集并重复选择过程直到达到叶节点最终将所有选中的节点文本进行拼接形成检索到的上下文。折叠树 方法会先将整个 RAPTOR 树展平为单个层即所有节点在同一层级上进行比较计算查询向量与所有节点嵌入的余弦相似性并选择最相关的前 k 个节点并且因为 折叠树 和 向量数据库基础搜索 一样都是遍历所有向量同层所以只需要将数据存储到向量数据库中并执行 相似性搜索 其实就实现了 折叠树 检索策略。对比其他的 RAG 优化策略RAPTOR递归文档树 不仅保留了原始文档还将同类文档进行层层抽象层层总结而且在 文本嵌入模型 参数较小的情况下一般也能实现不错的效果低成本、高性能不过因为该数据存储的是 树状结构每次在更新/新增数据时操作对比其他优化策略麻烦很多。该论文并没有提供更新/新增数据的策略但是对于树状结构一般的更新策略有重新构建树结构新增了大量文档时可以重新构建整个 RAPTOR 树确保数据不会遗漏。增量更新如果新增的文档不是很多可以考虑只更新部分树结构将新文档的文本块作为新的叶子节点添加到树中然后根据这些新节点的内容调整或更新其上层节点的摘要信息。利用现有节点如果新文档与现有树中的某些节点内容相似可以考虑将新文档的文本块与现有节点合并然后重新生成这些节点的摘要以此来更新树结构。层次化更新根据新文档的内容和重要性选择在树的哪个层次上进行更新。如果新文档提供了对整个文档集的重要概述可能需要在较高的层次上更新如果新文档提供了细节信息可能只需要在较低层次上更新。02. RAPTOR 递归文档树的实现在 LangChain 中并没有针对 RAPTOR 的实现并且在该策略中涵盖了 高维数据降维、低维数据聚类、递归检索 等内容对于该检索策略目前使用得较少更新与新增缺陷导致外挂文档增加时复杂度成倍递增所以仅需要了解 RAPTOR 的运行流程即可。首先安装特定的第三方 Python 包pip install -U umap-learn scikit-learn tiktoken实现代码如下from typing import Optionalimport dotenvimport numpy as npimport pandasimport pandas as pdimport umapimport weaviatefrom langchain_community.document_loaders import UnstructuredFileLoaderfrom langchain_core.output_parsers import StrOutputParserfrom langchain_core.prompts import ChatPromptTemplatefrom langchain_huggingface import HuggingFaceEmbeddingsfrom langchain_openai import ChatOpenAIfrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom langchain_weaviate import WeaviateVectorStorefrom sklearn.mixture import GaussianMixturefrom weaviate.auth import AuthApiKeydotenv.load_dotenv()# 1.定义随机数种子、文本嵌入模型、大语言模型、向量数据库RANDOM_SEED 224embd HuggingFaceEmbeddings(model_namethenlper/gte-small,cache_folder./embeddings/,encode_kwargs{normalize_embeddings: True},)model ChatOpenAI(modelgpt-3.5-turbo-16k, temperature0)db WeaviateVectorStore(clientweaviate.connect_to_wcs(cluster_urlhttps://mbakeruerziae6psyex7ng.c0.us-west3.gcp.weaviate.cloud,auth_credentialsAuthApiKey(ZltPVa9ZSOxUcfafelsggGyyH6tnTYQYJvBx),),index_nameRaptorRAG,text_keytext,embeddingembd,)def global_cluster_embeddings(embeddings: np.ndarray, dim: int, n_neighbors: Optional[int] None, metric: str cosine,) - np.ndarray:使用UMAP对传递嵌入向量进行全局降维:param embeddings: 需要降维的嵌入向量:param dim: 降低后的维度:param n_neighbors: 每个向量需要考虑的邻居数量如果没有提供默认为嵌入数量的开方:param metric: 用于UMAP的距离度量默认为余弦相似性:return: 一个降维到指定维度的numpy嵌入数组if n_neighbors is None:n_neighbors int((len(embeddings) - 1) ** 0.5)return umap.UMAP(n_neighborsn_neighbors, n_componentsdim, metricmetric).fit_transform(embeddings)def local_cluster_embeddings(embeddings: np.ndarray, dim: int, n_neighbors: int 10, metric: str cosine,) - np.ndarray:使用UMAP对嵌入进行局部降维处理通常在全局聚类之后进行。:param embeddings: 需要降维的嵌入向量:param dim: 降低后的维度:param n_neighbors: 每个向量需要考虑的邻居数量:param metric: 用于UMAP的距离度量默认为余弦相似性:return: 一个降维到指定维度的numpy嵌入数组return umap.UMAP(n_neighborsn_neighbors, n_componentsdim, metricmetric,).fit_transform(embeddings)def get_optimal_clusters(embeddings: np.ndarray, max_clusters: int 50, random_state: int RANDOM_SEED,) - int:使用高斯混合模型结合贝叶斯信息准则BIC确定最佳的聚类数目。:param embeddings: 需要聚类的嵌入向量:param max_clusters: 最大聚类数:param random_state: 随机数:return: 返回最优聚类数# 1.获取最大聚类树最大聚类数不能超过嵌入向量的数量max_clusters min(max_clusters, len(embeddings))n_clusters np.arange(1, max_clusters)# 2.逐个设置聚类树并找出最优聚类数bics []for n in n_clusters:# 3.创建高斯混合模型并计算聚类结果gm GaussianMixture(n_componentsn, random_staterandom_state)gm.fit(embeddings)bics.append(gm.bic(embeddings))return n_clusters[np.argmin(bics)]def gmm_cluster(embeddings: np.ndarray, threshold: float, random_state: int 0) - tuple[list, int]:使用基于概率阈值的高斯混合模型GMM对嵌入进行聚类。:param embeddings: 需要聚类的嵌入向量降维:param threshold: 概率阈值:param random_state: 用于可重现的随机性种子:return: 包含聚类标签和确定聚类数目的元组# 1.获取最优聚类数n_clusters get_optimal_clusters(embeddings)# 2.创建高斯混合模型对象并嵌入数据gm GaussianMixture(n_componentsn_clusters, random_staterandom_state)gm.fit(embeddings)# 3.预测每个样本属于各个聚类的概率probs gm.predict_proba(embeddings)# 4.根据概率阈值确定每个嵌入的聚类标签labels [np.where(prob threshold)[0] for prob in probs]# 5.返回聚类标签和聚类数目return labels, n_clustersdef perform_clustering(embeddings: np.ndarray, dim: int, threshold: float) - list[np.ndarray]:对嵌入进行聚类首先全局降维然后使用高斯混合模型进行聚类最后在每个全局聚类中进行局部聚类。:param embeddings: 需要执行操作的嵌入向量列表:param dim: 指定的降维维度:param threshold: 概率阈值:return: 包含每个嵌入的聚类ID的列表每个数组代表一个嵌入的聚类标签。# 1.检测传入的嵌入向量当数据量不足时不进行聚类if len(embeddings) dim 1:return [np.array([0]) for _ in range(len(embeddings))]# 2.调用函数进行全局降维reduced_embeddings_global global_cluster_embeddings(embeddings, dim)# 3.对降维后的数据进行全局聚类global_clusters, n_global_clusters gmm_cluster(reduced_embeddings_global, threshold)# 4.初始化一个空列表用于存储所有嵌入的局部聚类标签all_local_clusters [np.array([]) for _ in range(len(embeddings))]total_clusters 0# 5.遍历每个全局聚类以执行局部聚类for i in range(n_global_clusters):# 6.提取属于当前全局聚类的嵌入向量global_cluster_embeddings_ embeddings[np.array([i in gc for gc in global_clusters])]# 7.如果当前全局聚类中没有嵌入向量则跳过循环if len(global_cluster_embeddings_) 0:continue# 8.如果当前全局聚类中的嵌入量很少直接将它们分配到一个聚类中if len(global_cluster_embeddings_) dim 1:local_clusters [np.array([0]) for _ in global_cluster_embeddings_]n_local_clusters 1else:# 9.执行局部降维和聚类reduced_embeddings_local local_cluster_embeddings(global_cluster_embeddings_, dim)local_clusters, n_local_clusters gmm_cluster(reduced_embeddings_local, threshold)# 10.分配局部聚类ID调整已处理的总聚类数目for j in range(n_local_clusters):local_cluster_embeddings_ global_cluster_embeddings_[np.array([j in lc for lc in local_clusters])]indices np.where((embeddings local_cluster_embeddings_[:, None]).all(-1))[1]for idx in indices:all_local_clusters[idx] np.append(all_local_clusters[idx], j total_clusters)total_clusters n_local_clustersreturn all_local_clustersdef embed(texts: list[str]) - np.ndarray:将传递的的文本列表转换成嵌入向量列表:param texts: 需要转换的文本列表:return: 生成的嵌入向量列表并转换成numpy数组text_embeddings embd.embed_documents(texts)return np.array(text_embeddings)def embed_cluster_texts(texts: list[str]) - pandas.DataFrame:对文本列表进行嵌入和聚类,并返回一个包含文本、嵌入和聚类标签的数据框。该函数将嵌入生成和聚类结合成一个步骤。:param texts: 需要处理的文本列表:return: 返回包含文本、嵌入和聚类标签的数据框text_embeddings_np embed(texts)cluster_labels perform_clustering(text_embeddings_np, 10, 0.1)df pd.DataFrame()df[text] textsdf[embd] list(text_embeddings_np)df[cluster] cluster_labelsreturn dfdef fmt_txt(df: pd.DataFrame) - str:将数据框中的文本格式化成单个字符串:param df: 需要处理的数据框内部涵盖text、embd、cluster三个字段:return: 返回合并格式化后的字符串unique_txt df[text].tolist()return --- --- \n --- ---.join(unique_txt)def embed_cluster_summarize_texts(texts: list[str], level: int) - tuple[pd.DataFrame, pd.DataFrame]:对传入的文本列表进行嵌入、聚类和总结。该函数首先问文本生成嵌入基于相似性对他们进行聚类扩展聚类分配以便处理然后总结每个聚类中的内容。:param texts: 需要处理的文本列表:param level: 一个整数可以定义处理的深度:return: 包含两个数据框的元组- 第一个 DataFrame (df_clusters) 包括原始文本、它们的嵌入以及聚类分配。- 第二个 DataFrame (df_summary) 包含每个聚类的摘要信息、指定的处理级别以及聚类标识符。# 1.嵌入和聚类文本生成包含text、embd、cluster的数据框df_clusters embed_cluster_texts(texts)# 2.定义变量用于扩展数据框以便更方便地操作聚类expanded_list []# 3.扩展数据框条目将文档和聚类配对便于处理for index, row in df_clusters.iterrows():for cluster in row[cluster]:expanded_list.append({text: row[text], embd: row[embd], cluster: cluster})# 4.从扩展列表创建一个新的数据框expanded_df pd.DataFrame(expanded_list)# 5.获取唯一的聚类标识符以进行处理all_clusters expanded_df[cluster].unique()# 6.创建汇总Prompt、汇总链template Here is a sub-set of LangChain Expression Language doc.LangChain Expression Language provides a way to compose chain in LangChain.Give a detailed summary of the documentation provided.Documentation:{context}prompt ChatPromptTemplate.from_template(template)chain prompt | model | StrOutputParser()# 7.格式化每个聚类中的文本以进行总结summaries []for i in all_clusters:df_cluster expanded_df[expanded_df[cluster] i]formatted_txt fmt_txt(df_cluster)summaries.append(chain.invoke({context: formatted_txt}))# 8.创建一个DataFrame来存储总结及其对应的聚类和级别df_summary pd.DataFrame({summaries: summaries,level: [level] * len(summaries),cluster: list(all_clusters),})return df_clusters, df_summarydef recursive_embed_cluster_summarize(texts: list[str], level: int 1, n_levels: int 3,) - dict[int, tuple[pd.DataFrame, pd.DataFrame]]:递归地嵌入、聚类和总结文本直到达到指定的级别或唯一聚类数变为1将结果存储在每个级别处。:param texts: 要处理的文本列表:param level: 当前递归级别从1开始:param n_levels: 递归地最大深度默认为3:return: 一个字典其中键是递归级别值是包含该级别处聚类DataFrame和总结DataFrame的元组。# 1.定义字典用于存储每个级别处的结果results {}# 2.对当前级别执行嵌入、聚类和总结df_clusters, df_summary embed_cluster_summarize_texts(texts, level)# 3.存储当前级别的结果results[level] (df_clusters, df_summary)# 4.确定是否可以继续递归并且有意义unique_clusters df_summary[cluster].nunique()if level n_levels and unique_clusters 1:# 5.使用总结作为下一级递归的输入文本new_texts df_summary[summaries].tolist()next_level_results recursive_embed_cluster_summarize(new_texts, level 1, n_levels)# 6.将下一级的结果合并到当前结果字典中results.update(next_level_results)return results# 2.定义文档加载器、文本分割器(中英文场景)loaders [UnstructuredFileLoader(./流浪地球.txt),UnstructuredFileLoader(./电商产品数据.txt),UnstructuredFileLoader(./项目API文档.md),]text_splitter RecursiveCharacterTextSplitter(chunk_size500,chunk_overlap0,separators[\n\n, \n, 。||, \.\s|\!\s|\?\s, |;\s, |,\s, , ],is_separator_regexTrue,)# 3.循环分割并加载文本docs []for loader in loaders:docs.extend(loader.load_and_split(text_splitter))# 4.构建文档树最多3层leaf_texts [doc.page_content for doc in docs]results recursive_embed_cluster_summarize(leaf_texts, level1, n_levels3)# 5.遍历文档树结果从每个级别提取总结并将它们添加到all_texts中all_texts leaf_texts.copy()for level in sorted(results.keys()):summaries results[level][1][summaries].tolist()all_texts.extend(summaries)# 6.将all_texts添加到向量数据库db.add_texts(all_texts)# 7.执行相似性检索折叠树retriever db.as_retriever(search_typemmr)search_docs retriever.invoke(流浪地球中的人类花了多长时间才流浪到新的恒星系)print(search_docs)print(len(search_docs))输出内容[Document(page_content我们的船继续航行到了地球黑夜的部分在这里阳光和地球发动机的光柱都照不到在大西洋清凉的海风中我们这些孩子第一次看到了星空。天啊那是怎样的景象啊美得让我们心醉。小星老师一手搂着我们一手指着星空看孩子们那就是半人马座那就是比邻星那就是我们的新家说完她哭了起来我们也都跟着哭了周围的水手和船长这些铁打的汉子也流下了眼泪。所有的人都用泪眼探望着老师指的方向星空在泪水中扭曲抖动唯有那个星星是不动的那是黑夜大海狂浪中远方陆地的灯塔那是冰雪荒原中快要冻死的孤独旅人前方隐现的火光那是我们心中的太阳是人类在未来一百代人的苦海中惟一的希望和支撑……\n\n在回家的航程中我们看到了启航的第一个信号夜空中出现了一个巨大的彗星那是月球。人类带不走月球就在月球上也安装了行星发动机把它推离地球轨道以免在地球加速时相撞。月球上行星发动机产生的巨大彗尾使大海笼罩在一片蓝光之中群星看不见了。月球移动产生的引力潮汐使大海巨浪冲天我们改乘飞机向南半球的家飞去。\n\n启航的日子终于到了), Document(page_content离开木星时地球已达到了逃逸速度它不再需要返回潜藏着死亡的太阳向广漠的外太空飞去漫长的流浪时代开始了。\n\n就在木星暗红色的阴影下我的儿子在地层深处出生了。\n\n第3章 叛乱\n\n离开木星后亚洲大陆上一万多台地球发动机再次全功率开动这一次它们要不停地运行500年不停地加速地球。这500年中发动机将把亚洲大陆上一半的山脉用做燃料消耗掉。\n\n从四个多世纪死亡的恐惧中解脱出来人们长出了一口气。但预料中的狂欢并没有出现接下来发生的事情出乎所有人的想像。\n\n在地下城的庆祝集会后我一个人穿上密封服来到地面。童年时熟悉的群山已被超级挖掘机夷为平地大地上只有裸露的岩石和坚硬的冻土冻土上到处有白色的斑块那是大海潮留下的盐渍。面前那座爷爷和爸爸度过了一生的曾有千万人口的大城市现在已是一片废墟高楼钢筋外露的残骸在地球发动机光柱的蓝光中拖着长长的影子好像是史前巨兽的化石……一次次的洪水和小行星的撞击已摧毁了地面上的一切各大陆上的城市和植被都荡然无存地球表面已变成火星一样的荒漠。), Document(page_content“可老师我们来不及的地球来不及的它还来不及加速到足够快航行足够远太阳就爆炸了”\n\n“时间是够的要相信联合政府这我说了多少遍如果你们还不相信我们就退一万步说人类将自豪地去死因为我们尽了最大的努力”\n\n人类的逃亡分为五步第一步用地球发动机使地球停止转动使发动机喷口固定在地球运行的反方向第二步全功率开动地球发动机使地球加速到逃逸速度飞出太阳系第三步在外太空继续加速飞向比邻星第四步在中途使地球重新自转掉转发动机方向开始减速第五步地球泊入比邻星轨道成为这颗恒星的卫星。人们把这五步分别称为刹车时代、逃逸时代、流浪时代I加速、流浪时代II减速、新太阳时代。\n\n整个移民过程将延续两千五百年时间一百代人。), Document(page_content我们很快到达了海边看到城市摩天大楼的尖顶伸出海面退潮时白花花的海水从大楼无数的窗子中流出形成一道道瀑布……刹车时代刚刚结束其对地球的影响已触目惊心地球发动机加速造成的潮汐吞没了北半球三分之二的大城市发动机带来的全球高温融化了极地冰川更给这大洪水推波助澜波及到南半球。爷爷在三十年前亲眼目睹了百米高的巨浪吞没上海的情景他现在讲这事的时候眼还直勾勾的。事实上我们的星球还没启程就已面目全非了谁知道在以后漫长的外太空流浪中还有多少苦难在等着我们呢)]4感兴趣的同学也可以阅读代码在代码中每一行我都添加上了对应的注释。除了 RAPTOR 的优化思想LangChain 团队在路演中还演示了一种基于 Token2Text 的优化策略——ColBERT该策略会为段落中的每个 Token 生成一个受上下文影响的向量在计算文档得分时并不是使用文档的向量相似性计算而是通过 查询嵌入 与 每个Token嵌入向量 的最大相似性之和不过由于性能偏低并且需要高昂的计算成本带来的提升不大导致目前应用并不广接受度较低。感兴趣的同学可以通过以下资料进行阅读学习LangChain 文档RAGatouille | ️ LangChainColBERT 英文博文How ColBERT Helps Developers Overcome the Limits of RAG | HackerNoon03. 索引阶段优化策略总结至此在 RAG 的 索引阶段这些具有代表性的优化策略其实我们已经讲解完了涵盖了 分割策略(语义分割、递归字符分割)、多向量/表征检索、RAPTOR递归文档树检索其中 特定Embeddings 即考虑使用维度更高的文本嵌入模型并没有复杂的使用技巧。目前在 索引阶段使用最多的优化策略还是在 文档加载/分割 与 特定Embeddings 上因为无论是 多向量/表征索引 还是 RAPTOR递归文档树都会使用额外的 LLM 对文档进行相应的总结、信息提取、分类汇总等数据越多信息失真的概率会越大所以谨慎使用