MrDhifallah commited on
Commit
591d899
·
verified ·
1 Parent(s): 9506658

Update README_OLD_BACKUP.md

Browse files
Files changed (1) hide show
  1. README_OLD_BACKUP.md +0 -872
README_OLD_BACKUP.md CHANGED
@@ -1,872 +0,0 @@
1
- <!-- ============================================================ -->
2
- <!-- CAFF - Context-Aware Feedback Filtering -->
3
- <!-- Official repository README -->
4
- <!-- ============================================================ -->
5
-
6
- <h1 align="center">
7
- CAFF: Context-Aware Feedback Filtering for Multi-Hop Biomedical Knowledge Graph Evidence Selection
8
- </h1>
9
-
10
- <p align="center">
11
- <a href="#"><img alt="Paper" src="https://img.shields.io/badge/Paper-IEEE%20TKDE%20(under%20review)-1f6feb?style=flat-square"></a>
12
- <a href="#license"><img alt="License" src="https://img.shields.io/badge/License-MIT-2ea44f?style=flat-square"></a>
13
- <img alt="Python" src="https://img.shields.io/badge/Python-3.10%2B-3776AB?style=flat-square&logo=python&logoColor=white">
14
- <img alt="PyTorch" src="https://img.shields.io/badge/PyTorch-2.0%2B-EE4C2C?style=flat-square&logo=pytorch&logoColor=white">
15
- <img alt="CUDA" src="https://img.shields.io/badge/CUDA-11.8-76B900?style=flat-square&logo=nvidia&logoColor=white">
16
- <img alt="Status" src="https://img.shields.io/badge/Status-Research%20Code-orange?style=flat-square">
17
- </p>
18
-
19
- <p align="center">
20
- <b>Marwan Dhifallah</b><sup>*</sup> &nbsp;·&nbsp; <b>Yu Liu</b><br>
21
- <i>Dalian University of Technology, Dalian, China</i><br>
22
- <code>marwan@mail.dlut.edu.cn</code> &nbsp;·&nbsp; <code>yuliu@dlut.edu.cn</code>
23
- </p>
24
-
25
- <p align="center">
26
- <i>Official PyTorch implementation of the paper</i><br>
27
- <b>"CAFF: Context-Aware Feedback Filtering for Multi-Hop Biomedical Knowledge Graph Evidence Selection"</b><br>
28
- <i>(under review, IEEE Transactions on Knowledge and Data Engineering, 2026).</i>
29
- </p>
30
-
31
- ---
32
-
33
- ## Table of Contents
34
-
35
- 1. [TL;DR](#tldr)
36
- 2. [The Context Blindness Error (CBE)](#the-context-blindness-error-cbe)
37
- 3. [Key Contributions](#key-contributions)
38
- 4. [Method Overview](#method-overview)
39
- - [Stage 1 BFS Candidate Stratification](#stage-1-bfs-candidate-stratification)
40
- - [Stage 2 Contextual Summary Vector (CSV)](#stage-2-contextual-summary-vector-csv)
41
- - [Stage 3 Dynamic Bilinear Modulation (DBM)](#stage-3-dynamic-bilinear-modulation-dbm)
42
- - [Stage 4 Hop-Conditioned Context Contrast (HC3) Loss](#stage-4-hop-conditioned-context-contrast-hc3-loss)
43
- 5. [Theoretical Guarantees](#theoretical-guarantees)
44
- 6. [Repository Structure](#repository-structure)
45
- 7. [Installation](#installation)
46
- 8. [Data Preparation](#data-preparation)
47
- 9. [Training](#training)
48
- 10. [Evaluation](#evaluation)
49
- 11. [Main Results](#main-results)
50
- 12. [Ablation Study](#ablation-study)
51
- 13. [Hyperparameters](#hyperparameters)
52
- 14. [Reproducibility](#reproducibility)
53
- 15. [Hardware Requirements](#hardware-requirements)
54
- 16. [Limitations](#limitations)
55
- 17. [Citation](#citation)
56
- 18. [License](#license)
57
- 19. [Acknowledgements](#acknowledgements)
58
- 20. [Contact](#contact)
59
-
60
- ---
61
-
62
- ## TL;DR
63
-
64
- > Existing triple filters for multi-hop KG-RAG score each candidate from `(Query, relation, BFS_depth)` alone , they are **blind** to which triples were retained at the previous hop. We prove this blindness incurs an **irreducible** Bayes error floor `ε* > 0` (Theorem 1, via the Data Processing Inequality). **CAFF** closes this gap with a three-piece, filtering-layer-only feedback loop:
65
- >
66
- > - **CSV** a parameter-free, permutation-invariant summary of the previously retained set.
67
- > - **DBM** a low-rank, sigmoid-gated perturbation of the bilinear scoring matrix, *generated dynamically* from the CSV.
68
- > - **HC3** a contrastive loss that provably maximizes a variational lower bound on the conditional mutual information `I(Y; S | z)`.
69
- >
70
- > CAFF lifts PubMedQA accuracy from **76.9 → 79.6** (+2.7 pts) and BioASQ 7b macro-F1 from **71.1 → 74.3** (+3.2 pts) over the strongest depth-stratified baseline, with gains concentrated at the deepest hops (**+6.9** pts at hop 2, **+9.3** pts at hop 3) exactly where CBE is most severe.
71
-
72
- ---
73
-
74
- ## The Context Blindness Error (CBE)
75
-
76
- Consider the clinical query:
77
-
78
- > *"What drug targets the pathway of the causal gene of Fanconi anemia complementation group D1?"*
79
-
80
- The same hop-2 triple `⟨BRCA2, participates_in, HR-repair⟩` is:
81
- - **Diagnostically essential** when hop 1 retained `⟨FANCD1, causal_mutation, BRCA2⟩`,
82
- - **Irrelevant noise** when hop 1 retained only `⟨FANCD1, has_phenotype, bone-marrow-failure⟩`.
83
-
84
- A depth-stratified filter sees only `(Q, participates_in, ℓ=2)` and assigns **the same score in both cases**. It cannot distinguish the two evidential trajectories it commits the **Context Blindness Error**.
85
-
86
- Formally, for a context-agnostic filter `f ∈ F_agn` and any threshold `τ`:
87
-
88
- ```
89
- P( 𝟙[f(X) ≥ τ] ≠ Y ) ≥ R* + ε*, where ε* > 0
90
- ```
91
-
92
- with the closed-form lower bound
93
-
94
- ```
95
- ε* ≥ ½ · 𝔼_X [ Var_{Z|X} ( P(Y=1 | X, Z) ) ].
96
- ```
97
-
98
- This floor is **architectural**, not representational no parameter scaling of `f(Q, r, ℓ)` can recover information about `S_{ℓ-1}` that was never given to it as input.
99
-
100
- ---
101
-
102
- ## Key Contributions
103
-
104
- | # | Contribution | Paper Section |
105
- |---|---|---|
106
- | **C1** | Formal definition of the Context Blindness Error (CBE) and a proof that it induces an irreducible Bayes error floor `ε* > 0`, with a closed-form variance lower bound. | §4 |
107
- | **C2** | **Contextual Summary Vector (CSV)** a parameter-free, permutation-invariant encoder of the previously retained set, with a formal injectivity guarantee under linearly independent relation embeddings. | §5.2 |
108
- | **C3** | **Dynamic Bilinear Modulation (DBM)** a low-rank, sigmoid-gated, *dynamically generated* perturbation of the scoring matrix, with **zero per-candidate overhead** after one-time precomputation. | §5.3 |
109
- | **C4** | **HC3 loss** an InfoNCE-derived contrastive objective formally equivalent to maximizing a variational lower bound on the conditional mutual information `I(Y; S \| z)`. | §5.4 |
110
- | **C5** | State-of-the-art results on PubMedQA and BioASQ 7b, with hop-stratified ablations and a diagnostic context-swap experiment showing **1.84 bits** of context separation vs. **0.00 bits** for every context-agnostic baseline. | §7–§8 |
111
-
112
- ---
113
-
114
- ## Method Overview
115
-
116
- ```
117
- ┌─────────┐ ┌────────────┐ ┌─────────────────────────────────────────┐ ┌─────────────┐
118
- │ Query Q │──▶│ Entity │──▶│ BFS subgraph extraction (depth L=3) │──▶│ Candidate │
119
- └─────────┘ │ Linker │ └─────────────────────────────────────────┘ │ sets {C_ℓ} │
120
- └────────────┘ └──────┬──────┘
121
-
122
- ┌────────────────── CAFF filtering layer ──────────────┘
123
-
124
- ┌───────────────┐ z_{ℓ-1} ┌────────────────┐
125
- │ CSV │──────────────▶│ DBM gate │
126
- │ (Eq. 14) │ │ Δ_ℓ (Eq. 17) │
127
- └───────────────┘ └────────┬───────┘
128
-
129
- W^ctx_ℓ = W₀ + A_ℓB_ℓᵀ + Δ_ℓ
130
-
131
-
132
- s_ℓ = σ(qᵀ W^ctx_ℓ e_r + ...)
133
-
134
- ▼ (threshold θ)
135
- S_ℓ ────┐
136
- │ feedback
137
- └──▶ z_ℓ = CSV(S_ℓ)
138
-
139
-
140
- (next hop ℓ+1)
141
- ```
142
-
143
- ### Stage 1 BFS Candidate Stratification
144
-
145
- For each head entity at depth `ℓ-1`, retain at most `K_r = 20` triples per relation type, ranked by descending tail degree. This **frequency cap** prevents hub-entity relation embeddings from saturating the CSV.
146
-
147
- ### Stage 2 Contextual Summary Vector (CSV)
148
-
149
- Parameter-free, permutation-invariant, formally faithful:
150
-
151
- ```
152
- 1
153
- z_{ℓ-1} = ──── · Σ_{(h,r,t) ∈ S_{ℓ-1}} e_r (mean of frozen relation embeddings)
154
- |S_{ℓ-1}|
155
- ```
156
-
157
- with `z_{ℓ-1} = 0` when `S_{ℓ-1} = ∅` (so CAFF reduces *gracefully* to the depth-stratified baseline at hop 1).
158
-
159
- **Lemma (CSV faithfulness).** If the relation-embedding matrix `E` has full row rank, the map `Z ↦ EᵀZ` is **injective** on the simplex distinct retained-context distributions produce distinct CSVs.
160
-
161
- ### Stage 3 Dynamic Bilinear Modulation (DBM)
162
-
163
- A low-rank, sigmoid-gated, **runtime-generated** increment to the scoring matrix:
164
-
165
- ```
166
- g_ℓ = σ( P_ℓ z_{ℓ-1} + b_ℓ ) ∈ (0,1)^ρ (context gate, ρ = 16)
167
-
168
- Δ_ℓ = U_ℓ · diag(g_ℓ) · V_ℓᵀ (rank-ρ context perturbation)
169
-
170
- W^ctx_ℓ = W₀ + A_ℓ B_ℓᵀ + Δ_ℓ(g_ℓ)
171
- ───── ───────── ─────────
172
- shared depth-specific context-specific
173
- base (PCE correction) (CBE correction)
174
-
175
- s_ℓ = σ( qᵀ W^ctx_ℓ e_r + vᵀ(q ⊙ e_r) + β_ℓ )
176
- ```
177
-
178
- **Cost.** `W^ctx_ℓ` is precomputed **once per hop** (not per candidate). Per-hop overhead with `d=768`, `ρ=16`, `N_ℓ ≤ 500`:
179
- - gate: `O(ρd)`,
180
- - DBM assembly: `O(d²ρ)`,
181
- - candidate scoring: `O(N_ℓ d²)`,
182
- - total CAFF surcharge ≈ **9.4 × 10⁶ FLOPs / hop**, **zero per candidate**.
183
-
184
- > **DBM vs. LoRA / Adapters.** LoRA learns a *fixed* low-rank increment during fine-tuning. DBM **generates** its rank-ρ increment *dynamically at inference time* from the CSV context-specific modulation without a separate parameter set per context.
185
-
186
- ### Stage 4 Hop-Conditioned Context Contrast (HC3) Loss
187
-
188
- For each anchor `(Q, r, ℓ)`, mine a positive context `z^(a)` (where the triple was labeled 1) and up to 8 negative contexts `z^(b)` (where it was labeled 0):
189
-
190
- ```
191
- L_HC3 = (1 / |T_HC3|) · Σ max( 0, s(Q, r, ℓ, z^(b)) − s(Q, r, ℓ, z^(a)) + γ_C )
192
- ```
193
-
194
- with margin `γ_C = 0.25`. **Proposition.** Minimizing `L_HC3` maximizes a variational lower bound on `I(Y; S | z)`.
195
-
196
- The full objective:
197
-
198
- ```
199
- L = L_BCE + λ_D · L_DC + λ_C · L_HC3 (λ_D = 0.40, λ_C = 0.35)
200
- ```
201
-
202
- ---
203
-
204
- ## Theoretical Guarantees
205
-
206
- | # | Statement | Where |
207
- |---|---|---|
208
- | **Theorem 1** | Under positive CMI `I(Y; Z \| X) > 0`, every context-agnostic filter incurs an irreducible Bayes error floor `ε* > 0` with `ε* ≥ ½ · 𝔼_X[Var_{Z\|X}(π(X,Z))]`. | §4.2 |
209
- | **Lemma 1** | CBE forces label indistinguishability: there exist `(X, z₁, y=1)` and `(X, z₂, y=0)` receiving identical agnostic scores. | §3.3 |
210
- | **Lemma 2** | The CSV is injective on the simplex of retained-context distributions whenever the relation-embedding matrix has full row rank. | §5.2 |
211
- | **Proposition 1** | CSV noise stability: `𝔼[‖z̃ − z‖²] = σ²d / \|S_{ℓ-1}\|` context summaries are **most stable in the regime where they carry the most information**. | §5.2 |
212
- | **Proposition 2** | DBM rank sufficiency: with `ρ ≥ rank(Δ*)`, DBM can exactly represent any context-induced perturbation (Eckart–Young). | §6 |
213
- | **Proposition 3** | Minimizing `L_HC3` maximizes a variational lower bound on `I(Y; S \| z)`. | §5.4 |
214
- | **Proposition 4** | `ε*` is monotone non-decreasing in `I(Y; Z \| X)` (via Pinsker). | §4.2 |
215
- | **Theorem 2** | Strict TPR benefit: there exist contexts at which the optimal context-aware filter strictly outperforms the optimal context-agnostic one. | §6 |
216
-
217
- ---
218
-
219
-
220
-
221
- ---
222
-
223
-
224
- ---
225
-
226
- ## Repository Structure
227
-
228
- ```
229
- CAFF/
230
- ├── caff/ # Core package (importable)
231
- │ ├── __init__.py # Public API surface
232
- │ ├── config.py # CAFFConfig + AblationFlags dataclasses
233
- │ ├── csv.py # Contextual Summary Vector (Stage 2)
234
- │ ├── data.py # KG loader, BFS extractor, datasets
235
- │ ├── dbm.py # Dynamic Bilinear Modulation (Stage 3)
236
- │ ├── encoders.py # Frozen encoder + relation cache
237
- │ ├── evaluator.py # Metrics, MAP / NDCG, threshold tuning
238
- │ ├── losses.py # BCE + DC + HC3 loss objects
239
- │ ├── miners.py # DCMiner + HC3Miner + buffers
240
- │ ├── model.py # CAFFModel (CSV + DBM + scoring head)
241
- │ ├── scorer.py # DepthBilinear + HopScorer
242
- │ ├── trainer.py # CAFFTrainer + CheckpointManager
243
- │ └── utils/ # seeding, logging
244
-
245
- ├── scripts/ # Reproduction pipeline
246
- │ ├── convert_orphanet_xml_to_tsv.py # Orphanet XML -> TSV
247
- │ ├── convert_hpo_to_tsv.py # HPO obo -> TSV
248
- │ ├── convert_mondo_to_tsv.py # MONDO obo -> TSV (experimental)
249
- │ ├── build_kg.py # Base KG from Orphanet TSV
250
- │ ├── merge_hpo_into_kg.py # KG v2 = + HPO/OMIM annotations
251
- │ ├── merge_mondo_into_kg.py # KG v3 = + MONDO (experimental)
252
- │ ├── build_orphanet_qa.py # Sample QA records from KG
253
- │ ├── annotate_triples.py # Paper-spec shortest-path gold
254
- │ ├── extract_bfs.py # Precompute BFS candidates
255
- │ ├── threshold_sweep.py # Find optimal global theta
256
- │ └── per_hop_threshold_sweep.py # Per-hop theta tuning
257
-
258
- ├── configs/ # YAML training configs
259
- │ ├── caff_full.yaml # Paper config (BioLinkBERT, GPU)
260
- │ ├── caff_no_hc3.yaml # Ablation (no HC3 loss)
261
- │ ├── depthbilinear.yaml # Baseline (no CSV / DBM)
262
- │ ├── caff_orphanet.yaml # Repository default (CPU, KG v2)
263
- │ └── caff_smoke.yaml # Smoke test (tiny synthetic KG)
264
-
265
- ├── tests/ # 48+ unit tests (run by CI)
266
- │ ├── fixtures/ # Tiny synthetic KG
267
- │ ├── test_csv.py # CSV faithfulness
268
- │ ├── test_data.py # KG loader + dataset
269
- │ ├── test_dbm.py # DBM rank sufficiency
270
- │ ├── test_evaluator.py # Metrics + JSD diagnostic
271
- │ ├── test_losses.py # BCE / DC / HC3 loss math
272
- │ ├── test_miners.py # DC + HC3 mining (Phase 2)
273
- │ ├── test_scorer.py # Bilinear scoring
274
- │ └── test_smoke_pipeline.py # End-to-end smoke run
275
-
276
- ├── .github/workflows/
277
- │ └── tests.yml # CI: lint + pytest on every push
278
-
279
- ├── data/ # gitignored (raw + processed)
280
- ├── runs/ # gitignored (checkpoints, logs)
281
- ├── cache/ # gitignored (BFS + relation cache)
282
- ├── examples/ # short usage snippets
283
-
284
- ├── train.py # Training entry point
285
- ├── evaluate.py # Standalone evaluation script
286
- ├── context_swap_diagnostic.py # Appendix C diagnostic
287
-
288
- ├── README.md # This file
289
- ├── CONTRIBUTING.md # Contribution guidelines
290
- ├── PAPER_DISCREPANCIES.md # 10-section running experiment log
291
- ├── LICENSE # MIT
292
- ├── requirements.txt # Core dependencies
293
- ├── requirements-optional.txt # Optional (wandb, scispacy, openai)
294
- ├── .gitattributes
295
- └── .gitignore
296
- ```
297
-
298
- ### Notes on file purposes
299
-
300
- - **`caff/`** is the importable package. Its public API is declared in
301
- `__init__.py` and matches the names referenced from `train.py`,
302
- `evaluate.py`, and the test suite.
303
- - **`scripts/`** contains everything outside the training loop: data
304
- conversion, KG construction, QA sampling, threshold tuning. Each
305
- script is self-contained and can be run independently from the
306
- command line.
307
- - **`configs/`** holds five YAML files. The paper's `caff_full.yaml`
308
- expects BioLinkBERT-Large and the four-source KG and is the right
309
- starting point on GPU. `caff_orphanet.yaml` is the CPU-only
310
- default used throughout this repository's Implementation Reality
311
- Check section.
312
- - **`tests/`** runs in CI on every push. All commits on `main` have
313
- the suite green; see the badge near the top of this file.
314
- - **`PAPER_DISCREPANCIES.md`** is the source of truth for every
315
- empirical decision in the repo (bug fixes, threshold choices, KG
316
- scaling, the MONDO experiment). New experiments should append a
317
- new numbered section there before changing the headline numbers.
318
-
319
- ## Installation
320
-
321
- ### Prerequisites
322
-
323
- - **Python** ≥ 3.10
324
- - **CUDA** 11.8 (a single NVIDIA A100-80GB or equivalent is recommended)
325
- - **Git LFS** (for downloading model checkpoints, when released)
326
-
327
- ### Setup
328
-
329
- ```bash
330
- # 1. Clone the repository
331
- git clone https://github.com/<your-org>/caff.git
332
- cd caff
333
-
334
- # 2. Create a clean virtual environment
335
- python -m venv .venv
336
- source .venv/bin/activate # Windows: .venv\Scripts\activate
337
-
338
- # 3. Install PyTorch (matched to your CUDA toolkit)
339
- pip install torch>=2.0 --index-url https://download.pytorch.org/whl/cu118
340
-
341
- # 4. Install remaining dependencies
342
- pip install -r requirements.txt
343
-
344
- # 5. (Optional) Download the BioLinkBERT-Large encoder
345
- python -c "from transformers import AutoModel; AutoModel.from_pretrained('michiyasunaga/BioLinkBERT-large')"
346
- ```
347
-
348
- ### Core Dependencies
349
-
350
- ```
351
- torch>=2.0
352
- transformers>=4.30
353
- scispacy>=0.5
354
- networkx>=3.0
355
- numpy, scipy, scikit-learn, pandas
356
- tqdm, pyyaml, wandb (optional)
357
- openai>=1.0 # only for end-to-end QA with GPT-3.5-turbo
358
- ```
359
-
360
- ---
361
-
362
- ## Data Preparation
363
- > **Note.** This section documents the *paper configuration*. The repository ships a CPU-only reproduction path with Orphanet + HPO + OMIM annotations; see [Implementation Reality Check](#implementation-reality-check) for the as-shipped workflow and the corresponding numbers.
364
-
365
- CAFF operates on a **merged biomedical KG** built from three primary sources, joined on shared **UMLS Concept Unique Identifiers (CUIs)**.
366
-
367
- | Source | Version | Used for | Access |
368
- |---|---|---|---|
369
- | **Orphanet** | 2024 release | Rare-disease ontology, gene–disease links | <https://www.orphadata.com> |
370
- | **DisGeNET** | v7.0 (2020) | Gene–disease associations | <https://www.disgenet.org> |
371
- | **OMIM** | 2023 update | Mendelian inheritance, gene–phenotype | <https://www.omim.org> |
372
- | **PubMedQA** | — | QA benchmark (1,000 questions) | <https://pubmedqa.github.io/> |
373
- | **BioASQ 7b** | 2019 release | QA benchmark (1,141 yes/no + factoid) | <http://bioasq.org> |
374
-
375
- > **Licensing note.** Orphanet, DisGeNET, OMIM, and BioASQ have their own access terms. We **do not redistribute** the raw data; users must obtain it directly from each source. The build script reproduces the merged KG deterministically.
376
-
377
- ### Build the merged KG
378
-
379
- ```bash
380
- python scripts/build_kg.py \
381
- --orphanet data/raw/orphanet/ \
382
- --disgenet data/raw/disgenet/all_gene_disease_associations.tsv \
383
- --omim data/raw/omim/ \
384
- --umls data/raw/umls/MRCONSO.RRF \
385
- --out data/processed/kg.parquet \
386
- --min-relation-freq 50
387
- ```
388
-
389
- After construction, the merged KG contains:
390
-
391
- | Property | Value |
392
- |---|---|
393
- | Entities `\|V\|` | **148,423** |
394
- | Triples `\|E\|` | **2,318,941** |
395
- | Relation types `\|R\|` (after singleton removal) | **42** |
396
- | Max BFS depth `L` | 3 |
397
-
398
- ### Annotate gold relevance
399
-
400
- Triples on any shortest path from a seed entity to the gold answer entity receive `y = 1`; all others `y = 0`. This yields **≈ 3.7 M** labeled training instances across hop depths 1–3.
401
-
402
- ```bash
403
- python scripts/annotate_triples.py \
404
- --kg data/processed/kg.parquet \
405
- --pubmedqa data/benchmarks/pubmedqa.json \
406
- --linker scispacy + UMLS \
407
- --out data/processed/gold_pubmedqa.jsonl
408
- ```
409
-
410
- ---
411
-
412
- ## Training
413
- > **Note.** This section documents the *paper configuration*. The repository ships a CPU-only reproduction path with Orphanet + HPO + OMIM annotations; see [Implementation Reality Check](#implementation-reality-check) for the as-shipped workflow and the corresponding numbers.
414
-
415
- ### Quick start - full CAFF on PubMedQA
416
-
417
- ```bash
418
- python train.py --config configs/caff_full.yaml
419
- ```
420
-
421
- ### Reproduce the full benchmark suite
422
-
423
- ```bash
424
- # 1. Strongest baseline (depth-stratified bilinear, no context)
425
- python train.py --config configs/depthbilinear.yaml -seed 42
426
-
427
- # 2. CAFF without HC3 loss (CSV + DBM only — ablates the CMI bound)
428
- python train.py --config configs/caff_no_hc3.yaml -seed 42
429
-
430
- # 3. Full CAFF
431
- python train.py --config configs/caff_full.yaml -seed 42
432
-
433
- # 4. Repeat across the three reported seeds
434
- for s in 42 1337 2024; do
435
- python train.py --config configs/caff_full.yaml --seed $s
436
- done
437
- ```
438
-
439
- ### Key training hyperparameters
440
-
441
- | Hyperparameter | Default | Notes |
442
- |---|---:|---|
443
- | Optimizer | AdamW | weight decay `1e-2` |
444
- | Base learning rate | `3e-4` | cosine decay to `1e-5`, 2-epoch linear warmup |
445
- | Batch size | 256 | |
446
- | Epochs | 30 | early stopping on dev, patience 5 |
447
- | Gradient clip | `‖∇‖₂ ≤ 1.0` | |
448
- | Random seeds | `{42, 1337, 2024}` | three runs reported |
449
-
450
- ---
451
-
452
- ## Evaluation
453
-
454
- ### Filtering-layer metrics
455
-
456
- ```bash
457
- python evaluate.py \
458
- --checkpoint runs/caff_full/seed_42/best.pt \
459
- --benchmark pubmedqa \
460
- --metrics precision recall f1 map ndcg@10 \
461
- --hop-stratified
462
- ```
463
-
464
- ### End-to-end question answering (GPT-3.5-turbo backbone)
465
-
466
- ```bash
467
- export OPENAI_API_KEY=<your-key>
468
-
469
- python evaluate.py \
470
- --checkpoint runs/caff_full/seed_42/best.pt \
471
- --benchmark pubmedqa \
472
- --llm-backbone gpt-3.5-turbo \
473
- --temperature 0 --top-p 1 \
474
- --metrics accuracy
475
- ```
476
-
477
- Triples are serialized as one per line:
478
-
479
- ```
480
- [head] -- [relation] --> [tail]
481
- ```
482
-
483
- ### Diagnostic context-swap experiment (Appendix C)
484
-
485
- This experiment **directly measures CBE**: it presents the same `(Q, r, ℓ=2)` under two semantically opposing upstream contexts and reports the Jensen–Shannon divergence between the resulting score distributions.
486
-
487
- ```bash
488
- python context_swap_diagnostic.py \
489
- --checkpoint runs/caff_full/seed_42/best.pt \
490
- --report-bits
491
- ```
492
-
493
- > Every context-agnostic baseline yields **JSD = 0.00 bits** (the empirical signature of CBE). CAFF achieves **JSD = 1.84 bits** concrete proof that the architectural fix is doing what the theory predicts.
494
-
495
- ---
496
-
497
- ## Main Results
498
- > **Note.** The numbers in this section are the *paper headline* reproduced from the manuscript. For the as-shipped CPU pipeline (Orphanet + HPO + OMIM, bert-base-uncased), see [Implementation Reality Check](#implementation-reality-check) directly below, where F1 = 0.522 +/- 0.001 is reported with full 3-seed validation.
499
-
500
- ### End-to-end QA (mean over 3 seeds)
501
-
502
- | Method | PubMedQA Acc. | PubMedQA MAP | BioASQ 7b Macro-F1 | BioASQ MAP |
503
- |---|---:|---:|---:|---:|
504
- | BM25 | 68.2 | 0.571 | 61.3 | 0.519 |
505
- | DPR-Bio | 72.4 | 0.618 | 65.7 | 0.562 |
506
- | BioRAG | 74.1 | 0.641 | 68.9 | 0.591 |
507
- | SubgraphRAG | 75.6 | 0.658 | 69.8 | 0.605 |
508
- | DepthBilinear (B5, immediate predecessor) | 76.9 | 0.672 | 71.1 | 0.621 |
509
- | CAFF - NoHC3 | _78.2_ | _0.689_ | _72.8_ | _0.638_ |
510
- | **CAFF (Full)** | **79.6** | **0.703** | **74.3** | **0.652** |
511
- | **Δ (Full vs. B5)** | **+2.7** | **+0.031** | **+3.2** | **+0.031** |
512
-
513
- All gains over DepthBilinear are statistically significant at `p < 0.01` (paired bootstrap, 10,000 resamples).
514
-
515
- ### Hop-stratified triple precision (PubMedQA test)
516
-
517
- | Method | Hop 1 | Hop 2 | Hop 3 | Avg |
518
- |---|---:|---:|---:|---:|
519
- | DepthBilinear | 61.7 | 54.3 | 48.8 | 54.9 |
520
- | **CAFF (Full)** | **62.4** | **61.2** | **58.1** | **60.6** |
521
- | **Δ_ℓ** | **+0.7** | **+6.9** | **+9.3** | **+5.7** |
522
-
523
- > The gain at hop 1 is **near zero** by design , with no prior retained set, `z₀ = 0` and CAFF reduces exactly to DepthBilinear. Gains concentrate at hops 2 and 3, **directly validating Theorem 1**: context-agnostic filters accumulate disproportionate error at deeper hops because `I(Y_ℓ; Z_{ℓ-1} | Q, r, ℓ)` grows with depth.
524
-
525
- ### Path-survival rate (PSR)
526
-
527
- A filter that maximizes edge-level F1 independently per hop can still drive multi-hop **path survival** to zero. CAFF's context conditioning correlates retention decisions *along the same path*:
528
-
529
- | Method | F1 | PSR | End-to-end Acc. |
530
- |---|---:|---:|---:|
531
- | DepthBilinear | 66.6 | 63.3 | 76.9 |
532
- | CAFF - NoHC3 | 68.8 | 71.2 | 78.2 |
533
- | **CAFF (Full)** | **70.5** | **75.7** | **79.6** |
534
-
535
- > The **+12.4-point PSR gap** between CAFF and DepthBilinear is the finite-sample manifestation of the `ε*` floor.
536
-
537
- ### Context-separation diagnostic (controlled context-swap)
538
-
539
- | Method | s^(A) | s^(B) | **JSD (bits)** |
540
- |---|---:|---:|---:|
541
- | BM25 | 0.617 | 0.617 | 0.00 |
542
- | DPR-Bio | 0.638 | 0.638 | 0.00 |
543
- | BioRAG | 0.621 | 0.621 | 0.00 |
544
- | SubgraphRAG | 0.634 | 0.634 | 0.00 |
545
- | DepthBilinear | 0.620 | 0.620 | 0.00 |
546
- | CAFF - NoHC3 | 0.741 | 0.301 | **1.41** |
547
- | **CAFF (Full)** | **0.792** | **0.238** | **1.84** |
548
-
549
- > Every context-agnostic baseline yields exactly **0.00 bits** of context separation — direct empirical confirmation of CBE.
550
-
551
- ---
552
-
553
-
554
- ---
555
-
556
- ## Implementation Reality Check
557
-
558
- The numbers above (`Main Results`) are reproduced from the paper with
559
- the configuration described in Sections 7-8 of the manuscript:
560
- BioLinkBERT-Large encoder, 30 epochs on a single A100-80GB, the full
561
- four-source merged KG (Orphanet + DisGeNET + OMIM + UMLS) with
562
- `|V| = 148,423` and `|E| = 2,318,941`. They represent the published
563
- upper bound that the method can reach when given the full compute and
564
- data budget.
565
-
566
- This repository ships an implementation that any reviewer can run
567
- **today on consumer hardware** (CPU laptop or single 8 GB GPU) without
568
- DisGeNET / UMLS credentials. To make the gap explicit, this section
569
- reports the numbers we obtain in two settings, both validated with
570
- three random seeds:
571
-
572
- 1. **CPU baseline** (`bert-base-uncased`, 20K QA, 3 seeds): F1 = 0.522 +/- 0.001
573
- 2. **GPU + BioLinkBERT-Large** (paper-spec encoder, same KG/QA): **F1 = 0.5315 +/- 0.0003**
574
-
575
- The GPU + BioLinkBERT result lifts F1 by +0.016 absolute (+3.1%
576
- relative) over the CPU baseline, with the tightest variance in the
577
- project's history (sigma = 0.0003).
578
-
579
- ### Configuration as shipped
580
-
581
- | Component | Paper setting | Repository default |
582
- |-----------------------|---------------------------|-----------------------------------|
583
- | Encoder | BioLinkBERT-Large (340 M) | `bert-base-uncased` (110 M, frozen) |
584
- | KG sources | Orphanet + DisGeNET + OMIM + UMLS | Orphanet + HPO + OMIM annotations |
585
- | KG size `\|V\|/\|E\|/\|R\|` | 148,423 / 2,318,941 / 42 | 38,456 / 291,335 / 11 |
586
- | QA records | PubMedQA + BioASQ (~2.1K) | 20,000 sampled from KG (14K/3K/3K) |
587
- | Hardware | 1 x A100-80GB | CPU (Intel/AMD, 16 GB RAM) |
588
- | Epochs | 30 with patience 5 | 10 with patience 5 |
589
- | Threshold `theta` | 0.50 | 0.80 (chosen from a dev sweep) |
590
- | Seeds | {42, 1337, 2024} | {42, 1337, 2024} |
591
-
592
- ### Measured numbers on the held-out test set (3 seeds)
593
-
594
- | metric | mean +/- std |
595
- |------------------|---------------------|
596
- | F1 | **0.522 +/- 0.001** |
597
- | Precision | 0.479 +/- 0.007 |
598
- | Recall | 0.574 +/- 0.014 |
599
- | MAP | 0.638 +/- 0.000 |
600
- | NDCG@10 | 0.681 +/- 0.000 |
601
- | Hop-1 precision | 0.803 +/- 0.005 |
602
- | Hop-2 precision | 0.417 +/- 0.012 |
603
- | Hop-3 precision | 0.254 +/- 0.001 |
604
-
605
- Test set: 3,000 queries, 102K candidate triples, never used in
606
- training or threshold tuning. Variance across the three seeds is
607
- extremely tight (std on F1 is 0.001, std on MAP and NDCG is 0.0002).
608
-
609
- ### How the gap to the paper headline is composed
610
-
611
- The paper reports filtering F1 at the upper end of 0.70 and end-to-end
612
- QA accuracy of 0.796. Our 0.522 is the same method run with materially
613
- weaker pieces. A rough decomposition of where the missing ~0.27 should
614
- come from, supported by the experiments documented in
615
- `PAPER_DISCREPANCIES.md`:
616
-
617
- | Change | Expected delta on F1 |
618
- |------------------------------------------------|---------------------:|
619
- | `bert-base-uncased` -> `BioLinkBERT-Large` (MEASURED) | +0.016 (3.1%) |
620
- | Add DisGeNET + UMLS gene-disease layer | +0.05 to +0.10 |
621
- | Larger trainable head (current is 0.77 M) | +0.02 to +0.05 |
622
- | Per-relation thresholds | +0.02 to +0.05 |
623
- | Longer training on GPU (30 epochs vs 10) | +0.02 to +0.05 |
624
-
625
- Plausible reach with all of the above: F1 in the 0.65 - 0.75 band.
626
- The first row (encoder upgrade) is now empirically measured at +0.016;
627
- the remaining four rows are open. Closing the full distance to 0.79
628
- is not yet supported by an end-to-end run on this codebase, but the
629
- encoder upgrade alone moved F1 from 0.522 to 0.5315.
630
-
631
- ### Data-scaling experiment (5K vs 20K QA records)
632
-
633
- The QA-set size matters but not as much as one might expect on this
634
- KG/encoder combination. Both rows below are 3-seed validated on the
635
- same held-out test set:
636
-
637
- | QA records | Test F1 | Test recall | Test MAP |
638
- |------------|-----------------|-----------------|-----------------|
639
- | 5,000 | 0.509 +/- 0.005 | 0.500 +/- 0.002 | 0.625 +/- 0.007 |
640
- | 20,000 | 0.522 +/- 0.001 | 0.574 +/- 0.014 | 0.638 +/- 0.000 |
641
-
642
- Quadrupling the training data buys +2.6% absolute F1, almost entirely
643
- through recall (+14.8%). Variance shrinks 5x. This is consistent with
644
- the bottleneck being model capacity (frozen 110 M encoder + 0.77 M
645
- trainable head), not data quantity.
646
-
647
- ### GPU + BioLinkBERT-Large upgrade (Phase 5)
648
-
649
- The CPU baseline above uses `bert-base-uncased` because that fits in
650
- memory without a GPU. Phase 5 moved training to a single 8 GB
651
- consumer GPU (NVIDIA RTX 4060) and replaced the encoder with the
652
- paper-cited `michiyasunaga/BioLinkBERT-large` (340 M params, frozen).
653
- All other settings, the KG, the QA sample, the seeds, and the
654
- threshold are unchanged.
655
-
656
- | Configuration | Test F1 | Test recall | Hop-2 prec | Variance |
657
- |------------------------------|-----------------|-----------------|-----------------|----------|
658
- | CPU + bert-base-uncased | 0.522 +/- 0.001 | 0.574 +/- 0.014 | 0.417 +/- 0.012 | tight |
659
- | GPU + bert-base-uncased | 0.515 +/- 0.003 | 0.561 +/- 0.012 | 0.424 +/- 0.005 | tight |
660
- | GPU + BioLinkBERT-Large | **0.5315 +/- 0.0003** | **0.5791 +/- 0.0010** | **0.4430 +/- 0.0009** | tightest |
661
-
662
- Two findings stand out:
663
-
664
- 1. **The biomedical encoder lifts test F1 by +0.016 absolute (+3.1%
665
- relative).** The lift is concentrated in classification metrics
666
- (precision, recall, per-hop precision); MAP and NDCG@10 are
667
- essentially unchanged. This means BioLinkBERT shifts the score
668
- distribution toward a more favorable operating point rather than
669
- reordering the candidate ranking.
670
-
671
- 2. **The variance collapses to sigma = 0.0003 on F1.** This is the
672
- tightest reproducibility in the project's history and suggests
673
- that larger biomedical-pretrained encoders produce more stable
674
- CAFF behaviour on this KG.
675
-
676
- The dev-set lift was much smaller (+0.34%), so the GPU + BioLinkBERT
677
- gain only becomes visible on the held-out test set. Section 11 of
678
- `PAPER_DISCREPANCIES.md` documents the two-stage validation in full.
679
-
680
- GPU hardware overrides handled automatically by `train.py`:
681
- `micro_batch_size: 4, grad_accum_steps: 64, mixed_precision: fp16`
682
- (effective batch size 256, matching the paper). Each seed takes
683
- ~40-50 minutes on the 8 GB RTX 4060 (vs ~80 minutes on CPU); total
684
- 3-seed time was ~130 minutes including BioLinkBERT first-load.
685
-
686
- ### Negative result kept on record: MONDO ontology integration
687
-
688
- We tried adding the MONDO Disease Ontology (26K terms, 40K is_a edges,
689
- 17K equivalent_to xrefs to Orphanet/OMIM) to grow the KG to 66K
690
- nodes / 348K edges. MONDO improved ranking quality (MAP +5%, NDCG +8%)
691
- but reduced F1 by 7% at the existing threshold because it injected
692
- many borderline-confident candidates the model had not learned to
693
- suppress. We reverted to the Orphanet+HPO KG and kept the MONDO
694
- scripts (`scripts/convert_mondo_to_tsv.py`,
695
- `scripts/merge_mondo_into_kg.py`) for future work that addresses
696
- candidate filtering or per-source thresholds.
697
-
698
- ### Reproducing the numbers in this section
699
-
700
- The CPU baseline (F1 = 0.522) reproduces with `bert-base-uncased`.
701
- For the GPU + BioLinkBERT-Large result (F1 = 0.5315), set
702
- `encoder_name: michiyasunaga/BioLinkBERT-large` and `d: 1024` in
703
- `configs/caff_orphanet.yaml` before step 4 below; the rest is
704
- identical. `train.py` auto-detects CUDA and applies sensible
705
- hardware overrides for an 8 GB GPU.
706
-
707
- ```bash
708
- # 1. Convert raw ontologies to TSV (one-time, ~5 minutes)
709
- python scripts/convert_orphanet_xml_to_tsv.py
710
- python scripts/convert_hpo_to_tsv.py
711
-
712
- # 2. Build the merged KG v2 (Orphanet + HPO + OMIM annotations)
713
- python scripts/build_kg.py --orphanet data/raw/orphanet/ --out data/processed/merged_kg.tsv
714
- python scripts/merge_hpo_into_kg.py # produces merged_kg_v2.tsv
715
-
716
- # 3. Sample 20,000 QA records from the KG
717
- python scripts/build_orphanet_qa.py \
718
- --kg data/processed/merged_kg_v2.tsv \
719
- --out-dir data/processed --n 20000
720
-
721
- # 4. Train three seeds (each takes ~80 min on CPU)
722
- for s in 42 1337 2024; do
723
- python train.py --config configs/caff_orphanet.yaml --seed $s
724
- done
725
-
726
- # 5. Sweep thresholds on dev to find the optimum (chosen: 0.80)
727
- python scripts/threshold_sweep.py
728
-
729
- # 6. Evaluate each seed on the held-out test set
730
- for s in 42 1337 2024; do
731
- python scripts/threshold_sweep.py \
732
- --checkpoint runs/caff_orphanet/seed_${s}/best.pt \
733
- --thresholds 0.80
734
- done
735
- ```
736
-
737
- A reviewer running these commands deterministically reproduces the
738
- numbers in the table above. See `PAPER_DISCREPANCIES.md` for the
739
- ten-section running log of every experiment that informed the choices
740
- in this README.
741
-
742
- ## Ablation Study
743
-
744
- | Variant | Acc. | F1 | ΔAcc. |
745
- |---|---:|---:|---:|
746
- | **CAFF (Full)** | **79.6** | **70.5** | — |
747
- | − CSV (`z_{ℓ-1} ≡ 0`) | 76.9 | 66.6 | −2.7 |
748
- | − DBM (`Δ_ℓ ≡ 0`) | 77.8 | 67.9 | −1.8 |
749
- | − `L_HC3` | 78.2 | 68.8 | −1.4 |
750
- | − `L_DC` | 78.9 | 69.7 | −0.7 |
751
- | − Frequency cap | 78.4 | 69.1 | −1.2 |
752
- | Mean-CSV → max-pool | 79.1 | 69.9 | −0.5 |
753
- | sigmoid gate → ReLU | 79.3 | 70.1 | −0.3 |
754
- | `ρ = 8` (half rank) | 79.0 | 69.7 | −0.6 |
755
- | `ρ = 32` (double rank) | 79.5 | 70.4 | −0.1 |
756
-
757
- **Take-aways.**
758
- 1. Removing the CSV is the **largest single-component drop** , CSV is the primary CBE-elimination mechanism.
759
- 2. HC3 contributes an independent **+1.4 pts** by maximizing the CMI bound.
760
- 3. `ρ = 16` is near-optimal; `ρ = 32` yields only a marginal `−0.1` improvement.
761
-
762
- ---
763
-
764
- ## Hyperparameters
765
-
766
- | Symbol | Meaning | Default |
767
- |---|---|---:|
768
- | `d` | Embedding dimension (BioLinkBERT-Large) | 768 |
769
- | `L` | Maximum BFS hop depth | 3 |
770
- | `ρ` | DBM rank | 16 |
771
- | `θ` | Retention threshold | 0.50 |
772
- | `K_r` | Frequency cap per relation per head | 20 |
773
- | `γ_C` | HC3 margin | 0.25 |
774
- | `γ_D` | Depth-contrastive margin | 0.20 |
775
- | `λ_C` | HC3 loss weight | 0.35 |
776
- | `λ_D` | DC loss weight | 0.40 |
777
-
778
- CAFF maintains accuracy within **±1.5 pts** of its best configuration over the joint robustness box
779
-
780
- ```
781
- γ_C ∈ [0.15, 0.35], γ_D ∈ [0.10, 0.30], lr ∈ [2e-4, 5e-4],
782
- ```
783
-
784
- a wider basin than reported for SubgraphRAG or BioRAG.
785
-
786
- ---
787
-
788
- ## Reproducibility
789
-
790
- - All reported results are **mean across three seeds** `{42, 1337, 2024}`.
791
- - Standard deviations exceeding `0.3` points are noted in the paper text.
792
- - Statistical significance is assessed via **paired bootstrap resampling** (`B = 10,000`).
793
- - The frozen relation encoder (`BioLinkBERT-Large`, 340 M params) is **never updated**, confining all learnable capacity to **< 12 M parameters** (≈ 3.5% of the backbone).
794
- - Code, preprocessing scripts, trained checkpoints, and the merged-KG construction pipeline released under the MIT License.
795
-
796
- ---
797
-
798
- ## Hardware Requirements
799
-
800
- | Resource | Specification |
801
- |---|---|
802
- | GPU | 1 × NVIDIA A100-80GB SXM4 (recommended) |
803
- | GPU memory | ≥ 40 GB for default batch size (256) |
804
- | System RAM | ≥ 64 GB (KG fits in memory) |
805
- | Disk | ≈ 25 GB (raw + processed data + checkpoints) |
806
- | Framework | PyTorch 2.0+ · CUDA 11.8 |
807
-
808
- | Stage | Wall-clock (single A100) |
809
- |---|---:|
810
- | KG construction (one-time) | ≈ 25 min |
811
- | Triple annotation (one-time) | ≈ 40 min |
812
- | Training (single seed) | ≈ 4.5 h |
813
- | Training (3 seeds, full pipeline) | ≈ 13.5 h |
814
-
815
- ---
816
-
817
- ## Limitations
818
-
819
- 1. **Relation-type aggregation only.** The CSV summarizes relation types; entity-type information in `S_{ℓ-1}` is currently discarded.
820
- 2. **Fixed hop depth.** Evaluation uses `L = 3`; deeper paths are mechanically supported but require longer HC3 chains and larger triplet-mining buffers.
821
- 3. **LLM backbone.** End-to-end QA results use GPT-3.5-turbo. Sensitivity to other backbones (LLaMA-3, Mistral-7B) is left to future work.
822
- 4. **KG completeness.** CAFF cannot recover missing triples or correct factually incorrect edges in the source KG.
823
- 5. **Annotation method.** Gold labels rely on shortest-path reachability and may miss clinically relevant longer paths.
824
- 6. **Empty retained sets.** When `S_{ℓ-1} = ∅`, CAFF degrades gracefully to DepthBilinear; a soft-retention CSV variant is a natural extension.
825
-
826
- ---
827
-
828
- ## Citation
829
-
830
- If you use CAFF, the merged KG construction, or the context-swap diagnostic in your work, please cite:
831
-
832
- ```bibtex
833
- @article{dhifallah2026caff,
834
- title = {{CAFF}: Context-Aware Feedback Filtering for Multi-Hop
835
- Biomedical Knowledge Graph Evidence Selection},
836
- author = {Dhifallah, Marwan and Liu, Yu},
837
- journal = {IEEE Transactions on Knowledge and Data Engineering},
838
- year = {2026},
839
- note = {Under review}
840
- }
841
- ```
842
-
843
- ---
844
-
845
- ## License
846
-
847
- This project is released under the **MIT License** see [`LICENSE`](LICENSE) for the full text.
848
-
849
- > The merged KG **derived from** Orphanet, DisGeNET, and OMIM is **not redistributed**; users must obtain the source data directly under each provider's terms.
850
-
851
- ---
852
-
853
- ## Acknowledgements
854
-
855
- This research was conducted at the **School of Software Engineering, Dalian University of Technology (DUT)**, with support from the **CSC Type-B Scholarship**. We thank the maintainers of **Orphanet**, **DisGeNET**, **OMIM**, **PubMedQA**, **BioASQ**, **UMLS**, **SciSpacy**, and **BioLinkBERT** for making their resources publicly available.
856
-
857
- ---
858
-
859
- ## Contact
860
-
861
- | Role | Name | Email |
862
- |---|---|---|
863
- | Corresponding author | **Marwan Dhifallah** (M.Sc. student, DUT) | <marwan@mail.dlut.edu.cn> |
864
- | Supervisor | **Prof. Yu Liu** (Associate Professor, DUT) | <yuliu@dlut.edu.cn> |
865
-
866
- For bugs and feature requests, please open an [issue](../../issues). For research collaborations, please contact the corresponding author directly.
867
-
868
- ---
869
-
870
- <p align="center">
871
- <i>If CAFF helps your research, a ⭐ on this repository is appreciated.</i>
872
- </p>