multabench 0.0.1__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.
- multabench-0.0.1/LICENSE +21 -0
- multabench-0.0.1/PKG-INFO +277 -0
- multabench-0.0.1/README.md +224 -0
- multabench-0.0.1/multabench/__init__.py +2 -0
- multabench-0.0.1/multabench/baselines/__init__.py +0 -0
- multabench-0.0.1/multabench/baselines/abstract_model.py +240 -0
- multabench-0.0.1/multabench/baselines/autogluon_mm.py +116 -0
- multabench-0.0.1/multabench/baselines/benchmarks/__init__.py +0 -0
- multabench-0.0.1/multabench/baselines/benchmarks/evaluate.py +109 -0
- multabench-0.0.1/multabench/baselines/catboost.py +40 -0
- multabench-0.0.1/multabench/baselines/contexttab.py +30 -0
- multabench-0.0.1/multabench/baselines/kumo_tabular.py +103 -0
- multabench-0.0.1/multabench/baselines/lgbm.py +28 -0
- multabench-0.0.1/multabench/baselines/preprocessing/__init__.py +0 -0
- multabench-0.0.1/multabench/baselines/preprocessing/categorical.py +24 -0
- multabench-0.0.1/multabench/baselines/preprocessing/dates.py +29 -0
- multabench-0.0.1/multabench/baselines/preprocessing/feature_types.py +48 -0
- multabench-0.0.1/multabench/baselines/preprocessing/image_embeddings.py +208 -0
- multabench-0.0.1/multabench/baselines/preprocessing/nulls.py +10 -0
- multabench-0.0.1/multabench/baselines/preprocessing/numerical.py +13 -0
- multabench-0.0.1/multabench/baselines/preprocessing/target.py +21 -0
- multabench-0.0.1/multabench/baselines/preprocessing/text_embeddings.py +271 -0
- multabench-0.0.1/multabench/baselines/preprocessing/validation.py +83 -0
- multabench-0.0.1/multabench/baselines/random_forest.py +35 -0
- multabench-0.0.1/multabench/baselines/realmlp.py +41 -0
- multabench-0.0.1/multabench/baselines/tabdpt.py +37 -0
- multabench-0.0.1/multabench/baselines/tabicl_v2.py +22 -0
- multabench-0.0.1/multabench/baselines/tabm.py +35 -0
- multabench-0.0.1/multabench/baselines/tabpfn.py +70 -0
- multabench-0.0.1/multabench/baselines/tabstar_v1.py +86 -0
- multabench-0.0.1/multabench/baselines/training/__init__.py +0 -0
- multabench-0.0.1/multabench/baselines/training/metrics.py +37 -0
- multabench-0.0.1/multabench/baselines/xgboost.py +38 -0
- multabench-0.0.1/multabench/benchmark/__init__.py +0 -0
- multabench-0.0.1/multabench/benchmark/collect.py +37 -0
- multabench-0.0.1/multabench/benchmark/load.py +129 -0
- multabench-0.0.1/multabench/benchmark/runs.py +35 -0
- multabench-0.0.1/multabench/benchmark/splits.py +53 -0
- multabench-0.0.1/multabench/benchmark/sweep.py +122 -0
- multabench-0.0.1/multabench/benchmark/utils/__init__.py +0 -0
- multabench-0.0.1/multabench/benchmark/utils/constants.py +2 -0
- multabench-0.0.1/multabench/benchmark/utils/curation.py +12 -0
- multabench-0.0.1/multabench/constants.py +10 -0
- multabench-0.0.1/multabench/datasets/__init__.py +0 -0
- multabench-0.0.1/multabench/datasets/all_datasets.py +241 -0
- multabench-0.0.1/multabench/datasets/hub.py +31 -0
- multabench-0.0.1/multabench/datasets/objects.py +37 -0
- multabench-0.0.1/multabench/dino/__init__.py +0 -0
- multabench-0.0.1/multabench/dino/constants.py +41 -0
- multabench-0.0.1/multabench/dino/dino_finetune.py +240 -0
- multabench-0.0.1/multabench/dino/image_loading.py +38 -0
- multabench-0.0.1/multabench/e5/__init__.py +0 -0
- multabench-0.0.1/multabench/e5/constants.py +61 -0
- multabench-0.0.1/multabench/e5/e5_finetune.py +317 -0
- multabench-0.0.1/multabench/embeddings/__init__.py +0 -0
- multabench-0.0.1/multabench/embeddings/compute.py +87 -0
- multabench-0.0.1/multabench/embeddings/embed.py +30 -0
- multabench-0.0.1/multabench/embeddings/embed_sweep.py +72 -0
- multabench-0.0.1/multabench/embeddings/hub.py +150 -0
- multabench-0.0.1/multabench/embeddings/jobs.py +32 -0
- multabench-0.0.1/multabench/finetune/train_args.py +29 -0
- multabench-0.0.1/multabench/finetune/utils.py +54 -0
- multabench-0.0.1/multabench/leaderboard/__init__.py +0 -0
- multabench-0.0.1/multabench/leaderboard/data/__init__.py +0 -0
- multabench-0.0.1/multabench/leaderboard/data/keys.py +45 -0
- multabench-0.0.1/multabench/leaderboard/data/loading.py +97 -0
- multabench-0.0.1/multabench/leaderboard/elo.py +112 -0
- multabench-0.0.1/multabench/leaderboard/large_results.py +193 -0
- multabench-0.0.1/multabench/leaderboard/paper_results.py +177 -0
- multabench-0.0.1/multabench/leaderboard/plots.py +159 -0
- multabench-0.0.1/multabench/leaderboard/utils.py +20 -0
- multabench-0.0.1/multabench/preprocessing/__init__.py +0 -0
- multabench-0.0.1/multabench/preprocessing/discretize.py +19 -0
- multabench-0.0.1/multabench/preprocessing/splits.py +89 -0
- multabench-0.0.1/multabench/result_keys.py +52 -0
- multabench-0.0.1/multabench/scripts/do_leaderboard.py +17 -0
- multabench-0.0.1/multabench/utils/__init__.py +0 -0
- multabench-0.0.1/multabench/utils/devices.py +23 -0
- multabench-0.0.1/multabench/utils/encoders.py +6 -0
- multabench-0.0.1/multabench/utils/hardware.py +143 -0
- multabench-0.0.1/multabench/utils/logging.py +23 -0
- multabench-0.0.1/multabench/utils/profiling.py +66 -0
- multabench-0.0.1/multabench/utils/timing.py +28 -0
- multabench-0.0.1/multabench/utils/warnings.py +97 -0
- multabench-0.0.1/multabench.egg-info/PKG-INFO +277 -0
- multabench-0.0.1/multabench.egg-info/SOURCES.txt +105 -0
- multabench-0.0.1/multabench.egg-info/dependency_links.txt +1 -0
- multabench-0.0.1/multabench.egg-info/requires.txt +50 -0
- multabench-0.0.1/multabench.egg-info/top_level.txt +1 -0
- multabench-0.0.1/pyproject.toml +68 -0
- multabench-0.0.1/setup.cfg +4 -0
- multabench-0.0.1/tests/test_collect.py +36 -0
- multabench-0.0.1/tests/test_core_imports.py +12 -0
- multabench-0.0.1/tests/test_elo.py +42 -0
- multabench-0.0.1/tests/test_embeddings.py +138 -0
- multabench-0.0.1/tests/test_feature_types.py +22 -0
- multabench-0.0.1/tests/test_hardware.py +74 -0
- multabench-0.0.1/tests/test_hub.py +29 -0
- multabench-0.0.1/tests/test_leaderboard_results.py +81 -0
- multabench-0.0.1/tests/test_metrics.py +41 -0
- multabench-0.0.1/tests/test_official_commit.py +37 -0
- multabench-0.0.1/tests/test_profiling.py +9 -0
- multabench-0.0.1/tests/test_registry.py +22 -0
- multabench-0.0.1/tests/test_splits.py +81 -0
- multabench-0.0.1/tests/test_sweep.py +89 -0
- multabench-0.0.1/tests/test_text_embeddings.py +16 -0
- multabench-0.0.1/tests/test_timing.py +21 -0
multabench-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alan Arazi
|
|
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.
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: multabench
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Benchmarking multimodal tabular learning: tables whose columns include text or images
|
|
5
|
+
Author: Alan Arazi, Eilam Shapira, Shoham Grunblat, Mor Ventura, Elad Hoffer, Gioia Blayer, David Holzmüller, Lennart Purucker, Gaël Varoquaux, Frank Hutter, Roi Reichart
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/alanarazi7/MulTaBench
|
|
8
|
+
Project-URL: Leaderboard, https://huggingface.co/spaces/multabench/leaderboard
|
|
9
|
+
Project-URL: Paper, https://arxiv.org/abs/2605.10616
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: huggingface_hub
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: pandas>=2.2.2
|
|
16
|
+
Requires-Dist: pyarrow
|
|
17
|
+
Requires-Dist: scikit-learn
|
|
18
|
+
Provides-Extra: bench
|
|
19
|
+
Requires-Dist: autogluon.core; extra == "bench"
|
|
20
|
+
Requires-Dist: nvidia-ml-py; extra == "bench"
|
|
21
|
+
Requires-Dist: peft; extra == "bench"
|
|
22
|
+
Requires-Dist: pillow; extra == "bench"
|
|
23
|
+
Requires-Dist: psutil; extra == "bench"
|
|
24
|
+
Requires-Dist: python-dotenv; extra == "bench"
|
|
25
|
+
Requires-Dist: setuptools<82; extra == "bench"
|
|
26
|
+
Requires-Dist: skrub; extra == "bench"
|
|
27
|
+
Requires-Dist: torch>=2.6.0; extra == "bench"
|
|
28
|
+
Requires-Dist: torchvision; extra == "bench"
|
|
29
|
+
Requires-Dist: transformers>=4.49.0; extra == "bench"
|
|
30
|
+
Provides-Extra: catboost
|
|
31
|
+
Requires-Dist: catboost; extra == "catboost"
|
|
32
|
+
Provides-Extra: lightgbm
|
|
33
|
+
Requires-Dist: lightgbm; extra == "lightgbm"
|
|
34
|
+
Provides-Extra: xgboost
|
|
35
|
+
Requires-Dist: xgboost; extra == "xgboost"
|
|
36
|
+
Provides-Extra: pytabkit
|
|
37
|
+
Requires-Dist: pytabkit; extra == "pytabkit"
|
|
38
|
+
Provides-Extra: tabicl
|
|
39
|
+
Requires-Dist: tabicl>=2.0.3; extra == "tabicl"
|
|
40
|
+
Provides-Extra: tabdpt
|
|
41
|
+
Requires-Dist: tabdpt>=1.1.9; extra == "tabdpt"
|
|
42
|
+
Provides-Extra: tabpfn
|
|
43
|
+
Requires-Dist: tabpfn>=9.2.0; extra == "tabpfn"
|
|
44
|
+
Provides-Extra: tabstar
|
|
45
|
+
Requires-Dist: tabstar>=1.1.15; extra == "tabstar"
|
|
46
|
+
Provides-Extra: autogluon-mm
|
|
47
|
+
Requires-Dist: autogluon.multimodal; extra == "autogluon-mm"
|
|
48
|
+
Provides-Extra: dev
|
|
49
|
+
Requires-Dist: matplotlib; extra == "dev"
|
|
50
|
+
Requires-Dist: pytest; extra == "dev"
|
|
51
|
+
Requires-Dist: streamlit; extra == "dev"
|
|
52
|
+
Dynamic: license-file
|
|
53
|
+
|
|
54
|
+
<h1 align="center">
|
|
55
|
+
<picture>
|
|
56
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alanarazi7/MulTaBench/master/assets/logo-dark.svg">
|
|
57
|
+
<img src="https://raw.githubusercontent.com/alanarazi7/MulTaBench/master/assets/logo.svg" alt="MulTaBench: Benchmarking Multimodal Tabular Learning" width="640">
|
|
58
|
+
</picture>
|
|
59
|
+
</h1>
|
|
60
|
+
|
|
61
|
+
> **Work in progress:** MulTaBench is moving towards a living leaderboard, and `master` will change
|
|
62
|
+
> gradually on the way there. To reproduce the paper, use the tag
|
|
63
|
+
> [`paper_version`](https://github.com/alanarazi7/MulTaBench/tree/paper_version) or the
|
|
64
|
+
> [`official-paper-version`](https://github.com/alanarazi7/MulTaBench/tree/official-paper-version) branch.
|
|
65
|
+
> The paper's results are kept in `multabench/leaderboard/legacy_results/` while every model is rerun under the
|
|
66
|
+
> leaderboard protocol.
|
|
67
|
+
|
|
68
|
+
A benchmark for multimodal tabular learning: tables whose columns include images or free text, not
|
|
69
|
+
just numbers and categories. It is 20 image-tabular and 20 text-tabular curated datasets, with 40
|
|
70
|
+
further datasets released alongside them, 80 in total. The benchmark evaluates tabular learners
|
|
71
|
+
under a target-aware setting, where the image and text encoders are fine-tuned on the task rather
|
|
72
|
+
than used frozen.
|
|
73
|
+
|
|
74
|
+
**Paper**: [MulTaBench: Benchmarking Multimodal Tabular Learning with Text and Image](https://arxiv.org/abs/2605.10616) (NeurIPS 2026 Spotlight)
|
|
75
|
+
**Datasets**: [huggingface.co/multabench](https://huggingface.co/multabench)
|
|
76
|
+
|
|
77
|
+
## Leaderboard
|
|
78
|
+
|
|
79
|
+
Default hyperparameters, `--size 10k`, 6 folds, frozen `e5-small` text and `dino-small` image embeddings
|
|
80
|
+
(ConTextTab reads raw text itself). Elo is fit on one battle per dataset and model pair, with RandomForest
|
|
81
|
+
at 1000 and 95% intervals from bootstrapping the datasets; a model with no result on a dataset has no
|
|
82
|
+
battles there. TabPFN-v2 and v2.5 take at most 10 classes, which leaves out 3 of the 80 datasets.
|
|
83
|
+
|
|
84
|
+
| # | Model | Elo, 80 datasets | 95% CI | Text, 40 | Images, 40 | Datasets |
|
|
85
|
+
|---|---|---|---|---|---|---|
|
|
86
|
+
| 1 | TabPFN-3.5 | 2221 | +230 / −176 | 2080 | 2458 | 80 |
|
|
87
|
+
| 2 | Kumo-Tabular-Small | 2139 | +190 / −151 | 2094 | 2268 | 80 |
|
|
88
|
+
| 3 | TabPFN-3.5-Fast | 2010 | +195 / −149 | 1906 | 2199 | 80 |
|
|
89
|
+
| 4 | TabICLv2 | 1745 | +137 / −115 | 1637 | 1941 | 80 |
|
|
90
|
+
| 5 | TabPFN-3 | 1666 | +145 / −117 | 1554 | 1866 | 80 |
|
|
91
|
+
| 6 | TabPFN-v2p5 | 1661 | +136 / −114 | 1647 | 1743 | 77 |
|
|
92
|
+
| 7 | TabPFN-v2 | 1407 | +117 / −97 | 1322 | 1559 | 77 |
|
|
93
|
+
| 8 | TabM | 1366 | +98 / −89 | 1348 | 1432 | 80 |
|
|
94
|
+
| 9 | ConTextTab | 1353 | +113 / −102 | 1279 | 1490 | 80 |
|
|
95
|
+
| 10 | CatBoost | 1298 | +66 / −55 | 1307 | 1321 | 80 |
|
|
96
|
+
| 11 | TabDPT | 1251 | +112 / −93 | 1116 | 1455 | 80 |
|
|
97
|
+
| 12 | RealMLP | 1082 | +81 / −71 | 1103 | 1059 | 80 |
|
|
98
|
+
| 13 | LightGBM | 1058 | +63 / −61 | 1093 | 1013 | 80 |
|
|
99
|
+
| 14 | RandomForest | 1000 | anchor | 1000 | 1000 | 80 |
|
|
100
|
+
| 15 | XGBoost | 988 | +67 / −65 | 1024 | 936 | 80 |
|
|
101
|
+
|
|
102
|
+
Each model's runs are in `multabench/leaderboard/models/<model>.csv`, one row per (dataset, fold), failed
|
|
103
|
+
runs included. `python -m multabench.leaderboard.elo` prints the table, with win rate, average rank and
|
|
104
|
+
normalized error; `--modalities text` or `--modalities image,image-text` restricts it to those datasets.
|
|
105
|
+
|
|
106
|
+
Leaderboard runs pass `--official`, which also requires a clean checkout of a commit on `master`, so the `git`
|
|
107
|
+
column of every row names code anyone can check out. The image runs of the first 11 models are the one exception: they
|
|
108
|
+
ran at `7f28e55`, on the branch of #148, which was squash-merged unchanged.
|
|
109
|
+
|
|
110
|
+
## Getting started
|
|
111
|
+
|
|
112
|
+
To use the datasets from another project, install the core, which needs neither torch nor any model package:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pip install multabench
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from multabench import Size, load_embeddings, load_split
|
|
120
|
+
split = load_split("MUL_IMAGE_PETFINDER", fold=0, size=Size.TEN_K) # split.x_train, split.y_train, split.x_test, ...
|
|
121
|
+
embeddings = load_embeddings("MUL_TEXT_WINE_REVIEW", fold=0, size=Size.TEN_K, encoder="e5-small")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
To run the benchmark:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
source init.sh && source .venv/bin/activate
|
|
128
|
+
cp .env.example .env # Hugging Face token
|
|
129
|
+
|
|
130
|
+
python benchmark.py --model light --dataset_name MUL_IMAGE_PETFINDER --fold 0 --text_encoder e5-small --image_encoder dino-small
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`pyproject.toml` lists the core dependencies, a `bench` extra for `benchmark.py` and one extra per model package, so
|
|
134
|
+
`pip install -e ".[bench,catboost]"` installs what one model needs; each wrapper imports its package only when it runs.
|
|
135
|
+
ConTextTab and Kumo are pinned to a git commit, which PyPI doesn't allow in extras, so `requirements-git.txt` lists them.
|
|
136
|
+
`init.sh` sets up the shared `.venv` from `requirements.txt`, which locks the core, every extra and those two.
|
|
137
|
+
|
|
138
|
+
`benchmark.py` is the single entry point: it evaluates one model on one dataset fold and writes the
|
|
139
|
+
result to `runs/<model>_<dataset>_<text_encoder>_<image_encoder>_<size>_<fold>.json` (`--output_dir` to change), with
|
|
140
|
+
`none` for an encoder the dataset has no column for. A run
|
|
141
|
+
that fails still writes its JSON, with `"status": "error"` and the error, and exits non-zero; a successful one has
|
|
142
|
+
`"status": "ok"`. A run whose JSON already exists is skipped, so a stopped sweep can be relaunched as is; `--overwrite`
|
|
143
|
+
reruns it. `--help` lists the available models. `--text_encoder` picks how text columns are embedded: `tfidf`, frozen
|
|
144
|
+
`e5-small` (the default) or `e5-large`, or `e5-small-tar`, E5 fine-tuned on the task with LoRA (target-aware).
|
|
145
|
+
`--image_encoder` does the same for image columns: frozen `dino-small` (the default) or `dino-large`, or
|
|
146
|
+
`dino-small-tar`. Each applies only to datasets with that kind of column.
|
|
147
|
+
|
|
148
|
+
`sweep.py` runs `benchmark.py` over models × datasets × encoders × folds. It writes the runs that
|
|
149
|
+
don't have a result yet to a jobs file (`sweep_jobs_<timestamp>.txt`, or `--jobs_file`), one command per line,
|
|
150
|
+
which any scheduler can run. `--modalities text`, `image` or `image-text` keeps only those datasets. Text datasets
|
|
151
|
+
run once per text encoder and image datasets once per image encoder; image datasets that also have text columns
|
|
152
|
+
always embed their text with frozen `e5-small`. The default encoders include the fine-tuned `e5-small-tar` and
|
|
153
|
+
`dino-small-tar`, so pass `--text_encoders`/`--image_encoders` to run fewer. Each run is its own process, and a
|
|
154
|
+
failed run doesn't stop the others:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
python sweep.py --models light,cat --modalities text --text_encoders e5-small # only write the jobs file
|
|
158
|
+
python sweep.py --models light,cat --modalities text --text_encoders e5-small --launcher local # run them here, one after another
|
|
159
|
+
python sweep.py --models light,cat --modalities text --text_encoders e5-small --launcher slurm \
|
|
160
|
+
--sbatch="--partition=cpu --cpus-per-task=8 --mem=32G" --submit
|
|
161
|
+
python sweep.py --models tabm,real --modalities text --text_encoders e5-small --launcher slurm \
|
|
162
|
+
--sbatch="--partition=gpu --gres=gpu:1 --cpus-per-task=8 --mem=32G" --submit
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
With `--launcher slurm`, each line becomes a task of a Slurm array; `--sbatch="..."` is passed to `sbatch`
|
|
166
|
+
as is (the `=` is needed, since the value starts with `--`), and without `--submit` the `sbatch` commands are only
|
|
167
|
+
printed. With a launcher, the sweep first downloads the runs' datasets and cached embeddings, since runs that
|
|
168
|
+
download the same files at once into a shared Hugging Face cache (e.g. on NFS) can fail. All the runs of a sweep get
|
|
169
|
+
the same `--sbatch`, so sweep the runs that need a GPU apart from those that don't (see below), as in the last two commands. Relaunching a sweep runs only what is still missing.
|
|
170
|
+
|
|
171
|
+
Every leaderboard run must use the same hardware, so their times compare: one NVIDIA H100 (80 GB), an Intel Xeon
|
|
172
|
+
Platinum 8481C with 8 CPU cores and 32 GB of RAM (`GPU_RUN_HARDWARE` in `multabench/utils/hardware.py`). Runs that need
|
|
173
|
+
no GPU, a CPU model (CatBoost, LightGBM, XGBoost, RandomForest) whose text is embedded with TF-IDF or read from the
|
|
174
|
+
cache, use an Intel Xeon Platinum 8581C with 8 CPU cores and 32 GB of RAM instead (`CPU_RUN_HARDWARE`). With
|
|
175
|
+
`--official` (`sweep.py --official` passes it to every run), `benchmark.py` checks the GPU, the processor, the CPUs it
|
|
176
|
+
may use and its memory limit, and raises before running if any differs; leaderboard runs always pass it. On Slurm,
|
|
177
|
+
request `--gres=gpu:1 --cpus-per-task=8 --mem=32G` on an H100 node, or `--cpus-per-task=8 --mem=32G` on an 8-core Xeon
|
|
178
|
+
8581C node for CPU runs. The sweeps' Slurm jobs run under `srun`, so a run sees only the CPUs its job asked for, even
|
|
179
|
+
when Slurm hands it a whole machine.
|
|
180
|
+
The cached embeddings are computed on one NVIDIA A100, 6 CPU cores and 32 GB of RAM for now
|
|
181
|
+
(`EMBEDDING_HARDWARE`); the hardware they ran on is recorded with them.
|
|
182
|
+
|
|
183
|
+
`collect_results.py` gathers the run JSONs into one CSV per model in `multabench/leaderboard/models/`, one row per
|
|
184
|
+
run, failed runs included: model, dataset, encoders, size and fold, the status, the metric with `test_score` (higher
|
|
185
|
+
is better) and `test_error`, train and inference time per 1K rows apart from embedding, embedding time per 1K rows,
|
|
186
|
+
and the hardware. A run already in a model's file is replaced by its rerun; the other rows are kept.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
python collect_results.py --output_dir runs
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
To use a benchmark split in your own code, `load_split` downloads the dataset and returns the same
|
|
193
|
+
train and test rows that `benchmark.py` evaluates on:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from multabench import Size, load_split
|
|
197
|
+
|
|
198
|
+
split = load_split("MUL_IMAGE_PETFINDER", fold=0, size=Size.TEN_K)
|
|
199
|
+
split.x_train, split.y_train, split.x_test, split.y_test, split.task_type, split.image_folder, split.image_column
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Cached embeddings
|
|
203
|
+
|
|
204
|
+
The frozen text and image embeddings are cached on Hugging Face, one public dataset repo per
|
|
205
|
+
encoder: `multabench/embeddings-e5-small` and `multabench/embeddings-dino-small`. A frozen encoder
|
|
206
|
+
doesn't depend on the split, so each distinct input of a dataset is embedded once, under
|
|
207
|
+
`<dataset>/`, in `embeddings.npz`, which maps every input, exactly as the encoder got it (e5's
|
|
208
|
+
`"passage: <column>: <value>"` after a run's missing-text fill, or the image file name), to its raw
|
|
209
|
+
encoder output (PCA is fitted per run, on the training rows), and `meta.json`, with the dataset's
|
|
210
|
+
rows and columns, the encoding time per feature, the hardware it ran on, the hardware it should run
|
|
211
|
+
on (`EMBEDDING_HARDWARE` in `multabench/utils/hardware.py`) and how they differ. A run looks its own
|
|
212
|
+
inputs up, so an embedding is never paired with another row's text, and an input missing from the
|
|
213
|
+
cache, e.g. after the dataset changed, raises. The cache is optional: `benchmark.py` reads it when
|
|
214
|
+
the dataset has embeddings for the encoder, and otherwise embeds live, so new encoders and datasets
|
|
215
|
+
need no cache to run. Only the frozen `e5-small` text embeddings are read so far. `load_embeddings`
|
|
216
|
+
returns them aligned with `load_split`'s rows:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from multabench import Size, load_embeddings
|
|
220
|
+
|
|
221
|
+
embeddings = load_embeddings("MUL_TEXT_WINE_REVIEW", fold=0, size=Size.TEN_K, encoder="e5-small")
|
|
222
|
+
embeddings.train["description"], embeddings.test["description"], embeddings.meta
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`python -m multabench.embeddings.embed` embeds one dataset, and `python -m
|
|
226
|
+
multabench.embeddings.embed_sweep` runs it over encoders × datasets, with the same `--launcher` and
|
|
227
|
+
`--sbatch` options as `sweep.py`, and uploads the result with `--upload`:
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
python -m multabench.embeddings.embed_sweep --launcher slurm --sbatch="--partition=a100 --gres=gpu:1 --cpus-per-task=6 --mem=32G" --submit
|
|
231
|
+
python -m multabench.embeddings.embed_sweep --upload
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Datasets
|
|
235
|
+
|
|
236
|
+
Every dataset is a public dataset repo in the [`multabench`](https://huggingface.co/multabench)
|
|
237
|
+
Hugging Face organization, downloaded on demand. Repos are named
|
|
238
|
+
`<core|extended>-<img|text>-<reg|cls>-<name>`; `core` and `extended` are two slices of the
|
|
239
|
+
benchmark. Each holds `data.parquet` with explicit column types (datetime,
|
|
240
|
+
categorical, numeric, string), `metadata.json` with the target, and, for image datasets, the images
|
|
241
|
+
in `images-*.zip`.
|
|
242
|
+
|
|
243
|
+
The paper used the curated CSV copies on the [`chico89`](https://www.kaggle.com/chico89/datasets)
|
|
244
|
+
Kaggle account, which the Hugging Face datasets were converted from. The two may differ: the
|
|
245
|
+
Hugging Face datasets store dates, categories and numbers as typed columns where the CSVs hold
|
|
246
|
+
strings, and a few non-numeric values in numeric columns were mapped (DVM car's `"1 mile"` to 1,
|
|
247
|
+
Anime Planet's `"Unknown"` to missing). Scores on the two versions are not directly comparable.
|
|
248
|
+
|
|
249
|
+
| What you are looking for | Where it is |
|
|
250
|
+
|--------------------------|-------------|
|
|
251
|
+
| A dataset's Hugging Face repo | the `MulTaBenchDatasetID` values in `multabench/datasets/all_datasets.py` |
|
|
252
|
+
| Where a dataset originally comes from | `MULTABENCH_SOURCES` in `multabench/datasets/all_datasets.py` |
|
|
253
|
+
| How a dataset was curated | the recipes in `multabench/benchmark/datasets/` at the [`paper_version`](https://github.com/alanarazi7/MulTaBench/tree/paper_version) tag |
|
|
254
|
+
| Size, task and feature counts per dataset | `multabench/leaderboard/results/datasets_summary{,_extra}.csv` at the [`paper_version`](https://github.com/alanarazi7/MulTaBench/tree/paper_version) tag |
|
|
255
|
+
|
|
256
|
+
The 40 datasets released alongside the benchmark were admitted on a weaker criterion and carry no
|
|
257
|
+
tier name of their own, so their scores must not be pooled with the 40 MulTaBench datasets.
|
|
258
|
+
|
|
259
|
+
## Protocol and TabArena
|
|
260
|
+
|
|
261
|
+
MulTaBench follows the [TabArena](https://tabarena.ai) protocol where doing so is cheap or affects
|
|
262
|
+
correctness, and deliberately keeps its own choices where full parity would multiply the compute.
|
|
263
|
+
MulTaBench is not an official TabArena leaderboard.
|
|
264
|
+
|
|
265
|
+
| | TabArena | MulTaBench | Status |
|
|
266
|
+
|---|---|---|---|
|
|
267
|
+
| Metrics | ROC AUC (binary), log loss (multiclass), RMSE (regression), via AutoGluon's scorers | Same | ✅ |
|
|
268
|
+
| Leaderboard | Elo via `bencheval`, RandomForest (default) anchored at 1000, missing results imputed with RandomForest and flagged, bootstrapped confidence intervals | Elo as in the paper, RandomForest anchored at 1000, bootstrapped confidence intervals; missing results are left out, neither imputed nor dropping the model, so a model is ranked on the datasets it ran on, which its Datasets column shows | |
|
|
269
|
+
| Splits | Fixed and part of the task definition | Same: a seeded function of the data | ✅ |
|
|
270
|
+
| Group and time structure | Audited per dataset; group-aware or forward-in-time splits where needed | Not audited: every dataset is split IID. Some datasets may have group or time structure, in which case IID splits can overestimate performance | |
|
|
271
|
+
| Runtime | Train and inference time per 1K rows | Same, with the time spent embedding text and images reported separately | ✅ |
|
|
272
|
+
| Hardware | Recorded per run; 8 CPU cores and 32 GB of RAM, with one NVIDIA L40S (48 GB) for GPU models | Recorded per run; every run uses 8 CPU cores and 32 GB of RAM, GPU models with one NVIDIA H100 (80 GB), checked by `benchmark.py --official` | |
|
|
273
|
+
| Outer splits | 3 folds, repeated 1–10 times depending on dataset size | 3 folds, repeated twice for every size | |
|
|
274
|
+
| Dataset size | Full size | `--size 10k` (the default, and the one the leaderboard reports) caps train and test at 10K and 5K rows; `--size full` keeps every row | |
|
|
275
|
+
| Inner validation | 8-fold bagging for every model | No bagging: one fit per split, and models that need validation hold out 10% of the training rows (at most 1,000). This favors models that ensemble internally (TabPFN, TabICL, TabDPT, TabM, RandomForest) over single GBDTs and RealMLP | |
|
|
276
|
+
| Hyperparameters | Default, tuned, and tuned + ensembled | Default only | |
|
|
277
|
+
| Model implementations | TabArena's model registry | MulTaBench's own wrappers, which add image and text embeddings | |
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/alanarazi7/MulTaBench/master/assets/logo-dark.svg">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/alanarazi7/MulTaBench/master/assets/logo.svg" alt="MulTaBench: Benchmarking Multimodal Tabular Learning" width="640">
|
|
5
|
+
</picture>
|
|
6
|
+
</h1>
|
|
7
|
+
|
|
8
|
+
> **Work in progress:** MulTaBench is moving towards a living leaderboard, and `master` will change
|
|
9
|
+
> gradually on the way there. To reproduce the paper, use the tag
|
|
10
|
+
> [`paper_version`](https://github.com/alanarazi7/MulTaBench/tree/paper_version) or the
|
|
11
|
+
> [`official-paper-version`](https://github.com/alanarazi7/MulTaBench/tree/official-paper-version) branch.
|
|
12
|
+
> The paper's results are kept in `multabench/leaderboard/legacy_results/` while every model is rerun under the
|
|
13
|
+
> leaderboard protocol.
|
|
14
|
+
|
|
15
|
+
A benchmark for multimodal tabular learning: tables whose columns include images or free text, not
|
|
16
|
+
just numbers and categories. It is 20 image-tabular and 20 text-tabular curated datasets, with 40
|
|
17
|
+
further datasets released alongside them, 80 in total. The benchmark evaluates tabular learners
|
|
18
|
+
under a target-aware setting, where the image and text encoders are fine-tuned on the task rather
|
|
19
|
+
than used frozen.
|
|
20
|
+
|
|
21
|
+
**Paper**: [MulTaBench: Benchmarking Multimodal Tabular Learning with Text and Image](https://arxiv.org/abs/2605.10616) (NeurIPS 2026 Spotlight)
|
|
22
|
+
**Datasets**: [huggingface.co/multabench](https://huggingface.co/multabench)
|
|
23
|
+
|
|
24
|
+
## Leaderboard
|
|
25
|
+
|
|
26
|
+
Default hyperparameters, `--size 10k`, 6 folds, frozen `e5-small` text and `dino-small` image embeddings
|
|
27
|
+
(ConTextTab reads raw text itself). Elo is fit on one battle per dataset and model pair, with RandomForest
|
|
28
|
+
at 1000 and 95% intervals from bootstrapping the datasets; a model with no result on a dataset has no
|
|
29
|
+
battles there. TabPFN-v2 and v2.5 take at most 10 classes, which leaves out 3 of the 80 datasets.
|
|
30
|
+
|
|
31
|
+
| # | Model | Elo, 80 datasets | 95% CI | Text, 40 | Images, 40 | Datasets |
|
|
32
|
+
|---|---|---|---|---|---|---|
|
|
33
|
+
| 1 | TabPFN-3.5 | 2221 | +230 / −176 | 2080 | 2458 | 80 |
|
|
34
|
+
| 2 | Kumo-Tabular-Small | 2139 | +190 / −151 | 2094 | 2268 | 80 |
|
|
35
|
+
| 3 | TabPFN-3.5-Fast | 2010 | +195 / −149 | 1906 | 2199 | 80 |
|
|
36
|
+
| 4 | TabICLv2 | 1745 | +137 / −115 | 1637 | 1941 | 80 |
|
|
37
|
+
| 5 | TabPFN-3 | 1666 | +145 / −117 | 1554 | 1866 | 80 |
|
|
38
|
+
| 6 | TabPFN-v2p5 | 1661 | +136 / −114 | 1647 | 1743 | 77 |
|
|
39
|
+
| 7 | TabPFN-v2 | 1407 | +117 / −97 | 1322 | 1559 | 77 |
|
|
40
|
+
| 8 | TabM | 1366 | +98 / −89 | 1348 | 1432 | 80 |
|
|
41
|
+
| 9 | ConTextTab | 1353 | +113 / −102 | 1279 | 1490 | 80 |
|
|
42
|
+
| 10 | CatBoost | 1298 | +66 / −55 | 1307 | 1321 | 80 |
|
|
43
|
+
| 11 | TabDPT | 1251 | +112 / −93 | 1116 | 1455 | 80 |
|
|
44
|
+
| 12 | RealMLP | 1082 | +81 / −71 | 1103 | 1059 | 80 |
|
|
45
|
+
| 13 | LightGBM | 1058 | +63 / −61 | 1093 | 1013 | 80 |
|
|
46
|
+
| 14 | RandomForest | 1000 | anchor | 1000 | 1000 | 80 |
|
|
47
|
+
| 15 | XGBoost | 988 | +67 / −65 | 1024 | 936 | 80 |
|
|
48
|
+
|
|
49
|
+
Each model's runs are in `multabench/leaderboard/models/<model>.csv`, one row per (dataset, fold), failed
|
|
50
|
+
runs included. `python -m multabench.leaderboard.elo` prints the table, with win rate, average rank and
|
|
51
|
+
normalized error; `--modalities text` or `--modalities image,image-text` restricts it to those datasets.
|
|
52
|
+
|
|
53
|
+
Leaderboard runs pass `--official`, which also requires a clean checkout of a commit on `master`, so the `git`
|
|
54
|
+
column of every row names code anyone can check out. The image runs of the first 11 models are the one exception: they
|
|
55
|
+
ran at `7f28e55`, on the branch of #148, which was squash-merged unchanged.
|
|
56
|
+
|
|
57
|
+
## Getting started
|
|
58
|
+
|
|
59
|
+
To use the datasets from another project, install the core, which needs neither torch nor any model package:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install multabench
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from multabench import Size, load_embeddings, load_split
|
|
67
|
+
split = load_split("MUL_IMAGE_PETFINDER", fold=0, size=Size.TEN_K) # split.x_train, split.y_train, split.x_test, ...
|
|
68
|
+
embeddings = load_embeddings("MUL_TEXT_WINE_REVIEW", fold=0, size=Size.TEN_K, encoder="e5-small")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
To run the benchmark:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
source init.sh && source .venv/bin/activate
|
|
75
|
+
cp .env.example .env # Hugging Face token
|
|
76
|
+
|
|
77
|
+
python benchmark.py --model light --dataset_name MUL_IMAGE_PETFINDER --fold 0 --text_encoder e5-small --image_encoder dino-small
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`pyproject.toml` lists the core dependencies, a `bench` extra for `benchmark.py` and one extra per model package, so
|
|
81
|
+
`pip install -e ".[bench,catboost]"` installs what one model needs; each wrapper imports its package only when it runs.
|
|
82
|
+
ConTextTab and Kumo are pinned to a git commit, which PyPI doesn't allow in extras, so `requirements-git.txt` lists them.
|
|
83
|
+
`init.sh` sets up the shared `.venv` from `requirements.txt`, which locks the core, every extra and those two.
|
|
84
|
+
|
|
85
|
+
`benchmark.py` is the single entry point: it evaluates one model on one dataset fold and writes the
|
|
86
|
+
result to `runs/<model>_<dataset>_<text_encoder>_<image_encoder>_<size>_<fold>.json` (`--output_dir` to change), with
|
|
87
|
+
`none` for an encoder the dataset has no column for. A run
|
|
88
|
+
that fails still writes its JSON, with `"status": "error"` and the error, and exits non-zero; a successful one has
|
|
89
|
+
`"status": "ok"`. A run whose JSON already exists is skipped, so a stopped sweep can be relaunched as is; `--overwrite`
|
|
90
|
+
reruns it. `--help` lists the available models. `--text_encoder` picks how text columns are embedded: `tfidf`, frozen
|
|
91
|
+
`e5-small` (the default) or `e5-large`, or `e5-small-tar`, E5 fine-tuned on the task with LoRA (target-aware).
|
|
92
|
+
`--image_encoder` does the same for image columns: frozen `dino-small` (the default) or `dino-large`, or
|
|
93
|
+
`dino-small-tar`. Each applies only to datasets with that kind of column.
|
|
94
|
+
|
|
95
|
+
`sweep.py` runs `benchmark.py` over models × datasets × encoders × folds. It writes the runs that
|
|
96
|
+
don't have a result yet to a jobs file (`sweep_jobs_<timestamp>.txt`, or `--jobs_file`), one command per line,
|
|
97
|
+
which any scheduler can run. `--modalities text`, `image` or `image-text` keeps only those datasets. Text datasets
|
|
98
|
+
run once per text encoder and image datasets once per image encoder; image datasets that also have text columns
|
|
99
|
+
always embed their text with frozen `e5-small`. The default encoders include the fine-tuned `e5-small-tar` and
|
|
100
|
+
`dino-small-tar`, so pass `--text_encoders`/`--image_encoders` to run fewer. Each run is its own process, and a
|
|
101
|
+
failed run doesn't stop the others:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python sweep.py --models light,cat --modalities text --text_encoders e5-small # only write the jobs file
|
|
105
|
+
python sweep.py --models light,cat --modalities text --text_encoders e5-small --launcher local # run them here, one after another
|
|
106
|
+
python sweep.py --models light,cat --modalities text --text_encoders e5-small --launcher slurm \
|
|
107
|
+
--sbatch="--partition=cpu --cpus-per-task=8 --mem=32G" --submit
|
|
108
|
+
python sweep.py --models tabm,real --modalities text --text_encoders e5-small --launcher slurm \
|
|
109
|
+
--sbatch="--partition=gpu --gres=gpu:1 --cpus-per-task=8 --mem=32G" --submit
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
With `--launcher slurm`, each line becomes a task of a Slurm array; `--sbatch="..."` is passed to `sbatch`
|
|
113
|
+
as is (the `=` is needed, since the value starts with `--`), and without `--submit` the `sbatch` commands are only
|
|
114
|
+
printed. With a launcher, the sweep first downloads the runs' datasets and cached embeddings, since runs that
|
|
115
|
+
download the same files at once into a shared Hugging Face cache (e.g. on NFS) can fail. All the runs of a sweep get
|
|
116
|
+
the same `--sbatch`, so sweep the runs that need a GPU apart from those that don't (see below), as in the last two commands. Relaunching a sweep runs only what is still missing.
|
|
117
|
+
|
|
118
|
+
Every leaderboard run must use the same hardware, so their times compare: one NVIDIA H100 (80 GB), an Intel Xeon
|
|
119
|
+
Platinum 8481C with 8 CPU cores and 32 GB of RAM (`GPU_RUN_HARDWARE` in `multabench/utils/hardware.py`). Runs that need
|
|
120
|
+
no GPU, a CPU model (CatBoost, LightGBM, XGBoost, RandomForest) whose text is embedded with TF-IDF or read from the
|
|
121
|
+
cache, use an Intel Xeon Platinum 8581C with 8 CPU cores and 32 GB of RAM instead (`CPU_RUN_HARDWARE`). With
|
|
122
|
+
`--official` (`sweep.py --official` passes it to every run), `benchmark.py` checks the GPU, the processor, the CPUs it
|
|
123
|
+
may use and its memory limit, and raises before running if any differs; leaderboard runs always pass it. On Slurm,
|
|
124
|
+
request `--gres=gpu:1 --cpus-per-task=8 --mem=32G` on an H100 node, or `--cpus-per-task=8 --mem=32G` on an 8-core Xeon
|
|
125
|
+
8581C node for CPU runs. The sweeps' Slurm jobs run under `srun`, so a run sees only the CPUs its job asked for, even
|
|
126
|
+
when Slurm hands it a whole machine.
|
|
127
|
+
The cached embeddings are computed on one NVIDIA A100, 6 CPU cores and 32 GB of RAM for now
|
|
128
|
+
(`EMBEDDING_HARDWARE`); the hardware they ran on is recorded with them.
|
|
129
|
+
|
|
130
|
+
`collect_results.py` gathers the run JSONs into one CSV per model in `multabench/leaderboard/models/`, one row per
|
|
131
|
+
run, failed runs included: model, dataset, encoders, size and fold, the status, the metric with `test_score` (higher
|
|
132
|
+
is better) and `test_error`, train and inference time per 1K rows apart from embedding, embedding time per 1K rows,
|
|
133
|
+
and the hardware. A run already in a model's file is replaced by its rerun; the other rows are kept.
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
python collect_results.py --output_dir runs
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
To use a benchmark split in your own code, `load_split` downloads the dataset and returns the same
|
|
140
|
+
train and test rows that `benchmark.py` evaluates on:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from multabench import Size, load_split
|
|
144
|
+
|
|
145
|
+
split = load_split("MUL_IMAGE_PETFINDER", fold=0, size=Size.TEN_K)
|
|
146
|
+
split.x_train, split.y_train, split.x_test, split.y_test, split.task_type, split.image_folder, split.image_column
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Cached embeddings
|
|
150
|
+
|
|
151
|
+
The frozen text and image embeddings are cached on Hugging Face, one public dataset repo per
|
|
152
|
+
encoder: `multabench/embeddings-e5-small` and `multabench/embeddings-dino-small`. A frozen encoder
|
|
153
|
+
doesn't depend on the split, so each distinct input of a dataset is embedded once, under
|
|
154
|
+
`<dataset>/`, in `embeddings.npz`, which maps every input, exactly as the encoder got it (e5's
|
|
155
|
+
`"passage: <column>: <value>"` after a run's missing-text fill, or the image file name), to its raw
|
|
156
|
+
encoder output (PCA is fitted per run, on the training rows), and `meta.json`, with the dataset's
|
|
157
|
+
rows and columns, the encoding time per feature, the hardware it ran on, the hardware it should run
|
|
158
|
+
on (`EMBEDDING_HARDWARE` in `multabench/utils/hardware.py`) and how they differ. A run looks its own
|
|
159
|
+
inputs up, so an embedding is never paired with another row's text, and an input missing from the
|
|
160
|
+
cache, e.g. after the dataset changed, raises. The cache is optional: `benchmark.py` reads it when
|
|
161
|
+
the dataset has embeddings for the encoder, and otherwise embeds live, so new encoders and datasets
|
|
162
|
+
need no cache to run. Only the frozen `e5-small` text embeddings are read so far. `load_embeddings`
|
|
163
|
+
returns them aligned with `load_split`'s rows:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from multabench import Size, load_embeddings
|
|
167
|
+
|
|
168
|
+
embeddings = load_embeddings("MUL_TEXT_WINE_REVIEW", fold=0, size=Size.TEN_K, encoder="e5-small")
|
|
169
|
+
embeddings.train["description"], embeddings.test["description"], embeddings.meta
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`python -m multabench.embeddings.embed` embeds one dataset, and `python -m
|
|
173
|
+
multabench.embeddings.embed_sweep` runs it over encoders × datasets, with the same `--launcher` and
|
|
174
|
+
`--sbatch` options as `sweep.py`, and uploads the result with `--upload`:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
python -m multabench.embeddings.embed_sweep --launcher slurm --sbatch="--partition=a100 --gres=gpu:1 --cpus-per-task=6 --mem=32G" --submit
|
|
178
|
+
python -m multabench.embeddings.embed_sweep --upload
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Datasets
|
|
182
|
+
|
|
183
|
+
Every dataset is a public dataset repo in the [`multabench`](https://huggingface.co/multabench)
|
|
184
|
+
Hugging Face organization, downloaded on demand. Repos are named
|
|
185
|
+
`<core|extended>-<img|text>-<reg|cls>-<name>`; `core` and `extended` are two slices of the
|
|
186
|
+
benchmark. Each holds `data.parquet` with explicit column types (datetime,
|
|
187
|
+
categorical, numeric, string), `metadata.json` with the target, and, for image datasets, the images
|
|
188
|
+
in `images-*.zip`.
|
|
189
|
+
|
|
190
|
+
The paper used the curated CSV copies on the [`chico89`](https://www.kaggle.com/chico89/datasets)
|
|
191
|
+
Kaggle account, which the Hugging Face datasets were converted from. The two may differ: the
|
|
192
|
+
Hugging Face datasets store dates, categories and numbers as typed columns where the CSVs hold
|
|
193
|
+
strings, and a few non-numeric values in numeric columns were mapped (DVM car's `"1 mile"` to 1,
|
|
194
|
+
Anime Planet's `"Unknown"` to missing). Scores on the two versions are not directly comparable.
|
|
195
|
+
|
|
196
|
+
| What you are looking for | Where it is |
|
|
197
|
+
|--------------------------|-------------|
|
|
198
|
+
| A dataset's Hugging Face repo | the `MulTaBenchDatasetID` values in `multabench/datasets/all_datasets.py` |
|
|
199
|
+
| Where a dataset originally comes from | `MULTABENCH_SOURCES` in `multabench/datasets/all_datasets.py` |
|
|
200
|
+
| How a dataset was curated | the recipes in `multabench/benchmark/datasets/` at the [`paper_version`](https://github.com/alanarazi7/MulTaBench/tree/paper_version) tag |
|
|
201
|
+
| Size, task and feature counts per dataset | `multabench/leaderboard/results/datasets_summary{,_extra}.csv` at the [`paper_version`](https://github.com/alanarazi7/MulTaBench/tree/paper_version) tag |
|
|
202
|
+
|
|
203
|
+
The 40 datasets released alongside the benchmark were admitted on a weaker criterion and carry no
|
|
204
|
+
tier name of their own, so their scores must not be pooled with the 40 MulTaBench datasets.
|
|
205
|
+
|
|
206
|
+
## Protocol and TabArena
|
|
207
|
+
|
|
208
|
+
MulTaBench follows the [TabArena](https://tabarena.ai) protocol where doing so is cheap or affects
|
|
209
|
+
correctness, and deliberately keeps its own choices where full parity would multiply the compute.
|
|
210
|
+
MulTaBench is not an official TabArena leaderboard.
|
|
211
|
+
|
|
212
|
+
| | TabArena | MulTaBench | Status |
|
|
213
|
+
|---|---|---|---|
|
|
214
|
+
| Metrics | ROC AUC (binary), log loss (multiclass), RMSE (regression), via AutoGluon's scorers | Same | ✅ |
|
|
215
|
+
| Leaderboard | Elo via `bencheval`, RandomForest (default) anchored at 1000, missing results imputed with RandomForest and flagged, bootstrapped confidence intervals | Elo as in the paper, RandomForest anchored at 1000, bootstrapped confidence intervals; missing results are left out, neither imputed nor dropping the model, so a model is ranked on the datasets it ran on, which its Datasets column shows | |
|
|
216
|
+
| Splits | Fixed and part of the task definition | Same: a seeded function of the data | ✅ |
|
|
217
|
+
| Group and time structure | Audited per dataset; group-aware or forward-in-time splits where needed | Not audited: every dataset is split IID. Some datasets may have group or time structure, in which case IID splits can overestimate performance | |
|
|
218
|
+
| Runtime | Train and inference time per 1K rows | Same, with the time spent embedding text and images reported separately | ✅ |
|
|
219
|
+
| Hardware | Recorded per run; 8 CPU cores and 32 GB of RAM, with one NVIDIA L40S (48 GB) for GPU models | Recorded per run; every run uses 8 CPU cores and 32 GB of RAM, GPU models with one NVIDIA H100 (80 GB), checked by `benchmark.py --official` | |
|
|
220
|
+
| Outer splits | 3 folds, repeated 1–10 times depending on dataset size | 3 folds, repeated twice for every size | |
|
|
221
|
+
| Dataset size | Full size | `--size 10k` (the default, and the one the leaderboard reports) caps train and test at 10K and 5K rows; `--size full` keeps every row | |
|
|
222
|
+
| Inner validation | 8-fold bagging for every model | No bagging: one fit per split, and models that need validation hold out 10% of the training rows (at most 1,000). This favors models that ensemble internally (TabPFN, TabICL, TabDPT, TabM, RandomForest) over single GBDTs and RealMLP | |
|
|
223
|
+
| Hyperparameters | Default, tuned, and tuned + ensembled | Default only | |
|
|
224
|
+
| Model implementations | TabArena's model registry | MulTaBench's own wrappers, which add image and text embeddings | |
|
|
File without changes
|