skills/tabular/gnn-on-knn-graph/SKILL.md
Constructs a customer similarity graph via KNN on mixed features, then trains a GraphSAGE GNN for node classification. Captures relational patterns that tree and linear models miss, adding ensemble diversity.
npx skillsauth add wenmin-wu/ds-skills tabular-gnn-on-knn-graphInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build a K-nearest-neighbor similarity graph from tabular features, then train a GraphSAGE GNN for node classification. Each row becomes a node; edges connect similar rows based on OHE categorical + scaled numerical distances. The GNN aggregates neighbor embeddings across hops, learning relational patterns invisible to pointwise models. Valuable in ensembles even at slightly lower solo AUC due to high prediction diversity.
import torch
from torch_geometric.nn import SAGEConv
from torch_geometric.loader import NeighborLoader
from cuml.neighbors import NearestNeighbors
from sklearn.preprocessing import OneHotEncoder, StandardScaler
import numpy as np
# 1. Build graph edges via KNN
ohe = OneHotEncoder(sparse_output=False).fit_transform(X_cat)
num = StandardScaler().fit_transform(X_num) * 3.0 # multiplier for numerics
features = np.hstack([ohe, num]).astype("float32")
knn = NearestNeighbors(n_neighbors=8).fit(features)
_, indices = knn.kneighbors(features)
src = np.repeat(np.arange(len(features)), 8)
dst = indices.flatten()
edge_index = torch.tensor(np.stack([src, dst]), dtype=torch.long)
# 2. Node features: categorical embeddings + scaled numerics (25-dim)
# (CatEmbed layer maps each categorical to a learned embedding)
# 3. Mini-batch training with subgraph sampling
loader = NeighborLoader(data, num_neighbors=[6, 4], batch_size=512)
for batch in loader:
out = model(batch.x, batch.edge_index)
loss = F.binary_cross_entropy_with_logits(out, batch.y, label_smoothing=0.01)
NearestNeighbors(k=8) on encoded features. Convert neighbor indices to edge_index.CatEmbed → Linear → SAGEConv → LayerNorm → SAGEConv → LayerNorm → MLP head. Residual connections with 0.5 scaling: x = x + 0.5 * conv(x).NeighborLoader with fanouts [6, 4]. Label-smoothed BCE loss (eps=0.01).| Decision | Guidance |
|---|---|
| K in KNN | k=8 balances graph density vs noise. Too high adds weak edges. |
| Numeric multiplier | 3.0 gives numerics comparable influence to OHE block in distance. Tune per dataset. |
| Fanouts | [6, 4] keeps mini-batch subgraphs tractable. Larger fanouts cost memory. |
| Residual scaling | 0.5 prevents over-smoothing across hops. |
| Label smoothing | eps=0.01 regularizes against noisy labels in synthetic/competition data. |
| GPU KNN | cuML drastically speeds KNN on large datasets. Fall back to sklearn if no GPU. |
| Ensemble role | GNN predictions are structurally diverse from GBDT/linear. Blend even if solo AUC is 1-2% lower. |
data-ai
Scaled Pinball Loss (SPL) metric for evaluating quantile forecasts, normalized by mean absolute successive differences of training data
data-ai
Walk backward through a time series and multiplicatively rescale segments when jumps exceed a fraction of the running mean to correct data collection anomalies
testing
Transform forecasting target to next/current ratio minus one so that optimizing MAE or squared error implicitly minimizes SMAPE
tools
Convert point forecasts to prediction intervals by scaling with logit-transformed quantile ratios passed through a Normal CDF