Repository: https://github.com/snap-stanford/KGReasoning Stars: 312 | Forks: 63 | Language: Python | License: MIT Topics: knowledge-graph, knowledge-base, embedding, reasoning
1. 專案概覽 (Project Overview)
1.1 專案背景與研究團隊
KGReasoning 是史丹佛大學 SNAP (Stanford Network Analysis Platform; 史丹佛網路分析平台) 實驗室釋出的官方程式碼庫,核心作者為 Hongyu Ren 與 Jure Leskovec 教授。這個 repo 是論文《Beta Embeddings for Multi-Hop Logical Reasoning in Knowledge Graphs (BetaE; Beta 嵌入式多跳邏輯推理)》(NeurIPS 2020) 的官方 PyTorch 實作,同時也整合了同系列另外兩個經典模型 —— Query2box (2020) 與 GQE (Graph Query Embedding; 圖查詢嵌入, 2018) —— 讓使用者能在同一套框架下比較三種 knowledge graph (KG; 知識圖譜) 多跳推理方法。
SNAP 實驗室長期專注於大規模圖形資料的表示學習 (representation learning; 表徵學習),KGReasoning 屬於其「query embedding (查詢嵌入)」研究脈絡的核心產出,與同實驗室的 OGB (Open Graph Benchmark; 開放圖形基準)、GraphSAGE、PinSAGE 等專案共享研究哲學:用簡潔、可重現的程式碼實作發表在頂級會議 (NeurIPS/ICLR) 的演算法。
1.2 解決什麼問題、為什麼重要
傳統的 knowledge graph completion (KGC; 知識圖譜補全) 只回答「單跳」問題,例如:「誰是《駭客任務》的導演?」對應圖上的一條邊 (駭客任務, 導演, ?)。但真實世界的問題往往是多跳且帶邏輯運算子的複雜查詢 (complex logical query; 複雜邏輯查詢),例如:
「找出曾與『克里斯多福·諾蘭』合作過,但『沒有』得過奧斯卡獎的演員,這些演員還演過哪些『科幻片』?」
這種查詢同時包含:
- 投影 (projection; p):沿著關係邊走一步(誰演過諾蘭的電影?)
- 交集 (intersection; i):多條件同時成立(既演過諾蘭電影、又演過科幻片)
- 否定 (negation; n):排除某條件(沒得過奧斯卡)
- 聯集 (union; u):多選一成立即可
若要在真實 KG(通常有數百萬節點、且不完整——存在遺漏的邊)上直接用符號式圖遍歷 (graph traversal; 圖遍歷) 回答這類查詢,會面臨兩大困境:
- 組合爆炸:多跳查詢的搜尋空間隨跳數指數成長。
- 不完整性:KG 本身缺邊,符號式方法答不出「隱含但未直接記錄」的答案。
KGReasoning 的核心貢献是把「邏輯查詢」轉換成「嵌入空間中的幾何運算」——每個實體是空間中的一個點/區域,每個邏輯運算子(投影/交集/聯集/否定)對應一個可學習的神經網路運算子。這樣一來,回答查詢就變成「在嵌入空間做幾何運算 + 最近鄰搜尋」,既能處理不完整的 KG(透過泛化能力補全隱含答案),複雜度也不再隨跳數爆炸(每一跳只是一次向量運算)。
1.3 在 snap-stanford 生態系中的定位
KGReasoning 是 SNAP query-embedding 系列研究的「集大成」版本:
flowchart LR
A["GQE (2018)
幾何:向量
邏輯:僅交集"] --> B["Query2box (2020)
幾何:超矩形盒子
邏輯:交集"]
B --> C["BetaE (2020)
幾何:Beta 分布
邏輯:交集+聯集+否定"]
C --> D["KGReasoning repo
統一框架整合三者"]
D --> E["下游生態
SMORE / ULTRA 等
後續 SNAP 查詢嵌入研究"]
此 repo 本身不含後續模型(如 SMORE 大規模訓練框架、或更晚期的 pre-training 式 KG 推理模型),但作為「baseline 官方實作」被大量後續論文引用與比較,是進入這個研究子領域的標準入口。
1.4 相關論文引用
1@inproceedings{ren2020beta,
2 title={Beta Embeddings for Multi-Hop Logical Reasoning in Knowledge Graphs},
3 author={Hongyu Ren and Jure Leskovec},
4 booktitle={Neural Information Processing Systems},
5 year={2020}
6}
延伸論文(repo 中實作但另外發表):
- Query2box: Query2box: Reasoning over Knowledge Graphs in Vector Space Using Box Embeddings (arXiv:2002.05969)
- GQE: Embedding Logical Queries on Knowledge Graphs (arXiv:1806.01445)
2. 核心架構 (Core Architecture)
2.1 系統架構圖
KGReasoning 只有 6 個核心 Python 檔案,架構極度精簡但職責分明:
flowchart TB
subgraph sg1["資料準備層"]
A["create_queries.py
從 KG 邊產生訓練/驗證/測試查詢"]
end
subgraph sg2["資料載入層"]
B["dataloader.py
TrainDataset / TestDataset
負採樣 + collate_fn"]
end
subgraph sg3["模型層 models.py"]
C["KGReasoning (nn.Module)
三種幾何運算子切換"]
C1["GQE:CenterIntersection
(向量 + 注意力交集)"]
C2["Query2box:CenterIntersection
+ BoxOffsetIntersection
(盒子中心+偏移)"]
C3["BetaE:BetaIntersection
+ BetaProjection
(Beta 分布參數)"]
C --> C1
C --> C2
C --> C3
end
subgraph sg4["工具層"]
D["util.py
query_structure 序列化
list2tuple/flatten"]
end
subgraph sg5["訓練/評估層"]
E["main.py
argparse 設定
訓練迴圈 + 評估迴圈
TensorBoard 記錄"]
end
A --> B
D --> B
B --> E
C --> E
D --> C
2.2 關鍵模組、類別與資料流說明
| 檔案 | 關鍵類別/函式 | 角色 |
|---|---|---|
create_queries.py | index_dataset, query sampling functions | 從 train.txt/valid.txt/test.txt 三元組出發,透過圖上隨機遊走 (random walk) 產生 14 種查詢結構的訓練/驗證/測試樣本 |
dataloader.py | TrainDataset, TestDataset, SingledirectionalOneShotIterator | 將查詢 + 答案包裝成 PyTorch Dataset;TrainDataset 做負採樣 (negative sampling),TestDataset 對每個查詢枚舉全部實體算分 |
util.py | list2tuple, tuple2list, flatten, eval_tuple, parse_time, set_global_seed | 查詢結構本質是巢狀 tuple,如 (e, (r,)) 代表 1-hop 查詢;這些函式負責在 list/tuple 間轉換以支援 pickle 序列化與 batch 攤平 |
models.py | KGReasoning, CenterIntersection, BoxOffsetIntersection, BetaIntersection, BetaProjection, Regularizer | 核心模型;依 --geo 參數 (vec/box/beta) 切換三種前向傳播路徑 forward_vec/forward_box/forward_beta |
main.py | parse_args, evaluate, main() | CLI 入口,組裝 dataloader + model + optimizer,執行訓練迴圈與 MRR/Hit@K 評估 |
資料流(以 BetaE 訓練一步為例):
flowchart LR
Q["查詢 tuple
((e,(r,)),(e,(r,)))
= 2i 查詢"] --> EMB1["實體嵌入
Beta(alpha, beta)"]
EMB1 --> PROJ["BetaProjection
沿關係 r 投影"]
PROJ --> INTER["BetaIntersection
注意力加權合併
兩條路徑的 alpha/beta"]
INTER --> DIST["KL 散度
查詢分布 vs 候選答案分布"]
DIST --> LOSS["margin loss
正樣本距離小
負樣本距離大"]
2.3 設計哲學與技術選擇分析
為何用 query_structure 當 dictionary key? 14 種查詢類型(1p/2p/3p/2i/3i/ip/pi/2in/3in/inp/pin/pni/2u/up)在程式碼中被編碼成巢狀 tuple(如 main.py 中的 query_name_dict),因為 tuple 是 Python 中唯一「可雜湊 (hashable)」的巢狀容器,能直接當 defaultdict 的 key,把「同結構的查詢」自動分組批次處理——這是一個很聰明但也犧牲可讀性的設計取捨(原始碼註解自嘲 “in case we run out of names”)。
為何三種模型共用一個 KGReasoning class? 作者選擇用單一 nn.Module、內部依 self.geo 分支呼叫不同 forward_* 方法,而非三個獨立 class。優點是共用資料載入、訓練迴圈、評估邏輯,程式碼量小;代價是 models.py 內有不少 if self.geo == 'box' / 'vec' / 'beta' 的條件分支,違反單一職責原則,但對一個研究比較型 repo 而言,這種「犧牲一點物件導向純度換取實驗切換方便性」是合理取捨。
幾何表示法的演進邏輯:
- GQE 用純向量 + 注意力交集,最簡單但無法表達「不確定性」與「聯集/否定」。
- Query2box 用超矩形盒子(中心 + 偏移量),盒子的體積天然表達「答案集合的不確定範圍」,交集運算變成盒子的幾何相交。
- BetaE 用 Beta 分布(用 alpha/beta 兩個參數描述機率密度),比盒子更能表達「模糊/機率性」的答案集合,且天生支援聯集與否定(分布的補集運算),是三者中表達力最強的版本。
3. 安裝與設定 (Installation & Setup)
3.1 環境需求
- Python 3.6+(原始碼使用
from __future__ import相容寫法,判斷是舊版 PyTorch 生態) - PyTorch(GPU 版本,建議支援 CUDA)
- 套件:
numpy,tqdm,tensorboardX,click(create_queries.py用click做 CLI) - GPU:官方範例全部用
CUDA_VISIBLE_DEVICES=0,在 CPU 上訓練 450,001 steps 幾乎不可行;建議至少一張 8GB+ 顯存的 GPU(hidden_dim=800時嵌入矩陣會佔用可觀顯存) - 磁碟空間:KG 資料集(FB15k / FB15k-237 / NELL995)解壓後約數 GB
3.2 完整安裝步驟
依照本機規範,Python 環境一律用 uv 建立隔離虛擬環境,避免污染系統 Python:
1# 1. clone 專案
2git clone https://github.com/snap-stanford/KGReasoning.git
3cd KGReasoning
4
5# 2. 用 uv 建立虛擬環境並安裝依賴
6uv venv --python 3.10
7source .venv/bin/activate
8
9# 3. 安裝 PyTorch(依你的 CUDA 版本調整,以下示範 CUDA 12.1)
10uv pip install torch --index-url https://download.pytorch.org/whl/cu121
11
12# 4. 安裝其餘依賴
13uv pip install numpy tqdm tensorboardX click
注意:原始 repo 沒有附
requirements.txt,上述套件清單是根據 import 陳述式反推的最小集合。若要長期維護,建議自行補一份requirements.txt或pyproject.toml。
3.3 資料集下載與準備
官方提供已經預處理好查詢的資料集打包檔:
1# 下載 BetaE/Query2box 論文使用的三個 KG(FB15k、FB15k-237、NELL995)
2wget http://snap.stanford.edu/betae/KG_data.zip
3unzip KG_data.zip -d data/
資料夾內每個 KG 包含:
| 檔案 | 內容 |
|---|---|
train.txt / valid.txt / test.txt | KG 的原始三元組邊 (head, relation, tail) |
id2rel.pkl / rel2id.pkl / ent2id.pkl / id2ent.pkl | 實體與關係的 ID 對照表 |
train-queries.pkl / valid-queries.pkl / test-queries.pkl | defaultdict(set),key 為查詢結構,value 為該結構下的具體查詢實例 |
train-answers.pkl | 訓練圖(僅 train.txt)下每個查詢的答案集合 |
valid-easy-answers.pkl / test-easy-answers.pkl | 在訓練圖中就能找到的「簡單」答案 |
valid-hard-answers.pkl / test-hard-answers.pkl | 只有加入 valid/test 邊後才能發現的「困難」答案(真正考驗泛化能力的部分) |
若想從零自建查詢(例如換成自己的 KG),使用 create_queries.py:
1python create_queries.py --dataset FB15k-237 --gen_train --gen_valid --gen_test \
2 --max_ans_num 100 --index_only
--index_only 會先幫實體/關係編號存成 ent2id.pkl 等對照表,後續再不加此旗標執行以產生實際查詢 pickle。
3.4 常見問題排解
| 問題 | 原因 | 解法 |
|---|---|---|
CUDA out of memory | hidden_dim(-d)設太大或 batch_size(-b)太大 | 官方範例 FB15k-237 用 -d 800 -b 512;顯存不足先降 -b 到 128 或 256 |
| 訓練極慢 | --cpu_num 設太低導致 DataLoader 成瓶頸 | 依 CPU 核心數調高 -cpu(官方範例用 1,但那是配合大 batch GPU 吃滿;一般機器可調到 4-8) |
| pickle 版本不相容 | Python 2/3 或 numpy 版本差異導致 pickle 讀取失敗 | 確認用官方打包的 .pkl 搭配對應 Python 版本讀取,或自行用 create_queries.py 重新產生 |
--geo box 訓練不收斂 | -boxm 的 center_reg 參數(如 0.02)需要調整 | 依論文附錄的超參數表逐一嘗試,不同資料集最佳值不同 |
| TensorBoard 沒有圖 | SummaryWriter 預設寫入 save_path 下 | 執行 tensorboard --logdir <save_path> 並確認路徑正確 |
4. 核心概念詳解 (Key Concepts)
4.1 查詢結構 (Query Structure) 是什麼
想像知識圖譜是一張巨大的人際關係圖。「查詢結構」描述的是「你要沿著這張圖做什麼形狀的搜尋」,而非「具體搜誰」。例如:
1p(1-hop projection):(e, (r,))—— 「從實體 e 出發,走一條關係 r 的邊」,如「誰是 e 的導演?」2i(2-way intersection):((e1,(r1,)), (e2,(r2,)))—— 「同時滿足兩個 1-hop 條件的交集」,如「既是 e1 的導演、又是 e2 的演員」2in:帶否定的交集,如「是 e1 的導演,但不是 e2 的演員」
這就像是 SQL 查詢的「查詢計畫(query plan)」——你不寫死「搜哪個人」,而是描述搜尋的形狀(幾個 JOIN、幾個 WHERE NOT),模型再依這個形狀去執行對應的幾何運算。
4.1.1 完整 14 種查詢結構對照表
main.py 的 query_name_dict 定義了全部支援的查詢形狀。理解這張表是讀懂整個 repo 的關鍵,因為訓練資料、評估指標、模型的 forward_* 分支全都圍繞這 14 種結構展開:
| 縮寫 | tuple 結構 | 中文語意 | 對應邏輯運算 | 類比自然語言查詢 |
|---|---|---|---|---|
1p | (e, (r,)) | 1 跳投影 | 單一關係投影 | 「e 的導演是誰?」 |
2p | (e, (r, r)) | 2 跳投影 | 兩次連續投影 | 「e 導演的其他電影的演員有誰?」 |
3p | (e, (r, r, r)) | 3 跳投影 | 三次連續投影 | 「…的…的…是誰?」(三層鏈式關係) |
2i | ((e,(r,)), (e,(r,))) | 2 路交集 | 兩個 1p 的交集 | 「同時是 e1 的導演、e2 的演員的人」 |
3i | ((e,(r,)), (e,(r,)), (e,(r,))) | 3 路交集 | 三個 1p 的交集 | 三個條件同時成立 |
ip | (((e,(r,)),(e,(r,))), (r,)) | 交集後投影 | 先交集、再投影一次 | 「(A 且 B) 的合作對象是誰?」 |
pi | ((e,(r,r)), (e,(r,))) | 投影後交集 | 一個 2p 與一個 1p 交集 | 「(A 的合作對象) 且 (B 的條件)」 |
2in | ((e,(r,)), (e,(r,n))) | 2 路交集帶否定 | 一個正條件、一個否定條件 | 「是 e1 的導演,但不是 e2 的演員」 |
3in | ((e,(r,)),(e,(r,)),(e,(r,n))) | 3 路交集帶否定 | 兩正一負 | 「同時滿足 A、B,但不滿足 C」 |
inp | (((e,(r,)),(e,(r,n))), (r,)) | 否定交集後投影 | 2in 再投影 | 「(A 且非 B) 的合作對象」 |
pin | ((e,(r,r)), (e,(r,n))) | 投影後否定交集 | 2p 與否定 1p 交集 | 「(A 的合作對象) 且非 (B 的條件)」 |
pni | ((e,(r,r,n)), (e,(r,))) | 否定投影後交集 | 投影鏈末端帶否定,再交集 | 「非 (A 的合作對象),但滿足 B」 |
2u-DNF | ((e,(r,)),(e,(r,)),(u,)) | 2 路聯集(析取範式展開) | A 或 B | 「是 e1 的導演,或是 e2 的演員」 |
up-DNF | (((e,(r,)),(e,(r,)),(u,)), (r,)) | 聯集後投影(DNF) | 2u 再投影一次 | 「(A 或 B) 的合作對象」 |
2u-DM | (((e,(r,n)),(e,(r,n))), (n,)) | 2 路聯集(De Morgan 展開) | 用雙重否定實作聯集 | 同 2u-DNF,但走 BetaE 的機率補集路徑 |
up-DM | (((e,(r,n)),(e,(r,n))), (n,r)) | 聯集後投影(De Morgan) | 2u-DM 再投影 | 同 up-DNF,走 De Morgan 路徑 |
DNF vs De Morgan 的差異(對應 main.py 的 --evaluate_union 參數,可選 DNF 或 DM):
- DNF (Disjunctive Normal Form; 析取範式):直接把聯集查詢拆成多個交集子查詢分別算距離,再取最小值(最像答案的那個子查詢決定分數)。GQE/Query2box 用此法,因為它們的幾何表示沒有原生的「否定」運算,無法走 De Morgan 路徑。
- De Morgan 轉換:利用邏輯恆等式
A ∪ B = ¬(¬A ∩ ¬B),把聯集運算轉換成「否定 + 交集 + 否定」的組合。只有 BetaE 能用此法,因為 Beta 分布天生支援否定(補集)運算,這也是論文能同時處理聯集與否定的關鍵設計。
4.2 為何用嵌入空間做邏輯運算(類比:地圖上的形狀運算)
把每個實體想成地圖上的一個點,每個查詢的「候選答案集合」想成地圖上的一塊區域:
flowchart LR
subgraph sg2["Query2box 直覺"]
B1["實體 e1
(一個點)"] -->|"沿關係 r1
投影"| B2["候選答案區域
(一個盒子)"]
B3["實體 e2
(一個點)"] -->|"沿關係 r2
投影"| B4["候選答案區域
(另一個盒子)"]
B2 --> B5["盒子相交
= 交集運算"]
B4 --> B5
B5 --> B6["落在交集盒子內的
實體 = 查詢答案"]
end
- 投影 (projection):從一個點出發,「走一步關係」相當於把這個點移動/擴散成一個新區域(因為一個 head 可能對應多個 tail,答案不再是單點而是一群候選)。
- 交集 (intersection):兩個區域的共同部分,就是同時滿足兩個條件的答案。
- 聯集 (union):多個區域的合併,滿足任一條件即可。
- 否定 (negation):BetaE 用 Beta 分布的特性可以直接定義「補集」的分布形狀,這是盒子模型做不到的(盒子沒有天然的「否定」幾何操作,Query2box 論文因此不支援否定查詢)。
4.3 三種幾何表示法的直覺比較
| 模型 | 幾何表示 | 直覺類比 | 能否表達否定/聯集 |
|---|---|---|---|
| GQE | 單一向量點 + 注意力加權平均 | 用「一個座標」代表答案的重心 | 否(只有交集) |
| Query2box | 中心點 + 偏移量 = 一個超矩形盒子 | 用「一個範圍框」代表所有可能答案,框越大表示越不確定 | 支援聯集(用 DNF/析取範式展開),不支援否定 |
| BetaE | 每個維度一個 Beta(α, β) 分布 | 用「機率密度」代表答案的信心分布,分布越平代表越不確定 | 支援聯集 + 否定(機率補集運算天然存在) |
一個直觀的類比:GQE 像用一個「點」猜答案在哪;Query2box 像用一個「畫框」圈出答案可能在的範圍;BetaE 像用一張「熱力圖」表達每個位置是答案的機率高低——熱力圖天生就能表達「非(NOT)」(把熱力圖反轉)與「或(OR)」(把兩張熱力圖疊加取最大值),這是點和框做不到的。
4.4 訓練目標:Margin Loss 與距離度量
三種模型都用「距離越近越像答案」的對比學習框架:
flowchart TB
A["查詢嵌入 q"] --> C{"距離函式
(依 geo 不同)"}
B1["正樣本答案嵌入 t+"] --> C
B2["負採樣答案嵌入 t-"] --> C
C --> D["GQE/Query2box:
L2 距離或盒子距離"]
C --> E["BetaE:
KL 散度"]
D --> F["margin loss
拉近正樣本、推遠負樣本"]
E --> F
- GQE/Query2box 用類似 TransE 的距離度量(歐氏距離),配合
gamma(margin,邊界值)做 hinge loss。 - BetaE 用 KL 散度 (Kullback-Leibler divergence) 衡量「查詢分布」與「答案分布」的差異,這是機率分布特有的距離度量,向量/盒子模型沒有對應概念。
4.5 評估指標:MRR 與 Hit@K(類比:搜尋引擎排名品質)
main.py 的 evaluate() 函式對每個查詢計算兩類指標,概念上等同於搜尋引擎排名評估:
- MRR (Mean Reciprocal Rank; 平均倒數排名):對每個查詢,把所有實體依「與查詢嵌入的距離」排序,找出正確答案排在第幾名,取倒數(第 1 名 = 1.0,第 10 名 = 0.1),再對所有查詢取平均。這個指標對「答案排名極前面」特別敏感——就像使用者只會看搜尋結果的第一頁,答案若排第 1 名比排第 5 名的加分幅度遠大於排第 100 名比排第 105 名。
- Hit@K(K 通常取 1/3/10):正確答案是否落在前 K 名之內的比例,比 MRR 更直觀但區分度較低。
Easy vs Hard 答案的評估邏輯(對應 valid-easy-answers.pkl 與 valid-hard-answers.pkl 的區分):評估時模型必須先把 Easy 答案(訓練圖中就查得到的答案)從候選排名中「過濾」掉,只針對 Hard 答案(只有加入驗證/測試邊後才浮現的答案)計算排名——這確保評估的是模型「推理出圖中隱含新知識」的能力,而不是死記訓練資料就能拿到高分。這個設計呼應第 1.2 節提到的「不完整性」問題核心。
flowchart LR
A["查詢 q"] --> B["對全部實體計算距離分數"]
B --> C["依分數排序全部實體"]
C --> D["過濾掉 Easy Answers
(訓練圖已知答案)"]
D --> E["找出 Hard Answer 的排名"]
E --> F["MRR = 1/排名
Hit@K = 排名<=K ? 1 : 0"]
5. 使用方式與程式碼範例 (Usage & Code Examples)
5.1 基礎用法:訓練 BetaE 模型
以下完整指令對應 example.sh 中 FB15k-237 資料集上的 BetaE 訓練設定:
1#!/usr/bin/env bash
2set -euo pipefail
3
4CUDA_VISIBLE_DEVICES=0 python main.py --cuda --do_train --do_valid --do_test \
5 --data_path data/FB15k-237-betae \
6 -n 128 \
7 -b 512 \
8 -d 400 \
9 -g 60 \
10 -lr 0.0001 \
11 --max_steps 450001 \
12 --cpu_num 1 \
13 --geo beta \
14 --valid_steps 15000 \
15 -betam "(1600,2)"
參數說明:
-n 128:每個正樣本配 128 個負採樣實體-b 512:batch size,每步處理 512 個查詢-d 400:嵌入維度(entity_dim = relation_dim = 400)-g 60:margin(gamma),BetaE 用 KL 散度所以 margin 值通常比向量模型(如 GQE 用 24)設得更大-betam "(1600,2)":BetaProjection 的 (hidden_dim, num_layers),即關係投影 MLP 用隱藏層 1600 維、2 層網路
5.2 進階用法:比較三種模型的訓練指令
1#!/usr/bin/env bash
2set -euo pipefail
3
4DATA=data/FB15k-237-betae
5TASKS="1p.2p.3p.2i.3i.ip.pi.2u.up"
6
7# GQE (向量模型):只支援交集,不支援否定查詢
8CUDA_VISIBLE_DEVICES=0 python main.py --cuda --do_train --do_valid --do_test \
9 --data_path "$DATA" -n 128 -b 512 -d 800 -g 24 \
10 -lr 0.0001 --max_steps 450001 --cpu_num 1 --geo vec \
11 --valid_steps 15000 --tasks "$TASKS"
12
13# Query2box (盒子模型):支援聯集,用 DNF 展開處理
14CUDA_VISIBLE_DEVICES=0 python main.py --cuda --do_train --do_valid --do_test \
15 --data_path "$DATA" -n 128 -b 512 -d 400 -g 24 \
16 -lr 0.0001 --max_steps 450001 --cpu_num 1 --geo box \
17 -boxm "(none,0.02)" --valid_steps 15000 --tasks "$TASKS"
18
19# BetaE (Beta 分布模型):完整支援否定查詢(額外測試 2in/3in/inp/pin/pni)
20CUDA_VISIBLE_DEVICES=0 python main.py --cuda --do_train --do_valid --do_test \
21 --data_path "$DATA" -n 128 -b 512 -d 400 -g 60 \
22 -lr 0.0001 --max_steps 450001 --cpu_num 1 --geo beta \
23 -betam "(1600,2)" --valid_steps 15000 \
24 --tasks "1p.2p.3p.2i.3i.ip.pi.2in.3in.inp.pin.pni.2u.up"
5.3 從 checkpoint 續訓或只做評估
1# 只做測試(載入既有 checkpoint,不重新訓練)
2CUDA_VISIBLE_DEVICES=0 python main.py --cuda --do_test \
3 --data_path data/FB15k-237-betae \
4 --geo beta -d 400 -betam "(1600,2)" \
5 --checkpoint_path logs/FB15k-237-betae/beta/xxxx-xx-xx
5.4 程式化讀取查詢資料的範例
若要在自己的分析腳本中檢視查詢資料的結構(例如做資料探索或除錯),可以這樣讀取:
1"""
2檢視 KGReasoning 查詢資料集的結構範例
3"""
4import pickle
5from collections import defaultdict
6
7DATA_PATH = "data/FB15k-237-betae"
8
9# 讀取查詢(key=查詢結構 tuple, value=set of 查詢實例)
10with open(f"{DATA_PATH}/test-queries.pkl", "rb") as f:
11 test_queries = pickle.load(f)
12
13# 讀取答案
14with open(f"{DATA_PATH}/test-hard-answers.pkl", "rb") as f:
15 test_hard_answers = pickle.load(f)
16
17query_name_dict = {
18 ('e', ('r',)): '1p',
19 (('e', ('r',)), ('e', ('r',))): '2i',
20 (('e', ('r',)), ('e', ('r', 'n'))): '2in',
21}
22
23for query_structure, queries in test_queries.items():
24 name = query_name_dict.get(query_structure, str(query_structure))
25 queries_list = list(queries)
26 print(f"查詢類型 {name}: 共 {len(queries_list)} 筆")
27 if queries_list:
28 sample_query = queries_list[0]
29 sample_answers = test_hard_answers[sample_query]
30 print(f" 範例查詢: {sample_query}")
31 print(f" 對應 hard answers 數量: {len(sample_answers)}")
執行後可以看到每種查詢類型(1p/2i/2in…)的樣本數與答案分佈,這對於評估「模型在哪種邏輯結構上表現較差」非常有用——通常帶否定 (n) 的查詢類型是所有模型的弱點。
5.5 解析 TensorBoard 訓練日誌並繪製各查詢類型表現
訓練結束後,main.py 會把每種查詢結構的 MRR/Hit@K 分別寫入 TensorBoard(writer.add_scalar 呼叫,tag 格式為 "{mode}_{query_name}_{metric}")。以下範例示範如何在不啟動 TensorBoard 網頁的情況下,直接用程式解析事件檔案,比較不同查詢結構的表現差異:
1"""
2從 TensorBoard event 檔案批次擷取各查詢結構的 MRR,
3用於離線比較 GQE / Query2box / BetaE 在同一資料集上的
4「哪種邏輯結構最容易/最難」分析。
5"""
6from tensorboard.backend.event_processing import event_accumulator
7
8def load_final_mrr(log_dir: str) -> dict[str, float]:
9 """讀取一個訓練 log 目錄,回傳 {查詢類型: 最終 test MRR}"""
10 ea = event_accumulator.EventAccumulator(log_dir)
11 ea.Reload()
12
13 results = {}
14 for tag in ea.Tags().get("scalars", []):
15 if tag.startswith("Test_") and tag.endswith("_MRR"):
16 # tag 範例:"Test_2in_MRR"
17 query_name = tag.replace("Test_", "").replace("_MRR", "")
18 events = ea.Scalars(tag)
19 results[query_name] = events[-1].value # 取最後一次記錄的值
20 return results
21
22
23if __name__ == "__main__":
24 beta_results = load_final_mrr("logs/FB15k-237-betae/beta/2026.01.01-00:00:00")
25 box_results = load_final_mrr("logs/FB15k-237-betae/box/2026.01.01-00:00:00")
26
27 print(f"{'查詢類型':<10}{'BetaE MRR':>12}{'Query2box MRR':>16}")
28 for query_name in sorted(beta_results.keys() | box_results.keys()):
29 beta_mrr = beta_results.get(query_name, float("nan"))
30 box_mrr = box_results.get(query_name, float("nan"))
31 print(f"{query_name:<10}{beta_mrr:>12.3f}{box_mrr:>16.3f}")
典型觀察(依論文報告的趨勢):帶否定 (2in/3in/inp/pin/pni) 的查詢類型只有 BetaE 支援,Query2box 完全無法評估;即使在兩者都支援的類型(如 1p/2i)中,BetaE 通常也略優於 Query2box,因為機率分布比幾何盒子有更強的表達彈性。
6. 進階應用場景 (Advanced Use Cases)
6.1 真實世界應用案例
- 生物醫學知識圖譜推理:把 KGReasoning 的 BetaE 用在 Hetionet 或藥物-基因-疾病三元組圖上,回答「哪些藥物同時作用於基因 X 的通路、但不會引發副作用 Y」這類多跳帶否定查詢——這正是藥物重定位 (drug repurposing; 藥物再利用) 研究常見的問題形式。
- 推薦系統的複雜條件查詢:電商知識圖譜可用 2i/3i 查詢回答「同時符合品牌 A 偏好且屬於類別 B、但排除已購買過的商品」。
- 企業知識圖譜的合規查詢:金融領域可用否定查詢排查「與受制裁實體有關聯、但未被列入現有黑名單」的間接風險節點。
6.2 與其他 snap-stanford 工具的整合
- OGB (Open Graph Benchmark):可將 OGB 提供的大規模 KG 資料集(如 ogbl-biokg)轉換成 KGReasoning 需要的
train.txt/valid.txt/test.txt格式,再用create_queries.py產生查詢,把 KGReasoning 的評估方法應用在 OGB 標準資料集上做比較研究。 - PyTorch Geometric (PyG):雖然 KGReasoning 未直接依賴 PyG,但社群常見做法是用 PyG 的
Data/HeteroData物件先做圖預處理與取樣,再轉換成 KGReasoning 期待的三元組格式,結合兩者的圖操作生態。
6.3 案例研究:資料集規模與訓練成本的實務估算
以官方三個資料集為例,實務規劃訓練資源時可參考下表(依 BetaE 論文與 repo 的預設超參數推估):
| 資料集 | 實體數 (nentity) | 關係數 (nrelation) | 訓練查詢量級 | 建議 -d/-b | 相對訓練時間(單張 GPU) |
|---|---|---|---|---|---|
| FB15k | ~15,000 | ~1,345 | 較大 | -d 800 -b 512 | 基準(電影/人物領域,關係稠密) |
| FB15k-237 | ~15,000 | ~237 | 中等 | -d 400 -b 512 | 略快於 FB15k(關係數少但更難泛化,因為移除了逆向關係造成的資料洩漏) |
| NELL995 | ~63,000 | ~200 | 較大 | -d 800 -b 512 | 實體數最多,單步計算量最大 |
FB15k vs FB15k-237 的重要差異(容易誤解之處):FB15k-237 是 FB15k 移除「逆關係洩漏」(例如同時存在 (A, 導演, B) 與 (B, 被導演, A) 這種可以直接反推的簡單逆關係)後的版本,因此 FB15k-237 上的分數天然會比 FB15k 低,兩者分數不可直接比較優劣,只能在各自資料集內比較不同模型。這是使用此 repo 做研究時最常見的誤讀之一。
6.4 效能調校與最佳實踐
- 負採樣數量 (
-n) 與訓練穩定性的權衡:負採樣數越多,梯度估計越穩定但每步計算量越大;官方預設 128,資源有限時可降到 64,但需相應調整學習率。 test_batch_size保持 1 的原因:TestDataset對每個查詢會枚舉「全部實體」計算排序分數(self.batch_entity_range),若 batch 太大會迅速吃滿顯存,官方預設test_batch_size=1是刻意為之的保守設定。--tasks分階段訓練策略:可先用純1p任務訓練(讓模型先學好基礎的單跳投影嵌入),穩定後再擴充到完整 14 種任務繼續訓練,比一開始就丟全部任務更容易收斂(源自論文附錄的訓練技巧)。center_reg(-boxm第二參數)調參:Query2box 的center_reg平衡「盒子內距離」與「盒子外距離」的權重,是影響 Query2box 收斂品質的關鍵超參數,建議在 0.01~0.05 範圍網格搜尋。
7. 與 AIKT 的關聯分析 (AIKT Integration Analysis)
7.1 此專案可作為 AIKT 的資料來源或分析工具嗎?
可以,且有兩種互補的角度:
作為「分析工具」:KGReasoning 本身就是 AIKT L4(graphify)的一種進階延伸想像——AIKT 目前的 graphify 建的是「程式碼結構圖」的靜態索引(誰引用誰),而 KGReasoning 展示的是「如何在圖上做複雜邏輯查詢並容忍圖不完整」的技術。若 AIKT 未來想讓 graphify 支援「多跳帶條件的程式碼查詢」(例如「找出呼叫了函式 A、但沒有寫測試、且屬於已棄用模組的類別」),KGReasoning 的查詢結構設計(1p/2i/2in…)是現成的形式化參考框架。
作為「資料來源」:這份 repo 本身(README + 6 個 .py 檔)就是 L2(ai-gh-save)與 L12(gh-tutorial-qd)處理的標準輸入樣本——本教學本身即是此流程的產出實例。
7.2 AIKT 的哪些 Layer 可以包裝或編排此專案?
flowchart TB
subgraph sg1["L1-L3 知識擷取"]
A1["L2 ai-gh-save
已擷取 README+程式碼結構"]
end
subgraph sg2["L4-L6 知識索引"]
A2["L4 graphify
可對 6 個 .py 檔建立
函式/類別依賴圖
(models.py 內 3 種 forward_* 路徑)"]
A3["L6 gitnexus
程式碼符號圖
查詢「KGReasoning class
依賴哪些 nn.Module」"]
end
subgraph sg3["L9-L10 研究工具"]
A4["L9 paper-search
搜尋 BetaE/Query2box/GQE
後續引用論文,追蹤
此研究脈絡最新進展"]
A5["L10 paper-qa-lite
針對已下載的 BetaE 論文
PDF 做本地 RAG 問答"]
end
subgraph sg4["L12-L15 教學輸出"]
A6["L12 gh-tutorial-qd
本教學文件的產出管道"]
A7["L15 paper-tutorial
結合論文+程式碼
做精讀教學(本專案
兼具論文與程式碼,
是絕佳候選)"]
end
subgraph sg5["L18-L19 研究延伸"]
A8["L18 research-pipeline-v2
若要做「KG 推理方法
比較研究」的多輪迭代分析"]
end
A1 --> A2
A1 --> A4
A2 --> A3
A4 --> A5
A1 --> A6
A4 --> A7
A5 --> A7
A7 --> A8
7.3 潛在的整合場景與價值
場景一:程式碼結構教學自動化。用 L4 graphify 對
models.py建索引,自動產出「KGReasoning.forward()依geo參數分派到forward_vec/forward_box/forward_beta三條路徑」的依賴圖,再餵給 L12 gh-tutorial-qd 輔助撰寫本教學的第 2 節架構圖——這正是本教學撰寫時人工完成的工作,未來可半自動化。場景二:論文+程式碼雙軌精讀。KGReasoning 是少數「論文與程式碼一一對應清晰」的 repo(
query_name_dict的 14 種結構直接對應論文 Table),適合用 L15 paper-tutorial 做「先讀論文 Section 3 的查詢結構定義,再對照main.py的query_name_dict」的雙軌教學,比純程式碼導讀或純論文導讀都更完整。場景三:研究脈絡追蹤。用 L9 paper-search 定期搜尋引用 BetaE 的最新論文(如後續的 pre-training 式 KG 推理模型),搭配 L18 research-pipeline-v2 做「三輪迭代」文獻回顧,追蹤這個子領域從 2018 GQE 到現今的技術演進,產出結構化的技術脈絡報告。
場景四:治理層的分流參考。此 repo 只有 6 個檔案、無 CI/測試、無
requirements.txt——這種「輕量研究型 repo」的特徵,可作為 AIKT L28 aikt-governance 判斷「這個 repo 適合直接讀原始碼教學,不需要啟動完整 docling 深度解析」的參考案例(副檔名分流優先序中,純.py+README.md走 gh-tutorial-qd 即已足夠,不需 L8 docling)。
7.4 機密邊界檢查
8. 優缺點與生態系定位 (Strengths, Limitations & Ecosystem Position)
8.1 核心優勢與創新點
- 統一框架比較三代模型:GQE / Query2box / BetaE 共用同一套資料載入、訓練迴圈與評估流程,讓研究者能在完全一致的實驗條件下比較三種幾何表示法的優劣,這是此 repo 相較於三篇論文各自獨立程式碼庫的最大加值。
- 查詢結構的形式化設計優雅:用巢狀 tuple 編碼 14 種邏輯查詢結構,雖然可讀性不是最佳,但兼顧了「可雜湊當 dict key 做批次分組」與「可任意巢狀擴展」兩個工程需求,是研究程式碼中少見的精巧設計。
- 官方 checkpoint 與資料集齊全:README 提供現成的資料下載連結,降低了重現論文結果的門檻,這對學術可重現性 (reproducibility) 貢獻很大。
- BetaE 的否定查詢支援是理論突破:在此之前的向量/盒子模型都無法優雅處理邏輯否定,BetaE 用機率分布的補集運算解決了這個長期痛點。
8.2 目前限制與改進空間
- 程式碼工程性不足:無單元測試、無 CI、無
requirements.txt/pyproject.toml、無型別註記 (type hints)——這是典型「研究可重現優先、工程規範其次」的學術程式碼,若要在生產環境使用需自行補強。 - 可擴展性受限:僅支援固定的 14 種查詢結構,若要處理更複雜的邏輯(如巢狀否定、任意深度的混合運算),需要修改
query_name_dict與對應的forward_*邏輯,缺乏一般化的查詢解析器 (query parser)。 - 記憶體效率:
TestDataset在評估階段對每個查詢枚舉全部實體算分(self.batch_entity_range),在百萬節點規模的 KG 上會遇到嚴重的記憶體與速度瓶頸,未使用近似最近鄰搜尋 (ANN; approximate nearest neighbor) 等加速技術。 - 停止維護狀態:從 repo 更新時間可推斷此 repo 主要是論文發表時的一次性交付,之後僅有零星維護,若需要跟上最新研究(如結合 pretrained language model 的 KG 推理),需另尋後續論文的程式碼庫。
8.3 與同領域工具的比較
| 工具 | 定位 | 與 KGReasoning 的關係 |
|---|---|---|
| OpenKE | 傳統 KGC 嵌入方法大集合(TransE/DistMult/ComplEx 等) | 只做單跳三元組評分,不支援多跳邏輯查詢;KGReasoning 是其查詢複雜度的延伸 |
| PyKEEN | KG 嵌入的通用 Python 函式庫,工程化程度高 | 工程完整度遠高於 KGReasoning(有測試、CI、文件),但同樣缺乏原生的多跳邏輯查詢支援 |
| SMORE (SNAP 後續專案) | 大規模分散式查詢嵌入訓練框架 | 可視為 KGReasoning 的「工業級擴展版」,解決其百萬節點規模下的效率瓶頸 |
| NBFNet | 基於路徑的歸納式 KG 推理 | 走不同技術路線(路徑聚合 vs 幾何嵌入),適合對比研究不同推理範式的優劣 |
8.4 適用場景建議
- 適合:學術研究複現、教學展示邏輯查詢嵌入的核心概念、中小規模 KG(十萬節點以下)的原型驗證、比較三種幾何表示法的實驗設計基礎。
- 不適合直接用於生產:若需要處理百萬級節點規模、或需要嚴謹的工程化保證(測試覆蓋率、型別安全),建議改用 PyKEEN 等工程化程度更高的函式庫,或參考 SMORE 的分散式訓練架構。
- 最佳切入時機:當你需要「快速理解多跳邏輯查詢嵌入」這個概念、或需要一份三種代表性方法(GQE/Query2box/BetaE)的對照實作時,KGReasoning 是目前最精簡、最權威的起點。
附錄:完整檔案清單
1KGReasoning/
2├── LICENSE # MIT License
3├── README.md # 專案說明、資料集連結、引用格式
4├── create_queries.py # 從 KG 邊產生 14 種結構的查詢 pickle
5├── dataloader.py # TrainDataset / TestDataset / SingledirectionalOneShotIterator
6├── example.sh # FB15k / FB15k-237 / NELL 三個資料集 × 三種模型的完整訓練指令範例
7├── main.py # CLI 入口:argparse 參數、訓練/評估迴圈、TensorBoard 記錄
8├── models.py # KGReasoning nn.Module + 各種 Intersection/Projection/Regularizer
9└── util.py # tuple/list 轉換、隨機種子設定、時間格式化等工具函式
Comments