logo
Loading...

正課第一節: RAG應用的基本介紹 - RAG技術: 智能助手開發實戰 - Cupoy

為初學者設計的「構建RAG(檔案檢索增強生成)簡單應用」課程大綱,總共約十個小時。 此大綱是根據以LangChain這個大型語言開發應用框架為主的課程,並考慮到學習者的背景進行難度分配。 第一節:R...

為初學者設計的「構建RAG(檔案檢索增強生成)簡單應用」課程大綱,總共約十個小時。 此大綱是根據以LangChain這個大型語言開發應用框架為主的課程,並考慮到學習者的背景進行難度分配。 第一節:RAG應用的基本介紹 🔍 RAG簡介 RAG是為了解決冰山底層的私領域資料而建立的。 在我們能夠觸及的網路表層資料其實才佔了大概10~15%, 真正那些無法觸及的私人資料或企業資料整整展了將近80~90%, 那要怎麼樣挖掘這些資料, 最後讓LLM能夠準確"檢索"到, 並且最後"生成"出我們想要的資料或是報告,變成非常重要的事情。 而 RAG 就會是現在做到這件事的 "主流技術"  📚 大語言模型的挑戰 2023年是大語言模型的爆發元年。無論是CloseAI(OpenAI啦)的GPT系列模型,還是Meta研究院開源的llama1,2,3 ,3.1模型、乃至於Google的Gemini模型等,它們在自然語言處理領域的出色表現令人驚嘆。甚至都達到多模態資料分析。 然而,這些模型在面對專業領域的知識時,常常會出現知識缺失或是幻覺等等的問題,無法提供準確答案。數據科學家通常會通過微調(paramters-level fine tune)模型來適應特定領域的需求,儘管這種方法效果卓越,但成本高昂且需要專業技術知識。 💡 解決方案:"RAG" 針對大語言模型的不足,參數化知識(即微調模型)存在顯著局限性,不僅難以保留所有訓練數據中的知識,每次更新知識都需耗費大量計算資源。模型參數也無法動態更新,這意味著參數化知識會隨時間過時。 小談 微調(fine-tine)  和 RAG 的差異 (雖然有談到參數化微調(parameters fine-tine)但此堂課重點絕對還是RAG! 請謹記這件事~大家記得有參數微調這件事即可。 什麼是RAG? 簡而言之,RAG 是一種為我們的 LLM 提供額外的上下文以生成更好、更具體的響應的技術。LLM 是在公開可用的數據上訓練的,它們確實是獨立的智能系統,但它們無法回答特定問題,因為它們缺乏回答這些查詢的上下文。借助RAG,我們為他們提供了必要的上下文,以便正確回答我們的查詢。 RAG 是一種將新知識或能力插入我們的 LLM 的方法,儘管這種知識插入不是永久性的。向 LLM 新增新知識或能力的另一種方法是對我們的特定數據進行微調 LLM。 通過微調添加新知識是相當棘手、困難、昂貴且永久性的。通過微調添加新功能甚至會影響它之前擁有的知識。在微調過程中,我們無法控制哪些權重將被更改,因此哪些功能將增加或減少。 RAG 自構思以來已經走過了漫長的道路,我們已經大大改進了檢索策略,甚至在被問到完全偏離主題的問題時如何保護我們的系統。所有這些都在我的RAG博客系列中得到了非常詳細的介紹: 密集向量檢索 該過程從將用戶的問題輸入到 Embedding Model 中開始。 Embedding Model 將問題轉換為密集向量表示。 然後,此向量用於查詢向量存儲。 Vector Store 包含上下文或參考資訊的預計算嵌入。 使用相似性搜索(通常是餘弦相似性),系統檢索最相關的上下文片段。(查看RAG 2.0以獲得更好的相似性搜索) 檢索步驟會提取語言模型在其訓練數據中可能沒有或可能無法準確回憶的相關信息。 情境學習 提示範本 (Prompt Template) 構建如何將檢索到的信息和使用者的查詢呈現給語言模型。 該範本包括 (1) 模型如何使用上下文的說明,(2) 使用者的原始查詢,以及 (3) 檢索到的上下文片段(標記為 Context: ref 1、ref 2 等)。 這種結構允許語言模型 「學習」 如何使用提供的資訊來回答手頭的特定問題。 然後,聊天模型會處理這個精心構建的提示。 通過上下文學習,該模型根據提供的特定任務和上下文調整其知識和能力,而無需更改其底層參數。 淺談RAG的評估法 什麼是RAGAs?評估 LLM 管道 RAGAs (Retrieval-A ugmented Generation Assessment) 是一個框架(GitHub、Docs),可為您提供必要的成分,説明您在元件級別評估 RAG 管道。 RAGAs 框架已成為評估檢索增強生成 (RAG) 系統的寶貴工具,解決了評估這些多組分管道的複雜挑戰。該框架引入了一套全面的指標,旨在評估RAG系統的各個元件和整體性能。通過利用大型語言模型 (LLM) 進行無參考評估,RAGA 旨在簡化評估過程,從而可能減少對大量人工註釋數據的需求。 評估數據 RAGA 的有趣之處在於,它最初是一個 「無參考 」評估的框架。這意味著,RAGA 不必在評估數據集中依賴人工註釋的真值標籤,而是在後台利用 LLM 來進行評估。 要評估RAG管道,RAGA需要以下資訊: question:作為RAG管道輸入的用戶查詢。輸入。 answer:從RAG管道生成的答案。輸出。 contexts:從外部知識源檢索到的上下文,用於回答 .question ground_truths:對 .這是唯一的人工註釋資訊。只有量度才需要此資訊(請參閱評估量度)。questioncontext_recall RAGA的核心是四個關鍵的元件級指標:上下文相關性、上下文回憶、忠實度和答案相關性。 這些指標提供了對RAG管道不同方面的見解,從檢索信息的品質到生成答案的準確性和相關性。 上下文相關性評估檢索數據中的信噪比,而上下文召回率(唯一需要真實度的指標)確保捕獲所有必要的資訊。Faithity 評估生成答案的事實準確性,而 Answer Relevancy 衡量響應解決給定問題的程度。 所有這些指標都按照 0 到 1 的等級進行標準化,便於解釋和比較。 雖然構建基本的 RAG 應用程式可能相對簡單,但優化其性能以供實際使用會帶來重大挑戰。RAGA 通過提供結構化的評估方法來解決這個問題,類似於傳統機器學習專案中使用的驗證流程。該框架不僅提供指標,還包括用於自動生成測試數據的工具,從而進一步簡化了評估過程。 RAGAs (Retrieval-A ugmented Generation Assessment) 是一個框架(GitHub、Docs),可為您提供必要的成分,説明您在元件級別評估 RAG 管道。 RAGAs 框架已成為評估檢索增強生成 (RAG) 系統的寶貴工具,解決了評估這些多組分管道的複雜挑戰。該框架引入了一套全面的指標,旨在評估RAG系統的各個元件和整體性能。通過利用大型語言模型 (LLM) 進行無參考評估,RAGA 旨在簡化評估過程,從而可能減少對大量人工註釋數據的需求。 RAGA 可用於評估RAG管道中的許多不同元件。 RAGAs 提供指標驅動的開發。 建立基線 更改可能改進檢索的內容 重新計算指標 但是,重要的是要注意,使用 LLM 進行無參考評估仍然是一個不斷發展的領域。絕對值不是 Metrics 驅動開發的重點。 稍微提一下 llamaindex 和 Langchain 的差異: 是否需要訪問外部數據源? RAG: 適合需要訪問外部資料庫、文檔或其他數據源的應用。RAG能檢索並整合外部資訊,適合頻繁變動的資料。 微調: 需要大量標記數據集來學習外部知識,且需要隨資料變更更新,對於頻繁變動的資料不太實用。 是否需要調整模型行為、寫作風格或特定領域知識? 微調: 擅長使模型適應特定語氣、術語和行業專業知識。適合需要特定寫作風格或行業行話的應用。 RAG: 強於訊息檢索,但在語言風格和領域特異性方面不如微調。適合需要整合外部知識但不要求特定風格的應用。 結論 外部數據需求: 優先選擇RAG 定制行為和風格: 優先選擇微調 檢索增強生成(RAG)與 Fine-tuning 相結合:雙劍合壁 當提示工程(Prompt Engineering)達到極限時,RAG可以成為強大盟友。它通過引入外部訊息來豐富並增強模型回答。而當RAG也不能完全解污某些困難場景時 —— 比如處理超長文本或非常複雜訊息 —— Fine-tuning 就登場了。 從理論到實際操作:確保您準備好迎接挑戰 在開始 Fine-tuning 之旅前要做足功夫!確保您已充分理解基礎模型及其局限性;準備好高品質、針對性強且具代表性的數據;最重要地——制定出清晰可量化成功指標以評估效果。 無論我們正在探索新領域還是希望建立起更加貼近用戶需求和企業文化風格匹配度高效能模型,在人工智慧發展日新月異今天, Fine-tuning 無疑提供了一種有效手段去實現這些目標。 總之,非參數化知識,即存儲在外部的知識源,更加方便且易於擴展。這種方法允許開發者無需為每一個特定任務重新訓練整個模型。他們可以簡單地為模型添加一個知識庫,從而提高答案的精確性。 為了結合兩種方法的優缺點,模型可以採用半參數化的方法,將非參數化的資料庫與參數化模型相結合,這種方法被稱為檢索增強生成(Retrieval-Augmented Generation,RAG)。 🛠️ RAG的優勢 RAG方法允許模型在不重新訓練的情況下,即時訪問和利用外部知識源,從而提高回答的準確性和相關性。這使得模型不僅能夠保持高效運行,還能夠動態更新知識庫,保持知識的新鮮度。 這就是RAG的基本介紹,它是一種融合了參數化和非參數化知識的創新方法,使得大語言模型在處理專業知識時更加靈活和高效。  🚀 —————————————————————————————————————————🚀 RAG也可以由淺至深?: 這張圖展示了檢索增強生成(RAG)和微調技術的工作流程,根據所需的外部知識和模型調適程度,將各種技術進行分類。讓我們逐一解釋圖中的各個部分: 外部知識需求 vs 模型調適需求:  橫軸(X軸)表示模型調適需求的高低。 縱軸(Y軸)表示外部知識需求的高低。 提示工程(Prompt Engineering):  初步提示(Prompt Preliminary Attempts):這是最基本的提示工程技術,需求最低的模型調適和外部知識。 添加少量示例的思維鏈(Adding Few-Shot Case COT):在初步提示的基礎上,添加一些少量示例來幫助模型理解和回答問題。 初級RAG(Naive RAG):  在這個階段,加入相關的上下文段落,以便模型能夠更好地生成回答。這種方法對外部知識需求較高,但對模型調適需求較低。 高級RAG(Advanced RAG):  包含索引優化、檢索前(pre-retrieve)優化和檢索後優化(post-retrieve)技術。這需要更高的外部知識和一定的模型調適,提供更準確和相關的回答。 模組化RAG(Modular RAG):  結合多個模組的有機組合。這是最複雜的RAG技術,需要高度的外部知識和模型調適。 微調(Fine-Tuning):  檢索器微調(Retriever Fine-Tuning):對檢索器進行微調以改進其性能。 協作微調(Collaborative Fine-Tuning):多個模型之間的協作微調。 生成器微調(Generator Fine-Tuning):專注於生成模型的微調。 綜合應用(All of the Above):  這個區域表示綜合應用RAG和微調技術,需要高度的外部知識和模型調適,達到最優的效果。 Prompt Engineering and Advanced RAG 補充 slide (有機會會講進階RAG) RAG工作流程圖: 主要步驟: 知識切片成Chunk 向量化Chunk入庫 Query檢索知識Chunk 構建Prompts 調用LLM生成回答 工作流程解釋: User(使用者): 使用者輸入查詢(Query),例如:「How do you evaluate the fact that OpenAI's CEO, Sam Altman, went through a sudden dismissal by the board in just three days, and then was rehired by the company, resembling a real-life version of 'Game of Thrones' in terms of power dynamics?」 Input(輸入): 查詢被傳送到LLM(大語言模型)。 Indexing(索引): 文檔被切片成較小的片段(Chunks),然後這些片段被向量化並存儲在數據庫中,形成索引。 Retrieval(檢索): 根據查詢從數據庫中檢索相關文檔,找到與查詢相關的片段(Chunks)。 Generation(生成): 檢索到的相關片段與查詢上下文被結合,形成提示(Prompts),這些提示會提供給LLM以生成回答。 例如,提示可能會包括 Question: How do you evaluate the fact that OpenAI's CEO, Sam Altman, ... dynamics? Please answer the above question based on the following information: Chunk 1: "Sam Altman Returns to OpenAI as CEO, Silicon Valley Drama Resembles the 'Zhen Huan' Comedy" Chunk 2: "The Drama Concludes? Sam Altman to Return as CEO of OpenAI, Board to Undergo Restructuring" Chunk 3: "The Personnel Turmoil at OpenAI Comes to an End: Who Won and Who Lost?" Combine Context and Prompts(結合上下文和提示): 結合查詢和檢索到的上下文片段,LLM根據這些信息生成更準確和有信息量的回答。 Output(輸出): Without RAG(無RAG):  如果沒有RAG技術,LLM可能會回應:「I am unable to provide comments on future events. Currently, I do not have any information regarding the dismissal and rehiring of OpenAI's CEO...」 With RAG(有RAG):  有RAG技術的情況下,LLM會回應:「This suggests significant internal disagreements within OpenAI regarding the company's future direction and strategic decisions. All of these twists and turns reflect power struggles and corporate governance issues within OpenAI...」 主要步驟分解過程 (with code): 知識切片成Chunk:將文檔拆分成較小的片段。 第一步要做的是將 pdf 內文切成小區塊(chunk),使用 langchain 來做切割 from langchain.document_loaders import PyPDFLoader from langchain.text_splitter import CharacterTextSplitter import re # 將一份 PDF 切成多個 chunks # 用 \n 分割, 每個 chunk size 上限為 256 def pdf_to_chunk(path, start_page=1, end_page=None): # 使用 PyPDFLoader 來加載 PDF 文件 loader = PyPDFLoader(path) # 將 PDF 文件的每一頁作為單獨的對象加載到 pages 列表中 pages = loader.load() # 計算 PDF 文件的總頁數 total_pages = len(pages) # 創建一個 CharacterTextSplitter 對象 # 用來將文本分割成多個塊 text_splitter = CharacterTextSplitter( separator="\\n", # 使用 "\n" 作為分隔符 chunk_size=256, # 每個塊的最大字符數為 256 chunk_overlap=20 # 塊之間有 20 個字符的重疊 ) # 如果沒有指定結束頁面,則默認處理到文檔的最後一頁 if end_page is None: end_page = len(pages) # 用來存儲所有塊的列表 lst_text = [] # 循環處理指定頁面範圍內的每一頁 for i in range(start_page-1, end_page): # 將當前頁面的文本分割成多個塊 chuncks = text_splitter.split_text(pages[i].page_content) # 遍歷每一個塊 for chunk in chuncks: # 使用正則表達式去除多餘的空白字符 text = re.sub(r'\\s+', ' ', chunk) # 將處理後的文本塊添加到列表中 lst_text.append(text) # 返回包含所有文本塊的列表 return lst_text # 調用 pdf_to_chunk 函數,將一個 PDF 文件的內容分割為多個文本塊 lst_chunk = pdf_to_chunk('your_file.pdf') # 替換 'your_file.pdf' 為實際的 PDF 文件路徑 # 打印第一個文本塊以驗證結果 print(lst_chunk[0]) 向量化Chunk入庫:將這些片段轉換為向量並存儲在數據庫中。 向量 DB 向量 DB 顧名思義就是用來儲存向量的,你可以把一張圖片或一段文字轉換成一個向量,再儲存至向量 DB 中,如何轉成向量的呢?是透過 Embedding 的技術,這邊就不詳談。 向量 DB 有很多種,可以參考 Ref [1] 有很多介紹,最後推薦的向量 DB 是 Qdrant,因其優秀的效能,RPS 遠高於其它對手,所以我也採用 Qdrant 來進行實作。 Embedding 嵌入 接著要將剛剛切好的每個 chunk 轉成向量,使用 Sentence-Transformers 套件,有各種 embedding 的 model 可以使用,可以參考 Ref [3],我選擇了速度快、較輕量、Performance 不錯的「all-MiniLM-L12-v2」,可以將每個 chunk 轉成 384 維的向量。 from sentence_transformers import SentenceTransformer # 將文本塊轉換為向量表示 def chunk_to_vector(chunks): # 初始化使用的預訓練模型 model = SentenceTransformer('all-MiniLM-L12-v2') # 將文本塊列表轉換為向量表示 arr_vector = model.encode(chunks) # 返回向量列表 return arr_vector # 調用 chunk_to_vector 函數,將文本塊列表轉換為向量表示 # arr_vectors 的形狀為 (n, 384),其中 n 是文本塊的數量,384 是向量的維度 arr_vectors = chunk_to_vector(lst_chunk) # 打印向量數組的形狀以驗證結果 print(arr_vectors.shape) # 例如輸出: (57, 384) 寫入 Qdrant 目前已經將 ””.pdf 切成 57 個 chunk,且已經轉換成向量,接下來就是要將這些向量寫入 Qdrant,供後續使用。 from qdrant_client import QdrantClient from qdrant_client.http import models from qdrant_client.http.models import PointStruct # 連接到 Qdrant 並創建一個集合 def connection(v_dim, collection): # v_dim: 向量維度, collection: 類似於資料庫中的表名稱 # 創建一個 Qdrant 客戶端,連接到本地 Qdrant 伺服器 client = QdrantClient("http://localhost:6333") # 重新創建一個集合(如果存在則刪除並重新創建) client.recreate_collection( collection_name=collection, # 集合名稱,相當於資料庫中的表名稱 vectors_config=models.VectorParams( distance=models.Distance.COSINE, # 使用余弦距離作為向量相似度度量 size=v_dim # 向量的維度,例如 384 ), optimizers_config=models.OptimizersConfigDiff(memmap_threshold=20000), # 優化器配置,用於優化存儲和查詢性能 hnsw_config=models.HnswConfigDiff(on_disk=True, m=16, ef_construct=100) # HNSW 配置,用於加速向量搜索 ) # 返回 Qdrant 客戶端對象,用於後續的操作 return client # 將向量及其對應的數據插入到 Qdrant 集合中 def upsert_vector(client, collection, vectors, data): # vectors: 向量數組 (Array) # data: 與向量對應的數據列表 (List of dictionaries),如 [{"text": "example text"}] # 遍歷所有的向量及其對應的數據 for i, vector in enumerate(vectors): client.upsert( # 插入或更新向量和數據 collection_name=collection, # 指定要插入的集合 points=[PointStruct( # 定義要插入的數據點 id=i, # 每個數據點的唯一標識符,這裡使用索引值 vector=vectors[i], # 要插入的向量 payload=data[i] # 與該向量相關聯的數據(例如文本) )] ) # 准備將文本塊列表(chunks)轉換為包含文本的字典列表 lst_dic_chunk = [] for chunk in lst_chunk: lst_dic_chunk.append({"text": chunk}) # 將每個文本塊轉換為字典,並添加到列表中 # 連接到 Qdrant 伺服器並創建/重新創建集合 qclient = connection(v_dim=384, collection='重點') # 將向量和對應的文本數據插入到 Qdrant 集合中 upsert_vector( client=qclient, collection='重點', # 指定集合名稱 vectors=arr_vectors, # 已經計算好的向量數組 data=lst_dic_chunk # 對應的文本數據列表 ) Query檢索知識Chunk:根據查詢檢索相關的片段。 Semantic Search 語義搜索 接下來關鍵的步驟是 Semantic Search,我們要從向量 DB 中查詢與「問題」最相關的內容。如前言中,我們的問題是:”與參考問件相關的問題”。我們要從向量 DB 中找到與該問題最相關的 3 個 chuuk,最後會將這些 chunk 連同問題一併餵給 OpenAI。 from qdrant_client import QdrantClient from qdrant_client.http import models from sentence_transformers import SentenceTransformer import numpy as np # 創建一個 Qdrant 客戶端,連接到本地 Qdrant 伺服器 qclient = QdrantClient("http://localhost:6333") # 初始化 SentenceTransformer 模型,用於將文本轉換為向量 model = SentenceTransformer('all-MiniLM-L12-v2') # 定義要查詢的問題文本 question = '請問台北捷運 Tpass 1200 月票是何時發行的,請用繁體中文回答' # 將問題文本編碼為向量表示 question_emb = model.encode(question) # 使用 Qdrant 進行向量搜索 # - collection_name: 指定要查詢的集合名稱 ('Tpass') # - query_vector: 用於查詢的向量 (question_emb) # - limit: 限制返回的結果數量 (3) result = qclient.search(collection_name='Tpass', query_vector=question_emb, limit=3) # 打印搜索結果 print(result) 構建Prompts:結合檢索到的片段與查詢形成提示。 調用LLM生成回答:使用提示生成準確的回答。 client = OpenAI() completion = client.chat.completions.create( model="gpt-3.5-turbo", messages=[ {"role": "system", "content": "你是一位聰明的 AI 助理"}, {"role": "user", "content": prompt} ] ) answer = completion.choices[0].message.content print(answer) # 根據提供的文件內容,台北捷運 Tpass 1200 月票是在112年6月15日開始發行的。 以下為LangChain+ChatGLM的一個RAG工作流程(上課會仔細講解): Reference:全端 LLM 應用開發(向量資料庫, Hugging Face, OpenAI, Azure ML, LangChain, FastAPI and more) :: 2023 iThome 鐵人賽