MaldiDeepKit 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. {maldideepkit-0.2.0 → maldideepkit-0.3.0/MaldiDeepKit.egg-info}/PKG-INFO +27 -7
  2. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/MaldiDeepKit.egg-info/SOURCES.txt +11 -0
  3. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/MaldiDeepKit.egg-info/requires.txt +8 -1
  4. {maldideepkit-0.2.0/MaldiDeepKit.egg-info → maldideepkit-0.3.0}/PKG-INFO +27 -7
  5. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/README.md +20 -5
  6. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/__init__.py +17 -3
  7. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/attention/mlp.py +9 -3
  8. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/augment/mixing.py +63 -11
  9. maldideepkit-0.3.0/maldideepkit/base/__init__.py +23 -0
  10. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/base/classifier.py +255 -50
  11. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/base/data.py +135 -18
  12. maldideepkit-0.3.0/maldideepkit/base/dual.py +266 -0
  13. maldideepkit-0.3.0/maldideepkit/dual_branch/__init__.py +22 -0
  14. maldideepkit-0.3.0/maldideepkit/dual_branch/_smiles.py +87 -0
  15. maldideepkit-0.3.0/maldideepkit/dual_branch/classifier.py +347 -0
  16. maldideepkit-0.3.0/maldideepkit/semisupervised/__init__.py +15 -0
  17. maldideepkit-0.3.0/maldideepkit/semisupervised/self_training.py +289 -0
  18. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/uncertainty/_base.py +7 -27
  19. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/uncertainty/mc_dropout.py +1 -1
  20. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/utils/calibration.py +34 -4
  21. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/utils/loss.py +80 -0
  22. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/utils/lr_finder.py +2 -0
  23. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/utils/reproducibility.py +2 -2
  24. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/utils/training.py +86 -15
  25. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/pyproject.toml +2 -0
  26. maldideepkit-0.3.0/requirements-all.txt +2 -0
  27. maldideepkit-0.3.0/requirements-rdkit.txt +1 -0
  28. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/requirements.txt +1 -1
  29. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_base.py +5 -5
  30. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_bin_scaling.py +2 -2
  31. maldideepkit-0.3.0/tests/test_dual_branch.py +534 -0
  32. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_maldiset_integration.py +1 -1
  33. maldideepkit-0.3.0/tests/test_sample_weight.py +277 -0
  34. maldideepkit-0.3.0/tests/test_self_training.py +281 -0
  35. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_sklearn_compat.py +1 -1
  36. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_transformer.py +1 -1
  37. maldideepkit-0.2.0/maldideepkit/base/__init__.py +0 -16
  38. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/LICENSE +0 -0
  39. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/MaldiDeepKit.egg-info/dependency_links.txt +0 -0
  40. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/MaldiDeepKit.egg-info/top_level.txt +0 -0
  41. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/_bin_scaling.py +0 -0
  42. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/_blocks.py +0 -0
  43. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/attention/__init__.py +0 -0
  44. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/augment/__init__.py +0 -0
  45. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/augment/spectra.py +0 -0
  46. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/blocks.py +0 -0
  47. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/cnn/__init__.py +0 -0
  48. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/cnn/cnn.py +0 -0
  49. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/py.typed +0 -0
  50. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/resnet/__init__.py +0 -0
  51. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/resnet/resnet.py +0 -0
  52. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/transformer/__init__.py +0 -0
  53. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/transformer/transformer.py +0 -0
  54. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/uncertainty/__init__.py +0 -0
  55. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/uncertainty/_result.py +0 -0
  56. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/uncertainty/conformal.py +0 -0
  57. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/uncertainty/laplace.py +0 -0
  58. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/utils/__init__.py +0 -0
  59. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/utils/ensemble.py +0 -0
  60. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/maldideepkit/utils/sam.py +0 -0
  61. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/requirements-dev.txt +0 -0
  62. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/requirements-docs.txt +0 -0
  63. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/requirements-uncertainty.txt +0 -0
  64. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/setup.cfg +0 -0
  65. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_augment.py +0 -0
  66. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_blocks.py +0 -0
  67. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_calibration.py +0 -0
  68. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_cnn.py +0 -0
  69. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_ensemble.py +0 -0
  70. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_loss.py +0 -0
  71. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_lr_finder.py +0 -0
  72. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_mlp.py +0 -0
  73. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_resnet.py +0 -0
  74. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_sam.py +0 -0
  75. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_uncertainty.py +0 -0
  76. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_warm_start.py +0 -0
  77. {maldideepkit-0.2.0 → maldideepkit-0.3.0}/tests/test_warping.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MaldiDeepKit
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: A catalog of sklearn-compatible deep learning classifiers for MALDI-TOF binned spectra
5
5
  Author-email: Ettore Rocchi <ettore.rocchi3@unibo.it>
6
6
  License-Expression: MIT
@@ -30,7 +30,7 @@ Requires-Dist: scikit-learn>=1.3.0
30
30
  Requires-Dist: matplotlib>=3.4.0
31
31
  Requires-Dist: torch>=2.0.0
32
32
  Requires-Dist: einops>=0.7.0
33
- Requires-Dist: maldiamrkit>=0.12.0
33
+ Requires-Dist: maldiamrkit>=0.19.0
34
34
  Provides-Extra: dev
35
35
  Requires-Dist: pytest>=7.0.0; extra == "dev"
36
36
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -47,6 +47,11 @@ Requires-Dist: ipykernel>=6.0.0; extra == "docs"
47
47
  Requires-Dist: ipywidgets>=8.0.0; extra == "docs"
48
48
  Provides-Extra: uncertainty
49
49
  Requires-Dist: laplace-torch<1.0,>=0.2; extra == "uncertainty"
50
+ Provides-Extra: rdkit
51
+ Requires-Dist: rdkit>=2026.3.1; extra == "rdkit"
52
+ Provides-Extra: all
53
+ Requires-Dist: rdkit>=2026.3.1; extra == "all"
54
+ Requires-Dist: laplace-torch<1.0,>=0.2; extra == "all"
50
55
  Dynamic: license-file
51
56
 
52
57
  # MaldiDeepKit
@@ -79,7 +84,7 @@ Dynamic: license-file
79
84
  <a href="#license">License</a>
80
85
  </p>
81
86
 
82
- MaldiDeepKit is part of the **MaldiSuite** ecosystem and complements [MaldiAMRKit](https://github.com/EttoreRocchi/MaldiAMRKit) and [MaldiBatchKit](https://github.com/EttoreRocchi/MaldiBatchKit): where MaldiAMRKit handles preprocessing, alignment and AMR-aware evaluation, and MaldiBatchKit harmonises multi-centre spectra, MaldiDeepKit focuses on the *classification* step, providing four PyTorch architectures wrapped in a unified scikit-learn estimator API with defaults calibrated for 6000-bin MALDI-TOF input.
87
+ MaldiDeepKit is part of the **MaldiSuite** ecosystem and complements [MaldiAMRKit](https://github.com/EttoreRocchi/MaldiAMRKit) and [MaldiBatchKit](https://github.com/EttoreRocchi/MaldiBatchKit): where MaldiAMRKit handles preprocessing, alignment and AMR-aware evaluation, and MaldiBatchKit harmonises multi-centre spectra, MaldiDeepKit focuses on the *classification* step, providing four PyTorch backbone architectures (MLP, 1-D CNN, 1-D ResNet, and 1-D Vision Transformer) plus a dual-branch classifier for spectrum + side-feature inputs and a self-training meta-classifier for semi-supervised data, all wrapped in a unified scikit-learn estimator API with defaults calibrated for 6000-bin MALDI-TOF input.
83
88
 
84
89
  ## Installation
85
90
 
@@ -87,7 +92,7 @@ MaldiDeepKit is part of the **MaldiSuite** ecosystem and complements [MaldiAMRKi
87
92
  pip install maldideepkit
88
93
  ```
89
94
 
90
- `maldiamrkit` is a core dependency and is installed automatically - MaldiDeepKit duck-types on the `MaldiSet` data model and reuses `maldiamrkit.alignment.Warping` for leak-safe spectral warping.
95
+ `maldiamrkit` is a core dependency and is installed automatically (MaldiDeepKit duck-types on the `MaldiSet` data model and reuses `maldiamrkit.alignment.Warping` for leak-safe spectral warping).
91
96
 
92
97
  To enable the Laplace-approximation estimator in `maldideepkit.uncertainty`, install the optional `uncertainty` extra (pulls in [`laplace-torch`](https://github.com/aleximmer/Laplace)):
93
98
 
@@ -97,6 +102,12 @@ pip install "maldideepkit[uncertainty]"
97
102
 
98
103
  Monte Carlo Dropout and split conformal prediction work without any extra.
99
104
 
105
+ The `smiles_to_fingerprint` helper used in `MaldiDualBranchClassifier` workflows requires RDKit, available via the `rdkit` extra:
106
+
107
+ ```bash
108
+ pip install "maldideepkit[rdkit]"
109
+ ```
110
+
100
111
  ### Install the full MaldiSuite
101
112
 
102
113
  To install MaldiDeepKit together with [MaldiAMRKit](https://github.com/EttoreRocchi/MaldiAMRKit) and [MaldiBatchKit](https://github.com/EttoreRocchi/MaldiBatchKit) at compatible versions, install the [`maldisuite`](https://pypi.org/project/maldisuite/) meta-package:
@@ -128,7 +139,7 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for coding conventions, testing, and PR
128
139
  - `MaldiTransformerClassifier` - 1-D Vision Transformer with global self-attention, pre-norm, LayerScale, and stochastic depth.
129
140
  - **MALDI-TOF defaults**: kernel sizes, depths, patch widths, and warmup / cosine-annealing schedules are tuned for 6000-bin spectra in the 2000-20000 Da range.
130
141
  - **Auto-scaling for non-default layouts**: every classifier ships a `from_spectrum(bin_width, input_dim, **overrides)` factory that rescales conv kernels and patches when the user trims the m/z range or picks a different bin width. See the [Spectrum scaling guide](https://maldideepkit.readthedocs.io/en/latest/spectrum_scaling.html).
131
- - **Training recipes**: AdamW-on-`weight_decay` dispatch, gradient clipping, linear warmup + cosine annealing, focal loss, label smoothing, mixed precision (AMP), Stochastic Weight Averaging, Sharpness-Aware Minimization, post-hoc threshold tuning, and temperature scaling - all exposed as classifier kwargs.
142
+ - **Training recipes**: AdamW-on-`weight_decay` dispatch, gradient clipping, linear warmup + cosine annealing, focal loss, label smoothing, mixed precision (AMP), Stochastic Weight Averaging, Sharpness-Aware Minimization, post-hoc threshold tuning, and temperature scaling (all exposed as classifier kwargs).
132
143
  - **Leak-safe spectral warping**: pass any sklearn-style transformer ([`maldiamrkit.alignment.Warping`](https://github.com/EttoreRocchi/MaldiAMRKit)) via `warping=`; it is fitted on the training fold only and applied to both splits during training and to new spectra at `predict` time, *before* per-feature standardization.
133
144
  - **MaldiSet integration**: pass a `maldiamrkit.MaldiSet` directly to `fit` / `predict`; MaldiDeepKit duck-types on the DataFrame-like `.X` attribute, so MaldiSuite's data model flows end-to-end.
134
145
  - **Persistence**: `save()` writes a state-dict `.pt` plus a hyperparameter `.json` (and a sibling `.warper.pkl` if a warper was fitted); `load()` fails fast on class or `input_dim` mismatches.
@@ -236,8 +247,15 @@ For more examples covering training recipes, calibration, attention inspection,
236
247
  | `MaldiCNNClassifier` | 1-D Conv1D + BatchNorm + ReLU + MaxPool blocks | Local pattern learning from binned spectra |
237
248
  | `MaldiResNetClassifier` | 1-D ResNet-18-style residual blocks | Deeper convolutional backbone |
238
249
  | `MaldiTransformerClassifier` | 1-D Vision Transformer (LayerScale, stochastic depth) | Long-range peak combinations via global self-attention |
250
+ | `MaldiDualBranchClassifier` | Spectrum branch + per-row side-feature branch + fused head | Multi-modal: condition on antibiotic fingerprint, instrument ID, patient feature, ... |
251
+
252
+ All five inherit from `BaseSpectralClassifier` (or its dual-input subclass `BaseDualSpectralClassifier`) and share the same hyperparameter surface for optimisation, device placement, early stopping, calibration, and persistence.
253
+
254
+ ### Semi-supervised wrapper
239
255
 
240
- All four inherit from `BaseSpectralClassifier` and share the same hyperparameter surface for optimisation, device placement, early stopping, calibration, and persistence.
256
+ | Class | Purpose |
257
+ |---|---|
258
+ | `MaldiSelfTrainingClassifier` | Iterative pseudo-labeling on a labeled seed plus an unlabeled pool (rows marked `-1` by default). Wraps any classifier (defaults to `MaldiMLPClassifier`); follows the `sklearn.semi_supervised.SelfTrainingClassifier` API. |
241
259
 
242
260
  ### Shared Training Knobs
243
261
 
@@ -247,7 +265,7 @@ All four inherit from `BaseSpectralClassifier` and share the same hyperparameter
247
265
  | Gradient clipping | `grad_clip_norm` | `clip_grad_norm_` before every step. Default on (`1.0`) for the deep models. |
248
266
  | Warmup + cosine annealing | `warmup_epochs` | Replaces plateau scheduler. Default `5` (deep models), `0` (MLP/CNN). |
249
267
  | Stochastic depth (Transformer) | `drop_path_rate` | Linearly ramped across blocks. Default `0.1`. |
250
- | LayerScale (Transformer) | `layerscale_init` | Per-channel residual scaling initialised near zero - crucial on small cohorts. |
268
+ | LayerScale (Transformer) | `layerscale_init` | Per-channel residual scaling initialised near zero (crucial on small cohorts). |
251
269
  | Focal loss | `loss="focal"` + `focal_gamma` | For imbalanced binary problems. |
252
270
  | Label smoothing | `label_smoothing` | Passed to both cross-entropy and focal paths. |
253
271
  | Stochastic Weight Averaging | `swa_start_epoch` | `AveragedModel` replaces best-val at end of fit. |
@@ -255,6 +273,8 @@ All four inherit from `BaseSpectralClassifier` and share the same hyperparameter
255
273
  | Temperature scaling | `calibrate_temperature` | One-parameter LBFGS calibration on val logits. |
256
274
  | Sharpness-Aware Minimization | `use_sam` + `sam_rho` | Two-pass training, ~2× compute. |
257
275
  | Spectral warping | `warping` | Any `Warping`-like sklearn transformer; fitted on train only, applied before standardization. |
276
+ | Per-sample weighting | `sample_weight` (`fit`) | sklearn-style weighted-mean loss; composes with `class_weight` and flows into early stopping, threshold tuning, and temperature scaling. |
277
+ | Warm-start / resume | `warm_start` (`fit`) | Reuse the existing fitted module as the training start point (continual / federated / fine-tuning). |
258
278
 
259
279
  ### Utilities
260
280
 
@@ -1,8 +1,10 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
+ requirements-all.txt
4
5
  requirements-dev.txt
5
6
  requirements-docs.txt
7
+ requirements-rdkit.txt
6
8
  requirements-uncertainty.txt
7
9
  requirements.txt
8
10
  MaldiDeepKit.egg-info/PKG-INFO
@@ -23,10 +25,16 @@ maldideepkit/augment/spectra.py
23
25
  maldideepkit/base/__init__.py
24
26
  maldideepkit/base/classifier.py
25
27
  maldideepkit/base/data.py
28
+ maldideepkit/base/dual.py
26
29
  maldideepkit/cnn/__init__.py
27
30
  maldideepkit/cnn/cnn.py
31
+ maldideepkit/dual_branch/__init__.py
32
+ maldideepkit/dual_branch/_smiles.py
33
+ maldideepkit/dual_branch/classifier.py
28
34
  maldideepkit/resnet/__init__.py
29
35
  maldideepkit/resnet/resnet.py
36
+ maldideepkit/semisupervised/__init__.py
37
+ maldideepkit/semisupervised/self_training.py
30
38
  maldideepkit/transformer/__init__.py
31
39
  maldideepkit/transformer/transformer.py
32
40
  maldideepkit/uncertainty/__init__.py
@@ -49,6 +57,7 @@ tests/test_bin_scaling.py
49
57
  tests/test_blocks.py
50
58
  tests/test_calibration.py
51
59
  tests/test_cnn.py
60
+ tests/test_dual_branch.py
52
61
  tests/test_ensemble.py
53
62
  tests/test_loss.py
54
63
  tests/test_lr_finder.py
@@ -56,6 +65,8 @@ tests/test_maldiset_integration.py
56
65
  tests/test_mlp.py
57
66
  tests/test_resnet.py
58
67
  tests/test_sam.py
68
+ tests/test_sample_weight.py
69
+ tests/test_self_training.py
59
70
  tests/test_sklearn_compat.py
60
71
  tests/test_transformer.py
61
72
  tests/test_uncertainty.py
@@ -5,7 +5,11 @@ scikit-learn>=1.3.0
5
5
  matplotlib>=3.4.0
6
6
  torch>=2.0.0
7
7
  einops>=0.7.0
8
- maldiamrkit>=0.12.0
8
+ maldiamrkit>=0.19.0
9
+
10
+ [all]
11
+ rdkit>=2026.3.1
12
+ laplace-torch<1.0,>=0.2
9
13
 
10
14
  [dev]
11
15
  pytest>=7.0.0
@@ -23,5 +27,8 @@ nbsphinx>=0.9.0
23
27
  ipykernel>=6.0.0
24
28
  ipywidgets>=8.0.0
25
29
 
30
+ [rdkit]
31
+ rdkit>=2026.3.1
32
+
26
33
  [uncertainty]
27
34
  laplace-torch<1.0,>=0.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MaldiDeepKit
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: A catalog of sklearn-compatible deep learning classifiers for MALDI-TOF binned spectra
5
5
  Author-email: Ettore Rocchi <ettore.rocchi3@unibo.it>
6
6
  License-Expression: MIT
@@ -30,7 +30,7 @@ Requires-Dist: scikit-learn>=1.3.0
30
30
  Requires-Dist: matplotlib>=3.4.0
31
31
  Requires-Dist: torch>=2.0.0
32
32
  Requires-Dist: einops>=0.7.0
33
- Requires-Dist: maldiamrkit>=0.12.0
33
+ Requires-Dist: maldiamrkit>=0.19.0
34
34
  Provides-Extra: dev
35
35
  Requires-Dist: pytest>=7.0.0; extra == "dev"
36
36
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -47,6 +47,11 @@ Requires-Dist: ipykernel>=6.0.0; extra == "docs"
47
47
  Requires-Dist: ipywidgets>=8.0.0; extra == "docs"
48
48
  Provides-Extra: uncertainty
49
49
  Requires-Dist: laplace-torch<1.0,>=0.2; extra == "uncertainty"
50
+ Provides-Extra: rdkit
51
+ Requires-Dist: rdkit>=2026.3.1; extra == "rdkit"
52
+ Provides-Extra: all
53
+ Requires-Dist: rdkit>=2026.3.1; extra == "all"
54
+ Requires-Dist: laplace-torch<1.0,>=0.2; extra == "all"
50
55
  Dynamic: license-file
51
56
 
52
57
  # MaldiDeepKit
@@ -79,7 +84,7 @@ Dynamic: license-file
79
84
  <a href="#license">License</a>
80
85
  </p>
81
86
 
82
- MaldiDeepKit is part of the **MaldiSuite** ecosystem and complements [MaldiAMRKit](https://github.com/EttoreRocchi/MaldiAMRKit) and [MaldiBatchKit](https://github.com/EttoreRocchi/MaldiBatchKit): where MaldiAMRKit handles preprocessing, alignment and AMR-aware evaluation, and MaldiBatchKit harmonises multi-centre spectra, MaldiDeepKit focuses on the *classification* step, providing four PyTorch architectures wrapped in a unified scikit-learn estimator API with defaults calibrated for 6000-bin MALDI-TOF input.
87
+ MaldiDeepKit is part of the **MaldiSuite** ecosystem and complements [MaldiAMRKit](https://github.com/EttoreRocchi/MaldiAMRKit) and [MaldiBatchKit](https://github.com/EttoreRocchi/MaldiBatchKit): where MaldiAMRKit handles preprocessing, alignment and AMR-aware evaluation, and MaldiBatchKit harmonises multi-centre spectra, MaldiDeepKit focuses on the *classification* step, providing four PyTorch backbone architectures (MLP, 1-D CNN, 1-D ResNet, and 1-D Vision Transformer) plus a dual-branch classifier for spectrum + side-feature inputs and a self-training meta-classifier for semi-supervised data, all wrapped in a unified scikit-learn estimator API with defaults calibrated for 6000-bin MALDI-TOF input.
83
88
 
84
89
  ## Installation
85
90
 
@@ -87,7 +92,7 @@ MaldiDeepKit is part of the **MaldiSuite** ecosystem and complements [MaldiAMRKi
87
92
  pip install maldideepkit
88
93
  ```
89
94
 
90
- `maldiamrkit` is a core dependency and is installed automatically - MaldiDeepKit duck-types on the `MaldiSet` data model and reuses `maldiamrkit.alignment.Warping` for leak-safe spectral warping.
95
+ `maldiamrkit` is a core dependency and is installed automatically (MaldiDeepKit duck-types on the `MaldiSet` data model and reuses `maldiamrkit.alignment.Warping` for leak-safe spectral warping).
91
96
 
92
97
  To enable the Laplace-approximation estimator in `maldideepkit.uncertainty`, install the optional `uncertainty` extra (pulls in [`laplace-torch`](https://github.com/aleximmer/Laplace)):
93
98
 
@@ -97,6 +102,12 @@ pip install "maldideepkit[uncertainty]"
97
102
 
98
103
  Monte Carlo Dropout and split conformal prediction work without any extra.
99
104
 
105
+ The `smiles_to_fingerprint` helper used in `MaldiDualBranchClassifier` workflows requires RDKit, available via the `rdkit` extra:
106
+
107
+ ```bash
108
+ pip install "maldideepkit[rdkit]"
109
+ ```
110
+
100
111
  ### Install the full MaldiSuite
101
112
 
102
113
  To install MaldiDeepKit together with [MaldiAMRKit](https://github.com/EttoreRocchi/MaldiAMRKit) and [MaldiBatchKit](https://github.com/EttoreRocchi/MaldiBatchKit) at compatible versions, install the [`maldisuite`](https://pypi.org/project/maldisuite/) meta-package:
@@ -128,7 +139,7 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for coding conventions, testing, and PR
128
139
  - `MaldiTransformerClassifier` - 1-D Vision Transformer with global self-attention, pre-norm, LayerScale, and stochastic depth.
129
140
  - **MALDI-TOF defaults**: kernel sizes, depths, patch widths, and warmup / cosine-annealing schedules are tuned for 6000-bin spectra in the 2000-20000 Da range.
130
141
  - **Auto-scaling for non-default layouts**: every classifier ships a `from_spectrum(bin_width, input_dim, **overrides)` factory that rescales conv kernels and patches when the user trims the m/z range or picks a different bin width. See the [Spectrum scaling guide](https://maldideepkit.readthedocs.io/en/latest/spectrum_scaling.html).
131
- - **Training recipes**: AdamW-on-`weight_decay` dispatch, gradient clipping, linear warmup + cosine annealing, focal loss, label smoothing, mixed precision (AMP), Stochastic Weight Averaging, Sharpness-Aware Minimization, post-hoc threshold tuning, and temperature scaling - all exposed as classifier kwargs.
142
+ - **Training recipes**: AdamW-on-`weight_decay` dispatch, gradient clipping, linear warmup + cosine annealing, focal loss, label smoothing, mixed precision (AMP), Stochastic Weight Averaging, Sharpness-Aware Minimization, post-hoc threshold tuning, and temperature scaling (all exposed as classifier kwargs).
132
143
  - **Leak-safe spectral warping**: pass any sklearn-style transformer ([`maldiamrkit.alignment.Warping`](https://github.com/EttoreRocchi/MaldiAMRKit)) via `warping=`; it is fitted on the training fold only and applied to both splits during training and to new spectra at `predict` time, *before* per-feature standardization.
133
144
  - **MaldiSet integration**: pass a `maldiamrkit.MaldiSet` directly to `fit` / `predict`; MaldiDeepKit duck-types on the DataFrame-like `.X` attribute, so MaldiSuite's data model flows end-to-end.
134
145
  - **Persistence**: `save()` writes a state-dict `.pt` plus a hyperparameter `.json` (and a sibling `.warper.pkl` if a warper was fitted); `load()` fails fast on class or `input_dim` mismatches.
@@ -236,8 +247,15 @@ For more examples covering training recipes, calibration, attention inspection,
236
247
  | `MaldiCNNClassifier` | 1-D Conv1D + BatchNorm + ReLU + MaxPool blocks | Local pattern learning from binned spectra |
237
248
  | `MaldiResNetClassifier` | 1-D ResNet-18-style residual blocks | Deeper convolutional backbone |
238
249
  | `MaldiTransformerClassifier` | 1-D Vision Transformer (LayerScale, stochastic depth) | Long-range peak combinations via global self-attention |
250
+ | `MaldiDualBranchClassifier` | Spectrum branch + per-row side-feature branch + fused head | Multi-modal: condition on antibiotic fingerprint, instrument ID, patient feature, ... |
251
+
252
+ All five inherit from `BaseSpectralClassifier` (or its dual-input subclass `BaseDualSpectralClassifier`) and share the same hyperparameter surface for optimisation, device placement, early stopping, calibration, and persistence.
253
+
254
+ ### Semi-supervised wrapper
239
255
 
240
- All four inherit from `BaseSpectralClassifier` and share the same hyperparameter surface for optimisation, device placement, early stopping, calibration, and persistence.
256
+ | Class | Purpose |
257
+ |---|---|
258
+ | `MaldiSelfTrainingClassifier` | Iterative pseudo-labeling on a labeled seed plus an unlabeled pool (rows marked `-1` by default). Wraps any classifier (defaults to `MaldiMLPClassifier`); follows the `sklearn.semi_supervised.SelfTrainingClassifier` API. |
241
259
 
242
260
  ### Shared Training Knobs
243
261
 
@@ -247,7 +265,7 @@ All four inherit from `BaseSpectralClassifier` and share the same hyperparameter
247
265
  | Gradient clipping | `grad_clip_norm` | `clip_grad_norm_` before every step. Default on (`1.0`) for the deep models. |
248
266
  | Warmup + cosine annealing | `warmup_epochs` | Replaces plateau scheduler. Default `5` (deep models), `0` (MLP/CNN). |
249
267
  | Stochastic depth (Transformer) | `drop_path_rate` | Linearly ramped across blocks. Default `0.1`. |
250
- | LayerScale (Transformer) | `layerscale_init` | Per-channel residual scaling initialised near zero - crucial on small cohorts. |
268
+ | LayerScale (Transformer) | `layerscale_init` | Per-channel residual scaling initialised near zero (crucial on small cohorts). |
251
269
  | Focal loss | `loss="focal"` + `focal_gamma` | For imbalanced binary problems. |
252
270
  | Label smoothing | `label_smoothing` | Passed to both cross-entropy and focal paths. |
253
271
  | Stochastic Weight Averaging | `swa_start_epoch` | `AveragedModel` replaces best-val at end of fit. |
@@ -255,6 +273,8 @@ All four inherit from `BaseSpectralClassifier` and share the same hyperparameter
255
273
  | Temperature scaling | `calibrate_temperature` | One-parameter LBFGS calibration on val logits. |
256
274
  | Sharpness-Aware Minimization | `use_sam` + `sam_rho` | Two-pass training, ~2× compute. |
257
275
  | Spectral warping | `warping` | Any `Warping`-like sklearn transformer; fitted on train only, applied before standardization. |
276
+ | Per-sample weighting | `sample_weight` (`fit`) | sklearn-style weighted-mean loss; composes with `class_weight` and flows into early stopping, threshold tuning, and temperature scaling. |
277
+ | Warm-start / resume | `warm_start` (`fit`) | Reuse the existing fitted module as the training start point (continual / federated / fine-tuning). |
258
278
 
259
279
  ### Utilities
260
280
 
@@ -28,7 +28,7 @@
28
28
  <a href="#license">License</a>
29
29
  </p>
30
30
 
31
- MaldiDeepKit is part of the **MaldiSuite** ecosystem and complements [MaldiAMRKit](https://github.com/EttoreRocchi/MaldiAMRKit) and [MaldiBatchKit](https://github.com/EttoreRocchi/MaldiBatchKit): where MaldiAMRKit handles preprocessing, alignment and AMR-aware evaluation, and MaldiBatchKit harmonises multi-centre spectra, MaldiDeepKit focuses on the *classification* step, providing four PyTorch architectures wrapped in a unified scikit-learn estimator API with defaults calibrated for 6000-bin MALDI-TOF input.
31
+ MaldiDeepKit is part of the **MaldiSuite** ecosystem and complements [MaldiAMRKit](https://github.com/EttoreRocchi/MaldiAMRKit) and [MaldiBatchKit](https://github.com/EttoreRocchi/MaldiBatchKit): where MaldiAMRKit handles preprocessing, alignment and AMR-aware evaluation, and MaldiBatchKit harmonises multi-centre spectra, MaldiDeepKit focuses on the *classification* step, providing four PyTorch backbone architectures (MLP, 1-D CNN, 1-D ResNet, and 1-D Vision Transformer) plus a dual-branch classifier for spectrum + side-feature inputs and a self-training meta-classifier for semi-supervised data, all wrapped in a unified scikit-learn estimator API with defaults calibrated for 6000-bin MALDI-TOF input.
32
32
 
33
33
  ## Installation
34
34
 
@@ -36,7 +36,7 @@ MaldiDeepKit is part of the **MaldiSuite** ecosystem and complements [MaldiAMRKi
36
36
  pip install maldideepkit
37
37
  ```
38
38
 
39
- `maldiamrkit` is a core dependency and is installed automatically - MaldiDeepKit duck-types on the `MaldiSet` data model and reuses `maldiamrkit.alignment.Warping` for leak-safe spectral warping.
39
+ `maldiamrkit` is a core dependency and is installed automatically (MaldiDeepKit duck-types on the `MaldiSet` data model and reuses `maldiamrkit.alignment.Warping` for leak-safe spectral warping).
40
40
 
41
41
  To enable the Laplace-approximation estimator in `maldideepkit.uncertainty`, install the optional `uncertainty` extra (pulls in [`laplace-torch`](https://github.com/aleximmer/Laplace)):
42
42
 
@@ -46,6 +46,12 @@ pip install "maldideepkit[uncertainty]"
46
46
 
47
47
  Monte Carlo Dropout and split conformal prediction work without any extra.
48
48
 
49
+ The `smiles_to_fingerprint` helper used in `MaldiDualBranchClassifier` workflows requires RDKit, available via the `rdkit` extra:
50
+
51
+ ```bash
52
+ pip install "maldideepkit[rdkit]"
53
+ ```
54
+
49
55
  ### Install the full MaldiSuite
50
56
 
51
57
  To install MaldiDeepKit together with [MaldiAMRKit](https://github.com/EttoreRocchi/MaldiAMRKit) and [MaldiBatchKit](https://github.com/EttoreRocchi/MaldiBatchKit) at compatible versions, install the [`maldisuite`](https://pypi.org/project/maldisuite/) meta-package:
@@ -77,7 +83,7 @@ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for coding conventions, testing, and PR
77
83
  - `MaldiTransformerClassifier` - 1-D Vision Transformer with global self-attention, pre-norm, LayerScale, and stochastic depth.
78
84
  - **MALDI-TOF defaults**: kernel sizes, depths, patch widths, and warmup / cosine-annealing schedules are tuned for 6000-bin spectra in the 2000-20000 Da range.
79
85
  - **Auto-scaling for non-default layouts**: every classifier ships a `from_spectrum(bin_width, input_dim, **overrides)` factory that rescales conv kernels and patches when the user trims the m/z range or picks a different bin width. See the [Spectrum scaling guide](https://maldideepkit.readthedocs.io/en/latest/spectrum_scaling.html).
80
- - **Training recipes**: AdamW-on-`weight_decay` dispatch, gradient clipping, linear warmup + cosine annealing, focal loss, label smoothing, mixed precision (AMP), Stochastic Weight Averaging, Sharpness-Aware Minimization, post-hoc threshold tuning, and temperature scaling - all exposed as classifier kwargs.
86
+ - **Training recipes**: AdamW-on-`weight_decay` dispatch, gradient clipping, linear warmup + cosine annealing, focal loss, label smoothing, mixed precision (AMP), Stochastic Weight Averaging, Sharpness-Aware Minimization, post-hoc threshold tuning, and temperature scaling (all exposed as classifier kwargs).
81
87
  - **Leak-safe spectral warping**: pass any sklearn-style transformer ([`maldiamrkit.alignment.Warping`](https://github.com/EttoreRocchi/MaldiAMRKit)) via `warping=`; it is fitted on the training fold only and applied to both splits during training and to new spectra at `predict` time, *before* per-feature standardization.
82
88
  - **MaldiSet integration**: pass a `maldiamrkit.MaldiSet` directly to `fit` / `predict`; MaldiDeepKit duck-types on the DataFrame-like `.X` attribute, so MaldiSuite's data model flows end-to-end.
83
89
  - **Persistence**: `save()` writes a state-dict `.pt` plus a hyperparameter `.json` (and a sibling `.warper.pkl` if a warper was fitted); `load()` fails fast on class or `input_dim` mismatches.
@@ -185,8 +191,15 @@ For more examples covering training recipes, calibration, attention inspection,
185
191
  | `MaldiCNNClassifier` | 1-D Conv1D + BatchNorm + ReLU + MaxPool blocks | Local pattern learning from binned spectra |
186
192
  | `MaldiResNetClassifier` | 1-D ResNet-18-style residual blocks | Deeper convolutional backbone |
187
193
  | `MaldiTransformerClassifier` | 1-D Vision Transformer (LayerScale, stochastic depth) | Long-range peak combinations via global self-attention |
194
+ | `MaldiDualBranchClassifier` | Spectrum branch + per-row side-feature branch + fused head | Multi-modal: condition on antibiotic fingerprint, instrument ID, patient feature, ... |
195
+
196
+ All five inherit from `BaseSpectralClassifier` (or its dual-input subclass `BaseDualSpectralClassifier`) and share the same hyperparameter surface for optimisation, device placement, early stopping, calibration, and persistence.
197
+
198
+ ### Semi-supervised wrapper
188
199
 
189
- All four inherit from `BaseSpectralClassifier` and share the same hyperparameter surface for optimisation, device placement, early stopping, calibration, and persistence.
200
+ | Class | Purpose |
201
+ |---|---|
202
+ | `MaldiSelfTrainingClassifier` | Iterative pseudo-labeling on a labeled seed plus an unlabeled pool (rows marked `-1` by default). Wraps any classifier (defaults to `MaldiMLPClassifier`); follows the `sklearn.semi_supervised.SelfTrainingClassifier` API. |
190
203
 
191
204
  ### Shared Training Knobs
192
205
 
@@ -196,7 +209,7 @@ All four inherit from `BaseSpectralClassifier` and share the same hyperparameter
196
209
  | Gradient clipping | `grad_clip_norm` | `clip_grad_norm_` before every step. Default on (`1.0`) for the deep models. |
197
210
  | Warmup + cosine annealing | `warmup_epochs` | Replaces plateau scheduler. Default `5` (deep models), `0` (MLP/CNN). |
198
211
  | Stochastic depth (Transformer) | `drop_path_rate` | Linearly ramped across blocks. Default `0.1`. |
199
- | LayerScale (Transformer) | `layerscale_init` | Per-channel residual scaling initialised near zero - crucial on small cohorts. |
212
+ | LayerScale (Transformer) | `layerscale_init` | Per-channel residual scaling initialised near zero (crucial on small cohorts). |
200
213
  | Focal loss | `loss="focal"` + `focal_gamma` | For imbalanced binary problems. |
201
214
  | Label smoothing | `label_smoothing` | Passed to both cross-entropy and focal paths. |
202
215
  | Stochastic Weight Averaging | `swa_start_epoch` | `AveragedModel` replaces best-val at end of fit. |
@@ -204,6 +217,8 @@ All four inherit from `BaseSpectralClassifier` and share the same hyperparameter
204
217
  | Temperature scaling | `calibrate_temperature` | One-parameter LBFGS calibration on val logits. |
205
218
  | Sharpness-Aware Minimization | `use_sam` + `sam_rho` | Two-pass training, ~2× compute. |
206
219
  | Spectral warping | `warping` | Any `Warping`-like sklearn transformer; fitted on train only, applied before standardization. |
220
+ | Per-sample weighting | `sample_weight` (`fit`) | sklearn-style weighted-mean loss; composes with `class_weight` and flows into early stopping, threshold tuning, and temperature scaling. |
221
+ | Warm-start / resume | `warm_start` (`fit`) | Reuse the existing fitted module as the training start point (continual / federated / fine-tuning). |
207
222
 
208
223
  ### Utilities
209
224
 
@@ -6,16 +6,23 @@ a scikit-learn compatible estimator with sensible defaults.
6
6
 
7
7
  Subpackages
8
8
  -----------
9
- - ``maldideepkit.base`` - ``BaseSpectralClassifier``, ``SpectralDataset``,
10
- ``make_loaders``.
9
+ - ``maldideepkit.base`` - ``BaseSpectralClassifier``,
10
+ ``BaseDualSpectralClassifier``, ``SpectralDataset``, ``make_loaders``.
11
11
  - ``maldideepkit.attention`` - ``MaldiMLPClassifier`` (MLP with optional
12
12
  sigmoid-gated attention).
13
13
  - ``maldideepkit.cnn`` - ``MaldiCNNClassifier`` (Conv1D blocks).
14
14
  - ``maldideepkit.resnet`` - ``MaldiResNetClassifier`` (1-D ResNet-18).
15
15
  - ``maldideepkit.transformer`` - ``MaldiTransformerClassifier`` (1-D ViT).
16
+ - ``maldideepkit.dual_branch`` - ``MaldiDualBranchClassifier``
17
+ (spectrum + per-row side feature) and ``smiles_to_fingerprint``
18
+ helper (requires the ``[rdkit]`` extra).
19
+ - ``maldideepkit.semisupervised`` - ``MaldiSelfTrainingClassifier``
20
+ (iterative pseudo-labeling meta-classifier).
16
21
  - ``maldideepkit.blocks`` - re-exports of every backbone and
17
22
  composable primitive for users embedding components into their own
18
23
  networks.
24
+ - ``maldideepkit.augment`` - per-batch training-time augmentations
25
+ (``SpectrumAugment``, MixUp / CutMix helpers).
19
26
  - ``maldideepkit.utils`` - reproducibility helpers and shared
20
27
  training primitives.
21
28
  - ``maldideepkit.uncertainty`` - uncertainty-quantification
@@ -38,22 +45,29 @@ from . import uncertainty
38
45
  from .attention.mlp import MaldiMLPClassifier
39
46
  from .base.classifier import BaseSpectralClassifier
40
47
  from .base.data import SpectralDataset, make_loaders
48
+ from .base.dual import BaseDualSpectralClassifier
41
49
  from .cnn.cnn import MaldiCNNClassifier
50
+ from .dual_branch import MaldiDualBranchClassifier, smiles_to_fingerprint
42
51
  from .resnet.resnet import MaldiResNetClassifier
52
+ from .semisupervised import MaldiSelfTrainingClassifier
43
53
  from .transformer.transformer import MaldiTransformerClassifier
44
54
 
45
- __version__ = "0.2.0"
55
+ __version__ = "0.3.0"
46
56
  __author__ = "Ettore Rocchi"
47
57
 
48
58
  __all__ = [
59
+ "BaseDualSpectralClassifier",
49
60
  "BaseSpectralClassifier",
50
61
  "MaldiCNNClassifier",
62
+ "MaldiDualBranchClassifier",
51
63
  "MaldiMLPClassifier",
52
64
  "MaldiResNetClassifier",
65
+ "MaldiSelfTrainingClassifier",
53
66
  "MaldiTransformerClassifier",
54
67
  "SpectralDataset",
55
68
  "__author__",
56
69
  "__version__",
57
70
  "make_loaders",
71
+ "smiles_to_fingerprint",
58
72
  "uncertainty",
59
73
  ]
@@ -261,13 +261,19 @@ class MaldiMLPClassifier(BaseSpectralClassifier):
261
261
  return logits
262
262
 
263
263
  def fit( # type: ignore[override]
264
- self, X: Any, y: Any, *, warm_start: bool = False
264
+ self,
265
+ X: Any,
266
+ y: Any,
267
+ *,
268
+ sample_weight: Any | None = None,
269
+ warm_start: bool = False,
265
270
  ) -> MaldiMLPClassifier:
266
271
  """Fit the model and cache attention weights from the final batch.
267
272
 
268
- See :meth:`BaseSpectralClassifier.fit` for shared parameters, including ``warm_start``.
273
+ See :meth:`BaseSpectralClassifier.fit` for shared parameters,
274
+ including ``sample_weight`` and ``warm_start``.
269
275
  """
270
- super().fit(X, y, warm_start=warm_start)
276
+ super().fit(X, y, sample_weight=sample_weight, warm_start=warm_start)
271
277
  if self.use_attention:
272
278
  X_np = _to_numpy(X)
273
279
  tail = X_np[: min(len(X_np), 64)]
@@ -38,7 +38,11 @@ def apply_mixup(
38
38
  y_oh: torch.Tensor,
39
39
  alpha: float,
40
40
  generator: torch.Generator | None = None,
41
- ) -> tuple[torch.Tensor, torch.Tensor]:
41
+ *,
42
+ sample_weight: torch.Tensor | None = None,
43
+ ) -> (
44
+ tuple[torch.Tensor, torch.Tensor] | tuple[torch.Tensor, torch.Tensor, torch.Tensor]
45
+ ):
42
46
  """Mixup: convex-combine two random permutations of the batch.
43
47
 
44
48
  Parameters
@@ -52,11 +56,19 @@ def apply_mixup(
52
56
  values 0.1-0.4 for tabular-ish inputs. Must be ``> 0``.
53
57
  generator : torch.Generator or None, default=None
54
58
  Seeded RNG for reproducibility.
59
+ sample_weight : torch.Tensor or None, keyword-only, default=None
60
+ Optional per-sample weight tensor of shape ``(batch,)``. When
61
+ given, the function returns a 3-tuple whose third element is
62
+ ``lam * sample_weight + (1 - lam) * sample_weight[perm]`` (the
63
+ same convex combination applied to ``y_oh``). When ``None``
64
+ the function returns a 2-tuple for backward compatibility.
55
65
 
56
66
  Returns
57
67
  -------
58
68
  tuple of torch.Tensor
59
- ``(x_mixed, y_mixed)`` with the same shapes as the inputs.
69
+ ``(x_mixed, y_mixed)`` or ``(x_mixed, y_mixed, sw_mixed)``
70
+ when ``sample_weight`` is provided. ``x_mixed`` and ``y_mixed``
71
+ have the same shape as the inputs.
60
72
  """
61
73
  if alpha <= 0:
62
74
  raise ValueError(f"mixup alpha must be > 0; got {alpha!r}.")
@@ -64,7 +76,11 @@ def apply_mixup(
64
76
  perm = torch.randperm(x.shape[0], generator=generator).to(x.device)
65
77
  x_mixed = lam * x + (1.0 - lam) * x[perm]
66
78
  y_mixed = lam * y_oh + (1.0 - lam) * y_oh[perm]
67
- return x_mixed, y_mixed
79
+ if sample_weight is None:
80
+ return x_mixed, y_mixed
81
+ sw = sample_weight.to(device=x.device)
82
+ sw_mixed = lam * sw + (1.0 - lam) * sw[perm]
83
+ return x_mixed, y_mixed, sw_mixed
68
84
 
69
85
 
70
86
  def apply_cutmix(
@@ -72,7 +88,12 @@ def apply_cutmix(
72
88
  y_oh: torch.Tensor,
73
89
  alpha: float,
74
90
  generator: torch.Generator | None = None,
75
- ) -> tuple[torch.Tensor, torch.Tensor]:
91
+ *,
92
+ sample_weight: torch.Tensor | None = None,
93
+ n_spec_cols: int | None = None,
94
+ ) -> (
95
+ tuple[torch.Tensor, torch.Tensor] | tuple[torch.Tensor, torch.Tensor, torch.Tensor]
96
+ ):
76
97
  """CutMix on 1-D spectra: splice a contiguous m/z window.
77
98
 
78
99
  A window of length ``w = round(n_bins * (1 - lam))`` is drawn
@@ -90,24 +111,55 @@ def apply_cutmix(
90
111
  value 1.0 (uniform over window fractions). Must be ``> 0``.
91
112
  generator : torch.Generator or None, default=None
92
113
  Seeded RNG for reproducibility.
114
+ n_spec_cols : int or None, keyword-only, default=None
115
+ Number of leading spectrum columns. When given (dual-input
116
+ models, where ``x`` is ``[ X_spec | X_side ]``), the spliced
117
+ window is drawn **inside the spectrum block only** and the
118
+ window fraction is taken relative to ``n_spec_cols`` rather
119
+ than the full width. The trailing side columns are instead
120
+ convex-combined with the same ``effective_lam`` and the same
121
+ permutation, so a contiguous m/z window never straddles the
122
+ spectrum / side boundary and ``(x, y)`` stay consistent.
123
+ ``None`` treats every column as spectrum.
124
+ sample_weight : torch.Tensor or None, keyword-only, default=None
125
+ Optional per-sample weight tensor of shape ``(batch,)``. When
126
+ given, the function returns a 3-tuple whose third element is
127
+ ``effective_lam * sample_weight + (1 - effective_lam) *
128
+ sample_weight[perm]`` (matching how labels are mixed). When
129
+ ``None`` the function returns a 2-tuple for backward
130
+ compatibility.
93
131
 
94
132
  Returns
95
133
  -------
96
134
  tuple of torch.Tensor
97
- ``(x_mixed, y_mixed)`` with the same shapes as the inputs.
135
+ ``(x_mixed, y_mixed)`` or ``(x_mixed, y_mixed, sw_mixed)``
136
+ when ``sample_weight`` is provided.
98
137
  """
99
138
  if alpha <= 0:
100
139
  raise ValueError(f"cutmix alpha must be > 0; got {alpha!r}.")
101
140
  batch, n_bins = x.shape
141
+ n_cut = n_bins if n_spec_cols is None else min(int(n_spec_cols), n_bins)
142
+ if n_cut <= 0:
143
+ raise ValueError(f"n_spec_cols must be > 0; got {n_spec_cols!r}.")
102
144
  lam = _sample_beta(alpha, generator)
103
- window = int(round(n_bins * (1.0 - lam)))
104
- window = max(0, min(window, n_bins))
145
+ window = int(round(n_cut * (1.0 - lam)))
146
+ window = max(0, min(window, n_cut))
105
147
  if window == 0:
106
- return x.clone(), y_oh.clone()
107
- start = int(torch.randint(0, n_bins - window + 1, (1,), generator=generator).item())
148
+ if sample_weight is None:
149
+ return x.clone(), y_oh.clone()
150
+ return x.clone(), y_oh.clone(), sample_weight.to(device=x.device).clone()
151
+ start = int(torch.randint(0, n_cut - window + 1, (1,), generator=generator).item())
108
152
  perm = torch.randperm(batch, generator=generator).to(x.device)
109
153
  x_mixed = x.clone()
110
154
  x_mixed[:, start : start + window] = x[perm][:, start : start + window]
111
- effective_lam = 1.0 - window / n_bins
155
+ effective_lam = 1.0 - window / n_cut
156
+ if n_cut < n_bins:
157
+ x_mixed[:, n_cut:] = (
158
+ effective_lam * x[:, n_cut:] + (1.0 - effective_lam) * x[perm][:, n_cut:]
159
+ )
112
160
  y_mixed = effective_lam * y_oh + (1.0 - effective_lam) * y_oh[perm]
113
- return x_mixed, y_mixed
161
+ if sample_weight is None:
162
+ return x_mixed, y_mixed
163
+ sw = sample_weight.to(device=x.device)
164
+ sw_mixed = effective_lam * sw + (1.0 - effective_lam) * sw[perm]
165
+ return x_mixed, y_mixed, sw_mixed
@@ -0,0 +1,23 @@
1
+ """Shared primitives for MaldiDeepKit classifiers.
2
+
3
+ Exposes:
4
+
5
+ - :class:`BaseSpectralClassifier` - abstract base for the four
6
+ single-input classifier families (MLP, CNN, ResNet, Transformer).
7
+ - :class:`BaseDualSpectralClassifier` - abstract base for dual-input
8
+ classifiers (e.g. spectrum + antibiotic fingerprint); the side
9
+ input is packed into ``X`` by concatenation to stay
10
+ sklearn-pipeline-friendly.
11
+ - :class:`SpectralDataset` / :func:`make_loaders` - data utilities.
12
+ """
13
+
14
+ from .classifier import BaseSpectralClassifier
15
+ from .data import SpectralDataset, make_loaders
16
+ from .dual import BaseDualSpectralClassifier
17
+
18
+ __all__ = [
19
+ "BaseDualSpectralClassifier",
20
+ "BaseSpectralClassifier",
21
+ "SpectralDataset",
22
+ "make_loaders",
23
+ ]