微课做动画的网站,公司内部网络建设方案,网站为什么要备案登记,自媒体哪里培训本文深入探讨了RAG#xff08;Retrieval Augmented Generation#xff09;技术的实现细节与优化策略#xff0c;指出在AI应用开发中#xff0c;RAG常被视为黑盒导致问题定位困难。文章从文档分块#xff08;Chunking#xff09;、索引增强#xff08;语义增强与反向HyDE…本文深入探讨了RAGRetrieval Augmented Generation技术的实现细节与优化策略指出在AI应用开发中RAG常被视为黑盒导致问题定位困难。文章从文档分块Chunking、索引增强语义增强与反向HyDE、编码Embedding、混合检索Hybrid Search到重排序Re-Ranking等关键环节进行了详细解析强调需结合具体场景对各模块进行调优以提升召回率与精确率的平衡并倡导从快速使用走向深度优化的实践路径。写在前面随着AI应用开发的普及RAG成了一个家喻户晓的词非常朴实且出镜率极高不过在平时也会经常听到一些声音“RAG效果不好可能需要微调模型”“xxx上的RAG产品不好用召回不精准”“我需要更强大的RAG工具不然这个效果很难提升”等等首先可以肯定光从上述一些话术中不能说明大家对于RAG的实践和结论是有问题的但是当进一步沟通的时候比较多的case中会发现比较那回答出“为什么你觉得RAG不好用”“有实际case吗比如什么样的query召回了什么样的知识”“我们的文档是怎么组织的如何编写的有做一些结构和分块上的处理吗”。日常我们会比较多的把RAG当成一个黑盒输入是我们沉淀的文档输出可能是整个AI应用反馈的最终结果如下图所示这样的方式下我们可能可以收获一定的初期收益但是当要持续优化或者扩展使用场景的时候可能会缺乏评估和应对的方式比较难去定位问题因此也不太能说清楚当下链路的诉求最后所对应的action也可能会偏离比较大。Fig1.RAG链路图-粗粒度版,有缺失下面想稍微深入探索下RAG链路和涉及的一些技术细节希望可以给到实践中的小伙伴一些参考从而可以更好的诊断问题、找到可优化的节点做出更合理的迭代设计。重新介绍下RAGRAG核心的功能就是针对用户Query补充和Query相关的且模型没有的信息同时要在两个维度上做要求1.召回率能够找到最相关的信息2.精确率不相关的信息不要RAG技术以及我们针对实践的设计主要就是锚定这两个维度指标的提升去的同时这两个指标在现实实践中是个权衡需要找到一个相对适合的值追求两者都很高不太现实或者说可能需要付出的代价不足以让我们这么去做。RAG是RetrievalAugmentationGeneration三个词的缩写代表了三个核心的行为Retrieve-检索、Augment-增强以及Generate-生成同时在三个核心动作之外还有一个embedding-编码具体见下图Fig2.RAG链路图-细粒度版下面分别针对笔者觉得可能影响RAG实践中效果的一些技术点图中标有编号的部分做进一步的描述。1. 文档分块-Chunking所谓兵马未动粮草先行要有一个好的检索结果首先要从我们的知识文档的优化开始我们实践中比较重视知识文档的内容沉淀但是在一些文档结构组织段落划分以及一些知识点的内聚性和正交性上会涉及少一点。我们来看下一份文档在语义chunking基础的按照token、字符、语句、段落切分大部分情况下效果都比较局限下是被如何处理的。# 设置本地数据保存目录 local_data_dir pathlib.Path(/Users/jiangdanyang/workspaces/python/MarioPython/src/RAG/dataset/ai-arxiv2) # 加载数据集如果本地存在则从本地加载否则下载 dataset load_dataset(jamescalam/ai-arxiv2, splittrain, cache_dirstr(local_data_dir)) # 初始化编码器 encoder OpenAIEncoder( nametext-embedding-3-small, openai_api_keyos.getenv(AI_API_KEY), openai_base_urlos.getenv(AI_API_BASE_URL) ) chunker StatisticalChunker( encoderencoder, min_split_tokens100, max_split_tokens500, plot_chunksTrue, enable_statisticsTrue, ) chunks_0 chunker(docs[dataset[content][0]], batch_size500)例子中是针对一篇论文做chunkingchunking会设置min_split_tokens最小chunk的tokens数和max_split_tokens最大chunk的tokens数chunking完之后的统计结果可见下面的图和表Chunking Statistics: - Total Documents: 474 - Total Chunks: 46 - Chunks by Threshold: 41 - Chunks by Max Chunk Size: 4 - Last Chunk: 1 - Minimum Token Size of Chunk: 54 - Maximum Token Size of Chunk: 495 - Similarity Chunk Ratio: 0.89可以先看下统计的结果文字描述简单做下解释整体Documents可以简单理解为句子数本部分设计的document都是该含义474整体切分的文件块chunk46个其中41chunk的切分是基于相似度的阈值可以理解为是按照语义正常划分出来的有4个是因为达到了500tokens数量被切分的还有最后1个chunk是到文章结尾了最大的chunk的token数495个最小的chunk的token数54个因为是lastchunk所以会出现小于min_split_tokens的情况最后SimilarityChunkRatio是统计这次切分的chunk89%的chunk是按照语义切分出来的41/46SimilarityChunkRatio可以比较好的说明当前外部文档的chunking的结果因为试想都是被max_split_tokens卡主划分的chunk后续在语义检索的时候结果也不会太好。实践中需要针对你的文档情况调整split的token大小在chunk的数量和相关性比例上达到一个平衡除了chunk的大小还有两个值需要关注Threshold就是所谓的相似度的下限上面例子中threshold是0.31该值越大chunk内的相关性越好WindowSize是被用于计算的document的数量大小默认是5即每次是选择连续的5个document计算相似度windowsize设置越大上下文切分的相关性越好但是同时chunking过程的计算量和耗时也更高chunk大小相对要大这篇论文《Meta-Chunking: Learning Text Segmentation and Semantic Completion via Logical Perception》也给出了一个基于逻辑和语义的chunking的方法有完整的效果评测可参考除了semantic Chunking之外还有面向多模态数据文档类型的Modality-Specific Chunking可以比较好的区分不同内容类型的文档块并面向文本、表格、代码、图使用不同的chunking策略和Agentic Chunking让能力强的LLM阅读全文判断给出切分策略上述都是工具箱里面的工具实践中需要结合自身的场景、知识现状、成本综合去权衡选择并且面向效果进行调优或者切换更适合的方式。2. 索引增强-Indexing索引增强这里介绍两种类型1.语义增强2.反向HyDE。语义增强语义增强就是将chunk和该chunk所在的文档内容这里是整片论文传给LLM让LLM结合整个文档对这段chunk作个概述然后把这个概述的信息append到chunk的内容中从而增强在后续进行语义检索时的精确性。DOCUMENT_CONTEXT_PROMPT document {doc_content} /document CHUNK_CONTEXT_PROMPT Here is the chunk we want to situate within the whole document chunk {chunk_content} /chunk Please give a short succinct context to situate this chunk within the overall document for the purposes of improving search retrieval of the chunk. Answer only with the succinct context and nothing else. 这里的LLM选择需要能力比较强的大模型最好可以有promptcache功能这样可以大大节省这一部的模型调用开销同时也有一些做法是可以增加前后两个chunk的内容对于整体文档比较长且前后文本关联度比较大的场景会有一些增强的效果。反向HyDEHyDEHypothetical Document Embeddings是正向query检索增强的一种方式即可以针对用户的query生成一些假设的答案或者做query扩写然后通过这些中间内容去做检索召回反向HyDE的意思是针对chunk可以视为answer生成这块chunk可能的question然后针对这些quetion进行索引构建关联到具体的chunk内容反向HyDE相比HyDE的优势是可以离线处理不影响实时调用的rt。Given the following text chunk, generate {n} different questions that this chunk would be a good answer to: Chunk: {chunk} Questions (enumarate the questions with 1.2., etc.):该方法比较适合这类问答型的知识比如一些答疑内容有明确的A和Q的或者可以作为后面hybridsearch中的关键词扩写提升后续混合检索的效果。3. 编码-EmbeddingEmbedding大家应该都很熟悉就是将输入的文本多模态内容转换成向量主要过程包含文字到token的切分然后每个token在词汇表中有对应的id每个tokenid都会对应同等维度不同embedding模型维度不同的向量可以看个简单的例子。first_sentence 直播分享会AI Living第一场 second_sentence 直播鲁班小组分享第77期 model SentenceTransformer(/Users/jiangdanyang/workspaces/python/Model/all-MiniLM-L6-v2) tokenized_first_sentence model.tokenize([first_sentence]) tokenized_second_sentence model.tokenize([second_sentence])编码之后是当前文本对应的tokens的tokenid列表这里影响编码的原因有这些编码模型的语言问题不同语言会有不同的分词和词汇表比如例子中使用的这个编码模型all-MiniLM-L6-v2在处理中文的文本时候就比较差可以看到返回的id有好些100不可识别的token中文的处理可以找相应的中文embedding模型但是不是所有语言都有对应的编码模型因为语种太多同时如果一些语种对应的数据语料太少不足以训练这样的一个模型。编码模型的词汇表大小例子中的all-MiniLM-L6-v2的词汇表大小是30522有些主流模型的词汇表大小基本都在5w以上有些10w以上词汇表小会导致一些词无法表示只能用一个兜底tokenid来代替会影响后续处理的效果词汇表大能精准标识文本的输入但是间接也会增加文本编码完之后的token大小。编码模型的语义空间不同的编码模型有自己的词汇表以及自己对应的向量语义空间向量语义空间的效果决定于该模型训练基于的数据集目前用于文本编码的模型基本都是现有世界知识的通用语义空间偏日常、大众化的关联如果我们需要在一个特定领域下有一个特殊的语义空间可能就需要找一个使用该领域下的数据训练的embedding模型或者需要自己SFT一个不然预期想要的效果和实际效果可能会有比较大的gap。顺便说下图知识的问题直接拿图片当知识处理过程可能就是OCR的文本提取或者是LLM对于图片的理解描述但是这里的干扰会很大比如中间过程的文本是不是你期望的样子和描述的维度这些都需要把握下不然后续的检索召回肯定也是一团浆糊4. 检索-HybridSearchHyBridSearch混合搜索本质上是结合了Term-based和Semantic-based两种模式的检索特性通过融合两种形式的算法来提升检索的准确性和相关性HybridSearch结合了SparseVector稀疏向量相似度计算----关键词匹配和DenseVector稠密向量相似度计算----语义匹配从而提升检索的效果可见下图Sparse向量主要是通过BM25为代表的算法生成BM25核心就是TF-IDF算法词频-反向文档频率返回是某个query相对每个文档编号的分数值具体算法如下。# Load the chunks corpus_json json.load(open(/Users/jiangdanyang/workspaces/python/MarioPython/src/RAG/dataset/corpus.json)) corpus_text [doc[text] for doc in corpus_json] # optional: create a stemmer english_stemmer snowballstemmer.stemmer(english) # Initialize the Tokenizer with the stemmer sparse_tokenizer Tokenizer( stemmerenglish_stemmer, lowerTrue, # lowercase the tokens stopwordsenglish,# or pass a list of stopwords splitterr\w,# by default r(?u)\b\w\w\b, can also be a function ) # Tokenize the corpus corpus_sparse_tokens ( sparse_tokenizer .tokenize( corpus_text, update_vocabTrue, # update the vocab as we tokenize return_asids ) ) # Create the BM25 retriever and attach your corpus_json to it sparse_index bm25s.BM25(corpuscorpus_json) # Now, index the corpus_tokens (the corpus_json is not used yet) sparse_index.index(corpus_sparse_tokens) # Return 10 the most relevant docs according to the query sparse_results, sparse_scores sparse_index.retrieve(query_tokens, k10)Dense向量主要是通过基于Transformer架构的embedding模型来进行编码生成同时针对查询query使用同样的embedding模型进行编码然后再进行向量的相似度比对找出最相似的n个结果。#Dense Index # create the vector database client qdrant QdrantClient(path/Users/jiangdanyang/workspaces/python/MarioPython/src/RAG/dataset/qdrant_data) # Create the embedding encoder dense_encoder SentenceTransformer(/Users/jiangdanyang/workspaces/python/Model/all-MiniLM-L6-v2) collection_name hybrid_search qdrant.recreate_collection( collection_namecollection_name, vectors_configmodels.VectorParams( sizedense_encoder.get_sentence_embedding_dimension(), distancemodels.Distance.COSINE ) ) # vectorize! qdrant.upload_points( collection_namecollection_name, points[ models.PointStruct( ididx, vectordense_encoder.encode(doc[text]).tolist(), payloaddoc ) for idx, doc in enumerate(corpus_json) # data is the variable holding all the enriched texts ] ) query_vector dense_encoder.encode(query).tolist() dense_results qdrant.search( collection_namecollection_name, query_vectorquery_vector, limit10 )最后针对上述两种方式找出的chunk做综合筛选这里可以有多种方式比如比较常用的就是先分别对Sparse向量和Dense向量计算出来的topn个结果的分值做归一化然后针对统一个Chunk按照一定的权重比如Sparse向量计算结果权重0.2Dense向量计算结果权重0.8计算一个最终分值最后返回topn个chunk列表给到下个节点# Normalize the two types of scores dense_scores np.array([doc.get(dense_score, 0) for doc in documents_with_scores]) sparse_scores np.array([doc.get(sparse_score, 0) for doc in documents_with_scores]) dense_scores_normalized (dense_scores - np.min(dense_scores)) / (np.max(dense_scores) - np.min(dense_scores)) sparse_scores_normalized (sparse_scores - np.min(sparse_scores)) / (np.max(sparse_scores) - np.min(sparse_scores)) alpha 0.2 weighted_scores (1 - alpha) * dense_scores_normalized alpha * sparse_scores_normalized如果当前场景的检索需要兼顾关键词和语义的时候可以考虑混合搜索需要结合文档内容、chunking和关键字词构建等环节相对于关键字词匹配检索混合搜索可以降低查询编写的规范性不一定要有特定的关键词出现以及提升查询的容错性可能会有拼写错误或者不恰当的描述相对于语义相似检索混合搜索可以增加一些领域专有信息的更精准匹配提升检索结果的准确性。5. 重排-ReRanking检索的优点是可以在海量的知识里面快速找到和用户query相关的内容块docs但是检索所返回出来的docs实际上可能部分和用户query关联度并不大这个时候就需要通过re-rank这一步对于检索返回出来的docs做关联度排序最终选取最相关的topk个doc做后续的上下文补充。在RAG链路中ReRanking的常用技术是Cross-Encoder交叉编码器本质一个Bert模型Encode-only的transformer架构计算query和每一个doc相关性返回0~1之间的结果1代表最相关示意图和代码示例如下from sentence_transformers import CrossEncoder cross_encoder CrossEncoder(/Users/jiangdanyang/workspaces/python/Model/jina-reranker-v1-tiny-en) hybrid_search_results {} with open(/Users/jiangdanyang/workspaces/python/MarioPython/src/RAG/dataset/dense_results.json) as f: dense_results json.load(f) for doc in dense_results: hybrid_search_results[doc[id]] doc with open(/Users/jiangdanyang/workspaces/python/MarioPython/src/RAG/dataset/sparse_results.json) as f: sparse_results json.load(f) for doc in sparse_results: hybrid_search_results[doc[id]] doc console.print(hybrid_search_results) # This is the query that we used for the retrieval of the above documents query What is context size of Mixtral? pairs [[query, doc[text]] for doc in hybrid_search_results.values()] scores cross_encoder.predict(pairs)最后进行排序选择topk个结果补充到context中然后调用模型拿最后的结果client OpenAI( api_keyos.getenv(AI_API_KEY), base_urlos.getenv(AI_API_BASE_URL) ) completion client.chat.completions.create( modelqwen_max, messages[ {role: system, content: You are chatbot, an research expert. Your top priority is to help guide users to understand reserach papers.}, {role: user, content: query}, {role: assistant, content: str(search_results)} ] )结语AI应用的开发实践进行得非常火热现阶段可能更多的是对已有的一些基建平台、开发编排工具、现成的横向基础产品做整合使用结合使用场景做链路设计。但是随着时间推移还是需要慢慢深入到部分细节往深水区慢慢前行本文讲述的RAG只是AI架构中的一块其他相关的技术在对待方式上也雷同都需要经历快速使用、技术细节了解、使用产品实现了解、应用中的设计实现迭代、面向效果的循环优化快速上手有捷径得益于比较好的基础设施建设成本比较低但是深入追寻效果切实提升效率或幸福感需要更深入的探寻希望对读到这里的小伙伴有帮助。读者福利如果大家对大模型感兴趣这套大模型学习资料一定对你有用对于0基础小白入门如果你是零基础小白想快速入门大模型是可以考虑的。一方面是学习时间相对较短学习内容更全面更集中。二方面是可以根据这些资料规划好学习计划和方向。作为一名老互联网人看着AI越来越火也总想为大家做点啥。干脆把我这几年整理的AI大模型干货全拿出来了。包括入门指南、学习路径图、精选书籍、视频课还有我录的一些实战讲解。全部免费不搞虚的。学习从来都是自己的事我能做的就是帮你把路铺平一点。资料都放在下面了有需要的直接拿能用到多少就看你自己了。这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以点击文章最下方的VX名片免费领取【保真100%】AI大模型学习路线汇总AI大模型时代的学习之旅从基础到前沿掌握人工智能的核心技能全套教程文末领取哈大模型实战案例光学理论是没用的要学会跟着一起做要动手实操才能将自己的所学运用到实际当中去这时候可以搞点实战案例来学习。大模型视频和PDF合集观看零基础学习书籍和视频看书籍和视频学习是最快捷也是最有效果的方式跟着视频中老师的思路从基础到深入还是很容易入门的。640套AI大模型报告合集这套包含640份报告的合集涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师还是对AI大模型感兴趣的爱好者这套报告合集都将为您提供宝贵的信息和启示。学会后的收获• 基于大模型全栈工程实现前端、后端、产品经理、设计、数据分析等通过这门课可获得不同能力• 能够利用大模型解决相关实际项目需求大数据时代越来越多的企业和机构需要处理海量数据利用大模型技术可以更好地处理这些数据提高数据分析和决策的准确性。因此掌握大模型应用开发技能可以让程序员更好地应对实际项目需求• 基于大模型和企业数据AI应用开发实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能学会Fine-tuning垂直训练大模型数据准备、数据蒸馏、大模型部署一站式掌握• 能够完成时下热门大模型垂直领域模型训练能力提高程序员的编码能力大模型应用开发需要掌握机器学习算法、深度学习框架等技术这些技术的掌握可以提高程序员的编码能力和分析能力让程序员更加熟练地编写高质量的代码。获取方式有需要的小伙伴可以点击文章最下方的微信名片添加免费领取【保证100%免费】