Upload NativeSpecZ-296M.ipynb
Browse files- NativeSpecZ-296M.ipynb +126 -0
NativeSpecZ-296M.ipynb
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# NativeSpecZ-296M — Demo Notebook (scale-up ablation)\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"A 296M-parameter unimodal foundation model for DESI spectra, trained from scratch (no AION pretrained weights) with wavelength-grid jitter augmentation. Hits the spec's ~300M target and is competitive with AION-base on DESI in-distribution, but does NOT beat AION on out-of-distribution data — the smaller NativeSpecZ-FM-76M is the recommended headline.\n",
|
| 10 |
+
"\n",
|
| 11 |
+
"**Approach A**: z head trained jointly with the encoder. \n",
|
| 12 |
+
"**Approach B**: `[Z_MASK]` token always masked.\n",
|
| 13 |
+
"\n",
|
| 14 |
+
"NOTE: load with `strict=False` — the current `hybrid_redshift.py` has `z_rerank_head` / `z_calib_head` modules added after this checkpoint was trained; they are unused in the `bin_residual` prediction path."
|
| 15 |
+
]
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"cell_type": "code",
|
| 19 |
+
"execution_count": null,
|
| 20 |
+
"metadata": {},
|
| 21 |
+
"outputs": [],
|
| 22 |
+
"source": [
|
| 23 |
+
"import os, sys, json\n",
|
| 24 |
+
"import numpy as np\n",
|
| 25 |
+
"import torch\n",
|
| 26 |
+
"from torch.utils.data import DataLoader\n",
|
| 27 |
+
"sys.path.append(\"code\")\n",
|
| 28 |
+
"from hybrid_redshift import HybridSpecZ, RawCollatorConfig, RawSpectraCollator, move_to_device\n",
|
| 29 |
+
"from data import SpectraListDataset, parse_mmu_example\n",
|
| 30 |
+
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
|
| 31 |
+
"print(f\"Device: {device}\")\n",
|
| 32 |
+
"torch.manual_seed(42); np.random.seed(42)"
|
| 33 |
+
]
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"cell_type": "markdown",
|
| 37 |
+
"metadata": {},
|
| 38 |
+
"source": [
|
| 39 |
+
"## Load the 296M model (strict=False)"
|
| 40 |
+
]
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"cell_type": "code",
|
| 44 |
+
"execution_count": null,
|
| 45 |
+
"metadata": {},
|
| 46 |
+
"outputs": [],
|
| 47 |
+
"source": [
|
| 48 |
+
"ckpt = torch.load(\"weights/best.pt\", map_location=device, weights_only=False)\n",
|
| 49 |
+
"a = ckpt[\"args\"]\n",
|
| 50 |
+
"model = HybridSpecZ(\n",
|
| 51 |
+
" d_model=a[\"d_model\"], conv_width=a[\"conv_width\"], layers=a[\"layers\"],\n",
|
| 52 |
+
" heads=a[\"heads\"], dropout=a[\"dropout\"], z_bins=a[\"z_bins\"],\n",
|
| 53 |
+
" stem_stride=a[\"stem_stride\"], rec_hidden_mult=a[\"rec_hidden_mult\"],\n",
|
| 54 |
+
" rec_refine_width=a[\"rec_refine_width\"], rec_refine_kernel=a[\"rec_refine_kernel\"],\n",
|
| 55 |
+
" layerscale_init=a[\"layerscale_init\"], prediction_mode=a[\"prediction_mode\"],\n",
|
| 56 |
+
" bin_temperature=a[\"bin_temperature\"], residual_scale=a[\"residual_scale\"],\n",
|
| 57 |
+
" candidate_topk=a[\"candidate_topk\"],\n",
|
| 58 |
+
").to(device)\n",
|
| 59 |
+
"missing, unexpected = model.load_state_dict(ckpt[\"model\"], strict=False)\n",
|
| 60 |
+
"model.eval()\n",
|
| 61 |
+
"print(f\"Params: {sum(p.numel() for p in model.parameters()):,}\")\n",
|
| 62 |
+
"print(f\"Missing keys (unused rerank/calib heads): {len(missing)}; unexpected: {len(unexpected)}\")"
|
| 63 |
+
]
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"cell_type": "markdown",
|
| 67 |
+
"metadata": {},
|
| 68 |
+
"source": [
|
| 69 |
+
"## Load DESI test data + run inference"
|
| 70 |
+
]
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"cell_type": "code",
|
| 74 |
+
"execution_count": null,
|
| 75 |
+
"metadata": {},
|
| 76 |
+
"outputs": [],
|
| 77 |
+
"source": [
|
| 78 |
+
"from datasets import load_dataset\n",
|
| 79 |
+
"from tqdm.auto import tqdm\n",
|
| 80 |
+
"import torch.nn.functional as F\n",
|
| 81 |
+
"\n",
|
| 82 |
+
"ds = load_dataset(\"MultimodalUniverse/desi\", split=\"train\", streaming=True).shuffle(seed=4242, buffer_size=5000)\n",
|
| 83 |
+
"samples = []\n",
|
| 84 |
+
"for ex in ds:\n",
|
| 85 |
+
" p = parse_mmu_example(ex)\n",
|
| 86 |
+
" if p is not None: samples.append(p)\n",
|
| 87 |
+
" if len(samples) >= 500: break\n",
|
| 88 |
+
"print(f\"Loaded {len(samples)} DESI spectra\")\n",
|
| 89 |
+
"\n",
|
| 90 |
+
"cfg = RawCollatorConfig(target_length=a[\"target_length\"], eval_mask_ratio=0.30, mask_mode=\"mixed_span\", mask_span_min=a[\"mask_span_min\"], mask_span_max=a[\"mask_span_max\"])\n",
|
| 91 |
+
"loader = DataLoader(SpectraListDataset(samples, np.arange(len(samples))), batch_size=8, shuffle=False, num_workers=2, collate_fn=RawSpectraCollator(cfg, train=False, seed=31415))\n",
|
| 92 |
+
"z_true_all, y_pred_all = [], []\n",
|
| 93 |
+
"with torch.no_grad():\n",
|
| 94 |
+
" for batch in tqdm(loader):\n",
|
| 95 |
+
" batch = move_to_device(batch, device)\n",
|
| 96 |
+
" with torch.autocast(device_type=device.type, dtype=torch.bfloat16, enabled=device.type==\"cuda\"):\n",
|
| 97 |
+
" out = model(batch[\"x\"], batch[\"valid\"], batch[\"loglam\"])\n",
|
| 98 |
+
" z_true_all.append(batch[\"z\"].cpu().numpy())\n",
|
| 99 |
+
" y_pred_all.append(out.get(\"y_pred\", out[\"y_mu\"]).float().cpu().numpy())\n",
|
| 100 |
+
"z_true = np.concatenate(z_true_all)\n",
|
| 101 |
+
"z_pred = np.clip(np.expm1(np.concatenate(y_pred_all)), 0, 6)\n",
|
| 102 |
+
"mae = float(np.mean(np.abs(z_pred - z_true)))\n",
|
| 103 |
+
"r = float(np.corrcoef(z_true, z_pred)[0,1])\n",
|
| 104 |
+
"print(f\"DESI MAE={mae:.4f} Pearson r={r:.4f}\")"
|
| 105 |
+
]
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"cell_type": "markdown",
|
| 109 |
+
"metadata": {},
|
| 110 |
+
"source": [
|
| 111 |
+
"## Held-out results (precomputed, n=2500/2000/2000) + comparison\n",
|
| 112 |
+
"\n",
|
| 113 |
+
"| Dataset | NativeSpecZ-296M | NativeSpecZ-76M (headline) | AION-base |\n",
|
| 114 |
+
"|---|---:|---:|---:|\n",
|
| 115 |
+
"| **DESI** | **0.067** | 0.069 | 0.074 |\n",
|
| 116 |
+
"| SDSS (real non-DESI) | 0.314 | 0.382 | **0.127** |\n",
|
| 117 |
+
"| VIPERS (real non-DESI) | 0.316 | **0.172** | 0.274 |\n",
|
| 118 |
+
"\n",
|
| 119 |
+
"The 296M ties the 76M on DESI (both beat AION) and improves SDSS over the 76M, but loses the 76M's strong VIPERS result. It beats AION-base on no OOD dataset, so the 76M remains the recommended headline; the 296M is the scale-up ablation that hits the ~300M target."
|
| 120 |
+
]
|
| 121 |
+
}
|
| 122 |
+
],
|
| 123 |
+
"metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python", "version": "3.11"}},
|
| 124 |
+
"nbformat": 4,
|
| 125 |
+
"nbformat_minor": 4
|
| 126 |
+
}
|