Download src/gnn4colliders/sequences/sample.py from HWresearch/GNN4Colliders: direct link, hf CLI and curl.
- Browser
- Download file 6.68 kB
-
https://huggingface.co/HWresearch/GNN4Colliders/resolve/main/src/gnn4colliders/sequences/sample.py
- Command line
-
hf download hf://HWresearch/GNN4Colliders/src/gnn4colliders/sequences/sample.py
-
curl -L -o sample.py https://huggingface.co/HWresearch/GNN4Colliders/resolve/main/src/gnn4colliders/sequences/sample.py
6.68 kB
| """Deterministic padded sequence samples for transformer models.""" | |
| from __future__ import annotations | |
| from collections.abc import Iterator, Sequence | |
| from dataclasses import dataclass | |
| from typing import Any | |
| import torch | |
| from gnn4colliders.data.events import PackedEvents | |
| from gnn4colliders.data.metadata import BatchMetadata, EventMetadata | |
| from gnn4colliders.data.sample import EventSample | |
| from gnn4colliders.features import build_node_features | |
| class SequenceSample: | |
| """One event represented as an ordered sequence of object tokens.""" | |
| tokens: torch.Tensor | |
| label: torch.Tensor | |
| global_features: torch.Tensor | None | |
| metadata: EventMetadata | |
| class SequenceBatch: | |
| """A padded batch with a boolean mask of valid tokens.""" | |
| tokens: torch.Tensor | |
| token_mask: torch.Tensor | |
| labels: torch.Tensor | |
| global_features: torch.Tensor | None | |
| metadata: BatchMetadata | |
| def to( | |
| self, device: torch.device | str, *, non_blocking: bool = False | |
| ) -> "SequenceBatch": | |
| return self._map(lambda value: value.to(device, non_blocking=non_blocking)) | |
| def pin_memory(self) -> "SequenceBatch": | |
| return self._map(lambda value: value.pin_memory()) | |
| def _map(self, move: Any) -> "SequenceBatch": | |
| metadata = BatchMetadata( | |
| fold=move(self.metadata.fold), | |
| weight=move(self.metadata.weight), | |
| sample_id=self.metadata.sample_id, | |
| extra=self.metadata.extra, | |
| ) | |
| return SequenceBatch( | |
| tokens=move(self.tokens), | |
| token_mask=move(self.token_mask), | |
| labels=move(self.labels), | |
| global_features=( | |
| None if self.global_features is None else move(self.global_features) | |
| ), | |
| metadata=metadata, | |
| ) | |
| def sequence_sample_from_event( | |
| event: EventSample, | |
| feature_branches: Sequence[Any], | |
| object_types: Sequence[str], | |
| scales: Sequence[float] | torch.Tensor, | |
| ) -> SequenceSample: | |
| """Build tokens from shared collider features without graph topology.""" | |
| tokens, _ = build_node_features( | |
| event.objects, feature_branches, object_types, scales | |
| ) | |
| globals_ = torch.as_tensor(event.global_features, dtype=torch.float32) | |
| return SequenceSample( | |
| tokens=tokens, | |
| label=torch.as_tensor(event.label), | |
| global_features=globals_ if globals_.numel() else None, | |
| metadata=event.event_metadata, | |
| ) | |
| def collate_sequence_batch( | |
| events: PackedEvents, indices: torch.Tensor | |
| ) -> SequenceBatch: | |
| """Pad the object rows of ``indices`` into a token batch, in order.""" | |
| if indices.numel() == 0: | |
| raise ValueError("cannot batch an empty sequence") | |
| rows, counts = events.gather_objects(indices) | |
| max_length = int(counts.max()) if counts.numel() else 0 | |
| batch_index = torch.repeat_interleave( | |
| torch.arange(counts.numel()), counts, output_size=rows.shape[0] | |
| ) | |
| position = ( | |
| torch.arange(rows.shape[0]) - (torch.cumsum(counts, 0) - counts)[batch_index] | |
| ) | |
| tokens = rows.new_zeros((counts.numel(), max_length, rows.shape[1])) | |
| tokens[batch_index, position] = rows | |
| token_mask = torch.zeros((counts.numel(), max_length), dtype=torch.bool) | |
| token_mask[batch_index, position] = True | |
| return SequenceBatch( | |
| tokens=tokens, | |
| token_mask=token_mask, | |
| labels=events.labels[indices], | |
| global_features=( | |
| None if events.global_features is None else events.global_features[indices] | |
| ), | |
| metadata=events.batch_metadata(indices), | |
| ) | |
| def batch_sequence_samples(samples: Sequence[SequenceSample]) -> SequenceBatch: | |
| """Pad a sequence of variable-length events in deterministic order.""" | |
| if not samples: | |
| raise ValueError("cannot batch an empty sequence") | |
| feature_widths = {int(sample.tokens.shape[1]) for sample in samples} | |
| if any(sample.tokens.ndim != 2 for sample in samples): | |
| raise ValueError("sequence tokens must have shape [objects, features]") | |
| if len(feature_widths) != 1: | |
| raise ValueError("all sequence samples must have the same feature width") | |
| max_length = max(int(sample.tokens.shape[0]) for sample in samples) | |
| feature_width = next(iter(feature_widths)) | |
| tokens = samples[0].tokens.new_zeros((len(samples), max_length, feature_width)) | |
| token_mask = torch.zeros( | |
| (len(samples), max_length), dtype=torch.bool, device=tokens.device | |
| ) | |
| for index, sample in enumerate(samples): | |
| length = int(sample.tokens.shape[0]) | |
| tokens[index, :length] = sample.tokens | |
| token_mask[index, :length] = True | |
| labels = torch.stack([torch.as_tensor(sample.label) for sample in samples]) | |
| globals_ = [sample.global_features for sample in samples] | |
| if any(value is None for value in globals_): | |
| if not all(value is None for value in globals_): | |
| raise ValueError("global_features must be present for every sample or none") | |
| batched_globals = None | |
| else: | |
| batched_globals = torch.stack( | |
| [value for value in globals_ if value is not None] | |
| ) | |
| return SequenceBatch( | |
| tokens=tokens, | |
| token_mask=token_mask, | |
| labels=labels, | |
| global_features=batched_globals, | |
| metadata=BatchMetadata.from_events([sample.metadata for sample in samples]), | |
| ) | |
| class SequenceDataLoader: | |
| """Small deterministic loader for variable-length sequence samples.""" | |
| def __init__( | |
| self, | |
| dataset: Sequence[SequenceSample], | |
| batch_size: int, | |
| *, | |
| shuffle: bool = False, | |
| seed: int = 12345, | |
| ) -> None: | |
| if batch_size < 1: | |
| raise ValueError("batch_size must be positive") | |
| self.dataset = dataset | |
| self.batch_size = batch_size | |
| self.shuffle = shuffle | |
| self.seed = seed | |
| self.epoch = 0 | |
| def set_epoch(self, epoch: int) -> None: | |
| self.epoch = epoch | |
| def __len__(self) -> int: | |
| return (len(self.dataset) + self.batch_size - 1) // self.batch_size | |
| def __iter__(self) -> Iterator[SequenceBatch]: | |
| indices = torch.arange(len(self.dataset), dtype=torch.long) | |
| if self.shuffle: | |
| generator = torch.Generator().manual_seed(self.seed + self.epoch) | |
| indices = indices[torch.randperm(len(indices), generator=generator)] | |
| for start in range(0, len(indices), self.batch_size): | |
| selected = [ | |
| self.dataset[int(index)] | |
| for index in indices[start : start + self.batch_size] | |
| ] | |
| yield batch_sequence_samples(selected) | |