From ba6ead6d7a41b7ed78bb228181b7262d0c75d2eb Mon Sep 17 00:00:00 2001 From: YurenHao0426 Date: Mon, 4 May 2026 23:10:10 -0500 Subject: =?UTF-8?q?Global=20rename=20GRAFT=20=E2=86=92=20KAFT=20(incl.=20i?= =?UTF-8?q?nternal=20class=20+=20filenames)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/trainers.py: GraphGrAPETrainer → KAFTTrainer; module docstring + comments. VanillaGrAPETrainer kept as-is (it is a separate control method, not KAFT). - experiments/: all 19 runners pick up the new class name; result keys ('Cora_GRAFT' etc) become 'Cora_KAFT'; OUT_DIRs renamed (e.g. bp_graft_depth_20seeds → bp_kaft_depth_20seeds). - figures/: data-lookup keys + display labels both 'KAFT'; output filename graft_depth_sweep.{pdf,png} → kaft_depth_sweep.{pdf,png}. - File rename: experiments/run_bp_graft_depth.py → run_bp_kaft_depth.py; figures/graft_depth_sweep.pdf → kaft_depth_sweep.pdf. - README aligned. Imports verified: from src.trainers import KAFTTrainer succeeds. --- README.md | 8 +-- experiments/run_ablation_20seeds.py | 6 +- experiments/run_bp_graft_depth.py | 111 ---------------------------------- experiments/run_bp_kaft_depth.py | 111 ++++++++++++++++++++++++++++++++++ experiments/run_combo_20seeds.py | 72 +++++++++++----------- experiments/run_cora_perturb.py | 4 +- experiments/run_cs_full.py | 14 ++--- experiments/run_dblp_depth.py | 8 +-- experiments/run_dblp_depth_scaling.py | 14 ++--- experiments/run_depth_extras.py | 6 +- experiments/run_dfagnn_depth.py | 4 +- experiments/run_grad_reach_20seeds.py | 22 +++---- experiments/run_hero_extras.py | 12 ++-- experiments/run_realworld_hero_L20.py | 18 +++--- experiments/run_resgcn_20seeds.py | 20 +++--- experiments/run_shallow_depth.py | 20 +++--- experiments/run_wikics_paper_setup.py | 14 ++--- figures/gen_depth_sweep_fig.py | 20 +++--- figures/gen_fig4_combined.py | 12 ++-- figures/gen_realworld_depth_fig.py | 18 +++--- figures/graft_depth_sweep.pdf | Bin 27139 -> 0 bytes figures/kaft_depth_sweep.pdf | Bin 0 -> 27139 bytes src/data.py | 2 +- src/trainers.py | 10 +-- 24 files changed, 262 insertions(+), 264 deletions(-) delete mode 100644 experiments/run_bp_graft_depth.py create mode 100644 experiments/run_bp_kaft_depth.py delete mode 100644 figures/graft_depth_sweep.pdf create mode 100644 figures/kaft_depth_sweep.pdf diff --git a/README.md b/README.md index c0979c6..e277ff4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # KAFT: Topology-Factorized Jacobian-Aligned Feedback for Deep GNNs -> Internal class names (`GraphGrAPETrainer`, file paths starting with -> `graft_*` / dataset keys `'GRAFT'`) are the original code identifier. +> Internal class names (`KAFTTrainer`, file paths starting with +> `kaft_*` / dataset keys `'KAFT'`) are the original code identifier. > The method is referred to as **KAFT** in the paper. Code release accompanying the NeurIPS 2026 submission. @@ -24,7 +24,7 @@ computed in O(1) parallel depth on GPUs. ``` src/ core method - trainers.py BPTrainer, GraphGrAPETrainer (= KAFT), DFA/DFA-GNN, alignment + trainers.py BPTrainer, KAFTTrainer (= KAFT), DFA/DFA-GNN, alignment data.py PyG dataset loaders, normalized  / row-Â, sparse-mm helpers experiments/ one runner per reported result block (see `## Reproducing`) figures/ figure generators + the four rendered PDFs in the paper @@ -51,7 +51,7 @@ CUDA_VISIBLE_DEVICES=0 python -u experiments/run_ablation_20seeds.py # Fig 2: Planetoid depth sweep (11 / 13 points) CUDA_VISIBLE_DEVICES=0 python -u experiments/run_shallow_depth.py -CUDA_VISIBLE_DEVICES=0 python -u experiments/run_bp_graft_depth.py +CUDA_VISIBLE_DEVICES=0 python -u experiments/run_bp_kaft_depth.py CUDA_VISIBLE_DEVICES=0 python -u experiments/run_dfagnn_depth.py CUDA_VISIBLE_DEVICES=0 python -u experiments/run_depth_extras.py CUDA_VISIBLE_DEVICES=0 python -u experiments/run_dblp_depth_scaling.py diff --git a/experiments/run_ablation_20seeds.py b/experiments/run_ablation_20seeds.py index 61055ed..d2bd434 100644 --- a/experiments/run_ablation_20seeds.py +++ b/experiments/run_ablation_20seeds.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Ablation study with 20 seeds: BP → DFA → DFA-GNN → VanillaGrAPE → GRAFT.""" +"""Ablation study with 20 seeds: BP → DFA → DFA-GNN → VanillaGrAPE → KAFT.""" import torch import numpy as np @@ -7,7 +7,7 @@ import json import os from scipy import stats as scipy_stats from src.data import load_dataset -from src.trainers import BPTrainer, DFATrainer, DFAGNNTrainer, VanillaGrAPETrainer, GraphGrAPETrainer +from src.trainers import BPTrainer, DFATrainer, DFAGNNTrainer, VanillaGrAPETrainer, KAFTTrainer device = 'cuda:0' SEEDS = list(range(20)) @@ -22,7 +22,7 @@ METHODS = { 'diffusion_alpha': 0.5, 'diffusion_iters': 10, 'lr_feedback': 0.5, 'num_probes': 64, 'topo_mode': 'fixed_A' }), - 'GRAFT': (GraphGrAPETrainer, { + 'KAFT': (KAFTTrainer, { 'diffusion_alpha': 0.5, 'diffusion_iters': 10, 'lr_feedback': 0.5, 'num_probes': 64, 'topo_mode': 'fixed_A' }), diff --git a/experiments/run_bp_graft_depth.py b/experiments/run_bp_graft_depth.py deleted file mode 100644 index 1e8dd76..0000000 --- a/experiments/run_bp_graft_depth.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 -"""H9: BP + GRAFT depth sweep on Cora/CiteSeer/PubMed. - -E1 already did DBLP L={8,12,16,20,24,32}. This fills the gap for Cora/CiteSeer/PubMed -at L={8,10,12,16,20} so we can plot Figure 4(a)-style depth curves on 4 datasets. - -BP + GRAFT only (GRAFT+ResGCN not needed for this figure — that's stacking table). -""" - -import torch -import numpy as np -import json -import os -from src.data import load_dataset -from src.trainers import BPTrainer, GraphGrAPETrainer - -device = 'cuda:0' -SEEDS = list(range(20)) -EPOCHS = 200 -DEPTHS = [8, 10, 12, 16, 20] -OUT_DIR = 'results/bp_graft_depth_20seeds' - -grape_extra = dict(diffusion_alpha=0.5, diffusion_iters=10, - lr_feedback=0.5, num_probes=64, topo_mode='fixed_A') - -METHODS = { - 'BP': (BPTrainer, {}), - 'GRAFT': (GraphGrAPETrainer, grape_extra), -} - - -def train_one(cls, common, extra, seed): - torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed) - t = cls(**common, **extra) - if hasattr(t, 'align_mode'): - t.align_mode = 'chain_norm' - bv, bt = 0, 0 - for ep in range(EPOCHS): - t.train_step() - if ep % 5 == 0: - v = t.evaluate('val_mask') - te = t.evaluate('test_mask') - if v > bv: bv, bt = v, te - del t; torch.cuda.empty_cache() - return bt - - -def main(): - os.makedirs(OUT_DIR, exist_ok=True) - per_seed_file = os.path.join(OUT_DIR, 'per_seed_data.json') - if os.path.exists(per_seed_file): - with open(per_seed_file) as f: - per_seed_data = json.load(f) - else: - per_seed_data = {} - - datasets_cfg = { - 'Cora': lambda: load_dataset('Cora', device=device), - 'CiteSeer': lambda: load_dataset('CiteSeer', device=device), - 'PubMed': lambda: load_dataset('PubMed', device=device), - } - - for ds_name, loader in datasets_cfg.items(): - data = loader() - for L in DEPTHS: - common = dict(data=data, hidden_dim=64, lr=0.01, weight_decay=5e-4, - num_layers=L, residual_alpha=0.0, backbone='gcn') - - for mname, (cls, extra) in METHODS.items(): - key = f"{ds_name}_L{L}_{mname}" - if key not in per_seed_data: - per_seed_data[key] = {} - - print(f"\n=== {key} (20 seeds) ===", flush=True) - for seed in SEEDS: - sk = str(seed) - if sk in per_seed_data[key]: - print(f" seed {seed}: cached ({per_seed_data[key][sk]*100:.1f}%)", flush=True) - continue - try: - acc = train_one(cls, common, extra, seed) - per_seed_data[key][sk] = acc - print(f" seed {seed}: {acc*100:.1f}%", flush=True) - except Exception as e: - print(f" seed {seed}: FAILED - {e}", flush=True) - per_seed_data[key][sk] = 0.0 - - with open(per_seed_file, 'w') as f: - json.dump(per_seed_data, f, indent=2) - del data; torch.cuda.empty_cache() - - # Summary - print(f"\n{'=' * 70}\nBP/GRAFT depth sweep summary\n{'=' * 70}") - results = {} - for ds in datasets_cfg: - print(f"\n{ds}:") - for L in DEPTHS: - for m in METHODS: - key = f"{ds}_L{L}_{m}" - vals = np.array([per_seed_data[key][str(s)] for s in SEEDS]) * 100 - results[key] = {'mean': float(vals.mean()), 'std': float(vals.std()), - 'per_seed': vals.tolist()} - print(f" L={L:2d} {m:<6} {vals.mean():5.1f} ± {vals.std():4.1f}") - - with open(os.path.join(OUT_DIR, 'results.json'), 'w') as f: - json.dump(results, f, indent=2) - print(f"\nSaved to {OUT_DIR}/results.json") - - -if __name__ == '__main__': - main() diff --git a/experiments/run_bp_kaft_depth.py b/experiments/run_bp_kaft_depth.py new file mode 100644 index 0000000..d3119e1 --- /dev/null +++ b/experiments/run_bp_kaft_depth.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""H9: BP + KAFT depth sweep on Cora/CiteSeer/PubMed. + +E1 already did DBLP L={8,12,16,20,24,32}. This fills the gap for Cora/CiteSeer/PubMed +at L={8,10,12,16,20} so we can plot Figure 4(a)-style depth curves on 4 datasets. + +BP + KAFT only (KAFT+ResGCN not needed for this figure — that's stacking table). +""" + +import torch +import numpy as np +import json +import os +from src.data import load_dataset +from src.trainers import BPTrainer, KAFTTrainer + +device = 'cuda:0' +SEEDS = list(range(20)) +EPOCHS = 200 +DEPTHS = [8, 10, 12, 16, 20] +OUT_DIR = 'results/bp_kaft_depth_20seeds' + +grape_extra = dict(diffusion_alpha=0.5, diffusion_iters=10, + lr_feedback=0.5, num_probes=64, topo_mode='fixed_A') + +METHODS = { + 'BP': (BPTrainer, {}), + 'KAFT': (KAFTTrainer, grape_extra), +} + + +def train_one(cls, common, extra, seed): + torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed) + t = cls(**common, **extra) + if hasattr(t, 'align_mode'): + t.align_mode = 'chain_norm' + bv, bt = 0, 0 + for ep in range(EPOCHS): + t.train_step() + if ep % 5 == 0: + v = t.evaluate('val_mask') + te = t.evaluate('test_mask') + if v > bv: bv, bt = v, te + del t; torch.cuda.empty_cache() + return bt + + +def main(): + os.makedirs(OUT_DIR, exist_ok=True) + per_seed_file = os.path.join(OUT_DIR, 'per_seed_data.json') + if os.path.exists(per_seed_file): + with open(per_seed_file) as f: + per_seed_data = json.load(f) + else: + per_seed_data = {} + + datasets_cfg = { + 'Cora': lambda: load_dataset('Cora', device=device), + 'CiteSeer': lambda: load_dataset('CiteSeer', device=device), + 'PubMed': lambda: load_dataset('PubMed', device=device), + } + + for ds_name, loader in datasets_cfg.items(): + data = loader() + for L in DEPTHS: + common = dict(data=data, hidden_dim=64, lr=0.01, weight_decay=5e-4, + num_layers=L, residual_alpha=0.0, backbone='gcn') + + for mname, (cls, extra) in METHODS.items(): + key = f"{ds_name}_L{L}_{mname}" + if key not in per_seed_data: + per_seed_data[key] = {} + + print(f"\n=== {key} (20 seeds) ===", flush=True) + for seed in SEEDS: + sk = str(seed) + if sk in per_seed_data[key]: + print(f" seed {seed}: cached ({per_seed_data[key][sk]*100:.1f}%)", flush=True) + continue + try: + acc = train_one(cls, common, extra, seed) + per_seed_data[key][sk] = acc + print(f" seed {seed}: {acc*100:.1f}%", flush=True) + except Exception as e: + print(f" seed {seed}: FAILED - {e}", flush=True) + per_seed_data[key][sk] = 0.0 + + with open(per_seed_file, 'w') as f: + json.dump(per_seed_data, f, indent=2) + del data; torch.cuda.empty_cache() + + # Summary + print(f"\n{'=' * 70}\nBP/KAFT depth sweep summary\n{'=' * 70}") + results = {} + for ds in datasets_cfg: + print(f"\n{ds}:") + for L in DEPTHS: + for m in METHODS: + key = f"{ds}_L{L}_{m}" + vals = np.array([per_seed_data[key][str(s)] for s in SEEDS]) * 100 + results[key] = {'mean': float(vals.mean()), 'std': float(vals.std()), + 'per_seed': vals.tolist()} + print(f" L={L:2d} {m:<6} {vals.mean():5.1f} ± {vals.std():4.1f}") + + with open(os.path.join(OUT_DIR, 'results.json'), 'w') as f: + json.dump(results, f, indent=2) + print(f"\nSaved to {OUT_DIR}/results.json") + + +if __name__ == '__main__': + main() diff --git a/experiments/run_combo_20seeds.py b/experiments/run_combo_20seeds.py index 1598964..acc8846 100644 --- a/experiments/run_combo_20seeds.py +++ b/experiments/run_combo_20seeds.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -"""Task 2ceadaa7: GRAFT + Forward Tricks combo experiments (20 seeds). +"""Task 2ceadaa7: KAFT + Forward Tricks combo experiments (20 seeds). -Combos: GRAFT+ResGCN, GRAFT+DropEdge, GRAFT+PairNorm, GRAFT+JKNet +Combos: KAFT+ResGCN, KAFT+DropEdge, KAFT+PairNorm, KAFT+JKNet Each compared to: BP, forward_trick_only, GRAFT_only, combo """ @@ -12,7 +12,7 @@ import json import os from scipy import stats as scipy_stats from src.data import load_dataset, spmm, build_normalized_adj -from src.trainers import BPTrainer, GraphGrAPETrainer, _FeedbackTrainerBase +from src.trainers import BPTrainer, KAFTTrainer, _FeedbackTrainerBase from run_deep_baselines import ResGCNTrainer, JKNetTrainer from run_dropedge import BPDropEdgeTrainer from run_pairnorm_baseline import BPPairNormTrainer, pairnorm @@ -28,10 +28,10 @@ grape_extra = dict(diffusion_alpha=0.5, diffusion_iters=10, # ═══════════════════════════════════════════════════════════════════════════ -# GRAFT + ResGCN combo (fixed version) +# KAFT + ResGCN combo (fixed version) # ═══════════════════════════════════════════════════════════════════════════ -class GRAFTResGCN(GraphGrAPETrainer): - """GRAFT backward + ResGCN forward (skip connections).""" +class GRAFTResGCN(KAFTTrainer): + """KAFT backward + ResGCN forward (skip connections).""" def forward(self): X = self.data['X'] @@ -57,10 +57,10 @@ class GRAFTResGCN(GraphGrAPETrainer): # ═══════════════════════════════════════════════════════════════════════════ -# GRAFT + DropEdge combo +# KAFT + DropEdge combo # ═══════════════════════════════════════════════════════════════════════════ -class GRAFTDropEdge(GraphGrAPETrainer): - """GRAFT backward + DropEdge forward (random edge dropping).""" +class GRAFTDropEdge(KAFTTrainer): + """KAFT backward + DropEdge forward (random edge dropping).""" def __init__(self, *args, drop_rate=0.5, **kwargs): super().__init__(*args, **kwargs) @@ -81,9 +81,9 @@ class GRAFTDropEdge(GraphGrAPETrainer): ).coalesce() def forward(self): - # DropEdge only in forward pass, GRAFT backward uses original A_hat + # DropEdge only in forward pass, KAFT backward uses original A_hat self.data['A_hat'] = self._drop_edges() - result = super().forward() # uses GraphGrAPETrainer.forward() + result = super().forward() # uses KAFTTrainer.forward() self.data['A_hat'] = self._A_hat_orig return result @@ -93,10 +93,10 @@ class GRAFTDropEdge(GraphGrAPETrainer): # ═══════════════════════════════════════════════════════════════════════════ -# GRAFT + PairNorm combo +# KAFT + PairNorm combo # ═══════════════════════════════════════════════════════════════════════════ -class GRAFTPairNorm(GraphGrAPETrainer): - """GRAFT backward + PairNorm forward (center & scale normalization).""" +class GRAFTPairNorm(KAFTTrainer): + """KAFT backward + PairNorm forward (center & scale normalization).""" def __init__(self, *args, pn_scale=1.0, **kwargs): super().__init__(*args, **kwargs) @@ -138,13 +138,13 @@ class GRAFTPairNorm(GraphGrAPETrainer): # ═══════════════════════════════════════════════════════════════════════════ -# GRAFT + JKNet combo +# KAFT + JKNet combo # ═══════════════════════════════════════════════════════════════════════════ -class GRAFTJKNet(GraphGrAPETrainer): - """GRAFT backward + JKNet forward (jumping knowledge max-pool). +class GRAFTJKNet(KAFTTrainer): + """KAFT backward + JKNet forward (jumping knowledge max-pool). Note: JKNet changes the output architecture. We max-pool hidden layers - and project to num_classes. GRAFT backward operates on hidden layers + and project to num_classes. KAFT backward operates on hidden layers as usual; the JK projection is treated as the output layer. """ @@ -187,7 +187,7 @@ class GRAFTJKNet(GraphGrAPETrainer): def _update_weights(self, inter, E0, deltas): """Override to handle JK projection separately.""" - # Update hidden layers using GRAFT feedback as usual + # Update hidden layers using KAFT feedback as usual X = self.data['X'] Hs = inter['Hs'] H0 = inter['H0'] @@ -265,7 +265,7 @@ def main(): per_seed_data = {} # Reuse existing per-seed data from other experiments - # BP, ResGCN, GRAFT from resgcn_20seeds + # BP, ResGCN, KAFT from resgcn_20seeds try: with open('results/resgcn_20seeds/per_seed_data.json') as f: resgcn_cache = json.load(f) @@ -292,11 +292,11 @@ def main(): 'DropEdge': (BPDropEdgeTrainer, {'drop_rate': 0.5}), 'PairNorm': (BPPairNormTrainer, {'pn_scale': 1.0}), 'JKNet': (JKNetTrainer, {}), - 'GRAFT': (GraphGrAPETrainer, grape_extra), - 'GRAFT+ResGCN': (GRAFTResGCN, grape_extra), - 'GRAFT+DropEdge': (GRAFTDropEdge, {**grape_extra, 'drop_rate': 0.5}), - 'GRAFT+PairNorm': (GRAFTPairNorm, {**grape_extra, 'pn_scale': 1.0}), - 'GRAFT+JKNet': (GRAFTJKNet, grape_extra), + 'KAFT': (KAFTTrainer, grape_extra), + 'KAFT+ResGCN': (GRAFTResGCN, grape_extra), + 'KAFT+DropEdge': (GRAFTDropEdge, {**grape_extra, 'drop_rate': 0.5}), + 'KAFT+PairNorm': (GRAFTPairNorm, {**grape_extra, 'pn_scale': 1.0}), + 'KAFT+JKNet': (GRAFTJKNet, grape_extra), } datasets_cfg = { @@ -330,7 +330,7 @@ def main(): cached = resgcn_cache[f"{ds_name}_BP"].get(sk) elif mname == 'ResGCN' and f"{ds_name}_ResGCN" in resgcn_cache: cached = resgcn_cache[f"{ds_name}_ResGCN"].get(sk) - elif mname == 'GRAFT' and f"{ds_name}_GRAFT" in resgcn_cache: + elif mname == 'KAFT' and f"{ds_name}_GRAFT" in resgcn_cache: cached = resgcn_cache[f"{ds_name}_GRAFT"].get(sk) elif mname == 'DropEdge': de_key = f"{ds_name}_gcn_L6" @@ -369,14 +369,14 @@ def main(): for ds in ['Cora', 'CiteSeer', 'DBLP']: print(f"\n--- {ds} GCN L=6 lr=0.01 ---") - print(f"{'Method':<18} {'Mean±Std':>12} {'vs GRAFT':>18} {'vs FwdTrick':>18}") + print(f"{'Method':<18} {'Mean±Std':>12} {'vs KAFT':>18} {'vs FwdTrick':>18}") print("-" * 70) - # Get GRAFT accs for comparison + # Get KAFT accs for comparison gr_accs = np.array([per_seed_data[f"{ds}_GRAFT"][str(s)] for s in SEEDS]) * 100 for mname in ['BP', 'ResGCN', 'DropEdge', 'PairNorm', 'JKNet', - 'GRAFT', 'GRAFT+ResGCN', 'GRAFT+DropEdge', 'GRAFT+PairNorm', 'GRAFT+JKNet']: + 'KAFT', 'KAFT+ResGCN', 'KAFT+DropEdge', 'KAFT+PairNorm', 'KAFT+JKNet']: key = f"{ds}_{mname}" if key not in per_seed_data or len(per_seed_data[key]) < 20: print(f" {mname:<16} MISSING ({len(per_seed_data.get(key, {}))} seeds)") @@ -387,8 +387,8 @@ def main(): results[key] = {'mean': float(m), 'std': float(s), 'accs': accs.tolist()} - # Paired t-test vs GRAFT - if mname != 'GRAFT': + # Paired t-test vs KAFT + if mname != 'KAFT': t_stat, p_val = scipy_stats.ttest_rel(accs, gr_accs) delta = m - gr_accs.mean() sig = '***' if p_val < 0.001 else ('**' if p_val < 0.01 else ('*' if p_val < 0.05 else 'ns')) @@ -402,8 +402,8 @@ def main(): # Paired t-test vs forward trick only fwd_map = { - 'GRAFT+ResGCN': 'ResGCN', 'GRAFT+DropEdge': 'DropEdge', - 'GRAFT+PairNorm': 'PairNorm', 'GRAFT+JKNet': 'JKNet' + 'KAFT+ResGCN': 'ResGCN', 'KAFT+DropEdge': 'DropEdge', + 'KAFT+PairNorm': 'PairNorm', 'KAFT+JKNet': 'JKNet' } if mname in fwd_map: fwd_key = f"{ds}_{fwd_map[mname]}" @@ -431,8 +431,8 @@ def main(): for ds in ['Cora', 'CiteSeer', 'DBLP']: print(f"\n{ds}:") gr_m = results.get(f"{ds}_GRAFT", {}).get('mean', 0) - for combo, fwd in [('GRAFT+ResGCN', 'ResGCN'), ('GRAFT+DropEdge', 'DropEdge'), - ('GRAFT+PairNorm', 'PairNorm'), ('GRAFT+JKNet', 'JKNet')]: + for combo, fwd in [('KAFT+ResGCN', 'ResGCN'), ('KAFT+DropEdge', 'DropEdge'), + ('KAFT+PairNorm', 'PairNorm'), ('KAFT+JKNet', 'JKNet')]: ck = f"{ds}_{combo}" fk = f"{ds}_{fwd}" if ck in results and fk in results: @@ -442,7 +442,7 @@ def main(): vs_fw = results[ck].get(f'vs_{fwd}', {}) better_than_both = c_m > gr_m and c_m > f_m marker = "✓ ADDITIVE" if better_than_both else "✗ not additive" - print(f" {combo}: {c_m:.1f} | GRAFT={gr_m:.1f} | {fwd}={f_m:.1f} → {marker}") + print(f" {combo}: {c_m:.1f} | KAFT={gr_m:.1f} | {fwd}={f_m:.1f} → {marker}") # Save with open(os.path.join(OUT_DIR, 'results.json'), 'w') as f: diff --git a/experiments/run_cora_perturb.py b/experiments/run_cora_perturb.py index 1dabc95..1124b97 100644 --- a/experiments/run_cora_perturb.py +++ b/experiments/run_cora_perturb.py @@ -13,7 +13,7 @@ import numpy as np import json import os from src.data import load_dataset, build_normalized_adj, build_row_normalized_adj -from src.trainers import BPTrainer, DFATrainer, GraphGrAPETrainer +from src.trainers import BPTrainer, DFATrainer, KAFTTrainer device = 'cuda:0' SEEDS = [0, 1, 2, 3, 4] @@ -111,7 +111,7 @@ def main(): common = dict(data=data, hidden_dim=64, lr=0.01, weight_decay=5e-4, num_layers=L, residual_alpha=0.0, backbone='gcn') bp_accs.append(train_one(BPTrainer, common, {}, seed)) - gr_accs.append(train_one(GraphGrAPETrainer, common, grape_extra, seed)) + gr_accs.append(train_one(KAFTTrainer, common, grape_extra, seed)) bp, gr = np.mean(bp_accs)*100, np.mean(gr_accs)*100 delta = gr - bp diff --git a/experiments/run_cs_full.py b/experiments/run_cs_full.py index d66fc59..d170e13 100644 --- a/experiments/run_cs_full.py +++ b/experiments/run_cs_full.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """H19 CitationFull-CiteSeer (4.2K, deg 2.5, 6-class) — same regime as Planetoid CiteSeer. -Quick BP + GRAFT depth sweep to confirm/extend the 'GRAFT wins on real sparse citation' story.""" +Quick BP + KAFT depth sweep to confirm/extend the 'KAFT wins on real sparse citation' story.""" import torch, sys, numpy as np, time import torch.nn as nn, torch.nn.functional as F @@ -9,7 +9,7 @@ from torch_geometric.nn import GCNConv from torch_geometric.utils import add_self_loops, degree sys.path.insert(0, '/home/yurenh2/graph-grape') -from src.trainers import GraphGrAPETrainer +from src.trainers import KAFTTrainer DATA_ROOT = '/home/yurenh2/graph-grape/data/CFull' device = torch.device('cuda:0') @@ -90,7 +90,7 @@ def bp_one(L, seed, d, train_mask, val_mask, test_mask, epochs=200, lr=5e-3, hid return bt -def graft_one(L, seed, d, A_hat, A_row, A_row_T, train_mask, val_mask, test_mask, +def kaft_one(L, seed, d, A_hat, A_row, A_row_T, train_mask, val_mask, test_mask, epochs=200, lr=5e-3, hidden=128): torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed) data = { @@ -99,7 +99,7 @@ def graft_one(L, seed, d, A_hat, A_row, A_row_T, train_mask, val_mask, test_mask 'num_features': d.x.shape[1], 'num_classes': int(d.y.max())+1, 'num_nodes': d.num_nodes, 'traces': {}, } - trainer = GraphGrAPETrainer( + trainer = KAFTTrainer( data=data, hidden_dim=hidden, lr=lr, weight_decay=0.0, lr_feedback=0.5, num_probes=64, topo_mode='fixed_A', max_topo_power=3, diffusion_alpha=0.5, diffusion_iters=10, @@ -133,14 +133,14 @@ def main(): t0 = time.time() bp = bp_one(L, s, d, tm, vm, tem) t1 = time.time() - gf = graft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem) + gf = kaft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem) t2 = time.time() bp_accs.append(bp); gf_accs.append(gf) - print(f' L={L} s={s}: BP={bp:.4f}({t1-t0:.0f}s) GRAFT={gf:.4f}({t2-t1:.0f}s)', flush=True) + print(f' L={L} s={s}: BP={bp:.4f}({t1-t0:.0f}s) KAFT={gf:.4f}({t2-t1:.0f}s)', flush=True) bp_m, bp_sd = np.mean(bp_accs), np.std(bp_accs) gf_m, gf_sd = np.mean(gf_accs), np.std(gf_accs) bp_res[L] = (bp_m, bp_sd); gf_res[L] = (gf_m, gf_sd) - print(f'>>> L={L}: BP {bp_m:.4f}±{bp_sd:.4f} GRAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True) + print(f'>>> L={L}: BP {bp_m:.4f}±{bp_sd:.4f} KAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True) if __name__ == '__main__': diff --git a/experiments/run_dblp_depth.py b/experiments/run_dblp_depth.py index d63b94a..f91440a 100644 --- a/experiments/run_dblp_depth.py +++ b/experiments/run_dblp_depth.py @@ -11,8 +11,7 @@ import os import time from torch_geometric.datasets import CitationFull from src.data import build_normalized_adj, build_row_normalized_adj, spmm, precompute_traces -from src.trainers import BPTrainer, DFATrainer, GraphGrAPETrainer -from benchmark_efficient import GraphGrAPEEfficient +from src.trainers import BPTrainer, DFATrainer, KAFTTrainer device = 'cuda:0' SEEDS = [0, 1, 2, 3, 4] @@ -108,7 +107,7 @@ def main(): row = {} for mname, cls, extra in [('BP', BPTrainer, {}), ('DFA', DFATrainer, dict(diffusion_alpha=0.5, diffusion_iters=10)), - ('GrAPE', GraphGrAPETrainer, grape_extra)]: + ('GrAPE', KAFTTrainer, grape_extra)]: accs = [train_one(cls, common, extra, s) for s in SEEDS] row[mname] = {'mean': float(np.mean(accs)), 'std': float(np.std(accs))} results[key] = row @@ -123,7 +122,6 @@ def main(): common = dict(data=dblp, hidden_dim=64, lr=0.01, weight_decay=5e-4, num_layers=L, residual_alpha=0.0, backbone=bb) bp_ms = time_method(BPTrainer, common, {}) - eff_ms = time_method(GraphGrAPEEfficient, common, dict(lr_feedback=0.5, num_probes=64, max_topo_power=3, diff_alpha=0.5, align_every=10)) key = f"DBLP_eff|{bb}|L={L}" @@ -146,7 +144,7 @@ def main(): key = f"{ds_name}|{bb}|L={L}|lr=0.01" row = {} for mname, cls, extra in [('BP', BPTrainer, {}), - ('GrAPE', GraphGrAPETrainer, grape_extra)]: + ('GrAPE', KAFTTrainer, grape_extra)]: accs = [train_one(cls, common, extra, s) for s in SEEDS] row[mname] = {'mean': float(np.mean(accs)), 'std': float(np.std(accs))} results[key] = row diff --git a/experiments/run_dblp_depth_scaling.py b/experiments/run_dblp_depth_scaling.py index 4c0bc11..86a5af1 100644 --- a/experiments/run_dblp_depth_scaling.py +++ b/experiments/run_dblp_depth_scaling.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 """E1: DBLP depth scaling — upgrade depth_stress 3-seed to 20 seeds on DBLP, extend to L={8,12,16,20,24,32}. Goal: confirm (or falsify) the preliminary -finding that GRAFT > ResGCN at L=16 (3-seed: 69.9 vs 63.7) and scales to L=32. +finding that KAFT > ResGCN at L=16 (3-seed: 69.9 vs 63.7) and scales to L=32. -BP vs ResGCN vs GRAFT vs GRAFT+ResGCN, GCN backbone, lr=0.01, 200 epochs.""" +BP vs ResGCN vs KAFT vs KAFT+ResGCN, GCN backbone, lr=0.01, 200 epochs.""" import torch import numpy as np import json import os from scipy import stats as scipy_stats -from src.trainers import BPTrainer, GraphGrAPETrainer +from src.trainers import BPTrainer, KAFTTrainer from run_deep_baselines import ResGCNTrainer from run_combo_20seeds import GRAFTResGCN from run_dblp_depth import load_dblp @@ -27,8 +27,8 @@ grape_extra = dict(diffusion_alpha=0.5, diffusion_iters=10, METHODS = { 'BP': (BPTrainer, {}), 'ResGCN': (ResGCNTrainer, {}), - 'GRAFT': (GraphGrAPETrainer, grape_extra), - 'GRAFT+ResGCN': (GRAFTResGCN, grape_extra), + 'KAFT': (KAFTTrainer, grape_extra), + 'KAFT+ResGCN': (GRAFTResGCN, grape_extra), } @@ -100,11 +100,11 @@ def main(): 'per_seed': vals.tolist()} print(f" {mname:<15} {vals.mean():5.1f} ± {vals.std():4.1f}") - # GRAFT vs ResGCN (paired) + # KAFT vs ResGCN (paired) g_accs = np.array([per_seed_data[f"DBLP_L{L}_GRAFT"][str(s)] for s in SEEDS]) * 100 r_accs = np.array([per_seed_data[f"DBLP_L{L}_ResGCN"][str(s)] for s in SEEDS]) * 100 t_gr, p_gr = scipy_stats.ttest_rel(g_accs, r_accs) - print(f" GRAFT vs ResGCN: Δ={g_accs.mean() - r_accs.mean():+.1f}, p={p_gr:.4f}") + print(f" KAFT vs ResGCN: Δ={g_accs.mean() - r_accs.mean():+.1f}, p={p_gr:.4f}") with open(os.path.join(OUT_DIR, 'results.json'), 'w') as f: json.dump(results, f, indent=2) diff --git a/experiments/run_depth_extras.py b/experiments/run_depth_extras.py index 66a7d45..e3b860b 100644 --- a/experiments/run_depth_extras.py +++ b/experiments/run_depth_extras.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """H11: Fill depth sweep at L=14 and L=18 to densify Fig 4(a). -3 methods (BP / DFA-GNN / GRAFT) × 4 datasets × 2 depths × 20 seeds = 480 runs. +3 methods (BP / DFA-GNN / KAFT) × 4 datasets × 2 depths × 20 seeds = 480 runs. """ import torch @@ -8,7 +8,7 @@ import numpy as np import json import os from src.data import load_dataset -from src.trainers import BPTrainer, DFAGNNTrainer, GraphGrAPETrainer +from src.trainers import BPTrainer, DFAGNNTrainer, KAFTTrainer from run_dblp_depth import load_dblp device = 'cuda:0' @@ -24,7 +24,7 @@ dfagnn_extra = dict(diffusion_alpha=0.5, diffusion_iters=10, max_topo_power=3) METHODS = { 'BP': (BPTrainer, {}), 'DFA-GNN': (DFAGNNTrainer, dfagnn_extra), - 'GRAFT': (GraphGrAPETrainer, grape_extra), + 'KAFT': (KAFTTrainer, grape_extra), } diff --git a/experiments/run_dfagnn_depth.py b/experiments/run_dfagnn_depth.py index ed6e6c3..1a36485 100644 --- a/experiments/run_dfagnn_depth.py +++ b/experiments/run_dfagnn_depth.py @@ -2,9 +2,9 @@ """H7: DFA-GNN depth sweep for Figure 4(a)-style plot. Runs DFA-GNN at L ∈ {4, 8, 10, 12, 16, 20} × {Cora, CiteSeer, PubMed, DBLP} × 20 seeds. -L=6 data already exists from prior experiments; L=2/3 skipped (CiteSeer L=2 GRAFT soft spot). +L=6 data already exists from prior experiments; L=2/3 skipped (CiteSeer L=2 KAFT soft spot). -Combined with existing BP and GRAFT depth data, produces 3-method depth curves for Figure 4(a). +Combined with existing BP and KAFT depth data, produces 3-method depth curves for Figure 4(a). """ import torch diff --git a/experiments/run_grad_reach_20seeds.py b/experiments/run_grad_reach_20seeds.py index b5ad53b..b9ac8b9 100644 --- a/experiments/run_grad_reach_20seeds.py +++ b/experiments/run_grad_reach_20seeds.py @@ -12,7 +12,7 @@ import json import os from scipy import stats as scipy_stats from src.data import load_dataset, spmm -from src.trainers import BPTrainer, GraphGrAPETrainer +from src.trainers import BPTrainer, KAFTTrainer device = 'cuda:0' ALL_SEEDS = list(range(20)) @@ -31,7 +31,7 @@ def measure_one(data, L, backbone, seed): torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed) bp = BPTrainer(**common) torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed) - gr = GraphGrAPETrainer(**common, **grape_extra) + gr = KAFTTrainer(**common, **grape_extra) gr.align_mode = 'chain_norm' for _ in range(EPOCHS): @@ -46,10 +46,10 @@ def measure_one(data, L, backbone, seed): loss.backward() bp_norms = [bp.weights[l].grad.norm().item() for l in range(L)] - # GRAFT feedback norms + # KAFT feedback norms Z_gr, inter = gr.forward() E0, E_bar = gr._output_error(Z_gr) - graft_norms = [] + kaft_norms = [] for l in range(L - 1): power = min(L - l, gr.max_topo_power) topo_E = E_bar @@ -57,13 +57,13 @@ def measure_one(data, L, backbone, seed): topo_E = spmm(A, topo_E) fb = topo_E @ gr.Rs[l] relu_gate = (inter['Zs'][l].detach() > 0).float() - graft_norms.append((relu_gate * fb).norm().item()) + kaft_norms.append((relu_gate * fb).norm().item()) bp_acc = bp.evaluate('test_mask') gr_acc = gr.evaluate('test_mask') del bp, gr; torch.cuda.empty_cache() - return bp_norms, graft_norms, bp_acc, gr_acc + return bp_norms, kaft_norms, bp_acc, gr_acc def main(): @@ -101,10 +101,10 @@ def main(): bn, gn, ba, ga = measure_one(data, L, backbone, seed) per_seed_data[key][seed_key] = { - 'bp_norms': bn, 'graft_norms': gn, + 'bp_norms': bn, 'kaft_norms': gn, 'bp_acc': ba, 'gr_acc': ga } - print(f" seed {seed}: BP {ba*100:.1f}% GRAFT {ga*100:.1f}%", flush=True) + print(f" seed {seed}: BP {ba*100:.1f}% KAFT {ga*100:.1f}%", flush=True) # Save incrementally with open(old_per_seed_file, 'w') as f: @@ -121,7 +121,7 @@ def main(): t_stat, p_val = scipy_stats.ttest_rel(gr_accs, bp_accs) avg_bp_norms = np.mean([sd[str(s)]['bp_norms'] for s in ALL_SEEDS], axis=0) - avg_gr_norms = np.mean([sd[str(s)]['graft_norms'] for s in ALL_SEEDS], axis=0) + avg_gr_norms = np.mean([sd[str(s)]['kaft_norms'] for s in ALL_SEEDS], axis=0) results[key] = { 'bp_acc_mean': float(bp_accs.mean()), @@ -142,10 +142,10 @@ def main(): sig = '***' if p_val < 0.001 else ('**' if p_val < 0.01 else ('*' if p_val < 0.05 else 'ns')) print(f"\n {key}:") print(f" BP: {bp_accs.mean():.1f} ± {bp_accs.std():.1f}%") - print(f" GRAFT: {gr_accs.mean():.1f} ± {gr_accs.std():.1f}%") + print(f" KAFT: {gr_accs.mean():.1f} ± {gr_accs.std():.1f}%") print(f" Δ: {(gr_accs-bp_accs).mean():+.1f} ± {(gr_accs-bp_accs).std():.1f}% t={t_stat:.2f} p={p_val:.4f} {sig}") print(f" BP norm L0: {avg_bp_norms[0]:.6f}") - print(f" GRAFT norm L0: {avg_gr_norms[0]:.4f}") + print(f" KAFT norm L0: {avg_gr_norms[0]:.4f}") with open(os.path.join(OUT_DIR, 'results.json'), 'w') as f: json.dump(results, f, indent=2) diff --git a/experiments/run_hero_extras.py b/experiments/run_hero_extras.py index c6fed4d..ced76b8 100644 --- a/experiments/run_hero_extras.py +++ b/experiments/run_hero_extras.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """E0a+E0c+E0e: hero-table coverage expansion. Adds 3 datasets (PubMed stack, -Coauthor-Physics, Coauthor-CS) × 5 methods (BP, DFA, DFA-GNN, GRAFT, -GRAFT+ResGCN) × 20 seeds, all GCN L=6. Goal: 6-row hero table of homophilous -citation/coauthor graphs where GRAFT or GRAFT+ResGCN is best per row.""" +Coauthor-Physics, Coauthor-CS) × 5 methods (BP, DFA, DFA-GNN, KAFT, +KAFT+ResGCN) × 20 seeds, all GCN L=6. Goal: 6-row hero table of homophilous +citation/coauthor graphs where KAFT or KAFT+ResGCN is best per row.""" import torch import numpy as np @@ -10,7 +10,7 @@ import json import os from scipy import stats as scipy_stats from src.data import load_dataset, spmm -from src.trainers import BPTrainer, DFATrainer, DFAGNNTrainer, GraphGrAPETrainer +from src.trainers import BPTrainer, DFATrainer, DFAGNNTrainer, KAFTTrainer from run_deep_baselines import ResGCNTrainer from run_combo_20seeds import GRAFTResGCN from run_large_graph_scout import load_and_check @@ -54,8 +54,8 @@ METHODS = { 'BP': (BPTrainer, {}), 'DFA': (DFATrainer, dfagnn_extra), 'DFA-GNN': (DFAGNNTrainer, dfagnn_extra), - 'GRAFT': (GraphGrAPETrainer, grape_extra), - 'GRAFT+ResGCN': (GRAFTResGCN, grape_extra), + 'KAFT': (KAFTTrainer, grape_extra), + 'KAFT+ResGCN': (GRAFTResGCN, grape_extra), } diff --git a/experiments/run_realworld_hero_L20.py b/experiments/run_realworld_hero_L20.py index 93a6c91..3b352f1 100644 --- a/experiments/run_realworld_hero_L20.py +++ b/experiments/run_realworld_hero_L20.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""H33: 20-seed extension of L=20 hero on 4 real-world datasets × {BP, DFA, DFA-GNN, GRAFT}. +"""H33: 20-seed extension of L=20 hero on 4 real-world datasets × {BP, DFA, DFA-GNN, KAFT}. Paper setup (5%/class, hidden=64, lr=0.01, no scheduler, 200 epochs, GCN backbone, no dropout/BN/res). Tightens DBLP std (0.121 at 10-seed bimodal) for paper-grade stats. @@ -17,7 +17,7 @@ from torch_geometric.nn import GCNConv from torch_geometric.utils import add_self_loops, degree sys.path.insert(0, '/home/yurenh2/graph-grape') -from src.trainers import GraphGrAPETrainer +from src.trainers import KAFTTrainer device = torch.device('cuda:2') @@ -92,7 +92,7 @@ def bp_one(L, seed, d, tm, vm, tem, epochs=200, lr=0.01, hidden=64): return bt -def graft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem, +def kaft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem, epochs=200, lr=0.01, hidden=64): torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed) data = { @@ -101,7 +101,7 @@ def graft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem, 'num_features': d.x.shape[1], 'num_classes': int(d.y.max())+1, 'num_nodes': d.num_nodes, 'traces': {}, } - trainer = GraphGrAPETrainer( + trainer = KAFTTrainer( data=data, hidden_dim=hidden, lr=lr, weight_decay=5e-4, lr_feedback=0.5, num_probes=64, topo_mode='fixed_A', max_topo_power=3, diffusion_alpha=0.5, diffusion_iters=10, @@ -149,21 +149,21 @@ def main(): t0 = time.time() bp = bp_one(L, s, d, tm, vm, tem) t1 = time.time() - gf = graft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem) + gf = kaft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem) t2 = time.time() bp_a.append(bp); gf_a.append(gf) - print(f' s={s} L={L}: BP={bp:.4f}({t1-t0:.0f}s) GRAFT={gf:.4f}({t2-t1:.0f}s)', flush=True) + print(f' s={s} L={L}: BP={bp:.4f}({t1-t0:.0f}s) KAFT={gf:.4f}({t2-t1:.0f}s)', flush=True) bp_m, bp_sd = float(np.mean(bp_a)), float(np.std(bp_a)) gf_m, gf_sd = float(np.mean(gf_a)), float(np.std(gf_a)) - out[name] = dict(seeds=seeds, BP=bp_a, GRAFT=gf_a, BP_mean=bp_m, BP_std=bp_sd, + out[name] = dict(seeds=seeds, BP=bp_a, KAFT=gf_a, BP_mean=bp_m, BP_std=bp_sd, GRAFT_mean=gf_m, GRAFT_std=gf_sd) - print(f' >>> {name} L=20 (seeds {s_lo}-{s_hi-1}): BP {bp_m:.4f}±{bp_sd:.4f} GRAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True) + print(f' >>> {name} L=20 (seeds {s_lo}-{s_hi-1}): BP {bp_m:.4f}±{bp_sd:.4f} KAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True) del d, A_hat, A_row, A_row_T torch.cuda.empty_cache() print('\n=== SUMMARY (this run) ===', flush=True) for k, v in out.items(): - print(f' {k}: BP {v["BP_mean"]:.4f}±{v["BP_std"]:.4f} GRAFT {v["GRAFT_mean"]:.4f}±{v["GRAFT_std"]:.4f}', flush=True) + print(f' {k}: BP {v["BP_mean"]:.4f}±{v["BP_std"]:.4f} KAFT {v["GRAFT_mean"]:.4f}±{v["GRAFT_std"]:.4f}', flush=True) if __name__ == '__main__': diff --git a/experiments/run_resgcn_20seeds.py b/experiments/run_resgcn_20seeds.py index 995a568..b56bb04 100644 --- a/experiments/run_resgcn_20seeds.py +++ b/experiments/run_resgcn_20seeds.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Task 7016bd94 Part 1: ResGCN vs GRAFT, 20 seeds, paired t-tests.""" +"""Task 7016bd94 Part 1: ResGCN vs KAFT, 20 seeds, paired t-tests.""" import torch import numpy as np @@ -7,7 +7,7 @@ import json import os from scipy import stats as scipy_stats from src.data import load_dataset -from src.trainers import BPTrainer, GraphGrAPETrainer +from src.trainers import BPTrainer, KAFTTrainer from run_deep_baselines import ResGCNTrainer from run_dblp_depth import load_dblp @@ -48,7 +48,7 @@ def main(): METHODS = { 'BP': (BPTrainer, {}), 'ResGCN': (ResGCNTrainer, {}), - 'GRAFT': (GraphGrAPETrainer, grape_extra), + 'KAFT': (KAFTTrainer, grape_extra), } datasets_cfg = { @@ -92,9 +92,9 @@ def main(): del data; torch.cuda.empty_cache() - # Paired t-tests: GRAFT vs ResGCN + # Paired t-tests: KAFT vs ResGCN print("\n" + "=" * 70) - print("Paired t-tests: GRAFT vs ResGCN (20 seeds)") + print("Paired t-tests: KAFT vs ResGCN (20 seeds)") print("-" * 70) for ds in ['Cora', 'CiteSeer', 'DBLP']: @@ -102,7 +102,7 @@ def main(): res_accs = np.array(results[f"{ds}_ResGCN"]['accs']) gr_accs = np.array(results[f"{ds}_GRAFT"]['accs']) - # GRAFT vs ResGCN + # KAFT vs ResGCN t_stat, p_val = scipy_stats.ttest_rel(gr_accs, res_accs) delta = gr_accs.mean() - res_accs.mean() sig = '***' if p_val < 0.001 else ('**' if p_val < 0.01 else ('*' if p_val < 0.05 else 'ns')) @@ -112,7 +112,7 @@ def main(): 'p_value': float(p_val), 'significant': bool(p_val < 0.05), } - # GRAFT vs BP + # KAFT vs BP t2, p2 = scipy_stats.ttest_rel(gr_accs, bp_accs) d2 = gr_accs.mean() - bp_accs.mean() sig2 = '***' if p2 < 0.001 else ('**' if p2 < 0.01 else ('*' if p2 < 0.05 else 'ns')) @@ -134,9 +134,9 @@ def main(): print(f"\n{ds}:") print(f" BP: {bp_accs.mean():.1f} ± {bp_accs.std():.1f}") print(f" ResGCN: {res_accs.mean():.1f} ± {res_accs.std():.1f}") - print(f" GRAFT: {gr_accs.mean():.1f} ± {gr_accs.std():.1f}") - print(f" GRAFT vs ResGCN: Δ{delta:+.1f}% p={p_val:.6f} {sig}") - print(f" GRAFT vs BP: Δ{d2:+.1f}% p={p2:.6f} {sig2}") + print(f" KAFT: {gr_accs.mean():.1f} ± {gr_accs.std():.1f}") + print(f" KAFT vs ResGCN: Δ{delta:+.1f}% p={p_val:.6f} {sig}") + print(f" KAFT vs BP: Δ{d2:+.1f}% p={p2:.6f} {sig2}") with open(os.path.join(OUT_DIR, 'results.json'), 'w') as f: json.dump(results, f, indent=2) diff --git a/experiments/run_shallow_depth.py b/experiments/run_shallow_depth.py index 68c9138..5f1cc26 100644 --- a/experiments/run_shallow_depth.py +++ b/experiments/run_shallow_depth.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """E2: Shallow depth (L=2,3,4) on 4 datasets. Last exploratory avenue after -E1 (deep scaling) and E0-extras (more datasets) both failed to extend GRAFT's -regime. If GRAFT still wins at L=2/3 (standard GNN depth), we can counter -the reviewer attack 'L=5,6 nobody uses'. If GRAFT matches BP only at L=5,6, +E1 (deep scaling) and E0-extras (more datasets) both failed to extend KAFT's +regime. If KAFT still wins at L=2/3 (standard GNN depth), we can counter +the reviewer attack 'L=5,6 nobody uses'. If KAFT matches BP only at L=5,6, paper stays at current scope and we ship.""" import torch @@ -11,7 +11,7 @@ import json import os from scipy import stats as scipy_stats from src.data import load_dataset -from src.trainers import BPTrainer, GraphGrAPETrainer +from src.trainers import BPTrainer, KAFTTrainer from run_deep_baselines import ResGCNTrainer from run_combo_20seeds import GRAFTResGCN from run_dblp_depth import load_dblp @@ -27,8 +27,8 @@ grape_extra = dict(diffusion_alpha=0.5, diffusion_iters=10, METHODS = { 'BP': (BPTrainer, {}), - 'GRAFT': (GraphGrAPETrainer, grape_extra), - 'GRAFT+ResGCN': (GRAFTResGCN, grape_extra), + 'KAFT': (KAFTTrainer, grape_extra), + 'KAFT+ResGCN': (GRAFTResGCN, grape_extra), } @@ -108,10 +108,10 @@ def main(): t, p = scipy_stats.ttest_rel(gr_accs, bp_accs) delta = gr_accs.mean() - bp_accs.mean() print(f" {ds} L={L}: BP {bp_accs.mean():5.1f}±{bp_accs.std():4.1f} " - f"GRAFT {gr_accs.mean():5.1f}±{gr_accs.std():4.1f} " - f"GRAFT+ResGCN {stk_accs.mean():5.1f}±{stk_accs.std():4.1f} " - f"Δ(GRAFT-BP)={delta:+.1f}, p={p:.4f}") - for mname, accs in [('BP', bp_accs), ('GRAFT', gr_accs), ('GRAFT+ResGCN', stk_accs)]: + f"KAFT {gr_accs.mean():5.1f}±{gr_accs.std():4.1f} " + f"KAFT+ResGCN {stk_accs.mean():5.1f}±{stk_accs.std():4.1f} " + f"Δ(KAFT-BP)={delta:+.1f}, p={p:.4f}") + for mname, accs in [('BP', bp_accs), ('KAFT', gr_accs), ('KAFT+ResGCN', stk_accs)]: key = f"{ds}_L{L}_{mname}" results[key] = {'mean': float(accs.mean()), 'std': float(accs.std()), 'per_seed': accs.tolist()} diff --git a/experiments/run_wikics_paper_setup.py b/experiments/run_wikics_paper_setup.py index a2bf879..c9525e6 100644 --- a/experiments/run_wikics_paper_setup.py +++ b/experiments/run_wikics_paper_setup.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """H15 WikiCS paper-setup depth sweep — Wikipedia academic articles. ~11.7K nodes, avg deg ~4.1, 10-class, undirected. Sparse + few-class -fits GRAFT's regime profile. Test BP vs GRAFT at L ∈ {3,5,10,14,20} × 5 seeds. +fits KAFT's regime profile. Test BP vs KAFT at L ∈ {3,5,10,14,20} × 5 seeds. """ import sys, time import numpy as np @@ -13,7 +13,7 @@ from torch_geometric.nn import GCNConv from torch_geometric.utils import add_self_loops, degree, to_undirected sys.path.insert(0, '/home/yurenh2/graph-grape') -from src.trainers import GraphGrAPETrainer +from src.trainers import KAFTTrainer device = torch.device('cuda:0') # CUDA_VISIBLE_DEVICES=2 maps cuda:0 → physical GPU 2 @@ -88,7 +88,7 @@ def bp_one(L, seed, d, tm, vm, tem, epochs=200, lr=0.01, hidden=64): return bt -def graft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem, +def kaft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem, epochs=200, lr=0.01, hidden=64): torch.manual_seed(seed); np.random.seed(seed); torch.cuda.manual_seed_all(seed) data = { @@ -97,7 +97,7 @@ def graft_one(L, seed, d, A_hat, A_row, A_row_T, tm, vm, tem, 'num_features': d.x.shape[1], 'num_classes': int(d.y.max())+1, 'num_nodes': d.num_nodes, 'traces': {}, } - trainer = GraphGrAPETrainer( + trainer = KAFTTrainer( data=data, hidden_dim=hidden, lr=lr, weight_decay=5e-4, lr_feedback=0.5, num_probes=64, topo_mode='fixed_A', max_topo_power=3, diffusion_alpha=0.5, diffusion_iters=10, @@ -133,13 +133,13 @@ def main(): t0 = time.time() bp = bp_one(L, s, d, tm, vm, tem) t1 = time.time() - gf = graft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem) + gf = kaft_one(L, s, d, A_hat, A_row, A_row_T, tm, vm, tem) t2 = time.time() bp_a.append(bp); gf_a.append(gf) - print(f' L={L} s={s}: BP={bp:.4f}({t1-t0:.0f}s) GRAFT={gf:.4f}({t2-t1:.0f}s)', flush=True) + print(f' L={L} s={s}: BP={bp:.4f}({t1-t0:.0f}s) KAFT={gf:.4f}({t2-t1:.0f}s)', flush=True) bp_m, bp_sd = np.mean(bp_a), np.std(bp_a) gf_m, gf_sd = np.mean(gf_a), np.std(gf_a) - print(f'>>> L={L}: BP {bp_m:.4f}±{bp_sd:.4f} GRAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True) + print(f'>>> L={L}: BP {bp_m:.4f}±{bp_sd:.4f} KAFT {gf_m:.4f}±{gf_sd:.4f} Δ={gf_m-bp_m:+.3f}', flush=True) if __name__ == '__main__': diff --git a/figures/gen_depth_sweep_fig.py b/figures/gen_depth_sweep_fig.py index 9604a6a..0d4da61 100644 --- a/figures/gen_depth_sweep_fig.py +++ b/figures/gen_depth_sweep_fig.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """H8: Generate Figure 4(a)-style depth sweep plot. -4 panels (Cora/CiteSeer/PubMed/DBLP), 3 curves per panel (BP/DFA-GNN/GRAFT). +4 panels (Cora/CiteSeer/PubMed/DBLP), 3 curves per panel (BP/DFA-GNN/KAFT). x = number of layers L; y = test accuracy (%) with shaded std band. Method distinguished by color only (per memory `feedback_viz_shape`: @@ -14,10 +14,10 @@ import matplotlib.pyplot as plt from matplotlib.colors import to_rgba DATASETS = ['Cora', 'CiteSeer', 'PubMed', 'DBLP'] -METHODS = ['BP', 'DFA-GNN', 'GRAFT'] +METHODS = ['BP', 'DFA-GNN', 'KAFT'] # Per-dataset depth grids — DBLP extends to 24, 32 from dblp_depth_scaling. # Other datasets cover 2..20. Missing entries (e.g. DFA-GNN at L=2/3, DBLP L=10 -# for BP/GRAFT) will be silently skipped by lookup(). +# for BP/KAFT) will be silently skipped by lookup(). DEPTHS_DEFAULT = [2, 3, 4, 6, 8, 10, 12, 14, 16, 18, 20] DEPTHS_DBLP = [2, 3, 4, 6, 8, 10, 12, 14, 16, 18, 20, 24, 32] DEPTHS_BY_DS = {ds: (DEPTHS_DBLP if ds == 'DBLP' else DEPTHS_DEFAULT) @@ -25,21 +25,21 @@ DEPTHS_BY_DS = {ds: (DEPTHS_DBLP if ds == 'DBLP' else DEPTHS_DEFAULT) # All result files we might need to consult SOURCES = [ - 'results/combo_20seeds/per_seed_data.json', # L=6 BP/GRAFT/stacks on Cora/CS/DBLP + 'results/combo_20seeds/per_seed_data.json', # L=6 BP/KAFT/stacks on Cora/CS/DBLP 'results/hero_extras_20seeds/per_seed_data.json', # L=6 on PubMed + Coauthor 'results/shallow_depth_20seeds/per_seed_data.json', # L=2,3,4 on 4ds 'results/dblp_depth_scaling_20seeds/per_seed_data.json', # DBLP L=8-32 - 'results/bp_graft_depth_20seeds/per_seed_data.json', # Cora/CS/PubMed L=8-20 + 'results/bp_kaft_depth_20seeds/per_seed_data.json', # Cora/CS/PubMed L=8-20 'results/dfagnn_depth_20seeds/per_seed_data.json', # DFA-GNN at all depths 'results/dfagnn_resgcn_20seeds/per_seed_data.json', # DFA-GNN L=6 Cora/CS/DBLP 'results/depth_extras_20seeds/per_seed_data.json', # L=14, L=18 × 4ds × 3 methods ] -# Colors — GRAFT brick red (main method), BP gray, DFA-GNN complementary blue +# Colors — KAFT brick red (main method), BP gray, DFA-GNN complementary blue COLORS = { 'BP': '#888888', # reference gray 'DFA-GNN': '#3B7AC2', # complementary blue - 'GRAFT': '#C23B3B', # brick red (our method) + 'KAFT': '#C23B3B', # brick red (our method) } GRID_COLOR = '#ECEFF3' @@ -143,10 +143,10 @@ def main(): frameon=False, loc='lower center', ncol=len(labels), bbox_to_anchor=(0.5, -0.005), handletextpad=0.6, columnspacing=1.8) - fig.savefig('/home/yurenh2/graph-grape/graft_depth_sweep.png', dpi=300, bbox_inches='tight') - fig.savefig('/home/yurenh2/graph-grape/graft_depth_sweep.pdf', bbox_inches='tight') + fig.savefig('/home/yurenh2/graph-grape/kaft_depth_sweep.png', dpi=300, bbox_inches='tight') + fig.savefig('/home/yurenh2/graph-grape/kaft_depth_sweep.pdf', bbox_inches='tight') plt.close(fig) - print('Saved /home/yurenh2/graph-grape/graft_depth_sweep.{png,pdf}') + print('Saved /home/yurenh2/graph-grape/kaft_depth_sweep.{png,pdf}') # Data dump print('\nData (mean ± std):') diff --git a/figures/gen_fig4_combined.py b/figures/gen_fig4_combined.py index 5b8d464..1cdee10 100644 --- a/figures/gen_fig4_combined.py +++ b/figures/gen_fig4_combined.py @@ -3,7 +3,7 @@ Each panel: 9 curves = 3 datasets × 3 methods. color = dataset (Cora / CiteSeer / PubMed) - linestyle = method (BP dashed, DFA-GNN dotted, GRAFT solid) + linestyle = method (BP dashed, DFA-GNN dotted, KAFT solid) Matches DFA-GNN Figure 4 layout. """ @@ -15,18 +15,18 @@ from matplotlib.colors import to_rgba from matplotlib.lines import Line2D DATASETS = ['Cora', 'CiteSeer', 'PubMed'] -METHODS = ['BP', 'DFA-GNN', 'GRAFT'] # data-lookup keys (unchanged) -DISPLAY_NAME = {'BP': 'BP', 'DFA-GNN': 'DFA-GNN', 'GRAFT': 'KAFT'} +METHODS = ['BP', 'DFA-GNN', 'KAFT'] # data-lookup keys (unchanged) +DISPLAY_NAME = {'BP': 'BP', 'DFA-GNN': 'DFA-GNN', 'KAFT': 'KAFT'} DEPTHS = [4, 6, 8, 10, 12, 14, 16, 18, 20] RATES = [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8] ATTACKS = ['add', 'remove', 'flip'] -# Method colors — consistent with other GRAFT figures +# Method colors — consistent with other KAFT figures METHOD_COLORS = { 'BP': '#888888', # gray 'DFA-GNN': '#3B7AC2', # complementary blue - 'GRAFT': '#C23B3B', # brick red (ours) + 'KAFT': '#C23B3B', # brick red (ours) } # Dataset linestyles DS_STYLE = { @@ -48,7 +48,7 @@ DEPTH_SOURCES = [ 'results/combo_20seeds/per_seed_data.json', 'results/hero_extras_20seeds/per_seed_data.json', 'results/shallow_depth_20seeds/per_seed_data.json', - 'results/bp_graft_depth_20seeds/per_seed_data.json', + 'results/bp_kaft_depth_20seeds/per_seed_data.json', 'results/dfagnn_depth_20seeds/per_seed_data.json', 'results/dfagnn_resgcn_20seeds/per_seed_data.json', 'results/depth_extras_20seeds/per_seed_data.json', # L=14, 18 diff --git a/figures/gen_realworld_depth_fig.py b/figures/gen_realworld_depth_fig.py index 1ff7e2d..a9fb6d4 100644 --- a/figures/gen_realworld_depth_fig.py +++ b/figures/gen_realworld_depth_fig.py @@ -8,39 +8,39 @@ import numpy as np import matplotlib.pyplot as plt from matplotlib.colors import to_rgba -# Aggregated paper-setup data: (mean, std) for BP and GRAFT at each depth +# Aggregated paper-setup data: (mean, std) for BP and KAFT at each depth DATA = { 'CFull-CiteSeer': { 'depths': [3, 5, 8, 10, 12, 14, 16, 18, 20], 'BP': [(0.870, 0.0072), (0.860, 0.0056), (0.825, 0.0208), (0.549, 0.1164), (0.365, 0.0209), (0.297, 0.0421), (0.230, 0.0209), (0.238, 0.0131), (0.209, 0.0319)], 'DFA': [(0.855, 0.0044), (0.834, 0.0106), (0.566, 0.0289), (0.425, 0.0993), (0.329, 0.1060), (0.368, 0.0604), (0.297, 0.0722), (0.243, 0.0661), (0.244, 0.0667)], 'DFA-GNN': [(0.858, 0.0038), (0.826, 0.0187), (0.581, 0.1085), (0.465, 0.0698), (0.289, 0.0677), (0.296, 0.1372), (0.244, 0.0673), (0.211, 0.0204), (0.193, 0.0051)], - 'GRAFT': [(0.857, 0.0006), (0.846, 0.0019), (0.829, 0.0021), (0.780, 0.0197), (0.667, 0.0630), (0.487, 0.0621), (0.430, 0.1145), (0.369, 0.0089), (0.380, 0.0258)], + 'KAFT': [(0.857, 0.0006), (0.846, 0.0019), (0.829, 0.0021), (0.780, 0.0197), (0.667, 0.0630), (0.487, 0.0621), (0.430, 0.1145), (0.369, 0.0089), (0.380, 0.0258)], }, 'CFull-DBLP': { 'depths': [3, 5, 8, 10, 12, 14, 16, 18, 20], 'BP': [(0.826, 0.0027), (0.814, 0.0006), (0.793, 0.0070), (0.710, 0.1180), (0.652, 0.0728), (0.559, 0.1132), (0.454, 0.0065), (0.469, 0.0077), (0.461, 0.0144)], 'DFA': [(0.829, 0.0031), (0.819, 0.0076), (0.736, 0.0409), (0.703, 0.0025), (0.682, 0.0257), (0.548, 0.1104), (0.532, 0.1206), (0.533, 0.1209), (0.447, 0.0000)], 'DFA-GNN': [(0.832, 0.0024), (0.823, 0.0033), (0.766, 0.0362), (0.617, 0.1203), (0.617, 0.1203), (0.523, 0.1018), (0.447, 0.0000), (0.447, 0.0000), (0.531, 0.1187)], - 'GRAFT': [(0.827, 0.0024), (0.825, 0.0090), (0.813, 0.0121), (0.786, 0.0032), (0.730, 0.0315), (0.701, 0.0020), (0.700, 0.0001), (0.610, 0.1150), (0.613, 0.1175)], + 'KAFT': [(0.827, 0.0024), (0.825, 0.0090), (0.813, 0.0121), (0.786, 0.0032), (0.730, 0.0315), (0.701, 0.0020), (0.700, 0.0001), (0.610, 0.1150), (0.613, 0.1175)], }, 'CFull-PubMed (biomed)': { 'depths': [3, 5, 8, 10, 12, 14, 16, 18, 20], 'BP': [(0.845, 0.0018), (0.833, 0.0023), (0.825, 0.0026), (0.824, 0.0025), (0.699, 0.0096), (0.499, 0.1413), (0.399, 0.0000), (0.500, 0.1421), (0.399, 0.0000)], 'DFA': [(0.822, 0.0041), (0.793, 0.0188), (0.585, 0.1353), (0.531, 0.0768), (0.484, 0.0833), (0.431, 0.0446), (0.427, 0.0383), (0.399, 0.0000), (0.399, 0.0000)], 'DFA-GNN': [(0.822, 0.0040), (0.750, 0.0551), (0.604, 0.1572), (0.522, 0.1154), (0.462, 0.0888), (0.399, 0.0000), (0.438, 0.0550), (0.399, 0.0000), (0.466, 0.0945)], - 'GRAFT': [(0.830, 0.0068), (0.814, 0.0049), (0.789, 0.0099), (0.732, 0.0713), (0.690, 0.0585), (0.646, 0.0134), (0.603, 0.0086), (0.545, 0.1031), (0.525, 0.0887)], + 'KAFT': [(0.830, 0.0068), (0.814, 0.0049), (0.789, 0.0099), (0.732, 0.0713), (0.690, 0.0585), (0.646, 0.0134), (0.603, 0.0086), (0.545, 0.1031), (0.525, 0.0887)], }, 'Coauthor-Physics': { 'depths': [3, 5, 8, 10, 12, 14, 16, 18, 20], 'BP': [(0.949, 0.0005), (0.943, 0.0014), (0.937, 0.0011), (0.829, 0.0344), (0.818, 0.0387), (0.770, 0.0151), (0.743, 0.0038), (0.682, 0.1000), (0.521, 0.0215)], 'DFA': [(0.948, 0.0007), (0.920, 0.0067), (0.711, 0.0227), (0.686, 0.1275), (0.560, 0.0751), (0.506, 0.0005), (0.557, 0.0737), (0.559, 0.0762), (0.505, 0.0000)], 'DFA-GNN': [(0.947, 0.0012), (0.836, 0.0451), (0.712, 0.0369), (0.567, 0.0720), (0.505, 0.0003), (0.505, 0.0000), (0.505, 0.0000), (0.559, 0.0756), (0.505, 0.0000)], - 'GRAFT': [(0.947, 0.0008), (0.943, 0.0004), (0.922, 0.0092), (0.867, 0.0368), (0.749, 0.0423), (0.686, 0.0122), (0.614, 0.0771), (0.666, 0.0010), (0.667, 0.0003)], + 'KAFT': [(0.947, 0.0008), (0.943, 0.0004), (0.922, 0.0092), (0.867, 0.0368), (0.749, 0.0423), (0.686, 0.0122), (0.614, 0.0771), (0.666, 0.0010), (0.667, 0.0003)], }, } -COLORS = {'BP': '#888888', 'DFA': '#7A5BAA', 'DFA-GNN': '#3B7AC2', 'GRAFT': '#C23B3B'} +COLORS = {'BP': '#888888', 'DFA': '#7A5BAA', 'DFA-GNN': '#3B7AC2', 'KAFT': '#C23B3B'} GRID = '#ECEFF3' TEXT = '#2F3437' @@ -57,7 +57,7 @@ legend_handles = {} for ax, ds in zip(axes, datasets): d = DATA[ds] xs = d['depths'] - for method in ['BP', 'DFA', 'DFA-GNN', 'GRAFT']: + for method in ['BP', 'DFA', 'DFA-GNN', 'KAFT']: means = np.array([v[0] for v in d[method]]) stds = np.array([v[1] for v in d[method]]) c = COLORS[method] @@ -81,9 +81,9 @@ for ax, ds in zip(axes, datasets): axes[0].set_ylabel('Test accuracy', fontsize=9, color=TEXT) -handles = [legend_handles[m] for m in ['BP', 'DFA', 'DFA-GNN', 'GRAFT']] +handles = [legend_handles[m] for m in ['BP', 'DFA', 'DFA-GNN', 'KAFT']] fig.tight_layout(rect=(0.0, 0.06, 1.0, 1.0), w_pad=1.5) -# Display label: GRAFT data key stays for the lookup, render as KAFT +# Display label: KAFT data key stays for the lookup, render as KAFT fig.legend(handles, ['BP', 'DFA', 'DFA-GNN', 'KAFT'], frameon=False, loc='lower center', ncol=4, bbox_to_anchor=(0.5, -0.005), handletextpad=0.6, columnspacing=1.8) diff --git a/figures/graft_depth_sweep.pdf b/figures/graft_depth_sweep.pdf deleted file mode 100644 index 21b06f2..0000000 Binary files a/figures/graft_depth_sweep.pdf and /dev/null differ diff --git a/figures/kaft_depth_sweep.pdf b/figures/kaft_depth_sweep.pdf new file mode 100644 index 0000000..21b06f2 Binary files /dev/null and b/figures/kaft_depth_sweep.pdf differ diff --git a/src/data.py b/src/data.py index 6e80285..baf5a8b 100644 --- a/src/data.py +++ b/src/data.py @@ -1,4 +1,4 @@ -"""Data loading and preprocessing for Graph-GrAPE experiments.""" +"""Data loading and preprocessing for KAFT experiments.""" import torch import torch.nn.functional as F diff --git a/src/trainers.py b/src/trainers.py index 651dffc..7589fc9 100644 --- a/src/trainers.py +++ b/src/trainers.py @@ -1,5 +1,5 @@ """ -Training methods for Graph-GrAPE experiments. +Training methods for KAFT experiments. Generalized to L-layer residual GCN. Methods compared: @@ -7,7 +7,7 @@ Methods compared: DFA — Fixed random R, P=I DFA-GNN — Fixed random R, P=Â^{L-l} VanillaGrAPE — Aligned R (per layer), P=I - GraphGrAPE — Aligned R (per layer) + topology P=Â^{L-l} + KAFT — Aligned R (per layer) + topology P=Â^{L-l} """ import torch @@ -179,7 +179,7 @@ class BPTrainer: # --------------------------------------------------------------------------- class _FeedbackTrainerBase: - """Shared logic for DFA / GrAPE variants, generalized to L layers.""" + """Shared logic for DFA / KAFT variants, generalized to L layers.""" def __init__(self, data, hidden_dim, lr, weight_decay, diffusion_alpha, diffusion_iters, @@ -608,10 +608,10 @@ class VanillaGrAPETrainer(_FeedbackTrainerBase): # --------------------------------------------------------------------------- -# Graph-GrAPE Trainer +# KAFT Trainer # --------------------------------------------------------------------------- -class GraphGrAPETrainer(_FeedbackTrainerBase): +class KAFTTrainer(_FeedbackTrainerBase): """Aligned R per layer + topology P = Â^{min(L-l, max_power)}.""" def __init__(self, data, hidden_dim, lr, weight_decay, -- cgit v1.2.3