Introducción a ResNet18 para la clasificación de imágenes
La clasificación de imágenes es una tarea fundamental en el campo de la visión por computadora. ResNet18, un modelo de red neurnoal convloucional, ofrece una combinación atractiva de rendimiento y eficiencia, lo que lo convierte en un excelente punto de partida para principiantes y proyectos con presupuesto limitado.
Preparación del Entorno de Trabajo
Para comenzar, asegúrese de tener un entorno con Python y PyTorch configurado. La instalación de las bibliotecas necesarias es el primer paso.
# Instalar las dependencias principales
# pip install torch torchvision pillow
Una vez instalado, procedemos a cargar un modelo preentrenado en el conjunto de datos ImageNet. Este modelo ya sabe reconocer miles de categorías de objetos comunes.
Implementación del Predictor
El proceso implica tres pasos clave: cargar el modelo, preparar la imagen de entrada y obtener la predicción. Modifiquemos la estructura y los nombres de variables del ejemplo típico.
import torch
from torchvision import models, transforms
from PIL import Image
def cargar_y_preparar_modelo():
"""Carga el modelo ResNet18 preentrenado y lo pone en modo evaluación."""
red_neuronal = models.resnet18(weights='IMAGENET1K_V1')
red_neuronal.eval()
return red_neuronal
def transformar_imagen(ruta_imagen):
"""Aplica las transformaciones necesarias a una imagen para el modelo."""
operaciones = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]),
])
imagen = Image.open(ruta_imagen)
tensor_entrada = operaciones(imagen)
return tensor_entrada.unsqueeze(0) # Añadir dimensión de lote
def ejecutar_prediccion(modelo, tensor_imagen):
"""Realiza la inferencia y devuelve las probabilidades."""
with torch.no_grad():
resultado = modelo(tensor_imagen)
probabilidades = torch.nn.functional.softmax(resultado[0], dim=0)
return probabilidades
# Flujo principal
if __name__ == "__main__":
mi_modelo = cargar_y_preparar_modelo()
mi_tensor = transformar_imagen("foto_ejemplo.jpg")
probabilidades_clases = ejecutar_prediccion(mi_modelo, mi_tensor)
indice_predicho = torch.argmax(probabilidades_clases).item()
confianza = probabilidades_clases[indice_predicho].item()
print(f"Clase predicha (índice): {indice_predicho}")
print(f"Nivel de confianza: {confianza:.2%}")
Interpretación de los Resultados
El código anterior devuelve un índice numérico. Para obtener el nombre legible de la clase, podemos cargar un archivo JSON que mapea estos índices a etiquetas.
import json
# Suponiendo que tenemos 'etiquetas_imagenet.json'
with open('etiquetas_imagenet.json', 'r') as archivo:
mapa_clases = json.load(archivo)
nombre_clase = mapa_clases[str(indice_predicho)][1]
print(f"El modelo cree que es un: {nombre_clase}")
Personalización mediante Aprendizaje por Transferencia
Para que el modelo reconozca nuestras propias categorías, podemos reentrenar su última capa. Este proceso se llama aprendizaje por transferencia.
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets
def ajustar_modelo_a_tareas_personalizadas(ruta_dataset, num_clases, epocas=5):
"""Modifica y entrena un ResNet18 para un conjunto de datos nuevo."""
# Cargar el modelo base
modelo_ajustable = models.resnet18(weights='IMAGENET1K_V1')
# Congelar todos los parámetros excepto la capa final
for parametro in modelo_ajustable.parameters():
parametro.requires_grad = False
# Reemplazar la última capa totalmente conectada
caracteristicas_entrada = modelo_ajustable.fc.in_features
modelo_ajustable.fc = nn.Linear(caracteristicas_entrada, num_clases)
# Preparar los datos
transformacion_entrenamiento = transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
conjunto_datos = datasets.ImageFolder(ruta_dataset, transform=transformacion_entrenamiento)
cargador_datos = torch.utils.data.DataLoader(conjunto_datos, batch_size=8, shuffle=True)
# Configurar optimizador y función de pérdida
optimizador = optim.SGD(modelo_ajustable.fc.parameters(), lr=0.001, momentum=0.9)
criterio = nn.CrossEntropyLoss()
# Bucle de entrenamiento
for epoca in range(epocas):
perdida_acumulada = 0.0
for imagenes, etiquetas in cargador_datos:
optimizador.zero_grad()
salidas = modelo_ajustable(imagenes)
perdida = criterio(salidas, etiquetas)
perdida.backward()
optimizador.step()
perdida_acumulada += perdida.item()
perdida_promedio = perdida_acumulada / len(cargador_datos)
print(f"Época {epoca+1}/{epocas}, Pérdida: {perdida_promedio:.4f}")
# Guardar los pesos del modelo entrenado
torch.save(modelo_ajustable.state_dict(), 'modelo_personalizado.pth')
return modelo_ajustable
# Ejemplo de uso:
# mi_modelo_personalizado = ajustar_modelo_a_tareas_personalizadas('./mi_dataset', 3)
Consideraciones y Solución de Problemas
Memoria insuficiente: Si el entrenamiento falla por falta de memoria en la GPU, reduzca el tamaño del lote (parámetro batch_size) o utilice imágenes de menor resolución.
Precisión baja: Asegúrese de tener suficientes imágenes por categoría (idealmente más de 50) y que estén bien etiquetadas. Experimentar con la tasa de aprendizaje (lr) puede mejorar los resultados.
Guardar y cargar el modelo: El proceso ya se muestra en la función anterior. Para usar un modelo guardado, se reconstruye la arquitectura y se cargan los pesos.
# Ejemplo para cargar un modelo guardado
modelo_cargado = models.resnet18(num_classes=NUM_CLASES_ORIGINAL) # Definir la misma arquitectura
modelo_cargado.load_state_dict(torch.load('modelo_personalizado.pth'))
modelo_cargado.eval()
Este flujo de trabajo demuestra que es posible experimentar con redes neuronales avanzadas de manera accesible, incluso con recursos computacionales modestos.