Download models/classifier.py from Sowaiba01/ThoraxNet: direct link, hf CLI and curl.
- Browser
- Download file 3.63 kB
-
https://huggingface.co/spaces/Sowaiba01/ThoraxNet/resolve/main/models/classifier.py
- Command line
-
hf download hf://spaces/Sowaiba01/ThoraxNet/models/classifier.py
-
curl -L -o classifier.py https://huggingface.co/spaces/Sowaiba01/ThoraxNet/resolve/main/models/classifier.py
3.63 kB
| """ | |
| Multi-label classification head with MC Dropout. | |
| Architecture: | |
| BioMedCLIP ViT-B/16 β LayerNorm β Dropout β Linear(512, 256) | |
| β GELU β Dropout β Linear(256, 14) | |
| The double-dropout design enables Monte Carlo uncertainty estimation at | |
| inference time by keeping dropout active (model.train() mode). | |
| """ | |
| from __future__ import annotations | |
| import torch | |
| import torch.nn as nn | |
| from models.backbone import BioMedCLIPVisionBackbone | |
| from data.dataset import NUM_CLASSES | |
| class ChestAIClassifier(nn.Module): | |
| """ | |
| Full model: backbone + classification head. | |
| Args: | |
| backbone_name: HF model ID for BioMedCLIP. | |
| num_classes: Number of output labels (14 for NIH ChestX-ray14). | |
| dropout_rate: Dropout probability β used in both training and MC inference. | |
| freeze_backbone: Start with frozen backbone (set False after warm-up). | |
| """ | |
| def __init__( | |
| self, | |
| backbone_name: str = "microsoft/BiomedCLIP-PubMedBERT_256-vit_base_patch16_224", | |
| num_classes: int = NUM_CLASSES, | |
| dropout_rate: float = 0.3, | |
| freeze_backbone: bool = True, | |
| ) -> None: | |
| super().__init__() | |
| self.backbone = BioMedCLIPVisionBackbone( | |
| model_name=backbone_name, | |
| pretrained=True, | |
| freeze=freeze_backbone, | |
| ) | |
| embed_dim = self.backbone.embed_dim | |
| self.head = nn.Sequential( | |
| nn.LayerNorm(embed_dim), | |
| nn.Dropout(dropout_rate), | |
| nn.Linear(embed_dim, 256), | |
| nn.GELU(), | |
| nn.Dropout(dropout_rate), | |
| nn.Linear(256, num_classes), | |
| ) | |
| self._dropout_rate = dropout_rate | |
| self._init_head_weights() | |
| def _init_head_weights(self) -> None: | |
| """Xavier uniform init for classification head linear layers.""" | |
| for m in self.head.modules(): | |
| if isinstance(m, nn.Linear): | |
| nn.init.xavier_uniform_(m.weight) | |
| nn.init.zeros_(m.bias) | |
| # ------------------------------------------------------------------ | |
| # Forward | |
| # ------------------------------------------------------------------ | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Standard forward pass (logits, not probabilities). | |
| Args: | |
| x: (B, 3, H, W) image tensor. | |
| Returns: | |
| (B, num_classes) raw logits. Apply sigmoid for probabilities. | |
| """ | |
| features = self.backbone(x) # (B, embed_dim) | |
| return self.head(features) # (B, num_classes) | |
| # ------------------------------------------------------------------ | |
| # Backbone freeze / unfreeze API (called by trainer during warm-up) | |
| # ------------------------------------------------------------------ | |
| def freeze_backbone(self) -> None: | |
| self.backbone.freeze() | |
| def unfreeze_backbone(self, last_n_blocks: int | None = None) -> None: | |
| self.backbone.unfreeze(last_n_blocks) | |
| # ------------------------------------------------------------------ | |
| # Utility | |
| # ------------------------------------------------------------------ | |
| def num_parameters(self) -> dict[str, int]: | |
| total = sum(p.numel() for p in self.parameters()) | |
| trainable = sum(p.numel() for p in self.parameters() if p.requires_grad) | |
| return {"total": total, "trainable": trainable} | |
| def summary(self) -> str: | |
| p = self.num_parameters | |
| return ( | |
| f"ChestAIClassifier | " | |
| f"total={p['total']:,} params | " | |
| f"trainable={p['trainable']:,} params" | |
| ) | |