Token Classification
Transformers
Safetensors
iona-denoising
mass-spectrometry
proteomics
tandem-mass-spectrometry
denoising
iona
custom_code
Instructions to use Gaolaboratory/iona-denoise-50m with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Gaolaboratory/iona-denoise-50m with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("token-classification", model="Gaolaboratory/iona-denoise-50m", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForTokenClassification model = AutoModelForTokenClassification.from_pretrained("Gaolaboratory/iona-denoise-50m", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 12,822 Bytes
d123296 fbc75bd d123296 fbc75bd d123296 fbc75bd d123296 fbc75bd d123296 fbc75bd d123296 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 | """Preprocessing and collation for Iona mass spectra."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
import torch
from transformers import BatchFeature, FeatureExtractionMixin
def _as_spectrum_batch(values: Any, name: str) -> tuple[list[torch.Tensor], bool]:
"""Normalize one spectrum or a batch of spectra into a tensor list."""
if isinstance(values, torch.Tensor):
if values.ndim == 1:
return [values], True
if values.ndim == 2:
return [row for row in values], False
raise ValueError(f"{name} must be one- or two-dimensional")
if not isinstance(values, (list, tuple)):
values = list(values)
if not values:
return [torch.empty(0)], True
first = values[0]
if isinstance(first, (list, tuple, torch.Tensor)) or hasattr(first, "ndim"):
return [torch.as_tensor(row) for row in values], False
return [torch.as_tensor(values)], True
class IonaProcessor(FeatureExtractionMixin):
"""Convert raw centroided spectra into padded Iona model inputs."""
model_input_names = ["mz", "log_intensity", "attention_mask"]
def __init__(
self,
max_peaks: int = 512,
padding_value: float = 0.0,
**kwargs,
):
if max_peaks <= 0:
raise ValueError("max_peaks must be positive")
super().__init__(
max_peaks=max_peaks,
padding_value=padding_value,
**kwargs,
)
self.max_peaks = max_peaks
self.padding_value = padding_value
def _process_one(
self, mz: torch.Tensor, intensity: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
mz = torch.as_tensor(mz, dtype=torch.float32)
intensity = torch.as_tensor(intensity, dtype=torch.float32)
if mz.ndim != 1 or intensity.ndim != 1:
raise ValueError("each mz and intensity spectrum must be one-dimensional")
if mz.shape != intensity.shape:
raise ValueError("each mz and intensity spectrum must have equal lengths")
if mz.numel() == 0:
raise ValueError("spectra must contain at least one peak")
if mz.numel() > self.max_peaks:
raise ValueError(f"spectra must contain at most {self.max_peaks} peaks")
if not torch.isfinite(mz).all() or not torch.isfinite(intensity).all():
raise ValueError("mz and intensity values must be finite")
if (intensity < 0).any():
raise ValueError("intensity values must be nonnegative")
base_peak = intensity.max()
if base_peak <= 0:
raise ValueError("spectra must contain at least one positive intensity")
selected = torch.arange(mz.numel(), device=mz.device)
log_intensity = torch.log1p(intensity)
log_intensity = log_intensity / log_intensity.max().clamp_min(1e-8)
labels = intensity / intensity.sum().clamp_min(1e-12)
return mz.contiguous(), log_intensity.contiguous(), labels.contiguous(), selected
def process_denoising_example(self, mz, intensity, noise) -> dict[str, list | int]:
"""Process one spectrum while preserving peak/noise-label alignment."""
noise = torch.as_tensor(noise, dtype=torch.bool)
mz_tensor = torch.as_tensor(mz, dtype=torch.float32)
intensity_tensor = torch.as_tensor(intensity, dtype=torch.float32)
if noise.ndim != 1 or noise.shape != mz_tensor.shape:
raise ValueError("mz, intensity, and noise must have equal one-dimensional shapes")
mass, log_intensity, _, selected = self._process_one(mz_tensor, intensity_tensor)
return {
"mz": mass.tolist(),
"log_intensity": log_intensity.tolist(),
"labels": noise[selected].float().tolist(),
}
def process_retrieval_example(self, consensus, experimental) -> dict[str, list]:
"""Process one consensus spectrum and its three experimental replicates."""
if len(experimental) != 3:
raise ValueError("retrieval examples must contain exactly three experimental spectra")
spectra = [consensus, *experimental]
processed = [
self(spectrum["mz"], spectrum["intensity"], padding=False) for spectrum in spectra
]
return {
"mz": [values["mz"] for values in processed],
"log_intensity": [values["log_intensity"] for values in processed],
}
def pad(
self,
encoded_inputs: list[dict[str, Any]],
*,
padding: bool | str = True,
max_length: int | None = None,
return_tensors: str | None = None,
**kwargs,
) -> BatchFeature:
"""Pad processed peak-classification examples to a common length."""
lengths = [len(example["mz"]) for example in encoded_inputs]
if padding == "max_length":
if max_length is None:
raise ValueError("max_length is required with padding='max_length'")
target_length = max_length
else:
target_length = max(lengths)
data: dict[str, list] = {
"mz": [],
"log_intensity": [],
"attention_mask": [],
"labels": [],
}
for example, length in zip(encoded_inputs, lengths):
pad = target_length - length
data["mz"].append(example["mz"] + [self.padding_value] * pad)
data["log_intensity"].append(example["log_intensity"] + [self.padding_value] * pad)
data["attention_mask"].append([1] * length + [0] * pad)
data["labels"].append(example["labels"] + [-100.0] * pad)
return BatchFeature(data=data, tensor_type=return_tensors)
def __call__(
self,
mz,
intensity,
*,
padding: bool | str = True,
truncation: bool = True,
max_length: int | None = None,
pad_to_multiple_of: int | None = None,
return_tensors: str | None = None,
return_labels: bool = False,
) -> BatchFeature:
"""Process raw m/z and intensity arrays into model-ready features."""
mz_batch, mz_was_single = _as_spectrum_batch(mz, "mz")
intensity_batch, intensity_was_single = _as_spectrum_batch(intensity, "intensity")
if len(mz_batch) != len(intensity_batch) or mz_was_single != intensity_was_single:
raise ValueError("mz and intensity must describe the same number of spectra")
processed = [self._process_one(m, i)[:3] for m, i in zip(mz_batch, intensity_batch)]
limit = self.max_peaks if max_length is None else max_length
if limit <= 0:
raise ValueError("max_length must be positive")
if truncation:
processed = [(m[:limit], li[:limit], y[:limit]) for m, li, y in processed]
elif any(m.numel() > limit for m, _, _ in processed) and padding == "max_length":
raise ValueError("a spectrum exceeds max_length while truncation is disabled")
lengths = [m.numel() for m, _, _ in processed]
target_length: int | None
if padding == "max_length":
target_length = limit
elif padding:
target_length = max(lengths)
else:
target_length = None
if target_length is not None and pad_to_multiple_of:
target_length = (
(target_length + pad_to_multiple_of - 1) // pad_to_multiple_of
) * pad_to_multiple_of
data: dict[str, list] = {
"mz": [],
"log_intensity": [],
"attention_mask": [],
}
if return_labels:
data["labels"] = []
for mass, log_int, labels in processed:
length = mass.numel()
padded_length = length if target_length is None else target_length
pad = padded_length - length
if pad < 0:
raise ValueError("a spectrum exceeds the requested padded length")
data["mz"].append(torch.cat([mass, mass.new_full((pad,), self.padding_value)]).tolist())
data["log_intensity"].append(
torch.cat([log_int, log_int.new_full((pad,), self.padding_value)]).tolist()
)
data["attention_mask"].append([1] * length + [0] * pad)
if return_labels:
data["labels"].append(torch.cat([labels, labels.new_zeros(pad)]).tolist())
if mz_was_single and not padding and return_tensors is None:
data = {name: values[0] for name, values in data.items()}
return BatchFeature(data=data, tensor_type=return_tensors)
@dataclass
class IonaDataCollatorForPreTraining:
"""Pad processed spectra and sample masked peaks for pretraining."""
mask_ratio: float = 0.15
min_masked: int = 1
pad_to_multiple_of: int | None = None
def __post_init__(self) -> None:
if not 0.0 <= self.mask_ratio <= 1.0:
raise ValueError("mask_ratio must be in [0, 1]")
if self.min_masked < 0:
raise ValueError("min_masked must be nonnegative")
def __call__(self, features: list[dict]) -> dict[str, torch.Tensor]:
if not features:
raise ValueError("features must not be empty")
lengths = [len(feature["mz"]) for feature in features]
max_length = max(lengths)
if self.pad_to_multiple_of:
max_length = (
(max_length + self.pad_to_multiple_of - 1) // self.pad_to_multiple_of
) * self.pad_to_multiple_of
batch_size = len(features)
mz = torch.zeros(batch_size, max_length, dtype=torch.float32)
log_intensity = torch.zeros_like(mz)
labels = torch.zeros_like(mz)
attention_mask = torch.zeros(batch_size, max_length, dtype=torch.long)
mask_positions = torch.zeros(batch_size, max_length, dtype=torch.bool)
for row, (feature, length) in enumerate(zip(features, lengths)):
if length == 0:
continue
mz[row, :length] = torch.as_tensor(feature["mz"], dtype=torch.float32)
log_intensity[row, :length] = torch.as_tensor(
feature["log_intensity"], dtype=torch.float32
)
target = feature.get("labels", feature.get("intensity_prob"))
if target is None:
raise ValueError("pretraining features must include labels")
labels[row, :length] = torch.as_tensor(target, dtype=torch.float32)
attention_mask[row, :length] = 1
n_masked = min(
length,
max(self.min_masked, int(round(length * self.mask_ratio))),
)
if n_masked:
mask_positions[row, torch.randperm(length)[:n_masked]] = True
return {
"mz": mz,
"log_intensity": log_intensity,
"attention_mask": attention_mask,
"mask_positions": mask_positions,
"labels": labels,
}
@dataclass
class IonaDataCollatorForRetrieval:
"""Flatten and pad four-spectrum analyte groups for contrastive training."""
def __call__(self, features: list[dict]) -> dict[str, torch.Tensor]:
if not features:
raise ValueError("features must not be empty")
mzs: list[list[float]] = []
log_intensities: list[list[float]] = []
group_ids: list[int] = []
for group_id, feature in enumerate(features):
group_mz = feature["mz"]
group_intensity = feature["log_intensity"]
if len(group_mz) != 4 or len(group_intensity) != 4:
raise ValueError("each retrieval example must contain four spectra")
mzs.extend(group_mz)
log_intensities.extend(group_intensity)
group_ids.extend([group_id] * 4)
target_length = max(max((len(mz) for mz in mzs), default=0), 1)
batch_size = len(mzs)
mz = torch.zeros(batch_size, target_length, dtype=torch.float32)
log_intensity = torch.zeros_like(mz)
attention_mask = torch.zeros(batch_size, target_length, dtype=torch.long)
for index, (mass, intensity) in enumerate(zip(mzs, log_intensities)):
length = len(mass)
if length == 0:
continue
mz[index, :length] = torch.as_tensor(mass, dtype=torch.float32)
log_intensity[index, :length] = torch.as_tensor(intensity, dtype=torch.float32)
attention_mask[index, :length] = 1
return {
"mz": mz,
"log_intensity": log_intensity,
"attention_mask": attention_mask,
"group_ids": torch.tensor(group_ids, dtype=torch.long),
}
IonaProcessor.register_for_auto_class("AutoProcessor")
|