File size: 5,635 Bytes
468b3a9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | {
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# NativeSpecZ-296M — Demo Notebook (scale-up ablation)\n",
"\n",
"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",
"\n",
"**Approach A**: z head trained jointly with the encoder. \n",
"**Approach B**: `[Z_MASK]` token always masked.\n",
"\n",
"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."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os, sys, json\n",
"import numpy as np\n",
"import torch\n",
"from torch.utils.data import DataLoader\n",
"sys.path.append(\"code\")\n",
"from hybrid_redshift import HybridSpecZ, RawCollatorConfig, RawSpectraCollator, move_to_device\n",
"from data import SpectraListDataset, parse_mmu_example\n",
"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
"print(f\"Device: {device}\")\n",
"torch.manual_seed(42); np.random.seed(42)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Load the 296M model (strict=False)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ckpt = torch.load(\"weights/best.pt\", map_location=device, weights_only=False)\n",
"a = ckpt[\"args\"]\n",
"model = HybridSpecZ(\n",
" d_model=a[\"d_model\"], conv_width=a[\"conv_width\"], layers=a[\"layers\"],\n",
" heads=a[\"heads\"], dropout=a[\"dropout\"], z_bins=a[\"z_bins\"],\n",
" stem_stride=a[\"stem_stride\"], rec_hidden_mult=a[\"rec_hidden_mult\"],\n",
" rec_refine_width=a[\"rec_refine_width\"], rec_refine_kernel=a[\"rec_refine_kernel\"],\n",
" layerscale_init=a[\"layerscale_init\"], prediction_mode=a[\"prediction_mode\"],\n",
" bin_temperature=a[\"bin_temperature\"], residual_scale=a[\"residual_scale\"],\n",
" candidate_topk=a[\"candidate_topk\"],\n",
").to(device)\n",
"missing, unexpected = model.load_state_dict(ckpt[\"model\"], strict=False)\n",
"model.eval()\n",
"print(f\"Params: {sum(p.numel() for p in model.parameters()):,}\")\n",
"print(f\"Missing keys (unused rerank/calib heads): {len(missing)}; unexpected: {len(unexpected)}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Load DESI test data + run inference"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from datasets import load_dataset\n",
"from tqdm.auto import tqdm\n",
"import torch.nn.functional as F\n",
"\n",
"ds = load_dataset(\"MultimodalUniverse/desi\", split=\"train\", streaming=True).shuffle(seed=4242, buffer_size=5000)\n",
"samples = []\n",
"for ex in ds:\n",
" p = parse_mmu_example(ex)\n",
" if p is not None: samples.append(p)\n",
" if len(samples) >= 500: break\n",
"print(f\"Loaded {len(samples)} DESI spectra\")\n",
"\n",
"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",
"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",
"z_true_all, y_pred_all = [], []\n",
"with torch.no_grad():\n",
" for batch in tqdm(loader):\n",
" batch = move_to_device(batch, device)\n",
" with torch.autocast(device_type=device.type, dtype=torch.bfloat16, enabled=device.type==\"cuda\"):\n",
" out = model(batch[\"x\"], batch[\"valid\"], batch[\"loglam\"])\n",
" z_true_all.append(batch[\"z\"].cpu().numpy())\n",
" y_pred_all.append(out.get(\"y_pred\", out[\"y_mu\"]).float().cpu().numpy())\n",
"z_true = np.concatenate(z_true_all)\n",
"z_pred = np.clip(np.expm1(np.concatenate(y_pred_all)), 0, 6)\n",
"mae = float(np.mean(np.abs(z_pred - z_true)))\n",
"r = float(np.corrcoef(z_true, z_pred)[0,1])\n",
"print(f\"DESI MAE={mae:.4f} Pearson r={r:.4f}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Held-out results (precomputed, n=2500/2000/2000) + comparison\n",
"\n",
"| Dataset | NativeSpecZ-296M | NativeSpecZ-76M (headline) | AION-base |\n",
"|---|---:|---:|---:|\n",
"| **DESI** | **0.067** | 0.069 | 0.074 |\n",
"| SDSS (real non-DESI) | 0.314 | 0.382 | **0.127** |\n",
"| VIPERS (real non-DESI) | 0.316 | **0.172** | 0.274 |\n",
"\n",
"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."
]
}
],
"metadata": {"kernelspec": {"display_name": "Python 3", "language": "python", "name": "python3"}, "language_info": {"name": "python", "version": "3.11"}},
"nbformat": 4,
"nbformat_minor": 4
}
|