背景幾個月前,基于知識的問答系統(Knowledge Base Question Answering,KBQA)還是個新概念。 現在,隨著大型語言模型(LLMs)的發展,帶有檢索增強生成(RAG)的KBQA對我們來說變得越來越容易了。 知識圖譜知識圖譜(Knowledge Graph)是用來表示現實世界中的實體及其關系的一種數據結構。
它通過節點 (表示實體)和邊 (表示實體間的關系)來構建一個連通的網絡。 
知識圖譜的主要目的是將分散的數據組織成有結構的信息,使得機器能夠理解和處理這些信息,從而在搜索、問答、推薦等應用中提供更智能的服務。 知識圖譜的基本組成部分1、實體(Entity):知識圖譜中的基本單元,代表現實世界中的對象,比如人、地點、事物等。 2、屬性(Attribute):描述實體的特征,比如人的姓名、年齡,地點的名稱、坐標等。 3、關系(Relation):表示實體之間的連接,比如“Tom 是 Mary 的朋友”這類描述實體間的關系。 舉個例子Mary had a little lamb, You’ve heard this tale before; But did you know she passed her plate, And had a little more! 繪制成KG,如下: 
為什么要使用知識圖譜?知識圖譜在很多方面都很有用。 1、可以運行圖算法并計算任何節點的中心性,以了解一個概念(節點)在整個工作體系中的重要性。 2、可以分析連接和斷開的概念集合,或者計算概念群體,以深入理解主題內容。 3、還能理解看似不相關的概念之間的聯系。 舉例來說,僅僅依靠簡單的語義相似性搜索來找到與查詢最相關的上下文并不總是有效的,尤其是當查詢沒有提供足夠的上下文來明確其真實意圖,或者當上下文分散在大量文本中時。 比如,考慮這個查詢: 告訴我《百年孤獨》中何塞·阿爾卡迪奧·布恩迪亞的家譜。
這本書中記錄了七代的何塞·阿爾卡迪奧·布恩迪亞,而且一半的人物都叫何塞·阿爾卡迪奧·布恩迪亞。 如果使用簡單的 RAG 流程來回答這個查詢,可能會非常具有挑戰性,甚至可能無法實現 。 另外,RAG 的另一個缺點是它無法引導你提出正確的問題 。在很多情況下,提出正確的問題比獲取答案更為關鍵。 圖增強生成(Graph Augmented generation,GAG)在一定程度上可以解決 RAG 的這些缺點。 更好的是,我們可以靈活組合,構建一個圖增強檢索增強生成流水線,以融合兩者的優點,達到事半功倍的效果。 將任何文本語料庫創建一個知識圖譜項目地址:https://rahulnyk./knowledge_graph/ 實現流程圖:
1、將文本語料庫分割成塊。為每個塊分配一個 chunk_id。 2、針對每個文本塊,使用大型語言模型提取概念及其語義關系。我們將這種關系賦予權重 W1。同一對概念之間可能存在多種關系。每種關系都是一對概念之間的邊。 3、考慮到在同一文本塊中出現的概念也會由于其上下文接近性而相關。我們將這種關系賦予權重 W2。請注意,同一對概念可能會在多個塊中出現。 4、將相似的對組合起來,求其權重之和,并串聯它們的關系?,F在,對于任意不同的概念對之間只有一條邊。該邊具有一定的權重和一系列關系作為其名稱。 5、它還計算每個節點的度數和節點的社區,分別用于調整圖中節點的大小和著色。 技術棧1、Mistral 7B使用 Mistral 7B Openorca 從文本塊中提取概念。它可以很好地遵循系統提示指令。
2、OllamaOllama 可以輕松在本地托管任何模型。Mistral 7B OpenOrca 版本已可與 Ollama 一起使用,開箱即用。
3、Pandas4、NetworkX這是一個 Python 庫,可以讓處理圖表變得超級簡單
5、PyvisPyvis 用于可視化的 Python 庫。Pyvis 使用 python 生成 Javascript 圖形可視化。
完整代碼1、初始化import pandas as pd import numpy as np import os from langchain.document_loaders import PyPDFLoader, UnstructuredPDFLoader, PyPDFium2Loader from langchain.document_loaders import PyPDFDirectoryLoader, DirectoryLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from pathlib import Path import random
## 輸入數據目錄 data_dir = 'cureus' inputdirectory = Path(f'./data_input/{data_dir}')
print(inputdirectory.absolute().as_uri()) ## 生成的結果文件(以CSV格式)存儲的位置 out_dir = data_dir outputdirectory = Path(f'./data_output/{out_dir}')
2、加載文檔loader = DirectoryLoader(inputdirectory, show_progress=True)
documents = loader.load()
splitter = RecursiveCharacterTextSplitter( chunk_size=1500, chunk_overlap=150, length_function=len, is_separator_regex=False, )
pages = splitter.split_documents(documents) print('chunks數量 = ', len(pages)) print(pages[3].page_content)

3、創建包含所有分塊的數據框from helpers.df_helpers import documents2Dataframe df = documents2Dataframe(pages) print(df.shape) df.head()

4、提取概念# 使用helpers/prompt函數從文本中提取概念 from helpers.df_helpers import df2Graph from helpers.df_helpers import graph2Df
如果regenerate設置為True,則會重新生成數據框,并將它們以CSV格式寫入,這樣就無需再次計算。數據框分為兩類: dfne 是邊的數據框(dataframe of edges)。 df 是塊的數據框(dataframe of chunks)。 如果regenerate為False,則數據框將從輸出目錄讀取,而不是重新計算。這意味著如果數據已經存在,程序將直接使用現有數據,無需再次處理。 # 要使用大語言模型(LLM)重新生成圖,將此設置為True。 regenerate = False
if regenerate: concepts_list = df2Graph(df, model='zephyr:latest') dfg1 = graph2Df(concepts_list) if not os.path.exists(outputdirectory): os.makedirs(outputdirectory) dfg1.to_csv(outputdirectory/'graph.csv', sep='|', index=False) df.to_csv(outputdirectory/'chunks.csv', sep='|', index=False) else: dfg1 = pd.read_csv(outputdirectory/'graph.csv', sep='|')
dfg1.replace('', np.nan, inplace=True) dfg1.dropna(subset=['node_1', 'node_2', 'edge'], inplace=True) dfg1['count'] = 4 # 將關系的權重增加到 4。 # 當計算上下文 proximity 時,將分配權重 1。 print(dfg1.shape) dfg1.head()
文章的每個文本塊運行此操作并將 json 轉換為 Pandas 數據幀: 
5、計算上下文 proximity(上下文相關性)def contextual_proximity(df: pd.DataFrame) -> pd.DataFrame: # 將數據框 melt(融化)為節點列表 dfg_long = pd.melt( df, id_vars=['chunk_id'], value_vars=['node_1', 'node_2'], value_name='node' ) dfg_long.drop(columns=['variable'], inplace=True)
# 用塊ID作為鍵進行自連接,會在同一個文本塊中出現的詞之間創建關聯。 # 使用塊ID作為鍵進行自連接將會創建一個在同一文本塊中出現的術語之間的關聯。 dfg_wide = pd.merge(dfg_long, dfg_long, on='chunk_id', suffixes=('_1', '_2'))
self_loops_drop = dfg_wide[dfg_wide['node_1'] == dfg_wide['node_2']].index dfg2 = dfg_wide.drop(index=self_loops_drop).reset_index(drop=True)
dfg2 = ( dfg2.groupby(['node_1', 'node_2']) .agg({'chunk_id': [','.join, 'count']}) .reset_index() ) dfg2.columns = ['node_1', 'node_2', 'chunk_id', 'count'] dfg2.replace('', np.nan, inplace=True) dfg2.dropna(subset=['node_1', 'node_2'], inplace=True)
dfg2 = dfg2[dfg2['count'] != 1] dfg2['edge'] = 'contextual proximity' return dfg2
dfg2 = contextual_proximity(dfg1) dfg2.tail()
得到一個與原始數據框非常相似的數據框。 
6、數據合并這2個Dataframedfg = pd.concat([dfg1, dfg2], axis=0) dfg = ( dfg.groupby(['node_1', 'node_2']) .agg({'chunk_id': ','.join, 'edge': ','.join, 'count': 'sum'}) .reset_index() )

7、計算 NetworkX 圖nodes = pd.concat([dfg['node_1'], dfg['node_2']], axis=0).unique() nodes.shape
輸出:(215,) import networkx as nx G = nx.Graph()
## 添加節點 for node in nodes: G.add_node( str(node) )
## 添加邊 for index, row in dfg.iterrows(): G.add_edge( str(row['node_1']), str(row['node_2']), title=row['edge'], weight=row['count']/4 )
8、對節點進行著色communities_generator = nx.community.girvan_newman(G) top_level_communities = next(communities_generator) next_level_communities = next(communities_generator) communities = sorted(map(sorted, next_level_communities)) # 社區是指圖中的一組節點 print('社區數量 = ', len(communities)) print(communities)
9、創建一個數據表(dataframe)來記錄每個社區的顏色import seaborn as sns palette = 'hls'
## Now add these colors to communities and make another dataframe # 在上一個問題中,我們已經創建了一個數據表 community\_colors,用于存儲每個社區的唯一顏色。 # 現在,我們需要將這些顏色添加到社區檢測算法的輸出 results 中,并創建一個新的數據表 community\_info,其中包含每個節點的社區 ID 和社區顏色信息。 def colors2Community(communities) -> pd.DataFrame: ## Define a color palette p = sns.color_palette(palette, len(communities)).as_hex() random.shuffle(p) rows = [] group = 0 for community in communities: color = p.pop() group += 1 for node in community: rows += [{'node': node, 'color': color, 'group': group}] df_colors = pd.DataFrame(rows) return df_colors
colors = colors2Community(communities) colors

10、給圖添加顏色for index, row in colors.iterrows(): G.nodes[row['node']]['group'] = row['group'] G.nodes[row['node']]['color'] = row['color'] G.nodes[row['node']]['size'] = G.degree[row['node']]
# 將一個 NetworkX 圖形 G 轉換為一個交互式的網絡可視化,并將其保存為 HTML 文件。 from pyvis.network import Network
graph_output_directory = './docs/index.html'
net = Network( notebook=False, # bgcolor='#1a1a1a', cdn_resources='remote', height='900px', width='100%', select_menu=True, # font_color='#cccccc', filter_menu=False, )
net.from_nx(G) # net.repulsion(node_distance=150, spring_length=400) net.force_atlas_2based(central_gravity=0.015, gravity=-31) # net.barnes_hut(gravity=-18100, central_gravity=5.05, spring_length=380) net.show_buttons(filter_=['physics'])
net.show(graph_output_directory, notebook=False)
輸出:./docs/index.html。 全部節點 
選擇需要查詢的節點: 
|