| """
|
| ===================================================================
|
| MODEL_MANAGER.PY - Gestión de Modelos Pre-entrenados
|
| ===================================================================
|
|
|
| Este módulo maneja la carga, configuración y gestión de modelos
|
| de deep learning pre-entrenados.
|
|
|
| Funcionalidades principales:
|
| 1. Carga de modelos (AlexNet, ResNet, VGG, etc.)
|
| 2. Extracción de capas convolucionales
|
| 3. Información detallada de capas (canales, dimensiones)
|
| 4. Caché de modelos para performance
|
|
|
| Uso:
|
| manager = ModelManager()
|
| model = manager.load_model('alexnet')
|
| layers = manager.get_conv_layers(model)
|
| ===================================================================
|
| """
|
|
|
| from config import (
|
| AVAILABLE_MODELS,
|
| DEFAULT_MODEL,
|
| DEFAULT_DEVICE,
|
| ENABLE_MODEL_CACHE
|
| )
|
| import torch
|
| import torch.nn as nn
|
| from torchvision import models
|
| from typing import Dict, List, Tuple, Optional
|
| from collections import OrderedDict
|
| import sys
|
|
|
|
|
| sys.path.append('..')
|
|
|
|
|
| class ModelManager:
|
| """
|
| Gestor de modelos de deep learning pre-entrenados.
|
|
|
| Maneja la carga, caché y consulta de información sobre modelos
|
| y sus capas convolucionales.
|
| """
|
|
|
| def __init__(self, device: Optional[str] = None):
|
| """
|
| Inicializa el gestor de modelos.
|
|
|
| Args:
|
| device: Dispositivo para computación ('cpu', 'cuda', etc.)
|
| Si es None, usa DEFAULT_DEVICE de config
|
| """
|
|
|
| if device is None:
|
| device = DEFAULT_DEVICE
|
|
|
|
|
| if device == 'cuda' and not torch.cuda.is_available():
|
| print("⚠️ CUDA no disponible, usando CPU")
|
| device = 'cpu'
|
|
|
| self.device = torch.device(device)
|
|
|
|
|
| self._model_cache: Dict[str, nn.Module] = {}
|
|
|
|
|
| self._layer_info_cache: Dict[str, Dict] = {}
|
|
|
| print(f"✅ ModelManager inicializado")
|
| print(f" Device: {self.device}")
|
| print(f" Caché habilitado: {ENABLE_MODEL_CACHE}")
|
|
|
| def get_available_models(self) -> List[str]:
|
| """
|
| Retorna lista de modelos disponibles.
|
|
|
| Returns:
|
| Lista con nombres de modelos soportados
|
| """
|
| return list(AVAILABLE_MODELS.keys())
|
|
|
| def get_model_info(self, model_name: str) -> Dict:
|
| """
|
| Obtiene información detallada de un modelo.
|
|
|
| Args:
|
| model_name: Nombre del modelo (ej: 'alexnet')
|
|
|
| Returns:
|
| Diccionario con información del modelo
|
|
|
| Raises:
|
| ValueError: Si el modelo no está soportado
|
| """
|
| if model_name not in AVAILABLE_MODELS:
|
| raise ValueError(
|
| f"Modelo '{model_name}' no soportado. "
|
| f"Disponibles: {self.get_available_models()}"
|
| )
|
|
|
| return AVAILABLE_MODELS[model_name]
|
|
|
| def load_model(
|
| self,
|
| model_name: str,
|
| pretrained: bool = True,
|
| force_reload: bool = False
|
| ) -> nn.Module:
|
| """
|
| Carga un modelo pre-entrenado.
|
|
|
| Args:
|
| model_name: Nombre del modelo ('alexnet', 'resnet18', 'vgg16')
|
| pretrained: Si cargar pesos pre-entrenados de ImageNet
|
| force_reload: Forzar recarga incluso si está en caché
|
|
|
| Returns:
|
| Modelo de PyTorch en modo evaluación
|
|
|
| Raises:
|
| ValueError: Si el modelo no está soportado
|
| """
|
|
|
| if model_name not in AVAILABLE_MODELS:
|
| raise ValueError(
|
| f"Modelo '{model_name}' no soportado. "
|
| f"Disponibles: {self.get_available_models()}"
|
| )
|
|
|
|
|
| if ENABLE_MODEL_CACHE and not force_reload:
|
| if model_name in self._model_cache:
|
| print(f"📦 Modelo '{model_name}' cargado desde caché")
|
| return self._model_cache[model_name]
|
|
|
| print(f"🔄 Cargando modelo '{model_name}'...")
|
| if pretrained:
|
| print(f" Descargando pesos pre-entrenados de ImageNet...")
|
|
|
|
|
| if model_name == 'alexnet':
|
| model = models.alexnet(pretrained=pretrained)
|
| elif model_name == 'resnet18':
|
| model = models.resnet18(pretrained=pretrained)
|
| elif model_name == 'vgg16':
|
| model = models.vgg16(pretrained=pretrained)
|
| else:
|
| raise ValueError(f"Modelo '{model_name}' no implementado")
|
|
|
|
|
| model = model.to(self.device)
|
| model.eval()
|
|
|
|
|
| if ENABLE_MODEL_CACHE:
|
| self._model_cache[model_name] = model
|
|
|
|
|
| num_params = sum(p.numel() for p in model.parameters())
|
|
|
| print(f"✅ Modelo '{model_name}' cargado exitosamente")
|
| print(f" Parámetros: {num_params:,}")
|
| print(f" Device: {self.device}")
|
|
|
| return model
|
|
|
| def get_conv_layers(
|
| self,
|
| model: nn.Module,
|
| model_name: Optional[str] = None
|
| ) -> List[str]:
|
| """
|
| Extrae nombres de todas las capas convolucionales del modelo.
|
|
|
| Args:
|
| model: Modelo de PyTorch
|
| model_name: Nombre del modelo (opcional, para mejor output)
|
|
|
| Returns:
|
| Lista ordenada de nombres de capas convolucionales
|
| """
|
| conv_layers = []
|
|
|
|
|
| for name, module in model.named_modules():
|
|
|
| if isinstance(module, nn.Conv2d):
|
|
|
| if name:
|
| conv_layers.append(name)
|
|
|
| if model_name:
|
| print(f"\n🔍 Capas convolucionales en '{model_name}':")
|
| for i, layer in enumerate(conv_layers, 1):
|
| print(f" {i:2d}. {layer}")
|
|
|
| return conv_layers
|
|
|
| def get_layer_info(
|
| self,
|
| model: nn.Module,
|
| layer_name: str
|
| ) -> Dict:
|
| """
|
| Obtiene información detallada sobre una capa específica.
|
|
|
| Args:
|
| model: Modelo de PyTorch
|
| layer_name: Nombre de la capa (ej: 'features.0')
|
|
|
| Returns:
|
| Diccionario con información:
|
| - 'name': Nombre de la capa
|
| - 'type': Tipo de módulo
|
| - 'num_channels': Número de canales de salida
|
| - 'kernel_size': Tamaño del kernel (si es Conv2d)
|
| - 'stride': Stride (si es Conv2d)
|
| - 'padding': Padding (si es Conv2d)
|
|
|
| Raises:
|
| ValueError: Si la capa no existe
|
| """
|
|
|
| cache_key = f"{id(model)}_{layer_name}"
|
| if cache_key in self._layer_info_cache:
|
| return self._layer_info_cache[cache_key]
|
|
|
|
|
| target_module = None
|
| for name, module in model.named_modules():
|
| if name == layer_name:
|
| target_module = module
|
| break
|
|
|
| if target_module is None:
|
| raise ValueError(f"Capa '{layer_name}' no encontrada en el modelo")
|
|
|
|
|
| info = {
|
| 'name': layer_name,
|
| 'type': type(target_module).__name__
|
| }
|
|
|
|
|
| if isinstance(target_module, nn.Conv2d):
|
| info['num_channels'] = target_module.out_channels
|
| info['in_channels'] = target_module.in_channels
|
| info['kernel_size'] = target_module.kernel_size
|
| info['stride'] = target_module.stride
|
| info['padding'] = target_module.padding
|
|
|
|
|
|
|
| info['approx_output_size'] = self._estimate_output_size(
|
| input_size=224,
|
| kernel_size=target_module.kernel_size[0],
|
| stride=target_module.stride[0],
|
| padding=target_module.padding[0]
|
| )
|
|
|
|
|
| self._layer_info_cache[cache_key] = info
|
|
|
| return info
|
|
|
| def _estimate_output_size(
|
| self,
|
| input_size: int,
|
| kernel_size: int,
|
| stride: int,
|
| padding: int
|
| ) -> int:
|
| """
|
| Estima el tamaño del feature map de salida.
|
|
|
| Fórmula: floor((input + 2*padding - kernel_size) / stride) + 1
|
|
|
| Args:
|
| input_size: Tamaño de entrada
|
| kernel_size: Tamaño del kernel
|
| stride: Stride
|
| padding: Padding
|
|
|
| Returns:
|
| Tamaño estimado de salida
|
| """
|
| return ((input_size + 2 * padding - kernel_size) // stride) + 1
|
|
|
| def print_model_summary(
|
| self,
|
| model: nn.Module,
|
| model_name: Optional[str] = None
|
| ):
|
| """
|
| Imprime un resumen del modelo con sus capas principales.
|
|
|
| Args:
|
| model: Modelo de PyTorch
|
| model_name: Nombre del modelo (opcional)
|
| """
|
| print("\n" + "=" * 70)
|
| print(
|
| f"📊 RESUMEN DEL MODELO{' - ' + model_name if model_name else ''}")
|
| print("=" * 70)
|
|
|
|
|
| total_params = sum(p.numel() for p in model.parameters())
|
| trainable_params = sum(p.numel()
|
| for p in model.parameters() if p.requires_grad)
|
|
|
| print(f"\n💾 Parámetros:")
|
| print(f" Total: {total_params:,}")
|
| print(f" Entrenables: {trainable_params:,}")
|
|
|
|
|
| conv_layers = self.get_conv_layers(model, model_name=None)
|
| print(f"\n🔍 Capas Convolucionales: {len(conv_layers)}")
|
|
|
|
|
| print(f"\n📋 Primeras capas:")
|
| for i, layer_name in enumerate(conv_layers[:5], 1):
|
| info = self.get_layer_info(model, layer_name)
|
| print(f" {i}. {layer_name}")
|
| print(
|
| f" Canales: {info.get('in_channels', '?')} → {info.get('num_channels', '?')}")
|
| if 'kernel_size' in info:
|
| print(
|
| f" Kernel: {info['kernel_size']}, Stride: {info['stride']}")
|
|
|
| if len(conv_layers) > 5:
|
| print(f" ... y {len(conv_layers) - 5} capas más")
|
|
|
| print("\n" + "=" * 70 + "\n")
|
|
|
| def clear_cache(self):
|
| """
|
| Limpia el caché de modelos y libera memoria.
|
| """
|
| if self._model_cache:
|
| num_models = len(self._model_cache)
|
| self._model_cache.clear()
|
| self._layer_info_cache.clear()
|
|
|
|
|
| if torch.cuda.is_available():
|
| torch.cuda.empty_cache()
|
|
|
| print(f"🧹 Caché limpiado ({num_models} modelo(s) removido(s))")
|
| else:
|
| print("ℹ️ Caché ya está vacío")
|
|
|
|
|
|
|
|
|
|
|
|
|
| def get_model_and_layers(model_name: str = DEFAULT_MODEL) -> Tuple[nn.Module, List[str]]:
|
| """
|
| Función de conveniencia para cargar modelo y obtener sus capas.
|
|
|
| Args:
|
| model_name: Nombre del modelo
|
|
|
| Returns:
|
| Tupla (modelo, lista de capas convolucionales)
|
| """
|
| manager = ModelManager()
|
| model = manager.load_model(model_name)
|
| layers = manager.get_conv_layers(model, model_name)
|
| return model, layers
|
|
|
|
|
|
|
|
|
|
|
|
|
| if __name__ == "__main__":
|
| print("🧪 Testing ModelManager...\n")
|
|
|
|
|
| manager = ModelManager()
|
|
|
|
|
| print("\n1️⃣ Cargando AlexNet...")
|
| model = manager.load_model('alexnet')
|
|
|
|
|
| print("\n2️⃣ Extrayendo capas convolucionales...")
|
| layers = manager.get_conv_layers(model, model_name='alexnet')
|
|
|
|
|
| print("\n3️⃣ Información de capa 'features.0'...")
|
| info = manager.get_layer_info(model, 'features.0')
|
| print(f" Información: {info}")
|
|
|
|
|
| print("\n4️⃣ Resumen del modelo...")
|
| manager.print_model_summary(model, 'alexnet')
|
|
|
|
|
| print("\n5️⃣ Probando caché...")
|
| model2 = manager.load_model('alexnet')
|
|
|
| print("\n✅ Testing completado!")
|
|
|