Download mil_module/mil_core.py from MuskAI/LTF-v3.3: direct link, hf CLI and curl.
- Browser
- Download file 7.01 kB
-
https://huggingface.co/MuskAI/LTF-v3.3/resolve/main/mil_module/mil_core.py
- Command line
-
hf download hf://MuskAI/LTF-v3.3/mil_module/mil_core.py
-
curl -L -o mil_core.py https://huggingface.co/MuskAI/LTF-v3.3/resolve/main/mil_module/mil_core.py
7.01 kB
| """Compose independent token augmentation, transformation and MIL pooling stages.""" | |
| from __future__ import annotations | |
| import inspect | |
| from typing import Any, Dict, Optional, Set, Tuple, Union | |
| import torch | |
| import torch.nn as nn | |
| from .cross_shuffle import CrossTokenShuffle | |
| from .mil_head_beta import MILHead | |
| def _forward_param_names(module: nn.Module) -> Set[str]: | |
| try: | |
| return set(inspect.signature(module.forward).parameters.keys()) | |
| except (TypeError, ValueError): | |
| return set() | |
| class CrossShuffleStage(nn.Module): | |
| """Optional cross-batch token replacement; disabled stages act as the identity.""" | |
| def __init__(self, use: bool = True) -> None: | |
| super().__init__() | |
| self._use = use | |
| self.op: Optional[CrossTokenShuffle] | |
| if use: | |
| self.op = CrossTokenShuffle() | |
| else: | |
| self.op = None | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| y: Optional[torch.Tensor], | |
| *, | |
| apply: bool, | |
| replace_ratio: float = 0.5, | |
| with_replacement: bool = False, | |
| detach_inserted: bool = True, | |
| min_keep_original: int = 0, | |
| ) -> torch.Tensor: | |
| if not self._use or not apply or self.op is None or y is None: | |
| return x | |
| if y.dim() != 1 or y.shape[0] != x.shape[0]: | |
| raise ValueError( | |
| f"y must be 1D [B], got {tuple(y.shape)} for B={x.shape[0]}" | |
| ) | |
| return self.op( | |
| x, y, | |
| replace_ratio=replace_ratio, | |
| with_replacement=with_replacement, | |
| detach_inserted=detach_inserted, | |
| min_keep_original=min_keep_original, | |
| ) | |
| class TransStage(nn.Module): | |
| """Apply an optional token transformation with supported keyword arguments. | |
| A missing core acts as the identity. Fixed-length masking can be passed | |
| through key_padding_mask without dropping tokens. | |
| """ | |
| def __init__(self, core: Optional[nn.Module] = None) -> None: | |
| super().__init__() | |
| self.core = core | |
| self._params = _forward_param_names(core) if core is not None else set() | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| *, | |
| p_shuffle: float = 0.0, | |
| p_drop: float = 0.0, | |
| key_padding_mask: Optional[torch.Tensor] = None, | |
| **kwargs: Any, | |
| ) -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]: | |
| if self.core is None: | |
| return x | |
| if "p_shuffle" in self._params: | |
| call_kw: Dict[str, Any] = {} | |
| for name, val in ( | |
| ("p_shuffle", p_shuffle), | |
| ("p_drop", p_drop), | |
| ("key_padding_mask", key_padding_mask), | |
| ): | |
| if name in self._params: | |
| call_kw[name] = val | |
| for k, v in kwargs.items(): | |
| if k in self._params: | |
| call_kw[k] = v | |
| return self.core(x, **call_kw) | |
| kw2: Dict[str, Any] = {} | |
| if key_padding_mask is not None and "src_key_padding_mask" in self._params: | |
| kw2["src_key_padding_mask"] = key_padding_mask | |
| for k, v in kwargs.items(): | |
| if k in self._params: | |
| kw2[k] = v | |
| return self.core(x, **kw2) if kw2 else self.core(x) | |
| class MILHeadStage(nn.Module): | |
| """Classify instances and pool tokens [B, N, D] using the wrapped MIL head.""" | |
| def __init__(self, **mil_kw: Any) -> None: | |
| super().__init__() | |
| self.head = MILHead(**mil_kw) | |
| def forward(self, x: torch.Tensor, mask: Optional[torch.Tensor] = None) -> Dict[str, Any]: | |
| return self.head(x, mask=mask) | |
| class MILCore(nn.Module): | |
| """Compose Cross-Shuffle, token transformation and MIL head stages. | |
| Fixed-length bags can use attention masks instead of token dropping. | |
| Optional masks and transformation arguments are forwarded to supported stages. | |
| """ | |
| def __init__( | |
| self, | |
| d_in: int, | |
| num_classes: int = 2, | |
| mil_head_pooling: str = "attn", | |
| mil_head_kwargs: Optional[Dict[str, Any]] = None, | |
| *, | |
| use_cross_shuffle: bool = True, | |
| trans: Optional[nn.Module] = None, | |
| allow_trans_token_drop: bool = False, | |
| ) -> None: | |
| super().__init__() | |
| self.allow_trans_token_drop = bool(allow_trans_token_drop) | |
| self.shuffle = CrossShuffleStage(use=use_cross_shuffle) | |
| self.trans = TransStage(core=trans) | |
| mil_kw: Dict[str, Any] = {"d_in": d_in, "num_classes": num_classes, "pooling": mil_head_pooling} | |
| if mil_head_kwargs: | |
| mil_kw.update(mil_head_kwargs) | |
| self.head = MILHeadStage(**mil_kw) | |
| def forward( | |
| self, | |
| tokens: torch.Tensor, | |
| y: Optional[torch.Tensor] = None, | |
| mask: Optional[torch.Tensor] = None, | |
| *, | |
| do_cross_shuffle: bool = False, | |
| replace_ratio: float = 0.5, | |
| with_replacement: bool = False, | |
| detach_inserted: bool = True, | |
| cross_shuffle_min_keep: int = 0, | |
| p_shuffle: float = 0.0, | |
| p_drop: float = 0.0, | |
| trans_key_padding_mask: Optional[torch.Tensor] = None, | |
| trans_extra: Optional[Dict[str, Any]] = None, | |
| return_dict: bool = False, | |
| ): | |
| x = self.shuffle( | |
| tokens, y, | |
| apply=do_cross_shuffle, | |
| replace_ratio=replace_ratio, | |
| with_replacement=with_replacement, | |
| detach_inserted=detach_inserted, | |
| min_keep_original=cross_shuffle_min_keep, | |
| ) | |
| trans_p_drop = float(p_drop) if self.allow_trans_token_drop else 0.0 | |
| tex = dict(trans_extra or {}) | |
| trans_out = self.trans( | |
| x, | |
| p_shuffle=p_shuffle, | |
| p_drop=trans_p_drop, | |
| key_padding_mask=trans_key_padding_mask, | |
| **tex, | |
| ) | |
| if isinstance(trans_out, tuple) and len(trans_out) == 2: | |
| x, trans_mil_mask = trans_out | |
| if mask is not None: | |
| raise ValueError( | |
| "MILCore: `trans` returned (x, mil_valid); omit `mask` and use the trans-stage mask." | |
| ) | |
| mask = trans_mil_mask | |
| else: | |
| x = trans_out # type: ignore[assignment] | |
| if mask is not None and mask.shape != x.shape[:2]: | |
| raise ValueError( | |
| f"mask must be [B,N], got {tuple(mask.shape)} vs x {tuple(x.shape[:2])}" | |
| ) | |
| mil_out = self.head(x, mask=mask) | |
| if not return_dict: | |
| return mil_out["bag_logits"] | |
| return {"bag_logits": mil_out["bag_logits"], "mil_head": mil_out, "features": x} | |
| def forward_logits( | |
| self, | |
| tokens: torch.Tensor, | |
| y: Optional[torch.Tensor] = None, | |
| mask: Optional[torch.Tensor] = None, | |
| **kwargs: Any, | |
| ) -> torch.Tensor: | |
| extra = dict(kwargs) | |
| extra.pop("return_dict", None) | |
| return self.forward(tokens, y, mask, return_dict=False, **extra) | |
| CrossShuffleTransMIL = MILCore | |