lazy-tfm 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lazy_tfm-0.1.0/LICENSE +21 -0
- lazy_tfm-0.1.0/PKG-INFO +406 -0
- lazy_tfm-0.1.0/README.md +358 -0
- lazy_tfm-0.1.0/pyproject.toml +247 -0
- lazy_tfm-0.1.0/pyproject.toml.orig +250 -0
- lazy_tfm-0.1.0/src/lazy/__init__.py +119 -0
- lazy_tfm-0.1.0/src/lazy/_inputs.py +170 -0
- lazy_tfm-0.1.0/src/lazy/_namespace.py +58 -0
- lazy_tfm-0.1.0/src/lazy/_typing.py +22 -0
- lazy_tfm-0.1.0/src/lazy/base.py +686 -0
- lazy_tfm-0.1.0/src/lazy/datasets.py +1211 -0
- lazy_tfm-0.1.0/src/lazy/distributions.py +786 -0
- lazy_tfm-0.1.0/src/lazy/grid.py +452 -0
- lazy_tfm-0.1.0/src/lazy/metrics.py +1035 -0
- lazy_tfm-0.1.0/src/lazy/models/__init__.py +60 -0
- lazy_tfm-0.1.0/src/lazy/models/_conformance.py +126 -0
- lazy_tfm-0.1.0/src/lazy/models/_device.py +85 -0
- lazy_tfm-0.1.0/src/lazy/models/_ensemble.py +601 -0
- lazy_tfm-0.1.0/src/lazy/models/_hub.py +451 -0
- lazy_tfm-0.1.0/src/lazy/models/_icl_stream.py +405 -0
- lazy_tfm-0.1.0/src/lazy/models/_limix_preprocess.py +202 -0
- lazy_tfm-0.1.0/src/lazy/models/_limix_source.py +294 -0
- lazy_tfm-0.1.0/src/lazy/models/_limix_stream.py +370 -0
- lazy_tfm-0.1.0/src/lazy/models/_members.py +369 -0
- lazy_tfm-0.1.0/src/lazy/models/_progress.py +104 -0
- lazy_tfm-0.1.0/src/lazy/models/_transforms.py +231 -0
- lazy_tfm-0.1.0/src/lazy/models/lazy_model.py +345 -0
- lazy_tfm-0.1.0/src/lazy/models/limix.py +538 -0
- lazy_tfm-0.1.0/src/lazy/models/registry.py +93 -0
- lazy_tfm-0.1.0/src/lazy/models/tabfm.py +820 -0
- lazy_tfm-0.1.0/src/lazy/models/tabicl.py +382 -0
- lazy_tfm-0.1.0/src/lazy/models/tabpfn.py +458 -0
- lazy_tfm-0.1.0/src/lazy/plotting/__init__.py +62 -0
- lazy_tfm-0.1.0/src/lazy/plotting/diagnostics.py +718 -0
- lazy_tfm-0.1.0/src/lazy/plotting/paper.mplstyle +50 -0
- lazy_tfm-0.1.0/src/lazy/plotting/style.py +813 -0
- lazy_tfm-0.1.0/src/lazy/selection.py +474 -0
lazy_tfm-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Biprateep Dey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
lazy_tfm-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lazy-tfm
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Lazy but Accurate z for Yinz: conditional distributions from tabular foundation models
|
|
5
|
+
Keywords: foundation model,tabular,in-context learning,probabilistic regression,conditional density estimation,photometric redshift,astronomy
|
|
6
|
+
Author: Biprateep Dey
|
|
7
|
+
Author-email: Biprateep Dey <biprateep@pitt.edu>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
18
|
+
Requires-Dist: numpy>=1.26
|
|
19
|
+
Requires-Dist: scipy>=1.11
|
|
20
|
+
Requires-Dist: scikit-learn>=1.4
|
|
21
|
+
Requires-Dist: pandas>=2.1
|
|
22
|
+
Requires-Dist: matplotlib>=3.8
|
|
23
|
+
Requires-Dist: huggingface-hub>=0.24
|
|
24
|
+
Requires-Dist: h5py>=3.10
|
|
25
|
+
Requires-Dist: tqdm>=4.66
|
|
26
|
+
Requires-Dist: lazy-tfm[limix,tabfm,tabicl,tabpfn] ; extra == 'all'
|
|
27
|
+
Requires-Dist: torch>=2.9.1 ; extra == 'limix'
|
|
28
|
+
Requires-Dist: einops>=0.8 ; extra == 'limix'
|
|
29
|
+
Requires-Dist: kditransform>=1.0 ; extra == 'limix'
|
|
30
|
+
Requires-Dist: nvtx>=0.2 ; extra == 'limix'
|
|
31
|
+
Requires-Dist: qp-prob>=1.1 ; extra == 'qp'
|
|
32
|
+
Requires-Dist: tabfm[pytorch]>=1.0.1 ; extra == 'tabfm'
|
|
33
|
+
Requires-Dist: safetensors>=0.4.3 ; extra == 'tabfm'
|
|
34
|
+
Requires-Dist: tabicl>=2.2.0 ; extra == 'tabicl'
|
|
35
|
+
Requires-Dist: tabpfn>=9.0.0 ; extra == 'tabpfn'
|
|
36
|
+
Requires-Python: >=3.12
|
|
37
|
+
Project-URL: Source Code, https://github.com/biprateep/lazy-tfm
|
|
38
|
+
Project-URL: Documentation, https://lazy-tfm.readthedocs.io
|
|
39
|
+
Project-URL: Bug Tracker, https://github.com/biprateep/lazy-tfm/issues
|
|
40
|
+
Project-URL: Changelog, https://github.com/biprateep/lazy-tfm/blob/main/CHANGELOG.md
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Provides-Extra: limix
|
|
43
|
+
Provides-Extra: qp
|
|
44
|
+
Provides-Extra: tabfm
|
|
45
|
+
Provides-Extra: tabicl
|
|
46
|
+
Provides-Extra: tabpfn
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
|
|
49
|
+
# LAZY
|
|
50
|
+
|
|
51
|
+
**L**azy but **A**ccurate ***z***\* for **Y**inz — full conditional distributions
|
|
52
|
+
of a continuous target from pretrained tabular foundation models.
|
|
53
|
+
|
|
54
|
+
\*where *z* is whatever you want to predict from tabular features: a
|
|
55
|
+
redshift, a metallicity, a mass, a yield.
|
|
56
|
+
|
|
57
|
+
[](https://pypi.org/project/lazy-tfm/)
|
|
58
|
+
[](https://pypi.org/project/lazy-tfm/)
|
|
59
|
+
[](https://github.com/biprateep/lazy-tfm/actions/workflows/testing-and-coverage.yml)
|
|
60
|
+
[](https://lazy-tfm.readthedocs.io)
|
|
61
|
+
[](https://github.com/biprateep/lazy-tfm/blob/main/LICENSE)
|
|
62
|
+
|
|
63
|
+
The models are pretrained and never fine-tuned. You hand them labelled rows as
|
|
64
|
+
*context* and they answer queries in one forward pass — no training loop, no
|
|
65
|
+
hyper-parameter search, no per-dataset retraining. Hence lazy.
|
|
66
|
+
|
|
67
|
+
The package grew out of photometric redshifts, so its worked examples and
|
|
68
|
+
benchmark tools (the LSST DESC DC1 catalogue, the HSC spectroscopic selection,
|
|
69
|
+
the Data Challenge metrics) are photo-z's; the estimators work on any tabular
|
|
70
|
+
regression problem.
|
|
71
|
+
|
|
72
|
+
> **Pre-release.** The API is still moving and the paper numbers are not final.
|
|
73
|
+
> Pin a version if you depend on this.
|
|
74
|
+
|
|
75
|
+
**Documentation:** [lazy-tfm.readthedocs.io](https://lazy-tfm.readthedocs.io) ·
|
|
76
|
+
**Tutorial:** [open in Colab](https://colab.research.google.com/github/biprateep/lazy-tfm/blob/tutorials/introduction.ipynb) ·
|
|
77
|
+
**Changelog:** [CHANGELOG.md](https://github.com/biprateep/lazy-tfm/blob/main/CHANGELOG.md) · **Contributing:** [CONTRIBUTING.md](https://github.com/biprateep/lazy-tfm/blob/main/CONTRIBUTING.md)
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
```console
|
|
82
|
+
pip install lazy-tfm # grid, metrics, plots, datasets
|
|
83
|
+
pip install 'lazy-tfm[tabfm]' # + the TabFM backend
|
|
84
|
+
pip install 'lazy-tfm[tabicl]' # + the TabICLv2 backend
|
|
85
|
+
pip install 'lazy-tfm[tabpfn]' # + the TabPFN backend (v2 to v3.5)
|
|
86
|
+
pip install 'lazy-tfm[limix]' # + the LimiX-2 backend (its code installs separately)
|
|
87
|
+
pip install 'lazy-tfm[all]' # + all four
|
|
88
|
+
pip install 'lazy-tfm[qp]' # + qp interoperability, for RAIL
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Pretrained weights are not bundled: they are fetched from the Hugging Face Hub
|
|
92
|
+
at the first `fit` and cached thereafter. TabFM's classification checkpoint is
|
|
93
|
+
~6.6 GB and carries a **non-commercial** licence from Google; TabICLv2's is
|
|
94
|
+
~100 MB (BSD-3-Clause); the TabPFN checkpoints run from ~41 MB to ~880 MB and
|
|
95
|
+
are **non-commercial** from Prior Labs, except `v2`, which is Apache-2.0 with an
|
|
96
|
+
attribution clause; LimiX-2's is ~1.6 GB under Stable AI's Apache-2.0-based
|
|
97
|
+
licence, which requires the attribution "Built with StableAI LimiX". Every licence is quoted in `lazy.CHECKPOINTS`. Warm the
|
|
98
|
+
cache ahead of time with `lazy.download_checkpoint("tabfm")`.
|
|
99
|
+
|
|
100
|
+
## Use
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
import lazy
|
|
104
|
+
from lazy import LazyModel, Grid
|
|
105
|
+
from lazy.datasets import fetch_dc1
|
|
106
|
+
|
|
107
|
+
train, test = fetch_dc1(split=True) # or fetch_dc1() for both, concatenated
|
|
108
|
+
X_train, X_test = train.features("mag-color"), test.features("mag-color")
|
|
109
|
+
|
|
110
|
+
X_test, z_test = X_test.iloc[:20_000], test.redshift[:20_000] # 391k in all
|
|
111
|
+
|
|
112
|
+
model = LazyModel() # TabPFN-3.5, the default; wants a GPU
|
|
113
|
+
model.fit(X_train, train.redshift)
|
|
114
|
+
|
|
115
|
+
pdfs = model.predict_proba(X_test, Grid.linear(0, 2, 200))
|
|
116
|
+
z = model.predict(X_test, method="z_peak") # or z_mean, z_weight, z_median
|
|
117
|
+
lo, med, hi = model.predict_quantiles(X_test, [0.16, 0.5, 0.84]).T
|
|
118
|
+
print(model.evaluate(X_test, z_test, lazy.DC1_GRID))
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
No GPU? TabPFN is slow on a CPU and refuses contexts above 5,000 rows there;
|
|
122
|
+
`LazyModel("tabicl")` is small, BSD-licensed and quick on a laptop. The
|
|
123
|
+
[supported models](https://lazy-tfm.readthedocs.io/en/latest/guide/models.html)
|
|
124
|
+
page compares them all.
|
|
125
|
+
|
|
126
|
+
`LazyModel(name, ...)` picks the backend by name, so switching is a string
|
|
127
|
+
change; the concrete classes (`TabPFNBarDistribution`, `LimiXBarDistribution`,
|
|
128
|
+
`TabICLQuantile`, `TabFMHistogram`) are importable and identical. The API is scikit-learn's, except `predict_proba` returns a
|
|
129
|
+
density on a grid of the target rather than class probabilities — the natural
|
|
130
|
+
output of a model that knows its own uncertainty. `get_params`/`set_params`/`clone` work, so models drop
|
|
131
|
+
into scikit-learn pipelines and search objects unmodified.
|
|
132
|
+
|
|
133
|
+
**`z_grid` belongs to the prediction, not the fit.** Nothing about fitting
|
|
134
|
+
depends on the output binning — these models place their internal bins by the
|
|
135
|
+
distribution of the context targets, and the grid only enters at the final exact
|
|
136
|
+
rebinning step — so one fitted model answers on as many grids as you like
|
|
137
|
+
without refitting:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
import numpy as np
|
|
141
|
+
|
|
142
|
+
model.predict_proba(X_test) # the native grid
|
|
143
|
+
model.predict_proba(X_test, Grid.linear(0, 3, 300))
|
|
144
|
+
model.predict_proba(X_test, np.linspace(0.005, 2.995, 300)) # or bin centres
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
With no grid a model answers on its *native* grid, the one it thinks in (for
|
|
148
|
+
TabPFN and LimiX-2, their 5,000 buckets), so nothing is lost to rebinning.
|
|
149
|
+
`predict_distribution(X)` returns that native answer itself, with exact
|
|
150
|
+
`pdf`/`cdf`/`ppf`/`mean`/`interval`/`rvs` in the vocabulary of scipy.stats and
|
|
151
|
+
LSST DESC's qp, and `.to_qp()` for RAIL.
|
|
152
|
+
|
|
153
|
+
| Backend | Method | Versions | Weights |
|
|
154
|
+
| -------- | ---------------------------------------------------------------- | -------- | -------- |
|
|
155
|
+
| `tabpfn` | Bucket masses of the bar distribution | `v2` … `v3.5` | 41 MB – 880 MB |
|
|
156
|
+
| `limix` | Bucket masses of LimiX-2's 5,000-bucket head | `v2` | ~1.6 GB |
|
|
157
|
+
| `tabicl` | Quantiles of an in-context regression head | `v2` | ~100 MB |
|
|
158
|
+
| `tabfm` | Hierarchy of in-context classifiers over equal-mass bins of the target | `v1.0` | ~6.6 GB |
|
|
159
|
+
|
|
160
|
+
**One set of parameters, on every model.** `kv_cache` (process the context
|
|
161
|
+
once, at fit; exact; on by default), `n_estimators`, `feature_shuffle`,
|
|
162
|
+
`transforms` (a shared vocabulary — `power`, `quantile`, `robust`, … — plus
|
|
163
|
+
recipes such as `"limix"`; `"auto"` keeps each model's own), `bag_size`
|
|
164
|
+
(per-member subsets of the context) and `z_grid` mean the same thing on every
|
|
165
|
+
backend. Each is translated to the model's own machinery where it has it and
|
|
166
|
+
built around the model where it does not, and registering a new backend
|
|
167
|
+
without them fails. LimiX-2 degrades above ~20,000 context rows and warns when
|
|
168
|
+
a larger context arrives without `bag_size`.
|
|
169
|
+
|
|
170
|
+
Memory is bounded by `chunk_size` on every backend, and chunking is exact: a
|
|
171
|
+
query row's answer never depends on which other query rows share its chunk
|
|
172
|
+
(bit for bit on TabPFN and TabICL, to float rounding on LimiX-2).
|
|
173
|
+
|
|
174
|
+
### For TabFM, install its repository build
|
|
175
|
+
|
|
176
|
+
`pip install 'lazy-tfm[tabfm]'` installs TabFM's PyPI release, which works
|
|
177
|
+
but has no KV-cache API: every chunk of query rows re-encodes the whole training
|
|
178
|
+
context, about **13.7 ms per member-row against 0.53 ms** on the cached path,
|
|
179
|
+
roughly **26× the compute** for identical answers. For anything beyond a few
|
|
180
|
+
thousand query rows, install the repository build as well:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pip install 'lazy-tfm[tabfm]'
|
|
184
|
+
pip install --force-reinstall --no-deps 'tabfm[pytorch] @ git+https://github.com/google-research/tabfm@fbb665569425fd2f490c6576b3af967876fe11ff'
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
`--force-reinstall` matters: the repository build calls itself 1.0.1, like the
|
|
188
|
+
release, so without it pip keeps the release and changes nothing.
|
|
189
|
+
A checkout of this repository gets the repository build automatically (`uv sync
|
|
190
|
+
--extra all`), pinned through `[tool.uv.sources]`. On the release build the
|
|
191
|
+
backend warns at `fit` with a `TabFMPerformanceWarning` rather than silently
|
|
192
|
+
taking 26× longer. To check which you have:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from lazy.models._icl_stream import streaming_available
|
|
196
|
+
streaming_available() # True means the fast path is in use
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Pass `kv_cache=False` to choose the slow path deliberately and silence the
|
|
200
|
+
warning.
|
|
201
|
+
|
|
202
|
+
### For LimiX-2, install its code
|
|
203
|
+
|
|
204
|
+
LimiX is not on PyPI. The `limix` extra brings its dependencies; the code
|
|
205
|
+
comes from the repository, at the commit this package was validated on (or
|
|
206
|
+
from a checkout named by `LAZY_LIMIX_SRC`):
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
pip install 'lazy-tfm[limix]'
|
|
210
|
+
pip install 'LimiX @ git+https://github.com/limix-ldm-ai/LimiX@516bf396333feb3198cf7aff8a6c10421f218e24'
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Upstream LimiX-2 has no key/value cache and its answers depend on how the
|
|
214
|
+
queries are chunked; the backend runs its network with a ported cache,
|
|
215
|
+
context-only preprocessing and a dedicated random generator, which removes
|
|
216
|
+
both. LimiX's network needs `triton`, so the backend is Linux-only for now.
|
|
217
|
+
Built with StableAI LimiX.
|
|
218
|
+
|
|
219
|
+
They all write onto any `Grid` you ask for — any number of bins, any
|
|
220
|
+
spacing, any range. (TabFM's ten-class ceiling constrains its internal
|
|
221
|
+
hierarchy, never your output grid.)
|
|
222
|
+
|
|
223
|
+
### Progress bars
|
|
224
|
+
|
|
225
|
+
Every backend draws a `tqdm` bar while it predicts, counting whatever that
|
|
226
|
+
model's loop actually iterates over. For 20,000 DC1 test galaxies on a
|
|
227
|
+
1,000-galaxy context (one GB10 GPU):
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
TabPFN v3.5: 100%|██████████| 20000/20000 [00:15<00:00, 1279.46row/s, buckets=5000, context=1000]
|
|
231
|
+
TabICL v2: 100%|██████████| 20000/20000 [00:10<00:00, 1968.82row/s, context=1000, quantiles=999]
|
|
232
|
+
TabFM v1.0 (20,000 rows): 100%|██████████| 33/33 [08:47<00:00, 16.00s/stage, context=34, dither=3/3, level=fine 10/10]
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
TabPFN, LimiX and TabICL answer in chunks of query rows, so their bars count
|
|
236
|
+
rows. TabFM is a hierarchy of in-context classifications, each over every
|
|
237
|
+
query row, so its bar counts *stages* — `n_dither × (1 + n_coarse_bins)` of
|
|
238
|
+
them — and shows which dither and level is running on how many context rows.
|
|
239
|
+
|
|
240
|
+
`progress="auto"` (the default) shows the bar on a terminal or in a notebook and
|
|
241
|
+
stays silent when output goes to a file, so batch logs stay clean. Pass
|
|
242
|
+
`progress=True` to force it on or `progress=False` to turn it off. The bar goes
|
|
243
|
+
to stderr; `verbose=True` log messages go to stdout.
|
|
244
|
+
|
|
245
|
+
## The harder benchmark: a biased training set
|
|
246
|
+
|
|
247
|
+
DC1 hands every training galaxy a redshift. Real spectroscopic samples do not
|
|
248
|
+
look like that — they are bright, incomplete, and cut in redshift by which
|
|
249
|
+
features fall in the observed window — and that mismatch, not the estimator, is
|
|
250
|
+
usually what limits a survey's redshifts. `fetch_dc1_biased` merges both DC1
|
|
251
|
+
files and cuts the realistic case out of them:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from lazy.datasets import fetch_dc1_biased
|
|
255
|
+
|
|
256
|
+
split = fetch_dc1_biased() # 35,011 biased / 10,000 calibration / 19,383 test
|
|
257
|
+
split.biased # what a HSC-like campaign would have got
|
|
258
|
+
split.calibration # representative, yours to repair the model with
|
|
259
|
+
split.test # representative, held out from all of it
|
|
260
|
+
print(split.summary()) # each subset against the parent catalogue
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Both files are merged and reshuffled, then cut in two: the first part is run
|
|
264
|
+
through the selection and what it keeps is the training set, the second is
|
|
265
|
+
divided at random into the calibration sample and the test set. Where the cut
|
|
266
|
+
falls is solved for, not chosen, because the selection keeps a near-fixed
|
|
267
|
+
fraction of whatever it sees — `n_train` is the knob. The calibration sample
|
|
268
|
+
comes *out* of the hold-out rather than on top of it, so no galaxy is both given
|
|
269
|
+
to a model and scored on.
|
|
270
|
+
|
|
271
|
+
`n_train` cannot reach the DC1 training file's 43,486: the selection keeps 8.66
|
|
272
|
+
per cent of a representative sample, so all 434,476 galaxies yield at most
|
|
273
|
+
37,626, and that with nothing left to test on. 35,000 is the largest round
|
|
274
|
+
number that still leaves a usable hold-out.
|
|
275
|
+
|
|
276
|
+
`control=True` adds `split.unbiased`, exactly as many galaxies drawn at random
|
|
277
|
+
from the same pool — the control that separates the selection from the sample
|
|
278
|
+
size. It is drawn last, so asking for it changes nothing else in the split.
|
|
279
|
+
|
|
280
|
+
The selection is a port of RAIL's HSC `GridSelection`: each galaxy is kept with
|
|
281
|
+
the HSC spectroscopic success rate of its (i, g−z) pixel, below a
|
|
282
|
+
colour-dependent redshift ceiling. It is reproduced galaxy-for-galaxy against
|
|
283
|
+
the runs this work reports. It also acts on any photometry, so a catalogue of
|
|
284
|
+
your own can be biased the same way:
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
from lazy.selection import grid_selection, selection_summary
|
|
288
|
+
|
|
289
|
+
keep, diagnostics = grid_selection(catalog.raw, catalog.redshift)
|
|
290
|
+
print(selection_summary(keep, catalog.raw["I"], catalog.redshift))
|
|
291
|
+
# i in [16.0, 20.0) ... fraction 0.661
|
|
292
|
+
# i in [24.0, 25.3) ... fraction 0.0005
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`diagnostics` carries each galaxy's pixel success rate and redshift ceiling —
|
|
296
|
+
everything the selection knew, for a method that tries to estimate it back.
|
|
297
|
+
|
|
298
|
+
## Which model, exactly
|
|
299
|
+
|
|
300
|
+
A backbone is a family, not a model, so every backend takes a `version` and each
|
|
301
|
+
version is a separately pinned checkpoint:
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
lazy.list_versions("tabpfn")
|
|
305
|
+
# ['v2', 'v2.5', 'v2.6', 'v3', 'v3.5', 'v3.5-fast']
|
|
306
|
+
|
|
307
|
+
model = LazyModel("tabpfn", version="v2.5")
|
|
308
|
+
model.name_ # 'tabpfn:v2.5' -- also the label evaluate() puts on its row
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Sweeping `version` compares model versions on equal terms, and a results table
|
|
312
|
+
says which one produced each row rather than only which family. What actually
|
|
313
|
+
answered is recorded on the fitted model, ready to be written out beside the
|
|
314
|
+
numbers:
|
|
315
|
+
|
|
316
|
+
```python
|
|
317
|
+
model.fit(X_train, z_train).provenance_
|
|
318
|
+
# {'backend': 'tabpfn', 'version': 'v2.5',
|
|
319
|
+
# 'repo_id': 'Prior-Labs/tabpfn_2_5',
|
|
320
|
+
# 'filename': 'tabpfn-v2.5-regressor-v2.5_default.ckpt',
|
|
321
|
+
# 'revision': '6c45f3a6d0d07c6c5f62572e04a0c2929de91b8b',
|
|
322
|
+
# 'package': 'tabpfn 9.0.0', 'lazy': 'lazy-tfm 0.1.0', 'device': 'cuda',
|
|
323
|
+
# 'n_estimators': 8, 'transforms': ['auto', ...], 'kv_cache': True, ...}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
It holds the weights, the code that read them and the ensemble that answered,
|
|
327
|
+
and no local paths, so it means the same thing on another machine and
|
|
328
|
+
survives a trip through JSON. The rest of the settings are in `get_params()`. `CHECKPOINTS` is keyed
|
|
329
|
+
`"backend:version"` and is the authority on what each name loads;
|
|
330
|
+
`download_checkpoint("tabpfn", "v2.5")` warms exactly that one.
|
|
331
|
+
|
|
332
|
+
Versions are not interchangeable. TabPFN `v2` is pretrained for at most 10,000
|
|
333
|
+
context rows and `v2.5` for 50,000, against a million for `v3`; `v2` is also the
|
|
334
|
+
only one under a commercial-use licence. `lazy.get_checkpoint(name, version)`
|
|
335
|
+
carries both notes.
|
|
336
|
+
|
|
337
|
+
## Caches and offline use
|
|
338
|
+
|
|
339
|
+
Nothing platform-specific is baked in: the wheel is pure Python
|
|
340
|
+
(`py3-none-any`), the lockfile resolves for macOS/Linux/Windows on Python
|
|
341
|
+
3.12–3.14, and the device is chosen at run time (`device="auto"`).
|
|
342
|
+
|
|
343
|
+
| Cache | Default | Override |
|
|
344
|
+
| ----- | ------- | -------- |
|
|
345
|
+
| Pretrained weights | `~/.cache/huggingface/hub` | `HF_HOME` |
|
|
346
|
+
| Benchmark catalogues | `~/.cache/lazy-tfm` | `LAZY_DATA_HOME`, else `XDG_CACHE_HOME` |
|
|
347
|
+
|
|
348
|
+
For a compute node with no network, warm both on a login node first, then run
|
|
349
|
+
with `download_checkpoint`/`is_cached` and `fetch_dc1(..., download_if_missing=False)`
|
|
350
|
+
so a missing file fails immediately instead of hanging. The ~14 MB HSC selection
|
|
351
|
+
grid caches beside the catalogues and `fetch_hsc_grid` takes the same flag. The
|
|
352
|
+
checkpoint a model loads is the one `download_checkpoint` fetches — the pinned revision is handed
|
|
353
|
+
to the backend rather than left to its default.
|
|
354
|
+
|
|
355
|
+
Memory is bounded by default on every backend via `chunk_size` (8,192 or
|
|
356
|
+
16,384 query rows), and chunking is exact, so the default costs nothing.
|
|
357
|
+
|
|
358
|
+
## What's in the box
|
|
359
|
+
|
|
360
|
+
| Module | Contents |
|
|
361
|
+
| ---------------- | ---------------------------------------------------------------------------- |
|
|
362
|
+
| `lazy.models` | `LazyModel`, the concrete backends, and the name registry |
|
|
363
|
+
| `lazy.grid` | `Grid`: binning, normalisation, mass-conserving rebinning |
|
|
364
|
+
| `lazy.metrics` | LSST DESC PZ Data Challenge point and PDF metrics, and `summarize` |
|
|
365
|
+
| `lazy.selection` | The HSC spectroscopic selection function, for biasing a catalogue of your own |
|
|
366
|
+
| `lazy.plotting` | Publication figure style, and the standard diagnostic figures |
|
|
367
|
+
| `lazy.datasets` | The DC1 catalogue (download, checksum, cache), `Catalog.features`, `fetch_dc1_biased` |
|
|
368
|
+
|
|
369
|
+
## Develop
|
|
370
|
+
|
|
371
|
+
```console
|
|
372
|
+
git clone https://github.com/biprateep/lazy-tfm
|
|
373
|
+
cd lazy-tfm
|
|
374
|
+
uv sync # locked environment, including dev tooling
|
|
375
|
+
uv run pre-commit install
|
|
376
|
+
uv run pytest
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
The test suite needs neither a GPU nor a checkpoint. See
|
|
380
|
+
[CONTRIBUTING.md](https://github.com/biprateep/lazy-tfm/blob/main/CONTRIBUTING.md) for the checks CI runs, building the docs and
|
|
381
|
+
making a release.
|
|
382
|
+
|
|
383
|
+
## Repository layout
|
|
384
|
+
|
|
385
|
+
This repository is the library alone: the public, installable package, its
|
|
386
|
+
tests and its documentation. The production runs and figures behind the paper
|
|
387
|
+
live in a separate repository that uses this package through its public API.
|
|
388
|
+
|
|
389
|
+
## Citing
|
|
390
|
+
|
|
391
|
+
A paper is in preparation. Until it appears, cite the repository and the
|
|
392
|
+
checkpoint revisions recorded in `lazy.CHECKPOINTS` — a foundation model's
|
|
393
|
+
weights are part of the method. The `provenance_` of a fitted model is that
|
|
394
|
+
citation for the run you actually did.
|
|
395
|
+
|
|
396
|
+
Built from the
|
|
397
|
+
[LINCC Frameworks Python Project Template](https://github.com/lincc-frameworks/python-project-template).
|
|
398
|
+
|
|
399
|
+
## License
|
|
400
|
+
|
|
401
|
+
MIT, for this code. The pretrained checkpoints carry their own licences; TabFM's
|
|
402
|
+
is non-commercial, and LimiX-2's requires the attribution "Built with StableAI
|
|
403
|
+
LimiX". The HSC selection grid is redistributed by DESC's
|
|
404
|
+
[rail_astro_tools](https://github.com/LSSTDESC/rail_astro_tools) (MIT) and
|
|
405
|
+
derives from HSC PDR2 (Aihara et al. 2019); `lazy.selection` downloads it from
|
|
406
|
+
there, at a pinned commit, rather than bundling it.
|