MaldiDeepKit 0.1.0__py3-none-any.whl
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.
- maldideepkit/__init__.py +54 -0
- maldideepkit/_bin_scaling.py +52 -0
- maldideepkit/_blocks.py +80 -0
- maldideepkit/attention/__init__.py +5 -0
- maldideepkit/attention/mlp.py +319 -0
- maldideepkit/augment/__init__.py +15 -0
- maldideepkit/augment/mixing.py +113 -0
- maldideepkit/augment/spectra.py +251 -0
- maldideepkit/base/__init__.py +15 -0
- maldideepkit/base/classifier.py +1079 -0
- maldideepkit/base/data.py +322 -0
- maldideepkit/blocks.py +39 -0
- maldideepkit/cnn/__init__.py +5 -0
- maldideepkit/cnn/cnn.py +316 -0
- maldideepkit/py.typed +0 -0
- maldideepkit/resnet/__init__.py +5 -0
- maldideepkit/resnet/resnet.py +380 -0
- maldideepkit/transformer/__init__.py +7 -0
- maldideepkit/transformer/transformer.py +492 -0
- maldideepkit/utils/__init__.py +22 -0
- maldideepkit/utils/calibration.py +134 -0
- maldideepkit/utils/ensemble.py +132 -0
- maldideepkit/utils/loss.py +138 -0
- maldideepkit/utils/lr_finder.py +173 -0
- maldideepkit/utils/reproducibility.py +70 -0
- maldideepkit/utils/sam.py +121 -0
- maldideepkit/utils/training.py +386 -0
- maldideepkit-0.1.0.dist-info/METADATA +301 -0
- maldideepkit-0.1.0.dist-info/RECORD +32 -0
- maldideepkit-0.1.0.dist-info/WHEEL +5 -0
- maldideepkit-0.1.0.dist-info/licenses/LICENSE +21 -0
- maldideepkit-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Composable per-batch augmentations for MALDI-TOF binned spectra.
|
|
2
|
+
|
|
3
|
+
All transforms operate on a ``torch.Tensor`` of shape ``(batch, n_bins)``.
|
|
4
|
+
Each augmentation step is gated by a parameter and is a no-op at its
|
|
5
|
+
default value. Applied in order:
|
|
6
|
+
|
|
7
|
+
1. Additive Gaussian noise (``noise_std``).
|
|
8
|
+
2. Per-sample intensity jitter (``intensity_jitter``).
|
|
9
|
+
3. Random peak dropout (``peak_dropout_rate``).
|
|
10
|
+
4. Per-sample m/z shift (``mz_shift_max_bins``).
|
|
11
|
+
5. Spline-based m/z warp (``mz_warp_max_bins`` + ``mz_warp_n_knots``).
|
|
12
|
+
6. Gaussian blur (``blur_sigma``).
|
|
13
|
+
|
|
14
|
+
Only invoked on training batches. All m/z-axis parameters are specified
|
|
15
|
+
in *bins*, not Daltons.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import math
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
import torch
|
|
24
|
+
from scipy.interpolate import CubicSpline
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SpectrumAugment:
|
|
28
|
+
"""Composable per-batch spectrum augmentation.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
noise_std : float, default=0.0
|
|
33
|
+
Standard deviation of additive Gaussian noise.
|
|
34
|
+
intensity_jitter : float, default=0.0
|
|
35
|
+
Half-range of the per-sample multiplicative jitter: every
|
|
36
|
+
sample is scaled by ``1 + U(-jitter, jitter)``. Must be in
|
|
37
|
+
``[0, 1)``.
|
|
38
|
+
peak_dropout_rate : float, default=0.0
|
|
39
|
+
Per-bin Bernoulli zero-out probability. Must be in ``[0, 1)``.
|
|
40
|
+
mz_shift_max_bins : int, default=0
|
|
41
|
+
Per-sample global m/z shift, drawn uniformly in
|
|
42
|
+
``[-mz_shift_max_bins, +mz_shift_max_bins]`` and applied with
|
|
43
|
+
:func:`torch.roll`. Units are bins. Must be non-negative.
|
|
44
|
+
mz_warp_max_bins : int, default=0
|
|
45
|
+
Peak amplitude (in bins) of a smooth cubic-spline warp of the
|
|
46
|
+
m/z axis. ``0`` disables the warp. Runs on CPU. A warning is
|
|
47
|
+
emitted when the amplitude exceeds 5 % of ``n_bins``, since
|
|
48
|
+
beyond that the boundary clipping starts to dominate the
|
|
49
|
+
augmentation distribution. Must be non-negative.
|
|
50
|
+
mz_warp_n_knots : int, default=10
|
|
51
|
+
Number of interior spline control points for the m/z warp.
|
|
52
|
+
Only used when ``mz_warp_max_bins > 0``. Must be non-negative.
|
|
53
|
+
blur_sigma : float, default=0.0
|
|
54
|
+
Standard deviation (in bins) of a 1-D Gaussian blur along the
|
|
55
|
+
m/z axis. Zero disables the blur.
|
|
56
|
+
random_state : int, optional
|
|
57
|
+
If provided, the transform is seeded for deterministic batches.
|
|
58
|
+
When ``None`` (default), PyTorch's global RNG is used.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
noise_std: float = 0.0,
|
|
64
|
+
intensity_jitter: float = 0.0,
|
|
65
|
+
peak_dropout_rate: float = 0.0,
|
|
66
|
+
mz_shift_max_bins: int = 0,
|
|
67
|
+
mz_warp_max_bins: int = 0,
|
|
68
|
+
mz_warp_n_knots: int = 10,
|
|
69
|
+
blur_sigma: float = 0.0,
|
|
70
|
+
random_state: int | None = None,
|
|
71
|
+
) -> None:
|
|
72
|
+
if noise_std < 0:
|
|
73
|
+
raise ValueError(f"noise_std must be >= 0; got {noise_std!r}.")
|
|
74
|
+
if not 0.0 <= intensity_jitter < 1.0:
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"intensity_jitter must be in [0, 1); got {intensity_jitter!r}."
|
|
77
|
+
)
|
|
78
|
+
if not 0.0 <= peak_dropout_rate < 1.0:
|
|
79
|
+
raise ValueError(
|
|
80
|
+
f"peak_dropout_rate must be in [0, 1); got {peak_dropout_rate!r}."
|
|
81
|
+
)
|
|
82
|
+
if mz_shift_max_bins < 0:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"mz_shift_max_bins must be >= 0; got {mz_shift_max_bins!r}."
|
|
85
|
+
)
|
|
86
|
+
if mz_warp_max_bins < 0:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"mz_warp_max_bins must be >= 0; got {mz_warp_max_bins!r}."
|
|
89
|
+
)
|
|
90
|
+
if mz_warp_n_knots < 0:
|
|
91
|
+
raise ValueError(f"mz_warp_n_knots must be >= 0; got {mz_warp_n_knots!r}.")
|
|
92
|
+
if blur_sigma < 0:
|
|
93
|
+
raise ValueError(f"blur_sigma must be >= 0; got {blur_sigma!r}.")
|
|
94
|
+
self.noise_std = float(noise_std)
|
|
95
|
+
self.intensity_jitter = float(intensity_jitter)
|
|
96
|
+
self.peak_dropout_rate = float(peak_dropout_rate)
|
|
97
|
+
self.mz_shift_max_bins = int(mz_shift_max_bins)
|
|
98
|
+
self.mz_warp_max_bins = int(mz_warp_max_bins)
|
|
99
|
+
self.mz_warp_n_knots = int(mz_warp_n_knots)
|
|
100
|
+
self.blur_sigma = float(blur_sigma)
|
|
101
|
+
self.random_state = random_state
|
|
102
|
+
self._generator: torch.Generator | None = None
|
|
103
|
+
self._np_rng: np.random.Generator | None = None
|
|
104
|
+
self._blur_kernel: torch.Tensor | None = None
|
|
105
|
+
|
|
106
|
+
def _generator_for(self, device: torch.device) -> torch.Generator | None:
|
|
107
|
+
if self.random_state is None:
|
|
108
|
+
return None
|
|
109
|
+
if self._generator is None or self._generator.device != device:
|
|
110
|
+
self._generator = torch.Generator(device=device)
|
|
111
|
+
self._generator.manual_seed(int(self.random_state))
|
|
112
|
+
return self._generator
|
|
113
|
+
|
|
114
|
+
def _numpy_generator(self) -> np.random.Generator:
|
|
115
|
+
if self._np_rng is None:
|
|
116
|
+
self._np_rng = np.random.default_rng(self.random_state)
|
|
117
|
+
return self._np_rng
|
|
118
|
+
|
|
119
|
+
def _is_identity(self) -> bool:
|
|
120
|
+
return (
|
|
121
|
+
self.noise_std == 0.0
|
|
122
|
+
and self.intensity_jitter == 0.0
|
|
123
|
+
and self.peak_dropout_rate == 0.0
|
|
124
|
+
and self.mz_shift_max_bins == 0
|
|
125
|
+
and self.mz_warp_max_bins == 0
|
|
126
|
+
and self.blur_sigma == 0.0
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def _apply_mz_shift(
|
|
130
|
+
self, X: torch.Tensor, gen: torch.Generator | None
|
|
131
|
+
) -> torch.Tensor:
|
|
132
|
+
k = self.mz_shift_max_bins
|
|
133
|
+
shifts = torch.randint(
|
|
134
|
+
low=-k,
|
|
135
|
+
high=k + 1,
|
|
136
|
+
size=(X.shape[0],),
|
|
137
|
+
generator=gen,
|
|
138
|
+
device=X.device,
|
|
139
|
+
)
|
|
140
|
+
out = torch.empty_like(X)
|
|
141
|
+
for i in range(X.shape[0]):
|
|
142
|
+
out[i] = torch.roll(X[i], shifts=int(shifts[i].item()), dims=0)
|
|
143
|
+
return out
|
|
144
|
+
|
|
145
|
+
def _apply_spline_warp(self, X: torch.Tensor) -> torch.Tensor:
|
|
146
|
+
rng = self._numpy_generator()
|
|
147
|
+
device = X.device
|
|
148
|
+
x_np = X.detach().cpu().numpy().copy()
|
|
149
|
+
n_samples, n_bins = x_np.shape
|
|
150
|
+
if self.mz_warp_max_bins > 0.05 * n_bins:
|
|
151
|
+
import warnings as _warnings
|
|
152
|
+
|
|
153
|
+
_warnings.warn(
|
|
154
|
+
f"mz_warp_max_bins={self.mz_warp_max_bins} exceeds 5% of "
|
|
155
|
+
f"n_bins={n_bins}; warped indices outside the support are "
|
|
156
|
+
"clipped before interpolation, which produces asymmetric "
|
|
157
|
+
"edge flattening. Consider reducing mz_warp_max_bins.",
|
|
158
|
+
stacklevel=2,
|
|
159
|
+
)
|
|
160
|
+
original_indices = np.arange(n_bins, dtype=np.float64)
|
|
161
|
+
n_knots = self.mz_warp_n_knots
|
|
162
|
+
knot_positions = np.linspace(0, n_bins - 1, n_knots + 2)
|
|
163
|
+
for i in range(n_samples):
|
|
164
|
+
knot_shifts = np.zeros(n_knots + 2)
|
|
165
|
+
if n_knots > 0:
|
|
166
|
+
knot_shifts[1:-1] = rng.uniform(
|
|
167
|
+
-self.mz_warp_max_bins,
|
|
168
|
+
self.mz_warp_max_bins,
|
|
169
|
+
size=n_knots,
|
|
170
|
+
)
|
|
171
|
+
spline = CubicSpline(knot_positions, knot_shifts, bc_type="clamped")
|
|
172
|
+
smooth_shifts = spline(original_indices)
|
|
173
|
+
warped = np.clip(original_indices + smooth_shifts, 0, n_bins - 1)
|
|
174
|
+
x_np[i] = np.interp(original_indices, warped, x_np[i])
|
|
175
|
+
return torch.from_numpy(x_np).to(device=device, dtype=X.dtype)
|
|
176
|
+
|
|
177
|
+
def _build_blur_kernel(
|
|
178
|
+
self, device: torch.device, dtype: torch.dtype
|
|
179
|
+
) -> torch.Tensor:
|
|
180
|
+
if (
|
|
181
|
+
self._blur_kernel is not None
|
|
182
|
+
and self._blur_kernel.device == device
|
|
183
|
+
and self._blur_kernel.dtype == dtype
|
|
184
|
+
):
|
|
185
|
+
return self._blur_kernel
|
|
186
|
+
radius = int(math.ceil(3.0 * self.blur_sigma))
|
|
187
|
+
xs = torch.arange(-radius, radius + 1, device=device, dtype=dtype)
|
|
188
|
+
kernel = torch.exp(-0.5 * (xs / self.blur_sigma) ** 2)
|
|
189
|
+
kernel = kernel / kernel.sum()
|
|
190
|
+
self._blur_kernel = kernel.view(1, 1, -1)
|
|
191
|
+
return self._blur_kernel
|
|
192
|
+
|
|
193
|
+
def _apply_blur(self, X: torch.Tensor) -> torch.Tensor:
|
|
194
|
+
kernel = self._build_blur_kernel(X.device, X.dtype)
|
|
195
|
+
padding = kernel.shape[-1] // 2
|
|
196
|
+
x3 = X.unsqueeze(1)
|
|
197
|
+
out = torch.nn.functional.conv1d(x3, kernel, padding=padding)
|
|
198
|
+
return out.squeeze(1)
|
|
199
|
+
|
|
200
|
+
def __call__(self, X: torch.Tensor) -> torch.Tensor:
|
|
201
|
+
"""Apply the enabled augmentations to ``X``.
|
|
202
|
+
|
|
203
|
+
Returns the input unchanged if no augmentation is enabled.
|
|
204
|
+
"""
|
|
205
|
+
if self._is_identity():
|
|
206
|
+
return X
|
|
207
|
+
gen = self._generator_for(X.device)
|
|
208
|
+
out = X
|
|
209
|
+
|
|
210
|
+
if self.noise_std > 0.0:
|
|
211
|
+
noise = (
|
|
212
|
+
torch.randn(out.shape, generator=gen, device=out.device)
|
|
213
|
+
* self.noise_std
|
|
214
|
+
)
|
|
215
|
+
out = out + noise
|
|
216
|
+
|
|
217
|
+
if self.intensity_jitter > 0.0:
|
|
218
|
+
jitter = (
|
|
219
|
+
torch.rand((out.shape[0], 1), generator=gen, device=out.device) * 2.0
|
|
220
|
+
- 1.0
|
|
221
|
+
) * self.intensity_jitter
|
|
222
|
+
out = out * (1.0 + jitter)
|
|
223
|
+
|
|
224
|
+
if self.peak_dropout_rate > 0.0:
|
|
225
|
+
keep = 1.0 - self.peak_dropout_rate
|
|
226
|
+
mask = torch.empty(out.shape, device=out.device).bernoulli_(
|
|
227
|
+
keep, generator=gen
|
|
228
|
+
)
|
|
229
|
+
out = out * mask
|
|
230
|
+
|
|
231
|
+
if self.mz_shift_max_bins > 0:
|
|
232
|
+
out = self._apply_mz_shift(out, gen)
|
|
233
|
+
|
|
234
|
+
if self.mz_warp_max_bins > 0:
|
|
235
|
+
out = self._apply_spline_warp(out)
|
|
236
|
+
|
|
237
|
+
if self.blur_sigma > 0.0:
|
|
238
|
+
out = self._apply_blur(out)
|
|
239
|
+
|
|
240
|
+
return out
|
|
241
|
+
|
|
242
|
+
def __repr__(self) -> str:
|
|
243
|
+
return (
|
|
244
|
+
f"SpectrumAugment(noise_std={self.noise_std}, "
|
|
245
|
+
f"intensity_jitter={self.intensity_jitter}, "
|
|
246
|
+
f"peak_dropout_rate={self.peak_dropout_rate}, "
|
|
247
|
+
f"mz_shift_max_bins={self.mz_shift_max_bins}, "
|
|
248
|
+
f"mz_warp_max_bins={self.mz_warp_max_bins}, "
|
|
249
|
+
f"mz_warp_n_knots={self.mz_warp_n_knots}, "
|
|
250
|
+
f"blur_sigma={self.blur_sigma})"
|
|
251
|
+
)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Shared primitives for MaldiDeepKit classifiers.
|
|
2
|
+
|
|
3
|
+
Exposes :class:`BaseSpectralClassifier`, the abstract base for all six
|
|
4
|
+
model families in the package, together with the :class:`SpectralDataset`
|
|
5
|
+
/ :func:`make_loaders` data utilities.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .classifier import BaseSpectralClassifier
|
|
9
|
+
from .data import SpectralDataset, make_loaders
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"BaseSpectralClassifier",
|
|
13
|
+
"SpectralDataset",
|
|
14
|
+
"make_loaders",
|
|
15
|
+
]
|