Implementación de una Red Neuronal Convolucional en PyTorch para la Detección de Tumores Cerebrales en Imágenes MRI

Preparación del Conjunto de Datos y Transformaciones

Para el procesamiento de imágenes de resonancia magnética (MRI), es fundamental estructurar los datos de manera eficiente. En lugar de cargar todas las imágenes en la memoria durante la inicialización, utilizaremos la clase Dataset de PyTorch para cargar las imágenes bajo demanda. Esto optimiza el uso de la memoria RAM.

A continuación, se define una clase personalizada que hereda de torch.utils.data.Dataset. Esta implementación utiliza pathlib para la gestión de rutas y PIL para la carga de imágenes, aplicando transformaciones estándar de torchvision.

import torch
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
from pathlib import Path

class BrainMRIDataset(Dataset):
    def __init__(self, root_dir, transform=None):
        self.root_dir = Path(root_dir)
        self.transform = transform
        self.image_paths = []
        self.labels = []

        # Estructura esperada: root_dir/yes/*.jpg y root_dir/no/*.jpg
        for label_name, label_val in [('yes', 1.0), ('no', 0.0)]:
            class_dir = self.root_dir / label_name
            if class_dir.exists():
                for img_path in class_dir.glob('*.jpg'):
                    self.image_paths.append(str(img_path))
                    self.labels.append(label_val)

    def __len__(self):
        return len(self.image_paths)

    def __getitem__(self, idx):
        img_path = self.image_paths[idx]
        label = self.labels[idx]

        # Cargar imagen y asegurar que esté en formato RGB
        image = Image.open(img_path).convert('RGB')

        if self.transform:
            image = self.transform(image)

        return {
            'image': image, 
            'label': torch.tensor(label, dtype=torch.float32)
        }

Pipeline de Transformaciones y DataLoader

Las imágenes deben ser redimensionadas y convertidas a tensores. El DataLoader se encarga de agrupar los datos en lotes (batches) y aleatorizar el orden para el entrenamiento.

# Definición del pipeline de transformaciones
transform_pipeline = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.ToTensor()
])

# Instanciar el dataset y el dataloader
dataset = BrainMRIDataset(root_dir='ruta/al/brain_tumor_dataset', transform=transform_pipeline)
dataloader = DataLoader(dataset, batch_size=32, shuffle=True, num_workers=2)

# Verificación de las dimensiones del lote
for batch in dataloader:
    print(f"Dimensiones de las imágenes: {batch['image'].shape}") # torch.Size([32, 3, 128, 128])
    print(f"Dimensiones de las etiquetas: {batch['label'].shape}") # torch.Size([32])
    break

Arquitectura de la Red Neuronal Convloucional (CNN)

Se diseña una arquitectura CNN para la clasificación binaria. El modelo extrae características espaciales mediante capas convolucionales y pooling, seguidas de capas totalmente conectadas para la clasificación final.

import torch.nn as nn
import torch.nn.functional as F

class TumorDetectionCNN(nn.Module):
    def __init__(self):
        super(TumorDetectionCNN, self).__init__()
        
        # Extractor de características
        self.feature_extractor = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2), # 128x128 -> 64x64
            
            nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2)  # 64x64 -> 32x32
        )
        
        # Clasificador (32 * 32 * 32 = 32768)
        self.classifier = nn.Sequential(
            nn.Linear(in_features=32768, out_features=128),
            nn.ReLU(inplace=True),
            nn.Dropout(p=0.5),
            nn.Linear(in_features=128, out_features=1)
        )

    def forward(self, x):
        features = self.feature_extractor(x)
        flattened = features.view(features.size(0), -1)
        logits = self.classifier(flattened)
        return torch.sigmoid(logits)

Ciclo de Entrenamiento y Evaluación

El entrenamiento utiliza la pérdida de entropía cruzada binaria (BCELoss) y el optimizador Adam. Se implementa un bucle de entrenamiento estándar y una fase de evaluación para calcular la precisión del modelo.

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = TumorDetectionCNN().to(device)

criterion = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)

# --- Fase de Entrenamiento ---
num_epochs = 15
for epoch in range(num_epochs):
    model.train()
    running_loss = 0.0
    
    for batch in dataloader:
        inputs = batch['image'].to(device)
        # Ajustar dimensiones de las etiquetas para que coincidan con la salida [batch_size, 1]
        labels = batch['label'].to(device).unsqueeze(1) 

        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item() * inputs.size(0)

    epoch_loss = running_loss / len(dataset)
    print(f'Epoch {epoch+1}/{num_epochs}, Pérdida: {epoch_loss:.4f}')

# --- Fase de Evaluación ---
model.eval()
correct_preds = 0
total_samples = 0

with torch.no_grad():
    for batch in dataloader:
        inputs = batch['image'].to(device)
        labels = batch['label'].to(device)

        outputs = model(inputs)
        # Aplicar umbral de 0.5 para clasificación binaria
        predicted = (outputs >= 0.5).float().squeeze()

        correct_preds += (predicted == labels).sum().item()
        total_samples += labels.size(0)

accuracy = 100 * correct_preds / total_samples
print(f'Precisión en el conjunto de evaluación: {accuracy:.2f}%')

Etiquetas: PyTorch Deep Learning CNN Computer Vision Medical Imaging

Publicado el 7-24 20:55