Spaces:
Sleeping
Sleeping
| """ | |
| Model Loader — supports multiple pretrained torchvision architectures | |
| with helpers for extracting named convolutional layers for Grad-CAM targeting. | |
| """ | |
| import torch | |
| import torch.nn as nn | |
| import torchvision.models as models | |
| from dataclasses import dataclass, field | |
| from typing import Dict, List, Optional, Tuple | |
| class ModelConfig: | |
| """Configuration for a pretrained vision model.""" | |
| name: str | |
| constructor: callable | |
| weights_class: object | |
| default_target_layer: str # Attribute path string for the default Grad-CAM layer | |
| cam_layers: List[str] # All available layers for Grad-CAM | |
| description: str | |
| # Registry of supported models | |
| MODEL_REGISTRY: Dict[str, ModelConfig] = { | |
| "ResNet-50": ModelConfig( | |
| name="ResNet-50", | |
| constructor=models.resnet50, | |
| weights_class=models.ResNet50_Weights.IMAGENET1K_V2, | |
| default_target_layer="layer4.2.conv3", | |
| cam_layers=["layer1", "layer2", "layer3", "layer4", "layer4.2.conv3"], | |
| description="50-layer residual network. Great balance of speed and accuracy.", | |
| ), | |
| "ResNet-101": ModelConfig( | |
| name="ResNet-101", | |
| constructor=models.resnet101, | |
| weights_class=models.ResNet101_Weights.IMAGENET1K_V2, | |
| default_target_layer="layer4.2.conv3", | |
| cam_layers=["layer1", "layer2", "layer3", "layer4", "layer4.2.conv3"], | |
| description="101-layer residual network. Higher accuracy, slower inference.", | |
| ), | |
| "VGG-16": ModelConfig( | |
| name="VGG-16", | |
| constructor=models.vgg16, | |
| weights_class=models.VGG16_Weights.IMAGENET1K_V1, | |
| default_target_layer="features.28", | |
| cam_layers=["features.14", "features.21", "features.28"], | |
| description="Classic VGG architecture. Very clear gradient signals.", | |
| ), | |
| "EfficientNet-B0": ModelConfig( | |
| name="EfficientNet-B0", | |
| constructor=models.efficientnet_b0, | |
| weights_class=models.EfficientNet_B0_Weights.IMAGENET1K_V1, | |
| default_target_layer="features.8.0", | |
| cam_layers=["features.3", "features.5", "features.7", "features.8.0"], | |
| description="Efficient compound-scaled architecture. Small and fast.", | |
| ), | |
| "DenseNet-121": ModelConfig( | |
| name="DenseNet-121", | |
| constructor=models.densenet121, | |
| weights_class=models.DenseNet121_Weights.IMAGENET1K_V1, | |
| default_target_layer="features.denseblock4.denselayer16.conv2", | |
| cam_layers=["features.denseblock2", "features.denseblock3", "features.denseblock4"], | |
| description="Dense connections between all layers. Strong feature reuse.", | |
| ), | |
| "MobileNet-V3": ModelConfig( | |
| name="MobileNet-V3", | |
| constructor=models.mobilenet_v3_large, | |
| weights_class=models.MobileNet_V3_Large_Weights.IMAGENET1K_V2, | |
| default_target_layer="features.16.block.2.0", | |
| cam_layers=["features.7", "features.12", "features.16.block.2.0"], | |
| description="Lightweight mobile-optimized network.", | |
| ), | |
| } | |
| def load_model(model_name: str, device: Optional[str] = None) -> Tuple[nn.Module, ModelConfig]: | |
| """ | |
| Load a pretrained model by name from the registry. | |
| Args: | |
| model_name: Key in MODEL_REGISTRY | |
| device: 'cpu', 'cuda', or None (auto-detect) | |
| Returns: | |
| (model, config) tuple | |
| """ | |
| if model_name not in MODEL_REGISTRY: | |
| raise ValueError(f"Unknown model '{model_name}'. Available: {list(MODEL_REGISTRY.keys())}") | |
| config = MODEL_REGISTRY[model_name] | |
| if device is None: | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| model = config.constructor(weights=config.weights_class) | |
| model = model.to(device) | |
| model.eval() | |
| # Disable inplace ops (VGG/EfficientNet/MobileNet use inplace ReLU | |
| # which causes autograd view errors with backward hooks) | |
| for module in model.modules(): | |
| if hasattr(module, 'inplace'): | |
| module.inplace = False | |
| return model, config | |
| def get_layer_by_name(model: nn.Module, layer_name: str) -> nn.Module: | |
| """ | |
| Navigate the model hierarchy using a dot-separated attribute path. | |
| E.g., 'layer4.2.conv3' → model.layer4[2].conv3 | |
| Handles both attribute access and integer indexing for Sequential/ModuleList. | |
| """ | |
| parts = layer_name.split(".") | |
| current = model | |
| for part in parts: | |
| if part.isdigit(): | |
| current = current[int(part)] | |
| else: | |
| current = getattr(current, part) | |
| return current | |
| def list_conv_layers(model: nn.Module, prefix: str = "") -> List[Tuple[str, nn.Module]]: | |
| """ | |
| Recursively list all Conv2d layers in the model with their full attribute paths. | |
| Useful for letting users explore all possible Grad-CAM target layers. | |
| """ | |
| conv_layers = [] | |
| for name, module in model.named_modules(): | |
| if isinstance(module, nn.Conv2d): | |
| conv_layers.append((name, module)) | |
| return conv_layers | |
| def get_model_summary(model: nn.Module) -> Dict: | |
| """Return a summary dict with parameter counts and layer info.""" | |
| total_params = sum(p.numel() for p in model.parameters()) | |
| trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) | |
| conv_count = sum(1 for m in model.modules() if isinstance(m, nn.Conv2d)) | |
| bn_count = sum(1 for m in model.modules() if isinstance(m, (nn.BatchNorm2d,))) | |
| return { | |
| "total_params": total_params, | |
| "trainable_params": trainable_params, | |
| "conv_layers": conv_count, | |
| "batch_norm_layers": bn_count, | |
| } | |