From bd9333eda60a9029a198acaeacb1eca4312bd1e8 Mon Sep 17 00:00:00 2001 From: YurenHao0426 Date: Mon, 4 May 2026 23:05:16 -0500 Subject: =?UTF-8?q?Initial=20release:=20GRAFT=20(KAFT)=20=E2=80=94=20NeurI?= =?UTF-8?q?PS=202026=20submission=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Topology-factorized Jacobian-aligned feedback for deep GNNs. Includes: - src/: GraphGrAPETrainer (KAFT) + BP / DFA / DFA-GNN / VanillaGrAPE baselines + multi-probe alignment estimator + dataset / sparse-mm utilities. - experiments/: 19 runners reproducing every figure / table in the paper. - figures/: 4 generators + the 4 PDFs cited in the report. - paper/: NeurIPS .tex and consolidated experiments_master notes. Smoke test: 50-epoch Cora GCN L=4 gives BP 77.3% / KAFT 79.0%. --- experiments/run_wikics_paper_setup.py | 146 ++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 experiments/run_wikics_paper_setup.py (limited to 'experiments/run_wikics_paper_setup.py') diff --git a/experiments/run_wikics_paper_setup.py b/experiments/run_wikics_paper_setup.py new file mode 100644 index 0000000..a2bf879 --- /dev/null +++ b/experiments/run_wikics_paper_setup.py @@ -0,0 +1,146 @@ +#!/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. +""" +import sys, time +import numpy as np +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch_geometric.datasets import WikiCS +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 + +device = torch.device('cuda:0') # CUDA_VISIBLE_DEVICES=2 maps cuda:0 → physical GPU 2 + + +def build_A_hat(edge_index, N): + edge_index, _ = add_self_loops(edge_index, num_nodes=N) + row, col = edge_index + deg = degree(row, num_nodes=N, dtype=torch.float) + dis = deg.pow(-0.5); dis[dis == float('inf')] = 0 + return torch.sparse_coo_tensor(edge_index, dis[row]*dis[col], (N, N)).coalesce() + + +def build_row_norm(edge_index, N): + ei, _ = add_self_loops(edge_index, num_nodes=N) + row, col = ei + deg = degree(row, num_nodes=N, dtype=torch.float).clamp(min=1) + A_row = torch.sparse_coo_tensor(ei, 1.0/deg[row], (N,N)).coalesce() + A_row_T = torch.sparse_coo_tensor(ei.flip(0), 1.0/deg[col], (N,N)).coalesce() + return A_row, A_row_T + + +def paper_split(N, y, seed, train_frac=0.05, n_val=500): + g = torch.Generator().manual_seed(seed) + train_mask = torch.zeros(N, dtype=torch.bool) + val_mask = torch.zeros(N, dtype=torch.bool) + test_mask = torch.zeros(N, dtype=torch.bool) + C = int(y.max()) + 1 + for c in range(C): + idx = (y == c).nonzero().flatten() + idx = idx[torch.randperm(idx.size(0), generator=g)] + n_tr = max(1, int(round(train_frac * idx.size(0)))) + train_mask[idx[:n_tr]] = True + remaining = (~train_mask).nonzero().flatten() + remaining = remaining[torch.randperm(remaining.size(0), generator=g)] + val_mask[remaining[:n_val]] = True + test_mask[remaining[n_val:]] = True + return train_mask, val_mask, test_mask + + +class GCN(nn.Module): + def __init__(self, in_dim, hidden, out_dim, L): + super().__init__() + self.convs = nn.ModuleList([GCNConv(in_dim if i==0 else hidden, + hidden if i