bstabdiff 0.1.0__tar.gz → 0.2.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.
@@ -0,0 +1,1480 @@
1
+ Metadata-Version: 2.4
2
+ Name: bstabdiff
3
+ Version: 0.2.1
4
+ Summary: BSTabDiff: Block-Subunit Diffusion Priors for High-Dimensional Tabular Data Generation (NeurIPS 2026)
5
+ Home-page: https://www.zadidhabib.com/bstabdiff.html
6
+ Author: Al Zadid Sultan Bin Habib, Md Younus Ahamed, Prashnna Kumar Gyawali, Gianfranco Doretto, Donald A. Adjeroh
7
+ License: MIT
8
+ Project-URL: Homepage, https://www.zadidhabib.com/bstabdiff.html
9
+ Project-URL: Repository, https://github.com/zadid6pretam/BSTabDiff
10
+ Project-URL: Issues, https://github.com/zadid6pretam/BSTabDiff/issues
11
+ Project-URL: Paper, https://openreview.net/forum?id=ciPCMXX1aj
12
+ Keywords: tabular-data,high-dimensional-data,HDLSS,synthetic-data,generative-modeling,diffusion,normalizing-flow,feature-ordering,GO-BS,missing-data,mixed-type-data,neurips
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Intended Audience :: Science/Research
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: numpy<3.0,>=2.2.6
23
+ Requires-Dist: pandas<3.0,>=2.2.3
24
+ Requires-Dist: scikit-learn<2.0,>=1.7.2
25
+ Requires-Dist: scipy<2.0,>=1.15.3
26
+ Requires-Dist: matplotlib<4.0,>=3.10.8
27
+ Requires-Dist: tqdm<5.0,>=4.67.3
28
+ Requires-Dist: torch<3.0,>=2.5.1
29
+ Requires-Dist: kmeans_gpu==0.0.5
30
+ Requires-Dist: optuna<5.0,>=4.8.0
31
+ Requires-Dist: psutil<8.0,>=7.0.0
32
+ Requires-Dist: codecarbon<4.0,>=3.2.6
33
+ Dynamic: license-file
34
+
35
+ # BSTabDiff: Block-Subunit Diffusion Priors for High-Dimensional Tabular Data Generation (NeurIPS 2026)
36
+
37
+
38
+ <!-- ========================================================= -->
39
+ <!-- BSTabDiff: Framework and Implementation -->
40
+ <!-- ========================================================= -->
41
+
42
+ ![Python](https://img.shields.io/badge/Python-3.8%2B-blue)
43
+ ![License](https://img.shields.io/badge/License-MIT-green)
44
+ ![Task](https://img.shields.io/badge/Task-Tabular%20Data%20Synthesis-orange)
45
+ ![Model](https://img.shields.io/badge/Model-BSTabDiff-blueviolet)
46
+ ![Architecture](https://img.shields.io/badge/Architecture-Block--Subunit%20Generator-informational)
47
+ ![Latent Prior](https://img.shields.io/badge/Latent%20Prior-Diffusion%20%2F%20Flow-purple)
48
+ ![Regime](https://img.shields.io/badge/Regime-HDLSS%20%26%20Beyond-critical)
49
+ ![Evaluation](https://img.shields.io/badge/Evaluation-18%20Datasets%20%7C%2010%20Baselines-teal)
50
+
51
+ <!-- ========================================================= -->
52
+ <!-- NeurIPS 2026: Main Conference -->
53
+ <!-- ========================================================= -->
54
+
55
+ [![NeurIPS](https://img.shields.io/badge/NeurIPS-2026%20Main%20Conference-8A2BE2)](https://neurips.cc/virtual/2026/loc/atlanta/poster/151140)
56
+ [![Status](https://img.shields.io/badge/Status-Accepted-brightgreen)](https://neurips.cc/virtual/2026/loc/atlanta/poster/151140)
57
+ [![NeurIPS Poster](https://img.shields.io/badge/NeurIPS-Poster%20Page-6A5ACD)](https://neurips.cc/virtual/2026/loc/atlanta/poster/151140)
58
+ [![OpenReview](https://img.shields.io/badge/OpenReview-Main%20Paper-B31B1B)](https://openreview.net/forum?id=ciPCMXX1aj)
59
+
60
+ <!-- ========================================================= -->
61
+ <!-- ICLR 2026: Earlier DeLTa Workshop Version -->
62
+ <!-- ========================================================= -->
63
+
64
+ [![Earlier Version](https://img.shields.io/badge/Earlier%20Version-ICLR%202026%20DeLTa-blue)](https://iclr.cc/virtual/2026/10017199)
65
+ [![ICLR Workshop Paper](https://img.shields.io/badge/ICLR-Workshop%20Paper-4169E1)](https://iclr.cc/virtual/2026/10017199)
66
+ [![Workshop OpenReview](https://img.shields.io/badge/OpenReview-Workshop%20Version-red)](https://openreview.net/forum?id=RKNDy0KhGT)
67
+ [![DeLTa Workshop](https://img.shields.io/badge/DeLTa%202026-Workshop%20Website-1f6feb)](https://delta-workshop.github.io/DeLTa2026/)
68
+
69
+
70
+
71
+ <p align="center">
72
+ <img src="./BSTabDiffArchi.png" alt="BSTabDiff Architecture" width="900">
73
+ </p>
74
+
75
+ BSTabDiff is a block-subunit generative framework designed primarily for **High-Dimensional Low-Sample Size (HDLSS) tabular data synthesis**. Instead of learning dense dependence directly in the original `m`-dimensional feature space, BSTabDiff organizes features into **`M` dependency-aware latent blocks, where `M << m`**, optionally using **Graph-guided Ordering with Block Segmentation (GO-BS)** or its feature-clustered variant **GO-BS-FC** to construct coherent feature neighborhoods. Global dependence is learned in the compact block-latent space using diffusion or normalizing-flow priors, while the full-dimensional table is reconstructed through block-conditioned copula decoding, feature-wise marginal transformations, and explicit missingness modeling. This design makes BSTabDiff particularly suitable for omics-style and other HDLSS problems, where direct high-dimensional density estimation is often ill-conditioned. Across HDLSS benchmarks, BSTabDiff produces high-fidelity synthetic data with strong downstream utility, often approaching real-data performance, while additional experiments indicate that the block-subunit design can also generalize beyond the strict HDLSS regime.
76
+
77
+ ## Citation
78
+
79
+ Al Zadid Sultan Bin Habib, Md Younus Ahamed, Prashnna Kumar Gyawali, Gianfranco Doretto, and Donald A. Adjeroh.
80
+ **“BSTabDiff: Block-Subunit Diffusion Priors for High-Dimensional Tabular Data Generation.”**
81
+ In *Advances in Neural Information Processing Systems (NeurIPS)*, 2026.
82
+
83
+
84
+ BibTeX:
85
+ ```bibtex
86
+ @inproceedings{habib2026bstabdiff,
87
+ title = {BSTabDiff: Block-Subunit Diffusion Priors for High-Dimensional Tabular Data Generation},
88
+ author = {Habib, Al Zadid Sultan Bin and Ahamed, Md Younus and Gyawali, Prashnna Kumar and Doretto, Gianfranco and Adjeroh, Donald A.},
89
+ booktitle = {Advances in Neural Information Processing Systems (NeurIPS)},
90
+ year = {2026}
91
+ }
92
+ ```
93
+
94
+ ### Publication Venues
95
+
96
+ **NeurIPS 2026 - Main Conference (Poster)**
97
+ [NeurIPS Poster Page](https://neurips.cc/virtual/2026/loc/atlanta/poster/151140) | [OpenReview](https://openreview.net/forum?id=ciPCMXX1aj)
98
+
99
+ **Earlier Version - ICLR 2026 DeLTa Workshop**
100
+ [ICLR Workshop Paper](https://iclr.cc/virtual/2026/10017199) | [OpenReview](https://openreview.net/forum?id=RKNDy0KhGT) | [Workshop Website](https://delta-workshop.github.io/DeLTa2026/)
101
+
102
+
103
+ ## Files and Repository Structure
104
+
105
+ The repository is organized into three main components:
106
+
107
+ ```text
108
+ BSTabDiff/
109
+ ├── bstabdiff/
110
+ ├── Experimental Notebooks/
111
+ ├── Smoke Tests/
112
+ ├── BSTabDiffArchi.png
113
+ ├── README.md
114
+ ├── requirements.txt
115
+ ├── pyproject.toml
116
+ ├── setup.cfg
117
+ └── LICENSE
118
+ ```
119
+
120
+ ### 1. Model Package: `bstabdiff/`
121
+
122
+ The `bstabdiff/` folder contains the core BSTabDiff implementation and the public Python API.
123
+
124
+ - **`__init__.py`**
125
+ Package initializer exposing the main BSTabDiff classes, utilities, and high-level API.
126
+
127
+ - **`bstabdiff_gobs.py`**
128
+ Extended BSTabDiff implementation with **Graph-guided Ordering with Block Segmentation (GO-BS)** and **GO-BS-FC**, including dependency-aware feature ordering, block construction, boundary refinement, and integration with the BSTabDiff generator.
129
+
130
+ - **`estimator.py`**
131
+ High-level estimator interface providing the user-facing `BSTabDiff` API for model initialization, `fit(...)`, synthetic sampling, and configuration of the underlying generator and ordering variants.
132
+
133
+ For most users, the recommended interface is:
134
+
135
+ ```python
136
+ from bstabdiff import BSTabDiff
137
+ ```
138
+
139
+ ### 2. `Experimental Notebooks/`
140
+
141
+ The **`Experimental Notebooks/`** folder contains cleaned versions of most of the notebooks used during the experiments reported in the previous ICLR Workshop paper, NeurIPS paper and NeurIPS rebuttal. These notebooks are provided primarily for **verification of the reported results and displayed outputs**, including dataset-specific experiments, downstream evaluation, ablation studies, fidelity analysis, GO-BS/GO-BS-FC experiments, resource analysis, and additional rebuttal experiments. Because the codebase evolved during development, some notebooks reference earlier or intermediate model/package names that were used at different stages of the project. They should therefore be viewed mainly as experimental records for reproducing and inspecting the reported results, while the current `bstabdiff/` package should be used for new experiments and applications. As stated during the NeurIPS rebuttal, we provide the **current model package together with experimental notebooks containing displayed outputs/results** to make the reported findings easier to inspect and verify. We have cleaned and organized most of the notebooks before public release. A small number of auxiliary development notebooks are not yet included because they require additional cleanup and documentation before they can be released in a reasonably readable form. See the folder here: **[Experimental Notebooks](https://github.com/zadid6pretam/BSTabDiff/tree/main/Experimental%20Notebooks)**
142
+
143
+ ### 3. `Smoke Tests/`
144
+
145
+ The **`Smoke Tests/`** folder contains lightweight notebooks intended to verify installation, API behavior, training, sampling, tuning, and different supported data configurations without requiring the full experimental pipeline.
146
+
147
+ - **`BSTabDiff_SmokeTest.ipynb`**
148
+ Basic end-to-end smoke test for fitting BSTabDiff and generating synthetic samples.
149
+
150
+ - **`BSTabDiff_Tuning_Smoke_Test.ipynb`**
151
+ Lightweight demonstration of hyperparameter tuning and subsequent BSTabDiff training using selected parameters.
152
+
153
+ - **`BSTabDiff_MixedData_SmokeTest.ipynb`**
154
+ Demonstrates BSTabDiff on mixed-type data containing both continuous and categorical variables, including explicit missingness.
155
+
156
+ - **`BSTabDiff_PIP_Install_Check.ipynb`**
157
+ Google Colab smoke test demonstrating installation of BSTabDiff through `pip`, importing the installed package, fitting the model, and generating synthetic data in a clean notebook environment.
158
+
159
+ - **`BSTabDiff_CarbonTracking.ipynb`**
160
+ Demonstrates BSTabDiff energy and CO₂e tracking with CodeCarbon on the Colon dataset, together with a separate 5×5 cross-validated TSTR evaluation using Logistic Regression.
161
+
162
+ See the folder here: **[Smoke Tests](https://github.com/zadid6pretam/BSTabDiff/tree/main/Smoke%20Tests)**
163
+
164
+ ### Architecture Figure
165
+
166
+ - **`BSTabDiffArchi.png`**
167
+ High-level architecture figure illustrating the BSTabDiff framework, including block-latent generation, diffusion/flow priors, feature decoding, missingness modeling, and reconstruction in the observed feature space.
168
+
169
+ ### Other Top-Level Files
170
+
171
+ - **`requirements.txt`** - Main Python dependencies used by the repository.
172
+ - **`pyproject.toml`** - Build-system and package metadata.
173
+ - **`setup.cfg`** - Package configuration and installation metadata.
174
+ - **`LICENSE`** - Repository license.
175
+ - **`README.md`** - Project overview, installation instructions, usage examples, reproducibility information, and links to experimental resources.
176
+
177
+ ### Tested Environment
178
+
179
+ - numpy==2.2.6
180
+ - pandas==2.3.3
181
+ - scikit-learn==1.7.2
182
+ - scipy==1.15.3
183
+ - matplotlib==3.10.8
184
+ - tqdm==4.67.3
185
+ - torch==2.5.1
186
+ - catboost==1.2.8
187
+ - tabpfn==6.3.1
188
+ - kmeans_gpu==0.0.5
189
+ - optuna==4.8.0
190
+ - psutil==7.0.0
191
+ - codecarbon==3.2.6
192
+ - jupyter==1.1.1
193
+ - ipykernel==7.2.0
194
+
195
+ ## Installation
196
+
197
+ You can install **BSTabDiff** in several ways depending on your workflow.
198
+
199
+ ---
200
+
201
+ ### Option 1: Clone the Repository (Recommended for Development)
202
+
203
+ ```bash
204
+ git clone https://github.com/zadid6pretam/BSTabDiff.git
205
+ cd BSTabDiff
206
+ pip install -r requirements.txt
207
+ pip install -e .
208
+ ```
209
+
210
+ ### Option 2: Install Directly from GitHub (No Cloning Needed)
211
+
212
+ ```bash
213
+ pip install "git+https://github.com/zadid6pretam/BSTabDiff.git"
214
+ ```
215
+
216
+ ### Option 3: Use a Virtual Environment
217
+
218
+ ```bash
219
+ python -m venv bstabdiff-env
220
+ source bstabdiff-env/bin/activate # On Windows: bstabdiff-env\Scripts\activate
221
+
222
+ git clone https://github.com/zadid6pretam/BSTabDiff.git
223
+ cd BSTabDiff
224
+ pip install -r requirements.txt
225
+ pip install -e .
226
+ ```
227
+
228
+ ### Option 4: Local Install Without Editable Mode
229
+
230
+ ```bash
231
+ git clone https://github.com/zadid6pretam/BSTabDiff.git
232
+ cd BSTabDiff
233
+ pip install -r requirements.txt
234
+ pip install .
235
+ ```
236
+
237
+ ### Option 5: Install from PyPI (Planned)
238
+
239
+ ```bash
240
+ pip install bstabdiff
241
+ ```
242
+
243
+ ## Practical Configuration Rules
244
+
245
+ BSTabDiff has two main dataset-dependent structural choices:
246
+
247
+ 1. the number of latent blocks, `M`;
248
+ 2. whether to use **GO-BS** or **GO-BS-FC** for structure-aware feature ordering and block construction.
249
+
250
+ The following rules provide practical defaults for new datasets. They are intended as initialization guidelines rather than rigid prescriptions; users performing dataset-specific hyperparameter optimization may tune these choices directly.
251
+
252
+ ### 1. Selecting the Number of Blocks `M`
253
+
254
+ Let
255
+
256
+ - `n` = number of samples,
257
+ - `m` = number of features,
258
+ - `ρ = m / n` = feature-to-sample ratio,
259
+ - `κ = n × m` = sample-feature product (total number of data cells).
260
+
261
+ For **Optuna-tuned configurations**, `M` is treated as a dataset-specific hyperparameter and can be optimized directly.
262
+
263
+ For the **default/no-tuning configuration**, BSTabDiff initializes `M` using both the HDLSS severity `ρ` and dataset scale `κ`:
264
+
265
+ ```math
266
+ M=
267
+ \begin{cases}
268
+ m, & m \le 64\\
269
+ 64, & m>64 \text{ and } \rho<10\\
270
+ \min\left(\widetilde{M}(\kappa),m\right), & \rho\ge10
271
+ \end{cases}
272
+ ```
273
+
274
+ where
275
+
276
+ ```math
277
+ \widetilde M(\kappa)=
278
+ \begin{cases}
279
+ 32, & \kappa < 2.5\times10^{5}\\
280
+ 64, & 2.5\times10^{5}\leq\kappa<10^{6}\\
281
+ 128, & 10^{6}\leq\kappa<3\times10^{6}\\
282
+ 192, & \kappa\geq3\times10^{6}
283
+ \end{cases}
284
+ ```
285
+
286
+ In practical terms:
287
+
288
+ | Condition | Default `M` |
289
+ |---|---:|
290
+ | `m <= 64` | `m` |
291
+ | `m > 64` and `ρ < 10` | `64` |
292
+ | `ρ >= 10` and `κ < 2.5e5` | `32` |
293
+ | `ρ >= 10` and `2.5e5 <= κ < 1e6` | `64` |
294
+ | `ρ >= 10` and `1e6 <= κ < 3e6` | `128` |
295
+ | `ρ >= 10` and `κ >= 3e6` | `192` |
296
+
297
+ The final value is always capped by the number of available features:
298
+
299
+ ```python
300
+ M = min(M, m)
301
+ ```
302
+
303
+ A compact implementation is:
304
+
305
+ ```python
306
+ def select_default_M(n, m):
307
+ rho = m / n
308
+ kappa = n * m
309
+
310
+ if m <= 64:
311
+ return m
312
+
313
+ if rho < 10:
314
+ return 64
315
+
316
+ if kappa < 2.5e5:
317
+ M = 32
318
+ elif kappa < 1e6:
319
+ M = 64
320
+ elif kappa < 3e6:
321
+ M = 128
322
+ else:
323
+ M = 192
324
+
325
+ return min(M, m)
326
+ ```
327
+
328
+ This rule is designed as a practical initialization. If block coherence or held-out validation utility stabilizes at a smaller value, `M` can be reduced accordingly. For tuned experiments, the user may instead include `M` directly in the Optuna search space.
329
+
330
+ ### 2. Selecting GO-BS vs. GO-BS-FC
331
+
332
+ BSTabDiff provides two structure-aware ordering variants:
333
+
334
+ - **GO-BS**: the standard/sample clustered Graph-guided Ordering with Block Segmentation variant;
335
+ - **GO-BS-FC**: the feature clustered variant, designed to reduce ordering cost on sufficiently large high-dimensional datasets.
336
+
337
+ The choice is based on both:
338
+
339
+ - the dataset **shape**, characterized by `ρ = m / n`;
340
+ - the dataset **computational scale**, characterized by `κ = n × m`.
341
+
342
+ Let $\mathcal{R}_{\rho}(D)$ denote the dataset regime assigned by the BSTabDiff/DynaTab/GOTabPFN-style taxonomy, e.g. `HDLSS`, `HDHSS`, `LDHSS`, `LDLSS`, or `MixedRegime`. For each regime $r$, define a regime-specific scale threshold:
343
+
344
+ ```math
345
+ T_r=\mathrm{median}\left\{\kappa_i:\mathcal{R}_{\rho}(D_i)=r\right\}
346
+ ```
347
+
348
+ A new dataset is then classified as:
349
+
350
+ ```math
351
+ \mathrm{Scale}(D)=\mathrm{Micro}\quad\mathrm{if}\quad \kappa<T_{\mathcal{R}_{\rho}(D)}
352
+ ```
353
+
354
+ ```math
355
+ \mathrm{Scale}(D)=\mathrm{Macro}\quad\mathrm{if}\quad \kappa\ge T_{\mathcal{R}_{\rho}(D)}
356
+ ```
357
+
358
+
359
+ The default ordering variant is:
360
+
361
+ ```math
362
+ \mathcal{V}(D)=
363
+ \begin{cases}
364
+ \mathrm{GO\!-\!BS\!-\!FC}, & \mathrm{Scale}(D)=\mathrm{Macro}\ \land\ \mathcal{R}_{\rho}(D)\in\{\mathrm{HDLSS},\mathrm{HDHSS}\}\\
365
+ \mathrm{GO\!-\!BS}, & \mathrm{otherwise}
366
+ \end{cases}
367
+ ```
368
+ In practical terms:
369
+
370
+ | Dataset condition | Recommended variant |
371
+ |---|---|
372
+ | Macro-HDLSS | **GO-BS-FC** |
373
+ | Macro-HDHSS | **GO-BS-FC** |
374
+ | Micro-HDLSS | **GO-BS** |
375
+ | Micro-HDHSS | **GO-BS** |
376
+ | LDHSS | **GO-BS** |
377
+ | LDLSS | **GO-BS** |
378
+ | MixedRegime | **GO-BS** |
379
+
380
+ Therefore:
381
+
382
+ > **Use GO-BS-FC for Macro-HDLSS or Macro-HDHSS datasets; use GO-BS otherwise.**
383
+
384
+ The motivation is computational. For very large high-dimensional datasets, full ordering can become the primary bottleneck. GO-BS-FC first partitions the feature space into smaller feature clusters and performs ordering within these smaller subproblems, making it preferable for large `m` or large `κ`.
385
+
386
+ GO-BS remains the default for Micro-scale and otherwise computationally manageable datasets, where the standard sample clustered dependency construction can be performed affordably.
387
+
388
+ #### Near-boundary tie-breaker
389
+
390
+ If a dataset lies close to its regime-specific threshold $T_r$, bootstrap cluster stability can be used as a secondary diagnostic:
391
+
392
+ - more stable **sample clusters** → prefer **GO-BS**;
393
+ - more stable **feature clusters** → prefer **GO-BS-FC**.
394
+
395
+ This is a secondary tie breaker; the Micro/Macro rule above remains the primary default selection rule.
396
+
397
+ The examples below are complete runnable scripts. Each example uses the Colon dataset for illustration; for another dataset, update the dataset path, target column, and any dataset-specific pre-selected parameters.
398
+
399
+ ## Example Usage
400
+
401
+ The seven examples below are complete, standalone scripts. Each can be copied directly and adapted to a new dataset by changing the dataset path, target column, and where applicable dataset-specific pre-selected parameters.
402
+
403
+ ### Example 1 - Default, No Tuning, No Permutation
404
+ ```python
405
+ import os, random, warnings
406
+ os.environ["PYTHONWARNINGS"] = "ignore"
407
+ warnings.simplefilter("ignore")
408
+
409
+ import numpy as np
410
+ import pandas as pd
411
+ import torch
412
+ from bstabdiff import BSTabDiff
413
+
414
+ SEED = 42
415
+ CSV_PATH = "coloncancer_encoded.csv" #change your dataset file here
416
+ LABEL_COL = "label" #change your target column here
417
+ N_SYN = 200
418
+ DEVICE = "cuda:0" if torch.cuda.is_available() else "cpu"
419
+
420
+ def set_seed(seed):
421
+ random.seed(seed)
422
+ np.random.seed(seed)
423
+ torch.manual_seed(seed)
424
+ if torch.cuda.is_available():
425
+ torch.cuda.manual_seed_all(seed)
426
+
427
+ def select_default_M(n, m):
428
+ rho = m / n
429
+ kappa = n * m
430
+ if m <= 64:
431
+ return m
432
+ if rho < 10:
433
+ return 64
434
+ if kappa < 2.5e5:
435
+ M = 32
436
+ elif kappa < 1e6:
437
+ M = 64
438
+ elif kappa < 3e6:
439
+ M = 128
440
+ else:
441
+ M = 192
442
+ return min(M, m)
443
+
444
+ def load_data():
445
+ df = pd.read_csv(CSV_PATH)
446
+ if LABEL_COL not in df.columns:
447
+ raise ValueError(f"Label column '{LABEL_COL}' not found.")
448
+ y_raw = df[LABEL_COL].values
449
+ X_df = df.drop(columns=[LABEL_COL]).select_dtypes(include=[np.number])
450
+ X = np.nan_to_num(X_df.to_numpy(dtype=np.float32), nan=0.0, posinf=0.0, neginf=0.0)
451
+ _, y = np.unique(y_raw, return_inverse=True)
452
+ return X, y.astype(int)
453
+
454
+ set_seed(SEED)
455
+ X, y = load_data()
456
+ n, m = X.shape
457
+ M = select_default_M(n, m)
458
+
459
+ print(f"Dataset: n={n}, m={m}, rho={m/n:.4f}, kappa={n*m}, M={M}")
460
+ print(f"Device: {DEVICE}")
461
+
462
+ model = BSTabDiff(
463
+ feature_specs=None,
464
+ M=M,
465
+ blocks=None,
466
+ permute_features=False,
467
+ prior_type="diffusion",
468
+ device=DEVICE,
469
+ random_state=SEED,
470
+ prior_epochs=10000,
471
+ prior_batch=128,
472
+ prior_lr=1e-3,
473
+ verbose_every=0,
474
+ save_dir=None,
475
+ save_name="bstabdiff_default",
476
+ save_best=True,
477
+ use_ema=True,
478
+ ema_decay=0.999,
479
+ use_gobs=False,
480
+ use_gobs_fc=False
481
+ )
482
+
483
+ model.fit(X, y)
484
+ X_syn, R_syn, y_syn = model.sample(n_samples=N_SYN, return_mask=True)
485
+
486
+ print("X_syn:", X_syn.shape)
487
+ print("R_syn:", R_syn.shape)
488
+ print("y_syn:", y_syn.shape if y_syn is not None else None)
489
+ ```
490
+
491
+ ### Example 2 - No Permutation with Optuna Tuning
492
+ ```python
493
+ import os, gc, random, warnings
494
+ os.environ["PYTHONWARNINGS"] = "ignore"
495
+ warnings.filterwarnings("ignore")
496
+
497
+ import numpy as np
498
+ import pandas as pd
499
+ import torch
500
+ import optuna
501
+ from sklearn.model_selection import StratifiedShuffleSplit, RepeatedStratifiedKFold
502
+ from sklearn.pipeline import Pipeline
503
+ from sklearn.impute import SimpleImputer
504
+ from sklearn.preprocessing import StandardScaler
505
+ from sklearn.linear_model import LogisticRegression
506
+ from sklearn.metrics import accuracy_score, roc_auc_score
507
+ from sklearn.utils import check_random_state
508
+ from optuna.samplers import TPESampler
509
+ from bstabdiff import BSTabDiff
510
+
511
+ GLOBAL_SEED = 42
512
+ CV_SEED = 0
513
+ N_TRIALS = 5 # Increase for full tuning
514
+ INNER_VAL_SIZE = 0.2
515
+ CSV_PATH = "coloncancer_encoded.csv" #change your dataset file here
516
+ LABEL_COL = "label" #change your target column here
517
+ DEVICE = "cuda:0" if torch.cuda.is_available() else "cpu"
518
+
519
+ def set_seed(seed):
520
+ random.seed(seed)
521
+ np.random.seed(seed)
522
+ torch.manual_seed(seed)
523
+ if torch.cuda.is_available():
524
+ torch.cuda.manual_seed_all(seed)
525
+
526
+ def cleanup():
527
+ gc.collect()
528
+ if torch.cuda.is_available():
529
+ torch.cuda.empty_cache()
530
+
531
+ def load_data():
532
+ df = pd.read_csv(CSV_PATH)
533
+ if LABEL_COL not in df.columns:
534
+ raise ValueError(f"Label column '{LABEL_COL}' not found.")
535
+ y_raw = df[LABEL_COL].values
536
+ X_df = df.drop(columns=[LABEL_COL]).select_dtypes(include=[np.number])
537
+ X = np.nan_to_num(X_df.to_numpy(dtype=np.float32), nan=0.0, posinf=0.0, neginf=0.0)
538
+ _, y = np.unique(y_raw, return_inverse=True)
539
+ return X, y.astype(int)
540
+
541
+ def make_lr():
542
+ return Pipeline([
543
+ ("imp", SimpleImputer(strategy="median")),
544
+ ("sc", StandardScaler()),
545
+ ("clf", LogisticRegression(max_iter=10000, class_weight="balanced", solver="lbfgs"))
546
+ ])
547
+
548
+ def safe_auc_or_acc(clf, X, y):
549
+ try:
550
+ return float(roc_auc_score(y, clf.predict_proba(X)[:, 1]))
551
+ except Exception:
552
+ return float(accuracy_score(y, clf.predict(X)))
553
+
554
+ set_seed(GLOBAL_SEED)
555
+ X, y = load_data()
556
+ print(f"Dataset: n={X.shape[0]}, m={X.shape[1]}")
557
+ print(f"Device: {DEVICE}")
558
+
559
+ splitter = StratifiedShuffleSplit(n_splits=1, test_size=INNER_VAL_SIZE, random_state=GLOBAL_SEED)
560
+ (itr, iva), = splitter.split(X, y)
561
+ X_inner_tr, y_inner_tr = X[itr], y[itr]
562
+ X_inner_va, y_inner_va = X[iva], y[iva]
563
+
564
+ def objective(trial):
565
+ params = {
566
+ "M": trial.suggest_categorical("M", [8, 12, 16, 24, 32, 48, 64]),
567
+ "prior_type": trial.suggest_categorical("prior_type", ["diffusion", "flow"]),
568
+ "prior_epochs": trial.suggest_categorical("prior_epochs", [300, 500, 800, 1200, 1600]),
569
+ "prior_batch": trial.suggest_categorical("prior_batch", [16, 32, 64, 128]),
570
+ "prior_lr": trial.suggest_float("prior_lr", 5e-5, 5e-3, log=True),
571
+ "ema_decay": trial.suggest_float("ema_decay", 0.99, 0.9999, log=True),
572
+ "n_syn": trial.suggest_categorical("n_syn", [64, 100, 128, 200, 256])
573
+ }
574
+ trial_seed = GLOBAL_SEED + 1000 + trial.number
575
+ try:
576
+ model = BSTabDiff(
577
+ feature_specs=None,
578
+ M=params["M"],
579
+ blocks=None,
580
+ permute_features=False,
581
+ prior_type=params["prior_type"],
582
+ device=DEVICE,
583
+ random_state=trial_seed,
584
+ prior_epochs=params["prior_epochs"],
585
+ prior_batch=params["prior_batch"],
586
+ prior_lr=params["prior_lr"],
587
+ verbose_every=0,
588
+ save_dir=None,
589
+ save_name=f"bstabdiff_noperm_trial_{trial.number}",
590
+ save_best=True,
591
+ use_ema=True,
592
+ ema_decay=params["ema_decay"],
593
+ use_gobs=False,
594
+ use_gobs_fc=False
595
+ )
596
+ model.fit(X_inner_tr, y_inner_tr)
597
+ X_syn, _, y_syn = model.sample(n_samples=params["n_syn"], return_mask=True)
598
+ X_syn = np.nan_to_num(np.asarray(X_syn, dtype=np.float32), nan=0.0, posinf=0.0, neginf=0.0)
599
+ y_syn = np.asarray(y_syn).reshape(-1).astype(int)
600
+ if np.unique(y_syn).size < 2:
601
+ return 0.0
602
+ clf = make_lr()
603
+ clf.fit(X_syn, y_syn)
604
+ score = safe_auc_or_acc(clf, X_inner_va, y_inner_va)
605
+ trial.set_user_attr("raw_validation_score", score)
606
+ return score - 1e-4 * params["M"]
607
+ except Exception as e:
608
+ trial.set_user_attr("error", repr(e))
609
+ return 0.0
610
+ finally:
611
+ cleanup()
612
+
613
+ study = optuna.create_study(
614
+ direction="maximize",
615
+ sampler=TPESampler(seed=GLOBAL_SEED, multivariate=True, warn_independent_sampling=False)
616
+ )
617
+ study.optimize(objective, n_trials=N_TRIALS, show_progress_bar=True)
618
+
619
+ best = dict(study.best_params)
620
+ print("\nBest objective:", study.best_value)
621
+ print("Best parameters:", best)
622
+
623
+ def final_eval(best):
624
+ rng = check_random_state(CV_SEED)
625
+ rkf = RepeatedStratifiedKFold(n_splits=5, n_repeats=5, random_state=CV_SEED)
626
+ tstr_acc, tstr_auc, trtr_acc, trtr_auc = [], [], [], []
627
+ for fold, (tr, te) in enumerate(rkf.split(X, y), 1):
628
+ Xtr, ytr = X[tr], y[tr]
629
+ Xte, yte = X[te], y[te]
630
+ fold_seed = int(rng.randint(0, 2**31 - 1))
631
+ model = BSTabDiff(
632
+ feature_specs=None,
633
+ M=best["M"],
634
+ blocks=None,
635
+ permute_features=False,
636
+ prior_type=best["prior_type"],
637
+ device=DEVICE,
638
+ random_state=fold_seed,
639
+ prior_epochs=best["prior_epochs"],
640
+ prior_batch=best["prior_batch"],
641
+ prior_lr=best["prior_lr"],
642
+ verbose_every=0,
643
+ save_dir=None,
644
+ save_name="bstabdiff_noperm_best",
645
+ save_best=True,
646
+ use_ema=True,
647
+ ema_decay=best["ema_decay"],
648
+ use_gobs=False,
649
+ use_gobs_fc=False
650
+ )
651
+ model.fit(Xtr, ytr)
652
+ X_syn, _, y_syn = model.sample(n_samples=best["n_syn"], return_mask=True)
653
+ X_syn = np.nan_to_num(np.asarray(X_syn, dtype=np.float32), nan=0.0, posinf=0.0, neginf=0.0)
654
+ y_syn = np.asarray(y_syn).reshape(-1).astype(int)
655
+
656
+ clf = make_lr()
657
+ clf.fit(X_syn, y_syn)
658
+ p = clf.predict_proba(Xte)[:, 1]
659
+ tstr_acc.append(accuracy_score(yte, (p >= 0.5).astype(int)))
660
+ tstr_auc.append(roc_auc_score(yte, p))
661
+
662
+ clf_real = make_lr()
663
+ clf_real.fit(Xtr, ytr)
664
+ p_real = clf_real.predict_proba(Xte)[:, 1]
665
+ trtr_acc.append(accuracy_score(yte, (p_real >= 0.5).astype(int)))
666
+ trtr_auc.append(roc_auc_score(yte, p_real))
667
+
668
+ print(f"Fold {fold:02d}/25 | TSTR ACC={tstr_acc[-1]:.4f} AUC={tstr_auc[-1]:.4f}")
669
+ del model, clf, clf_real
670
+ cleanup()
671
+
672
+ print("\nTUNED NO PERMUTATION - STRICT 5x5")
673
+ print(f"TSTR ACC = {np.mean(tstr_acc):.4f} ± {np.std(tstr_acc):.4f}")
674
+ print(f"TSTR AUC = {np.mean(tstr_auc):.4f} ± {np.std(tstr_auc):.4f}")
675
+ print(f"TRTR ACC = {np.mean(trtr_acc):.4f} ± {np.std(trtr_acc):.4f}")
676
+ print(f"TRTR AUC = {np.mean(trtr_auc):.4f} ± {np.std(trtr_auc):.4f}")
677
+
678
+ final_eval(best)
679
+ study.trials_dataframe().to_csv("bstabdiff_optuna_noperm_trials.csv", index=False)
680
+ ```
681
+
682
+ ### Example 3 - GO-BS with Optuna Tuning
683
+ ```python
684
+ import os, gc, random, warnings
685
+ os.environ["PYTHONWARNINGS"] = "ignore"
686
+ warnings.filterwarnings("ignore")
687
+
688
+ import numpy as np
689
+ import pandas as pd
690
+ import torch
691
+ import optuna
692
+ from sklearn.model_selection import StratifiedShuffleSplit, RepeatedStratifiedKFold
693
+ from sklearn.pipeline import Pipeline
694
+ from sklearn.impute import SimpleImputer
695
+ from sklearn.preprocessing import StandardScaler
696
+ from sklearn.linear_model import LogisticRegression
697
+ from sklearn.metrics import accuracy_score, roc_auc_score
698
+ from sklearn.utils import check_random_state
699
+ from optuna.samplers import TPESampler
700
+ from bstabdiff import BSTabDiff
701
+
702
+ GLOBAL_SEED = 42
703
+ CV_SEED = 0
704
+ N_TRIALS = 5 # Increase for full tuning
705
+ INNER_VAL_SIZE = 0.2
706
+ CSV_PATH = "coloncancer_encoded.csv" #change your dataset file here
707
+ LABEL_COL = "label" #change your target column here
708
+ DEVICE = "cuda:0" if torch.cuda.is_available() else "cpu"
709
+
710
+ FIXED = {
711
+ "M": 8,
712
+ "prior_type": "flow",
713
+ "prior_epochs": 300,
714
+ "prior_batch": 64,
715
+ "prior_lr": 3.2931e-3,
716
+ "ema_decay": 0.9926,
717
+ "n_syn": 64
718
+ }
719
+
720
+ def set_seed(seed):
721
+ random.seed(seed)
722
+ np.random.seed(seed)
723
+ torch.manual_seed(seed)
724
+ if torch.cuda.is_available():
725
+ torch.cuda.manual_seed_all(seed)
726
+
727
+ def cleanup():
728
+ gc.collect()
729
+ if torch.cuda.is_available():
730
+ torch.cuda.empty_cache()
731
+
732
+ def load_data():
733
+ df = pd.read_csv(CSV_PATH)
734
+ if LABEL_COL not in df.columns:
735
+ raise ValueError(f"Label column '{LABEL_COL}' not found.")
736
+ y_raw = df[LABEL_COL].values
737
+ X_df = df.drop(columns=[LABEL_COL]).select_dtypes(include=[np.number])
738
+ X = np.nan_to_num(X_df.to_numpy(dtype=np.float32), nan=0.0, posinf=0.0, neginf=0.0)
739
+ _, y = np.unique(y_raw, return_inverse=True)
740
+ return X, y.astype(int)
741
+
742
+ def make_lr():
743
+ return Pipeline([
744
+ ("imp", SimpleImputer(strategy="median")),
745
+ ("sc", StandardScaler()),
746
+ ("clf", LogisticRegression(max_iter=10000, class_weight="balanced", solver="lbfgs"))
747
+ ])
748
+
749
+ def safe_auc_or_acc(clf, X, y):
750
+ try:
751
+ return float(roc_auc_score(y, clf.predict_proba(X)[:, 1]))
752
+ except Exception:
753
+ return float(accuracy_score(y, clf.predict(X)))
754
+
755
+ set_seed(GLOBAL_SEED)
756
+ X, y = load_data()
757
+ print(f"Dataset: n={X.shape[0]}, m={X.shape[1]}")
758
+ print(f"Device: {DEVICE}")
759
+ print("Fixed generator parameters:", FIXED)
760
+
761
+ splitter = StratifiedShuffleSplit(n_splits=1, test_size=INNER_VAL_SIZE, random_state=GLOBAL_SEED)
762
+ (itr, iva), = splitter.split(X, y)
763
+ X_inner_tr, y_inner_tr = X[itr], y[itr]
764
+ X_inner_va, y_inner_va = X[iva], y[iva]
765
+
766
+ def objective(trial):
767
+ params = {
768
+ "gobs_num_clusters": trial.suggest_categorical("gobs_num_clusters", [3, 4, 5, 6, 7, 8, 10, 12]),
769
+ "gobs_metric": trial.suggest_categorical("gobs_metric", ["correlation", "cosine", "manhattan", "euclidean", "kl_divergence"]),
770
+ "gobs_bins": trial.suggest_categorical("gobs_bins", [16, 24, 32, 48, 64]),
771
+ "gobs_top_k": trial.suggest_categorical("gobs_top_k", [None, 16, 32, 64, 128, 256]),
772
+ "gobs_refine_order": trial.suggest_categorical("gobs_refine_order", [True, False]),
773
+ "gobs_direction_select": trial.suggest_categorical("gobs_direction_select", [True, False]),
774
+ "gobs_refine_passes": trial.suggest_categorical("gobs_refine_passes", [0, 1, 2, 3]),
775
+ "gobs_boundary_refine_passes": trial.suggest_categorical("gobs_boundary_refine_passes", [0, 1, 3, 5, 8]),
776
+ "gobs_boundary_window": trial.suggest_categorical("gobs_boundary_window", [4, 8, 12, 16, 24]),
777
+ "gobs_lambda_cross": trial.suggest_float("gobs_lambda_cross", 0.1, 5.0, log=True),
778
+ "gobs_gamma_balance": trial.suggest_float("gobs_gamma_balance", 1e-8, 1e-2, log=True)
779
+ }
780
+ trial_seed = GLOBAL_SEED + 1000 + trial.number
781
+ try:
782
+ model = BSTabDiff(
783
+ feature_specs=None,
784
+ M=FIXED["M"],
785
+ blocks=None,
786
+ permute_features=False,
787
+ prior_type=FIXED["prior_type"],
788
+ device=DEVICE,
789
+ random_state=trial_seed,
790
+ prior_epochs=FIXED["prior_epochs"],
791
+ prior_batch=FIXED["prior_batch"],
792
+ prior_lr=FIXED["prior_lr"],
793
+ verbose_every=0,
794
+ save_dir=None,
795
+ save_name=f"bstabdiff_gobs_trial_{trial.number}",
796
+ save_best=True,
797
+ use_ema=True,
798
+ ema_decay=FIXED["ema_decay"],
799
+ use_gobs=True,
800
+ use_gobs_fc=False,
801
+ gobs_num_clusters=params["gobs_num_clusters"],
802
+ gobs_metric=params["gobs_metric"],
803
+ gobs_bins=params["gobs_bins"],
804
+ gobs_top_k=params["gobs_top_k"],
805
+ gobs_refine_order=params["gobs_refine_order"],
806
+ gobs_direction_select=params["gobs_direction_select"],
807
+ gobs_refine_passes=params["gobs_refine_passes"],
808
+ gobs_boundary_refine_passes=params["gobs_boundary_refine_passes"],
809
+ gobs_boundary_window=params["gobs_boundary_window"],
810
+ gobs_lambda_cross=params["gobs_lambda_cross"],
811
+ gobs_gamma_balance=params["gobs_gamma_balance"],
812
+ gobs_use_cpu_kmeans=False
813
+ )
814
+ model.fit(X_inner_tr, y_inner_tr)
815
+ X_syn, _, y_syn = model.sample(n_samples=FIXED["n_syn"], return_mask=True)
816
+ X_syn = np.nan_to_num(np.asarray(X_syn, dtype=np.float32), nan=0.0, posinf=0.0, neginf=0.0)
817
+ y_syn = np.asarray(y_syn).reshape(-1).astype(int)
818
+ if np.unique(y_syn).size < 2:
819
+ return 0.0
820
+ clf = make_lr()
821
+ clf.fit(X_syn, y_syn)
822
+ score = safe_auc_or_acc(clf, X_inner_va, y_inner_va)
823
+ trial.set_user_attr("raw_validation_score", score)
824
+ penalty = 1e-4 * params["gobs_num_clusters"]
825
+ if params["gobs_top_k"] is not None:
826
+ penalty += 1e-7 * params["gobs_top_k"]
827
+ return score - penalty
828
+ except Exception as e:
829
+ trial.set_user_attr("error", repr(e))
830
+ return 0.0
831
+ finally:
832
+ cleanup()
833
+
834
+ study = optuna.create_study(
835
+ direction="maximize",
836
+ sampler=TPESampler(seed=GLOBAL_SEED, multivariate=True, warn_independent_sampling=False)
837
+ )
838
+ study.optimize(objective, n_trials=N_TRIALS, show_progress_bar=True)
839
+
840
+ best = dict(study.best_params)
841
+ print("\nBest objective:", study.best_value)
842
+ print("Best GO-BS parameters:", best)
843
+
844
+ def final_eval(best):
845
+ rng = check_random_state(CV_SEED)
846
+ rkf = RepeatedStratifiedKFold(n_splits=5, n_repeats=5, random_state=CV_SEED)
847
+ tstr_acc, tstr_auc, trtr_acc, trtr_auc = [], [], [], []
848
+ for fold, (tr, te) in enumerate(rkf.split(X, y), 1):
849
+ Xtr, ytr = X[tr], y[tr]
850
+ Xte, yte = X[te], y[te]
851
+ fold_seed = int(rng.randint(0, 2**31 - 1))
852
+ model = BSTabDiff(
853
+ feature_specs=None,
854
+ M=FIXED["M"],
855
+ blocks=None,
856
+ permute_features=False,
857
+ prior_type=FIXED["prior_type"],
858
+ device=DEVICE,
859
+ random_state=fold_seed,
860
+ prior_epochs=FIXED["prior_epochs"],
861
+ prior_batch=FIXED["prior_batch"],
862
+ prior_lr=FIXED["prior_lr"],
863
+ verbose_every=0,
864
+ save_dir=None,
865
+ save_name="bstabdiff_gobs_best",
866
+ save_best=True,
867
+ use_ema=True,
868
+ ema_decay=FIXED["ema_decay"],
869
+ use_gobs=True,
870
+ use_gobs_fc=False,
871
+ gobs_num_clusters=best["gobs_num_clusters"],
872
+ gobs_metric=best["gobs_metric"],
873
+ gobs_bins=best["gobs_bins"],
874
+ gobs_top_k=best["gobs_top_k"],
875
+ gobs_refine_order=best["gobs_refine_order"],
876
+ gobs_direction_select=best["gobs_direction_select"],
877
+ gobs_refine_passes=best["gobs_refine_passes"],
878
+ gobs_boundary_refine_passes=best["gobs_boundary_refine_passes"],
879
+ gobs_boundary_window=best["gobs_boundary_window"],
880
+ gobs_lambda_cross=best["gobs_lambda_cross"],
881
+ gobs_gamma_balance=best["gobs_gamma_balance"],
882
+ gobs_use_cpu_kmeans=False
883
+ )
884
+ model.fit(Xtr, ytr)
885
+ X_syn, _, y_syn = model.sample(n_samples=FIXED["n_syn"], return_mask=True)
886
+ X_syn = np.nan_to_num(np.asarray(X_syn, dtype=np.float32), nan=0.0, posinf=0.0, neginf=0.0)
887
+ y_syn = np.asarray(y_syn).reshape(-1).astype(int)
888
+
889
+ clf = make_lr()
890
+ clf.fit(X_syn, y_syn)
891
+ p = clf.predict_proba(Xte)[:, 1]
892
+ tstr_acc.append(accuracy_score(yte, (p >= 0.5).astype(int)))
893
+ tstr_auc.append(roc_auc_score(yte, p))
894
+
895
+ clf_real = make_lr()
896
+ clf_real.fit(Xtr, ytr)
897
+ p_real = clf_real.predict_proba(Xte)[:, 1]
898
+ trtr_acc.append(accuracy_score(yte, (p_real >= 0.5).astype(int)))
899
+ trtr_auc.append(roc_auc_score(yte, p_real))
900
+
901
+ print(f"Fold {fold:02d}/25 | TSTR ACC={tstr_acc[-1]:.4f} AUC={tstr_auc[-1]:.4f}")
902
+ del model, clf, clf_real
903
+ cleanup()
904
+
905
+ print("\nTUNED GO-BS - STRICT 5x5")
906
+ print(f"TSTR ACC = {np.mean(tstr_acc):.4f} ± {np.std(tstr_acc):.4f}")
907
+ print(f"TSTR AUC = {np.mean(tstr_auc):.4f} ± {np.std(tstr_auc):.4f}")
908
+ print(f"TRTR ACC = {np.mean(trtr_acc):.4f} ± {np.std(trtr_acc):.4f}")
909
+ print(f"TRTR AUC = {np.mean(trtr_auc):.4f} ± {np.std(trtr_auc):.4f}")
910
+
911
+ final_eval(best)
912
+ study.trials_dataframe().to_csv("bstabdiff_optuna_gobs_trials.csv", index=False)
913
+ ```
914
+
915
+ ### Example 4 - GO-BS-FC with Optuna Tuning
916
+ ```python
917
+ import os, gc, random, warnings
918
+ os.environ["PYTHONWARNINGS"] = "ignore"
919
+ warnings.filterwarnings("ignore")
920
+
921
+ import numpy as np
922
+ import pandas as pd
923
+ import torch
924
+ import optuna
925
+ from sklearn.model_selection import StratifiedShuffleSplit, RepeatedStratifiedKFold
926
+ from sklearn.pipeline import Pipeline
927
+ from sklearn.impute import SimpleImputer
928
+ from sklearn.preprocessing import StandardScaler
929
+ from sklearn.linear_model import LogisticRegression
930
+ from sklearn.metrics import accuracy_score, roc_auc_score
931
+ from sklearn.utils import check_random_state
932
+ from optuna.samplers import TPESampler
933
+ from bstabdiff import BSTabDiff
934
+
935
+ GLOBAL_SEED = 42
936
+ CV_SEED = 0
937
+ N_TRIALS = 5 # Increase for full tuning
938
+ INNER_VAL_SIZE = 0.2
939
+ CSV_PATH = "coloncancer_encoded.csv" #change your dataset file here
940
+ LABEL_COL = "label" #change your target column here
941
+ DEVICE = "cuda:0" if torch.cuda.is_available() else "cpu"
942
+
943
+ FIXED = {
944
+ "M": 8,
945
+ "prior_type": "flow",
946
+ "prior_epochs": 300,
947
+ "prior_batch": 64,
948
+ "prior_lr": 3.2931e-3,
949
+ "ema_decay": 0.9926,
950
+ "n_syn": 64
951
+ }
952
+
953
+ def set_seed(seed):
954
+ random.seed(seed)
955
+ np.random.seed(seed)
956
+ torch.manual_seed(seed)
957
+ if torch.cuda.is_available():
958
+ torch.cuda.manual_seed_all(seed)
959
+
960
+ def cleanup():
961
+ gc.collect()
962
+ if torch.cuda.is_available():
963
+ torch.cuda.empty_cache()
964
+
965
+ def load_data():
966
+ df = pd.read_csv(CSV_PATH)
967
+ if LABEL_COL not in df.columns:
968
+ raise ValueError(f"Label column '{LABEL_COL}' not found.")
969
+ y_raw = df[LABEL_COL].values
970
+ X = np.nan_to_num(
971
+ df.drop(columns=[LABEL_COL]).select_dtypes(include=[np.number]).to_numpy(dtype=np.float32),
972
+ nan=0.0, posinf=0.0, neginf=0.0
973
+ )
974
+ _, y = np.unique(y_raw, return_inverse=True)
975
+ return X, y.astype(int)
976
+
977
+ def make_lr():
978
+ return Pipeline([
979
+ ("imp", SimpleImputer(strategy="median")),
980
+ ("sc", StandardScaler()),
981
+ ("clf", LogisticRegression(max_iter=10000, class_weight="balanced", solver="lbfgs"))
982
+ ])
983
+
984
+ def safe_auc_or_acc(clf, X, y):
985
+ try:
986
+ return float(roc_auc_score(y, clf.predict_proba(X)[:, 1]))
987
+ except Exception:
988
+ return float(accuracy_score(y, clf.predict(X)))
989
+
990
+ def sample_gobs_fc(trial):
991
+ return {
992
+ "gobs_num_clusters": trial.suggest_int("gobs_num_clusters", 3, 10),
993
+ "gobs_metric": trial.suggest_categorical("gobs_metric", ["correlation", "cosine", "euclidean", "manhattan"]),
994
+ "gobs_bins": trial.suggest_categorical("gobs_bins", [16, 32, 64]),
995
+ "gobs_top_k": trial.suggest_categorical("gobs_top_k", [None, 8, 16, 32, 64]),
996
+ "gobs_refine_order": trial.suggest_categorical("gobs_refine_order", [True, False]),
997
+ "gobs_direction_select": trial.suggest_categorical("gobs_direction_select", [True, False]),
998
+ "gobs_refine_passes": trial.suggest_int("gobs_refine_passes", 0, 3),
999
+ "gobs_boundary_refine_passes": trial.suggest_int("gobs_boundary_refine_passes", 1, 8),
1000
+ "gobs_boundary_window": trial.suggest_categorical("gobs_boundary_window", [4, 8, 12, 16]),
1001
+ "gobs_lambda_cross": trial.suggest_float("gobs_lambda_cross", 0.1, 5.0, log=True),
1002
+ "gobs_gamma_balance": trial.suggest_float("gobs_gamma_balance", 1e-6, 1e-1, log=True)
1003
+ }
1004
+
1005
+ def build_model(gobs, seed, name):
1006
+ return BSTabDiff(
1007
+ feature_specs=None,
1008
+ M=FIXED["M"],
1009
+ blocks=None,
1010
+ permute_features=False,
1011
+ prior_type=FIXED["prior_type"],
1012
+ device=DEVICE,
1013
+ random_state=seed,
1014
+ prior_epochs=FIXED["prior_epochs"],
1015
+ prior_batch=FIXED["prior_batch"],
1016
+ prior_lr=FIXED["prior_lr"],
1017
+ verbose_every=0,
1018
+ save_dir=None,
1019
+ save_name=name,
1020
+ save_best=True,
1021
+ use_ema=True,
1022
+ ema_decay=FIXED["ema_decay"],
1023
+ use_gobs=True,
1024
+ use_gobs_fc=True,
1025
+ gobs_use_cpu_kmeans=False,
1026
+ **gobs
1027
+ )
1028
+
1029
+ set_seed(GLOBAL_SEED)
1030
+ X, y = load_data()
1031
+ print(f"Dataset: n={X.shape[0]}, m={X.shape[1]}")
1032
+ print(f"Device: {DEVICE}")
1033
+ print("Fixed generator parameters:", FIXED)
1034
+
1035
+ splitter = StratifiedShuffleSplit(n_splits=1, test_size=INNER_VAL_SIZE, random_state=GLOBAL_SEED)
1036
+ (itr, iva), = splitter.split(X, y)
1037
+ X_inner_tr, y_inner_tr = X[itr], y[itr]
1038
+ X_inner_va, y_inner_va = X[iva], y[iva]
1039
+
1040
+ def objective(trial):
1041
+ gobs = sample_gobs_fc(trial)
1042
+ trial_seed = GLOBAL_SEED + 1000 + trial.number
1043
+ try:
1044
+ model = build_model(gobs, trial_seed, f"bstabdiff_gobs_fc_trial_{trial.number}")
1045
+ model.fit(X_inner_tr, y_inner_tr)
1046
+ X_syn, _, y_syn = model.sample(n_samples=FIXED["n_syn"], return_mask=True)
1047
+ X_syn = np.nan_to_num(np.asarray(X_syn, dtype=np.float32), nan=0.0, posinf=0.0, neginf=0.0)
1048
+ y_syn = np.asarray(y_syn).reshape(-1).astype(int)
1049
+ if np.unique(y_syn).size < 2:
1050
+ return 0.0
1051
+ clf = make_lr()
1052
+ clf.fit(X_syn, y_syn)
1053
+ score = safe_auc_or_acc(clf, X_inner_va, y_inner_va)
1054
+ trial.set_user_attr("raw_validation_score", score)
1055
+ return score - 1e-4 * FIXED["M"]
1056
+ except Exception as e:
1057
+ trial.set_user_attr("error", repr(e))
1058
+ return 0.0
1059
+ finally:
1060
+ cleanup()
1061
+
1062
+ study = optuna.create_study(
1063
+ direction="maximize",
1064
+ sampler=TPESampler(seed=GLOBAL_SEED, multivariate=True, warn_independent_sampling=False)
1065
+ )
1066
+ study.optimize(objective, n_trials=N_TRIALS, show_progress_bar=True)
1067
+
1068
+ best = dict(study.best_params)
1069
+ print("\nBest objective:", study.best_value)
1070
+ print("Best GO-BS-FC parameters:", best)
1071
+
1072
+ def final_eval(best):
1073
+ rng = check_random_state(CV_SEED)
1074
+ rkf = RepeatedStratifiedKFold(n_splits=5, n_repeats=5, random_state=CV_SEED)
1075
+ tstr_acc, tstr_auc, trtr_acc, trtr_auc = [], [], [], []
1076
+ for fold, (tr, te) in enumerate(rkf.split(X, y), 1):
1077
+ Xtr, ytr = X[tr], y[tr]
1078
+ Xte, yte = X[te], y[te]
1079
+ fold_seed = int(rng.randint(0, 2**31 - 1))
1080
+ model = build_model(best, fold_seed, "bstabdiff_gobs_fc_best")
1081
+ model.fit(Xtr, ytr)
1082
+ X_syn, _, y_syn = model.sample(n_samples=FIXED["n_syn"], return_mask=True)
1083
+ X_syn = np.nan_to_num(np.asarray(X_syn, dtype=np.float32), nan=0.0, posinf=0.0, neginf=0.0)
1084
+ y_syn = np.asarray(y_syn).reshape(-1).astype(int)
1085
+
1086
+ clf = make_lr()
1087
+ clf.fit(X_syn, y_syn)
1088
+ p = clf.predict_proba(Xte)[:, 1]
1089
+ tstr_acc.append(accuracy_score(yte, (p >= 0.5).astype(int)))
1090
+ tstr_auc.append(roc_auc_score(yte, p))
1091
+
1092
+ clf_real = make_lr()
1093
+ clf_real.fit(Xtr, ytr)
1094
+ p_real = clf_real.predict_proba(Xte)[:, 1]
1095
+ trtr_acc.append(accuracy_score(yte, (p_real >= 0.5).astype(int)))
1096
+ trtr_auc.append(roc_auc_score(yte, p_real))
1097
+
1098
+ print(f"Fold {fold:02d}/25 | TSTR ACC={tstr_acc[-1]:.4f} AUC={tstr_auc[-1]:.4f}")
1099
+ del model, clf, clf_real
1100
+ cleanup()
1101
+
1102
+ print("\nTUNED GO-BS-FC - STRICT 5x5")
1103
+ print(f"TSTR ACC = {np.mean(tstr_acc):.4f} ± {np.std(tstr_acc):.4f}")
1104
+ print(f"TSTR AUC = {np.mean(tstr_auc):.4f} ± {np.std(tstr_auc):.4f}")
1105
+ print(f"TRTR ACC = {np.mean(trtr_acc):.4f} ± {np.std(trtr_acc):.4f}")
1106
+ print(f"TRTR AUC = {np.mean(trtr_auc):.4f} ± {np.std(trtr_auc):.4f}")
1107
+
1108
+ final_eval(best)
1109
+ study.trials_dataframe().to_csv("bstabdiff_optuna_gobs_fc_trials.csv", index=False)
1110
+ ```
1111
+
1112
+ ### Example 5 - No Permutation with Pre-selected Parameters
1113
+ ```python
1114
+ import os, random, warnings
1115
+ os.environ["PYTHONWARNINGS"] = "ignore"
1116
+ warnings.filterwarnings("ignore")
1117
+
1118
+ import numpy as np
1119
+ import pandas as pd
1120
+ import torch
1121
+ from bstabdiff import BSTabDiff
1122
+
1123
+ SEED = 42
1124
+ CSV_PATH = "coloncancer_encoded.csv" #change your dataset file here
1125
+ LABEL_COL = "label" #change your target column here
1126
+ N_SYN = 64
1127
+ DEVICE = "cuda:0" if torch.cuda.is_available() else "cpu"
1128
+
1129
+ PARAMS = {
1130
+ "M": 8,
1131
+ "prior_type": "flow",
1132
+ "prior_epochs": 300,
1133
+ "prior_batch": 64,
1134
+ "prior_lr": 3.2931e-3,
1135
+ "ema_decay": 0.9926
1136
+ }
1137
+
1138
+ def set_seed(seed):
1139
+ random.seed(seed)
1140
+ np.random.seed(seed)
1141
+ torch.manual_seed(seed)
1142
+ if torch.cuda.is_available():
1143
+ torch.cuda.manual_seed_all(seed)
1144
+
1145
+ def load_data():
1146
+ df = pd.read_csv(CSV_PATH)
1147
+ if LABEL_COL not in df.columns:
1148
+ raise ValueError(f"Label column '{LABEL_COL}' not found.")
1149
+ y_raw = df[LABEL_COL].values
1150
+ X = np.nan_to_num(
1151
+ df.drop(columns=[LABEL_COL]).select_dtypes(include=[np.number]).to_numpy(dtype=np.float32),
1152
+ nan=0.0, posinf=0.0, neginf=0.0
1153
+ )
1154
+ _, y = np.unique(y_raw, return_inverse=True)
1155
+ return X, y.astype(int)
1156
+
1157
+ set_seed(SEED)
1158
+ X, y = load_data()
1159
+
1160
+ print(f"Dataset: n={X.shape[0]}, m={X.shape[1]}")
1161
+ print(f"Device: {DEVICE}")
1162
+ print("Pre-selected parameters:", PARAMS)
1163
+
1164
+ model = BSTabDiff(
1165
+ feature_specs=None,
1166
+ M=PARAMS["M"],
1167
+ blocks=None,
1168
+ permute_features=False,
1169
+ prior_type=PARAMS["prior_type"],
1170
+ device=DEVICE,
1171
+ random_state=SEED,
1172
+ prior_epochs=PARAMS["prior_epochs"],
1173
+ prior_batch=PARAMS["prior_batch"],
1174
+ prior_lr=PARAMS["prior_lr"],
1175
+ verbose_every=0,
1176
+ save_dir=None,
1177
+ save_name="bstabdiff_noperm_preselected",
1178
+ save_best=True,
1179
+ use_ema=True,
1180
+ ema_decay=PARAMS["ema_decay"],
1181
+ use_gobs=False,
1182
+ use_gobs_fc=False
1183
+ )
1184
+
1185
+ model.fit(X, y)
1186
+ X_syn, R_syn, y_syn = model.sample(n_samples=N_SYN, return_mask=True)
1187
+
1188
+ X_syn = np.asarray(X_syn, dtype=np.float32)
1189
+ R_syn = np.asarray(R_syn)
1190
+ y_syn = None if y_syn is None else np.asarray(y_syn).astype(int)
1191
+
1192
+ print("X_syn:", X_syn.shape)
1193
+ print("R_syn:", R_syn.shape)
1194
+ print("y_syn:", y_syn.shape if y_syn is not None else None)
1195
+
1196
+ pd.DataFrame(X_syn).to_csv("bstabdiff_noperm_synthetic_X.csv", index=False)
1197
+ if y_syn is not None:
1198
+ pd.DataFrame({"label": y_syn}).to_csv("bstabdiff_noperm_synthetic_y.csv", index=False)
1199
+
1200
+ print("Saved synthetic data.")
1201
+ ```
1202
+
1203
+ ### Example 6 - GO-BS with Pre-selected Parameters
1204
+ ```python
1205
+ import os, random, warnings
1206
+ os.environ["PYTHONWARNINGS"] = "ignore"
1207
+ warnings.filterwarnings("ignore")
1208
+
1209
+ import numpy as np
1210
+ import pandas as pd
1211
+ import torch
1212
+ from bstabdiff import BSTabDiff
1213
+
1214
+ SEED = 42
1215
+ CSV_PATH = "coloncancer_encoded.csv" #change your dataset file here
1216
+ LABEL_COL = "label" #change your target column here
1217
+ N_SYN = 64
1218
+ DEVICE = "cuda:0" if torch.cuda.is_available() else "cpu"
1219
+
1220
+ PARAMS = {
1221
+ "M": 8,
1222
+ "prior_type": "flow",
1223
+ "prior_epochs": 300,
1224
+ "prior_batch": 64,
1225
+ "prior_lr": 3.2931e-3,
1226
+ "ema_decay": 0.9926,
1227
+ "gobs_num_clusters": 3,
1228
+ "gobs_metric": "correlation",
1229
+ "gobs_bins": 24,
1230
+ "gobs_top_k": 16,
1231
+ "gobs_refine_order": False,
1232
+ "gobs_direction_select": False,
1233
+ "gobs_refine_passes": 3,
1234
+ "gobs_boundary_refine_passes": 5,
1235
+ "gobs_boundary_window": 16,
1236
+ "gobs_lambda_cross": 2.3989,
1237
+ "gobs_gamma_balance": 5.19e-6
1238
+ }
1239
+
1240
+ def set_seed(seed):
1241
+ random.seed(seed)
1242
+ np.random.seed(seed)
1243
+ torch.manual_seed(seed)
1244
+ if torch.cuda.is_available():
1245
+ torch.cuda.manual_seed_all(seed)
1246
+
1247
+ def load_data():
1248
+ df = pd.read_csv(CSV_PATH)
1249
+ if LABEL_COL not in df.columns:
1250
+ raise ValueError(f"Label column '{LABEL_COL}' not found.")
1251
+ y_raw = df[LABEL_COL].values
1252
+ X = np.nan_to_num(
1253
+ df.drop(columns=[LABEL_COL]).select_dtypes(include=[np.number]).to_numpy(dtype=np.float32),
1254
+ nan=0.0, posinf=0.0, neginf=0.0
1255
+ )
1256
+ _, y = np.unique(y_raw, return_inverse=True)
1257
+ return X, y.astype(int)
1258
+
1259
+ set_seed(SEED)
1260
+ X, y = load_data()
1261
+
1262
+ print(f"Dataset: n={X.shape[0]}, m={X.shape[1]}")
1263
+ print(f"Device: {DEVICE}")
1264
+ print("Pre-selected parameters:", PARAMS)
1265
+
1266
+ model = BSTabDiff(
1267
+ feature_specs=None,
1268
+ M=PARAMS["M"],
1269
+ blocks=None,
1270
+ permute_features=False,
1271
+ prior_type=PARAMS["prior_type"],
1272
+ device=DEVICE,
1273
+ random_state=SEED,
1274
+ prior_epochs=PARAMS["prior_epochs"],
1275
+ prior_batch=PARAMS["prior_batch"],
1276
+ prior_lr=PARAMS["prior_lr"],
1277
+ verbose_every=0,
1278
+ save_dir=None,
1279
+ save_name="bstabdiff_gobs_preselected",
1280
+ save_best=True,
1281
+ use_ema=True,
1282
+ ema_decay=PARAMS["ema_decay"],
1283
+ use_gobs=True,
1284
+ use_gobs_fc=False,
1285
+ gobs_num_clusters=PARAMS["gobs_num_clusters"],
1286
+ gobs_metric=PARAMS["gobs_metric"],
1287
+ gobs_bins=PARAMS["gobs_bins"],
1288
+ gobs_top_k=PARAMS["gobs_top_k"],
1289
+ gobs_refine_order=PARAMS["gobs_refine_order"],
1290
+ gobs_direction_select=PARAMS["gobs_direction_select"],
1291
+ gobs_refine_passes=PARAMS["gobs_refine_passes"],
1292
+ gobs_boundary_refine_passes=PARAMS["gobs_boundary_refine_passes"],
1293
+ gobs_boundary_window=PARAMS["gobs_boundary_window"],
1294
+ gobs_lambda_cross=PARAMS["gobs_lambda_cross"],
1295
+ gobs_gamma_balance=PARAMS["gobs_gamma_balance"],
1296
+ gobs_use_cpu_kmeans=False
1297
+ )
1298
+
1299
+ model.fit(X, y)
1300
+ X_syn, R_syn, y_syn = model.sample(n_samples=N_SYN, return_mask=True)
1301
+
1302
+ X_syn = np.asarray(X_syn, dtype=np.float32)
1303
+ R_syn = np.asarray(R_syn)
1304
+ y_syn = None if y_syn is None else np.asarray(y_syn).astype(int)
1305
+
1306
+ print("X_syn:", X_syn.shape)
1307
+ print("R_syn:", R_syn.shape)
1308
+ print("y_syn:", y_syn.shape if y_syn is not None else None)
1309
+
1310
+ pd.DataFrame(X_syn).to_csv("bstabdiff_gobs_synthetic_X.csv", index=False)
1311
+ if y_syn is not None:
1312
+ pd.DataFrame({"label": y_syn}).to_csv("bstabdiff_gobs_synthetic_y.csv", index=False)
1313
+
1314
+ print("Saved synthetic data.")
1315
+ ```
1316
+
1317
+ ### Example 7 - GO-BS-FC with Pre-selected Parameters
1318
+ ```python
1319
+ import os, random, warnings
1320
+ os.environ["PYTHONWARNINGS"] = "ignore"
1321
+ warnings.filterwarnings("ignore")
1322
+
1323
+ import numpy as np
1324
+ import pandas as pd
1325
+ import torch
1326
+ from bstabdiff import BSTabDiff
1327
+
1328
+ SEED = 42
1329
+ CSV_PATH = "coloncancer_encoded.csv" #change your dataset file here
1330
+ LABEL_COL = "label" #change your target column here
1331
+ N_SYN = 64
1332
+ DEVICE = "cuda:0" if torch.cuda.is_available() else "cpu"
1333
+
1334
+ PARAMS = {
1335
+ "M": 8,
1336
+ "prior_type": "flow",
1337
+ "prior_epochs": 300,
1338
+ "prior_batch": 64,
1339
+ "prior_lr": 3.2931e-3,
1340
+ "ema_decay": 0.9926,
1341
+ "gobs_num_clusters": 3,
1342
+ "gobs_metric": "correlation",
1343
+ "gobs_bins": 24,
1344
+ "gobs_top_k": 16,
1345
+ "gobs_refine_order": False,
1346
+ "gobs_direction_select": False,
1347
+ "gobs_refine_passes": 3,
1348
+ "gobs_boundary_refine_passes": 5,
1349
+ "gobs_boundary_window": 16,
1350
+ "gobs_lambda_cross": 2.3989,
1351
+ "gobs_gamma_balance": 5.19e-6
1352
+ }
1353
+
1354
+ def set_seed(seed):
1355
+ random.seed(seed)
1356
+ np.random.seed(seed)
1357
+ torch.manual_seed(seed)
1358
+ if torch.cuda.is_available():
1359
+ torch.cuda.manual_seed_all(seed)
1360
+
1361
+ def load_data():
1362
+ df = pd.read_csv(CSV_PATH)
1363
+ if LABEL_COL not in df.columns:
1364
+ raise ValueError(f"Label column '{LABEL_COL}' not found.")
1365
+ y_raw = df[LABEL_COL].values
1366
+ X = np.nan_to_num(
1367
+ df.drop(columns=[LABEL_COL]).select_dtypes(include=[np.number]).to_numpy(dtype=np.float32),
1368
+ nan=0.0, posinf=0.0, neginf=0.0
1369
+ )
1370
+ _, y = np.unique(y_raw, return_inverse=True)
1371
+ return X, y.astype(int)
1372
+
1373
+ set_seed(SEED)
1374
+ X, y = load_data()
1375
+
1376
+ print(f"Dataset: n={X.shape[0]}, m={X.shape[1]}")
1377
+ print(f"Device: {DEVICE}")
1378
+ print("Pre-selected parameters:", PARAMS)
1379
+
1380
+ model = BSTabDiff(
1381
+ feature_specs=None,
1382
+ M=PARAMS["M"],
1383
+ blocks=None,
1384
+ permute_features=False,
1385
+ prior_type=PARAMS["prior_type"],
1386
+ device=DEVICE,
1387
+ random_state=SEED,
1388
+ prior_epochs=PARAMS["prior_epochs"],
1389
+ prior_batch=PARAMS["prior_batch"],
1390
+ prior_lr=PARAMS["prior_lr"],
1391
+ verbose_every=0,
1392
+ save_dir=None,
1393
+ save_name="bstabdiff_gobs_fc_preselected",
1394
+ save_best=True,
1395
+ use_ema=True,
1396
+ ema_decay=PARAMS["ema_decay"],
1397
+ use_gobs=True,
1398
+ use_gobs_fc=True,
1399
+ gobs_num_clusters=PARAMS["gobs_num_clusters"],
1400
+ gobs_metric=PARAMS["gobs_metric"],
1401
+ gobs_bins=PARAMS["gobs_bins"],
1402
+ gobs_top_k=PARAMS["gobs_top_k"],
1403
+ gobs_refine_order=PARAMS["gobs_refine_order"],
1404
+ gobs_direction_select=PARAMS["gobs_direction_select"],
1405
+ gobs_refine_passes=PARAMS["gobs_refine_passes"],
1406
+ gobs_boundary_refine_passes=PARAMS["gobs_boundary_refine_passes"],
1407
+ gobs_boundary_window=PARAMS["gobs_boundary_window"],
1408
+ gobs_lambda_cross=PARAMS["gobs_lambda_cross"],
1409
+ gobs_gamma_balance=PARAMS["gobs_gamma_balance"],
1410
+ gobs_use_cpu_kmeans=False
1411
+ )
1412
+
1413
+ model.fit(X, y)
1414
+ X_syn, R_syn, y_syn = model.sample(n_samples=N_SYN, return_mask=True)
1415
+
1416
+ X_syn = np.asarray(X_syn, dtype=np.float32)
1417
+ R_syn = np.asarray(R_syn)
1418
+ y_syn = None if y_syn is None else np.asarray(y_syn).astype(int)
1419
+
1420
+ print("X_syn:", X_syn.shape)
1421
+ print("R_syn:", R_syn.shape)
1422
+ print("y_syn:", y_syn.shape if y_syn is not None else None)
1423
+
1424
+ pd.DataFrame(X_syn).to_csv("bstabdiff_gobs_fc_synthetic_X.csv", index=False)
1425
+ if y_syn is not None:
1426
+ pd.DataFrame({"label": y_syn}).to_csv("bstabdiff_gobs_fc_synthetic_y.csv", index=False)
1427
+
1428
+ print("Saved synthetic data.")
1429
+ ```
1430
+
1431
+ ### Mixed-Type Data with Missingness
1432
+
1433
+ BSTabDiff also supports **mixed-type tabular data** containing both continuous and categorical features, including datasets with missing values. Categorical variables are specified explicitly through `FeatureSpec`, while missingness is modeled jointly during generation.
1434
+
1435
+ For a complete runnable example using a dummy mixed-type dataset with categorical values and structured missingness, see: **[BSTabDiff Mixed-Data Smoke Test](https://github.com/zadid6pretam/BSTabDiff/blob/main/Smoke%20Tests/BSTabDiff_MixedData_SmokeTest.ipynb)**
1436
+
1437
+ ## Our Previous Related Work Involving Tabular Data
1438
+
1439
+ BSTabDiff is part of our broader line of work on tabular deep learning, feature ordering, multimodal learning, and high-dimensional tabular modeling.
1440
+
1441
+ ### GOTabPFN
1442
+ - **Title:** GOTabPFN: From Feature Ordering to Compact Tokenization for Tabular Foundation Models on High-Dimensional Data
1443
+ - **Venue:** ICML 2026
1444
+ - **GitHub:** https://github.com/zadid6pretam/GOTabPFN
1445
+
1446
+ ### iSyncTab
1447
+ - **Title:** iSyncTab: Learning Cross-Modal Feature Sequencing for Image-Tabular Data via Neural Synchrony
1448
+ - **Venue:** ECCV 2026
1449
+ - **GitHub:** https://github.com/zadid6pretam/iSyncTab
1450
+
1451
+ ### iStructTab
1452
+ - **Title:** iStructTab: Structured Feature Sequencing for Multimodal Learning of Image and Tabular Data
1453
+ - **Venue:** ICPR 2026
1454
+ - **GitHub:** https://github.com/zadid6pretam/iStructTab
1455
+
1456
+ ### DynaTab
1457
+ - **Title:** DynaTab: Dynamic Feature Ordering as Neural Rewiring for High-Dimensional Tabular Data
1458
+ - **Venue:** AAAI 2026 NeuroAI Workshop
1459
+ - **GitHub:** https://github.com/zadid6pretam/DynaTab
1460
+
1461
+ ### TabSeq
1462
+ - **Title:** TabSeq: A Framework for Deep Learning on Tabular Data via Sequential Ordering
1463
+ - **Venue:** ICPR 2024
1464
+ - **GitHub:** https://github.com/zadid6pretam/TabSeq
1465
+
1466
+ ### Side Projects
1467
+
1468
+ #### ZAYAN
1469
+ - **Title:** ZAYAN: Disentangled Contrastive Transformer for Tabular Remote Sensing Data
1470
+ - **Venue:** ICPR 2026
1471
+ - **GitHub:** https://github.com/zadid6pretam/ZAYAN
1472
+
1473
+ #### AugTab
1474
+ - **Title:** AugTab: Learnable Feature Augmentation for Low-Dimensional Tabular Data
1475
+ - **Venue:** ECML-PKDD 2026 Research Track
1476
+ - **GitHub:** https://github.com/zadid6pretam/AugTab
1477
+
1478
+ ## Contact
1479
+
1480
+ For any questions, issues, or suggestions related to this repository, please feel free to contact us or open an issue on GitHub.