Views
No views yet
v_s gives a monotonic OOD response in 4/5 grokking-favorable seeds vs 1/3 standard seeds (Mann-Whitney p=0.071, non-significant at this sample size). Read as shortcut concentration, not elimination under heavy regularization.1# code/experiments/causalgrok_camelyon_v2.py — get_config()
2def get_config(condition):
3 base = dict(seed=42, n_train=300, batch_size=32, img_size=96,
4 n_classes=2, log_every=50, device="cuda")
5 if condition == "standard":
6 base.update(condition="standard", lr=1e-3, weight_decay=1e-4,
7 n_epochs=3000, init_scale=1.0, use_grokfast=False)
8 elif condition == "grokking":
9 base.update(condition="grokking", lr=1e-3, weight_decay=5e-3,
10 n_epochs=3000, init_scale=4.0, use_grokfast=True,
11 grokfast_alpha=0.98, grokfast_lamb=2.0)
12 return base1if cfg["init_scale"] != 1.0:
2 for name, p in model.named_parameters():
3 if "weight" in name and p.dim() > 1:
4 p.data *= cfg["init_scale"]loss.backward(), before optimizer.step():1# code/utils/grokfast.py — gradfilter_ema()
2for name, p in model.named_parameters():
3 if p.requires_grad and p.grad is not None:
4 if name not in grads_ema:
5 grads_ema[name] = p.grad.data.detach().clone()
6 else:
7 grads_ema[name] = grads_ema[name] * alpha + p.grad.data * (1 - alpha) # alpha=0.98
8 p.grad.data = p.grad.data + grads_ema[name] * lamb # lamb=2.01criterion = nn.CrossEntropyLoss()
2logits = model(imgs)
3loss = criterion(logits, labels) # pure CE; irm_weight = 0.0 for every reported run
4loss.backward()
5if cfg["use_grokfast"]:
6 grads_ema = gradfilter_ema(model, grads_ema, alpha=0.98, lamb=2.0)
7torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
8optimizer.step()final.pt per run).| Hyperparameter | Standard | Grokking-favorable |
|---|---|---|
| Optimizer | AdamW | AdamW |
| Learning rate | 1e-3 | 1e-3 |
| Weight decay | 1e-4 | 5e-3 (50×) |
| Init scale | 1.0 | 4.0 |
| Grokfast EMA | off | on (alpha 0.98, lamb 2.0) |
| Grad clip (max-norm) | 1.0 | 1.0 |
| Batch size | 32 | 32 |
| Epochs | 3000 | 3000 |
| IRM weight in loss | 0.0 (diagnostic) | 0.0 (diagnostic) |
timm, no ImageNet pretraining), 96×96 input, 2-class head, 11,177,538 parameters.avgpool features (D=512); run on the saved checkpoints.code/experiments/mechinterp_m1.py): logistic-regression hospital and tumor probes at six ResNet stages.code/experiments/mechinterp_m4_ablation.py): project features orthogonal to a ~35-dim LDA-style hospital subspace, re-classify with the original head.code/experiments/mechinterp_m5_steering.py): steer h' = h + alpha · sigma · v_s along the dominant between-hospital direction, alpha ∈ [-3, +3].code/experiments/mechinterp_m6_neuron_ablation.py): zero top-K hospital-discriminating channels vs random-K and morphology-K controls, K ∈ {0,4,8,16,32,64,128,256}.runs/<run_id>/
├── config.json # full hyperparameter config
├── checkpoints/ep00200.pt … ep03000.pt, final.pt # ~44 MB each
├── results/history.json # per-checkpoint metrics (61 rows)
├── results/summary.json # final-summary fields
├── logs/train.log # launch command + per-checkpoint log lines
├── wandb/ # offline wandb run metadata
└── mechinterp/ # m1/m4/m5/m6 JSON + PNG outputs
figures/ # 7 paper figures (PNG + PDF) + m6_summary.csv (88-row results table)
paper/ # main.tex, example_paper.bib, compiled PDF, style files
code/ # training + mechanistic-interpretability source
logs/ # top-level training and M1/M4/M5/M6 driver logs
docs/ # TRAINING_DETAILS.md — exhaustive code/hyperparameter/metric/results reference.pt checkpoints total ~10 GB across 240 files.| Cond. | Seed | Run ID | Peak OOD | Peak ep | Final OOD |
|---|---|---|---|---|---|
| Grok | 7 | 20260508-183413_grokking_n1000_s7 | 0.6876 | 50 | 0.5882 |
| Grok | 42 | 20260505-080445_grokking_n1000_s42 | 0.7336 | 350 | 0.6639 |
| Grok | 123 | 20260505-100720_grokking_n1000_s123 | 0.7270 | 350 | 0.6447 |
| Grok | 456 | 20260505-100720_grokking_n1000_s456 | 0.6722 | 1100 | 0.5224 |
| Grok | 2024 | 20260508-183413_grokking_n1000_s2024 | 0.7056 | 400 | 0.5506 |
| Std | 42 | 20260505-100720_standard_n1000_s42 | 0.7615 | 1 | 0.6482 |
| Std | 123 | 20260508-183413_standard_n1000_s123 | 0.8880* | 1 | 0.6645 |
| Std | 456 | 20260508-183413_standard_n1000_s456 | 0.7450 | 1050 | 0.5783 |
docs/TRAINING_DETAILS.md.1import torch, timm
2
3model = timm.create_model("resnet18", pretrained=False, num_classes=2)
4sd = torch.load("runs/20260505-080445_grokking_n1000_s42/checkpoints/ep00400.pt",
5 map_location="cpu")
6model.load_state_dict(sd)
7model.eval()state_dict files; the init-scale rescaling is baked into the trained weights.1from huggingface_hub import hf_hub_download, snapshot_download
2# one file
3p = hf_hub_download("nileshsarkar-ai/CausalGrok",
4 "runs/20260505-080445_grokking_n1000_s42/checkpoints/ep00400.pt")
5# whole archive
6snapshot_download("nileshsarkar-ai/CausalGrok", local_dir="CausalGrok")code/utils/camelyon_data.py::get_camelyon_subsets auto-downloads it via the wilds package.1@misc{causalgrok2026,
2 title = {Interventional Analysis of Shortcut Geometry Under Grokking-Favorable Training},
3 author = {Sarkar, Nilesh},
4 year = {2026},
5 url = {https://github.com/nileshsarkar-ai/CausalGrok}
6}