{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":6927,"databundleVersionId":45059}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"#  Integrantes\nJose Andrés Criollo Echeverry,\nJuan Felipe Fernandez Losada,\nJuan Camilo Salgado Baldion, y\nJuan Diego Parra Patiño","metadata":{}},{"cell_type":"markdown","source":"Importación de librerías","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torchvision import datasets, transforms\nfrom torch.utils.data import DataLoader, random_split\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom sklearn.metrics import confusion_matrix, classification_report\nimport seaborn as sns\nfrom torchvision.utils import save_image, make_grid\nimport os","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:08:42.636147Z","iopub.execute_input":"2026-05-18T22:08:42.636789Z","iopub.status.idle":"2026-05-18T22:08:42.641345Z","shell.execute_reply.started":"2026-05-18T22:08:42.636749Z","shell.execute_reply":"2026-05-18T22:08:42.640441Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Carga y visualización del dataset","metadata":{}},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.ToTensor(),\n])\n\ndataset = datasets.FashionMNIST(\n    root='./data',\n    train=True,\n    download=True,\n    transform=transform\n)\n\n# Visualizar imágenes\nfig, axes = plt.subplots(1,6, figsize=(10,2))\nfor i in range(6):\n    img, label = dataset[i]\n    axes[i].imshow(img.squeeze(), cmap='gray')\n    axes[i].set_title(label)\n    axes[i].axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:08:45.853282Z","iopub.execute_input":"2026-05-18T22:08:45.853923Z","iopub.status.idle":"2026-05-18T22:08:46.087563Z","shell.execute_reply.started":"2026-05-18T22:08:45.853889Z","shell.execute_reply":"2026-05-18T22:08:46.086657Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# División Train / Validation","metadata":{}},{"cell_type":"code","source":"train_size = int(0.8 * len(dataset))\nval_size = len(dataset) - train_size\ntrain_dataset, val_dataset = random_split(dataset, [train_size, val_size])\n\ntrain_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=64, shuffle=False)\n\ntest_dataset = datasets.FashionMNIST(\n    root='./data',\n    train=False,\n    transform=transform\n)\ntest_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:08:48.750642Z","iopub.execute_input":"2026-05-18T22:08:48.751069Z","iopub.status.idle":"2026-05-18T22:08:48.778099Z","shell.execute_reply.started":"2026-05-18T22:08:48.751026Z","shell.execute_reply":"2026-05-18T22:08:48.777422Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Definición de la CNN","metadata":{}},{"cell_type":"markdown","source":"La arquitectura CNN diseñada se justifica por su adecuación al problema de clasificación de imágenes FashionMNIST, el cual consiste en imágenes en escala de grises de tamaño reducido (28×28) con patrones visuales moderadamente complejos. El modelo inicia con dos bloques convolucionales, donde la primera capa Conv2d con 32 filtros permite capturar características básicas locales como bordes, contornos y texturas simples, mientras que la segunda capa con 64 filtros incrementa la capacidad representacional para aprender patrones más abstractos y específicos, como formas distintivas de las prendas. El uso de kernels 3×3 con padding preserva la información espacial y es un estándar efectivo en CNNs modernas. Las capas ReLU introducen no linealidad, facilitando el aprendizaje de relaciones complejas, mientras que las capas MaxPooling reducen progresivamente la dimensionalidad espacial, disminuyendo el costo computacional y aportando invariancia a pequeñas traslaciones. La inclusión de Dropout (0.25) en los bloques convolucionales cumple una función clave de regularización, evitando que el modelo dependa excesivamente de un subconjunto de filtros y reduciendo el sobreajuste observado en arquitecturas sin esta técnica. Posteriormente, la red utiliza una capa completamente conectada de 128 neuronas, que actúa como un integrador global de las características extraídas, permitiendo combinarlas de forma efectiva para la toma de decisiones; en esta etapa se aplica un Dropout más agresivo (0.5), apropiado para capas densas, donde el riesgo de sobreajuste es mayor. Finalmente, la capa de salida con 10 neuronas corresponde directamente al número de clases del dataset y utiliza logits para la clasificación multiclase.","metadata":{}},{"cell_type":"code","source":"class CNN(nn.Module):\n    def __init__(self):\n        super(CNN, self).__init__()\n\n        self.conv = nn.Sequential(\n            nn.Conv2d(1, 32, kernel_size=3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n            nn.Dropout(0.25),\n\n            nn.Conv2d(32, 64, kernel_size=3, padding=1),\n            nn.ReLU(),\n            nn.MaxPool2d(2),\n            nn.Dropout(0.25)\n        )\n\n        self.fc = nn.Sequential(\n            nn.Flatten(),\n            nn.Linear(64 * 7 * 7, 128),\n            nn.ReLU(),\n            nn.Dropout(0.5),\n            nn.Linear(128, 10)\n        )\n\n    def forward(self, x):\n        x = self.conv(x)\n        x = self.fc(x)\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:08:51.350812Z","iopub.execute_input":"2026-05-18T22:08:51.351435Z","iopub.status.idle":"2026-05-18T22:08:51.357297Z","shell.execute_reply.started":"2026-05-18T22:08:51.351403Z","shell.execute_reply":"2026-05-18T22:08:51.356281Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Configuración de entrenamiento","metadata":{}},{"cell_type":"markdown","source":"Para acelerar el entrenamiento y aprovechar los recursos disponibles en Kaggle, se empleó paralelismo de datos utilizando DataParallel de PyTorch, distribuyendo cada batch entre las dos GPUs Tesla T4 disponibles. Este enfoque permite reducir el tiempo de entrenamiento sin modificar la arquitectura del modelo ni el procedimiento de optimización. La sincronización automática de gradientes garantiza coherencia en la actualización de pesos, manteniendo el mismo comportamiento del entrenamiento que en una sola GPU pero con mayor eficiencia computacional.","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = CNN()\n\nif torch.cuda.device_count() > 1:\n    print(f\"Usando {torch.cuda.device_count()} GPUs\")\n    model = nn.DataParallel(model)  # usa ambas T4\n   \nmodel = model.to(device)\n\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(\n    model.parameters(),\n    lr=0.001,\n    weight_decay=1e-4  # Regularización L2\n)\n\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer,\n    mode='min',\n    factor=0.5,\n    patience=2,\n)\n\n## EARLY STOPPING \npatience = 5\nbest_val_loss = float('inf')\nepochs_no_improve = 0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:08:54.062263Z","iopub.execute_input":"2026-05-18T22:08:54.062675Z","iopub.status.idle":"2026-05-18T22:08:54.547525Z","shell.execute_reply.started":"2026-05-18T22:08:54.062645Z","shell.execute_reply":"2026-05-18T22:08:54.546902Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Entrenamiento de la CNN","metadata":{}},{"cell_type":"code","source":"epochs = 50\ntrain_losses, val_losses = [], []\n\nfor epoch in range(epochs):\n    model.train()\n    train_loss = 0\n\n    for x, y in train_loader:\n        x, y = x.to(device), y.to(device)\n\n        optimizer.zero_grad()\n        output = model(x)\n        loss = criterion(output, y)\n        loss.backward()\n        optimizer.step()\n\n        train_loss += loss.item()\n\n    train_loss /= len(train_loader)\n    train_losses.append(train_loss)\n\n    # Validación\n    model.eval()\n    val_loss = 0\n    with torch.no_grad():\n        for x, y in val_loader:\n            x, y = x.to(device), y.to(device)\n            output = model(x)\n            loss = criterion(output, y)\n            val_loss += loss.item()\n\n    val_loss /= len(val_loader)\n    val_losses.append(val_loss)\n\n    print(f\"Epoch {epoch+1} / {epochs} | Train Loss: {train_loss:.4f} | Val Loss: {val_loss:.4f}\")\n    \n    # Scheduler\n    prev_lr = optimizer.param_groups[0]['lr']\n\n    scheduler.step(val_loss)\n\n    current_lr = optimizer.param_groups[0]['lr']\n    if current_lr != prev_lr:\n        print(f\"Learning rate reducido a {current_lr}\")\n\n    # Early stopping\n    if val_loss < best_val_loss:\n        best_val_loss = val_loss\n        epochs_no_improve = 0\n        torch.save(model.state_dict(), \"best_model.pth\")\n        print(\"Mejores pesos guardados\")\n    else:\n        epochs_no_improve += 1\n        if epochs_no_improve >= patience:\n            print(\"Early stopping activado\")\n            break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:08:59.091791Z","iopub.execute_input":"2026-05-18T22:08:59.092562Z","iopub.status.idle":"2026-05-18T22:20:04.123859Z","shell.execute_reply.started":"2026-05-18T22:08:59.092530Z","shell.execute_reply":"2026-05-18T22:20:04.123158Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Gráfica de pérdidas","metadata":{}},{"cell_type":"code","source":"plt.plot(train_losses, label='Entrenamiento')\nplt.plot(val_losses, label='Validación')\nplt.xlabel('Épocas')\nplt.ylabel('Pérdida')\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:01.375429Z","iopub.execute_input":"2026-05-18T22:22:01.375904Z","iopub.status.idle":"2026-05-18T22:22:01.485671Z","shell.execute_reply.started":"2026-05-18T22:22:01.375871Z","shell.execute_reply":"2026-05-18T22:22:01.485081Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Analisis ","metadata":{}},{"cell_type":"markdown","source":"El entrenamiento del modelo CNN presenta un **comportamiento estable, progresivo y bien regularizado** a lo largo de las 50 épocas, evidenciado por una **disminución consistente de la pérdida de entrenamiento** desde 0.5118 hasta aproximadamente 0.147, lo cual indica que el modelo aprende de forma efectiva las representaciones relevantes del conjunto FashionMNIST; paralelamente, la **pérdida de validación disminuye de 0.3696 hasta alrededor de 0.170**, con oscilaciones leves y controladas en las fases intermedias y finales, lo que sugiere una **buena capacidad de generalización** y ausencia de sobreajuste severo. Durante las primeras épocas, la reducción simultánea de ambas pérdidas confirma una convergencia rápida hacia una solución adecuada, mientras que en las etapas intermedias el modelo entra en una fase de ajuste fino donde las mejoras son más graduales; en este contexto, la **reducción dinámica del learning rate** resulta clave, ya que cada disminución del mismo permite al modelo escapar de mesetas y alcanzar mínimos más profundos en la pérdida de validación, evidenciado por la frecuencia con la que se guardan los mejores pesos. En las últimas épocas, aunque la pérdida de entrenamiento continúa disminuyendo lentamente, la pérdida de validación se estabiliza, lo cual es un comportamiento esperado cuando el modelo alcanza su límite de capacidad representacional; sin embargo, gracias al uso conjunto de **Dropout, regularización L2, scheduler y guardado del mejor modelo**, el sobreajuste se mantiene bajo control y las curvas no divergen significativamente. En conjunto, estos resultados demuestran que el modelo **converge correctamente**, logra un **equilibrio adecuado entre sesgo y varianza** y alcanza un desempeño robusto, siendo apropiado tanto para la tarea de clasificación como para su uso posterior en la evaluación de imágenes generadas por un modelo GAN.\n","metadata":{}},{"cell_type":"markdown","source":"La gráfica de pérdida muestra un comportamiento claramente positivo y coherente con un entrenamiento bien regularizado. Al inicio, se observa una caída pronunciada de la pérdida de entrenamiento, desde un valor cercano a 0.52 hasta aproximadamente 0.30 en las primeras 10 épocas, lo que indica que el modelo aprende rápidamente las características principales del conjunto de datos. De forma paralela, la pérdida de validación también disminuye de manera sostenida, pasando de alrededor de 0.37 a 0.22, lo que evidencia una buena capacidad de generalización en las fases tempranas. A partir de las épocas intermedias (alrededor de la época 15 en adelante), ambas curvas continúan descendiendo, pero de manera más gradual, lo cual es esperado cuando el modelo entra en una fase de ajuste fino. Es importante destacar que las curvas de entrenamiento y validación se mantienen cercanas entre sí y no divergen, lo que indica que no hay sobreajuste severo; por el contrario, la ligera diferencia observada se mantiene estable y controlada. En las últimas épocas, la pérdida de entrenamiento alcanza valores cercanos a 0.15, mientras que la pérdida de validación se estabiliza alrededor de 0.17, mostrando pequeñas oscilaciones normales asociadas al proceso de optimización y al uso de regularización. ","metadata":{}},{"cell_type":"markdown","source":"# Evaluación en Test","metadata":{}},{"cell_type":"code","source":"model.eval()\ncorrect, total = 0, 0\ny_true, y_pred = [], []\n\nwith torch.no_grad():\n    for x, y in test_loader:\n        x, y = x.to(device), y.to(device)\n        outputs = model(x)\n        _, predicted = torch.max(outputs, 1)\n        total += y.size(0)\n        correct += (predicted == y).sum().item()\n\n        y_true.extend(y.cpu().numpy())\n        y_pred.extend(predicted.cpu().numpy())\n\naccuracy = correct / total * 100\nprint(f\"Precisión en test: {accuracy:.2f}%\")\n\nclass_names = test_dataset.classes\nprint(classification_report(y_true, y_pred, target_names=class_names))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:06.207278Z","iopub.execute_input":"2026-05-18T22:22:06.207729Z","iopub.status.idle":"2026-05-18T22:22:08.412166Z","shell.execute_reply.started":"2026-05-18T22:22:06.207699Z","shell.execute_reply":"2026-05-18T22:22:08.411126Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cm = confusion_matrix(y_true, y_pred)\nplt.figure(figsize=(8,6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues')\nplt.xlabel('Predicción')\nplt.ylabel('Real')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:12.863865Z","iopub.execute_input":"2026-05-18T22:22:12.864697Z","iopub.status.idle":"2026-05-18T22:22:13.143154Z","shell.execute_reply.started":"2026-05-18T22:22:12.864663Z","shell.execute_reply":"2026-05-18T22:22:13.142113Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Analisis\n","metadata":{}},{"cell_type":"markdown","source":"A partir de la matriz de confusión y del reporte de métricas por clase, se puede concluir que el modelo presenta un **desempeño sólido y equilibrado**, alcanzando una **precisión global del 93.05%**, lo cual confirma una correcta generalización en el conjunto de prueba. **La matriz de confusión muestra una fuerte concentración de valores en la diagonal principal**, indicando que la mayoría de las muestras de cada clase son correctamente clasificadas; **clases como Trouser, Sandal, Bag y Ankle boot presentan resultados casi perfectos**, con muy pocas confusiones, lo cual se explica por sus características visuales distintivas dentro del conjunto FashionMNIST. En contraste, las principales confusiones se observan entre clases visualmente similares como **T‑shirt/top, Shirt, Pullover y Coat, especialmente en la clase Shirt, que presenta un recall más bajo (0.76) y un F1‑score de 0.79**, indicando que una fracción relevante de camisas es confundida con prendas superiores similares; este patrón es consistente con la naturaleza ambigua de dichas clases incluso para observadores humanos. A pesar de ello, **ninguna clase presenta un colapso en el desempeño**, ya que todas mantienen valores de **F1‑score por encima de 0.79**, lo que evidencia un aprendizaje balanceado. **Las métricas macro avg y weighted avg (0.93) confirman que el modelo no está sesgado hacia clases dominantes y que el rendimiento es homogéneo a lo largo de todas las categorías.**","metadata":{}},{"cell_type":"markdown","source":"# Transformaciones para GAN","metadata":{}},{"cell_type":"code","source":"transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize((0.5,), (0.5,)) \n])\n\ndata_loader = torch.utils.data.DataLoader(\n    datasets.FashionMNIST(\n        root='data',\n        train=True,\n        download=True,\n        transform=transform\n    ),\n    batch_size=128,   # mayor batch con GPU\n    shuffle=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:17.518556Z","iopub.execute_input":"2026-05-18T22:22:17.519230Z","iopub.status.idle":"2026-05-18T22:22:17.591658Z","shell.execute_reply.started":"2026-05-18T22:22:17.519198Z","shell.execute_reply":"2026-05-18T22:22:17.590809Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Modelo GAN","metadata":{}},{"cell_type":"markdown","source":"En la arquitectura del GAN utilizada para FashionMNIST se realizaron varias modificaciones importantes respecto al ejemplo base para MNIST, con el objetivo de mejorar la **estabilidad del entrenamiento y la calidad de las imágenes generadas**. En primer lugar, se incorporaron **capas de Batch Normalization en el generador**, lo cual no estaba presente en la versión original; este cambio es fundamental porque permite estabilizar la distribución de activaciones durante el entrenamiento, evitando oscilaciones fuertes y mejorando la convergencia del modelo, especialmente en datasets más complejos como FashionMNIST. Además, se ajustaron los hiperparámetros del optimizador, utilizando **Adam con betas (0.5, 0.999)** en lugar de los valores por defecto, siguiendo prácticas estándar en GANs, lo cual ayuda a reducir problemas como el colapso de modo y a lograr un equilibrio más estable entre el generador y el discriminador. También se incrementó el **batch size** y se utilizó una tasa de aprendizaje ligeramente mayor (0.0002), permitiendo un aprendizaje más robusto y aprovechando mejor la capacidad de las GPU. Por otro lado, se mantuvo la función de salida **Tanh en el generador junto con la normalización de los datos en el rango \\[-1,1]**, asegurando coherencia entre los datos reales y sintéticos. Aunque la estructura general totalmente conectada (fully connected) no se modificó para respetar el enfoque base del ejercicio, estas mejoras introducidas permiten que el modelo se adapte mejor a la mayor complejidad de FashionMNIST, logrando imágenes más estables y con mayor grado de realismo en comparación con la arquitectura original propuesta para MNIST.\n","metadata":{}},{"cell_type":"markdown","source":"## Discriminador","metadata":{}},{"cell_type":"code","source":"class DiscriminatorNet(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n\n        self.model = nn.Sequential(\n            nn.Linear(784, 1024),\n            nn.LeakyReLU(0.2),\n            nn.Dropout(0.3),\n\n            nn.Linear(1024, 512),\n            nn.LeakyReLU(0.2),\n            nn.Dropout(0.3),\n\n            nn.Linear(512, 256),\n            nn.LeakyReLU(0.2),\n            nn.Dropout(0.3),\n\n            nn.Linear(256, 1),\n            nn.Sigmoid()\n        )\n\n    def forward(self, x):\n        return self.model(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:20.791221Z","iopub.execute_input":"2026-05-18T22:22:20.791650Z","iopub.status.idle":"2026-05-18T22:22:20.797041Z","shell.execute_reply.started":"2026-05-18T22:22:20.791618Z","shell.execute_reply":"2026-05-18T22:22:20.796344Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Generador","metadata":{}},{"cell_type":"code","source":"class GeneratorNet(nn.Module):\n\n    def __init__(self):\n        super().__init__()\n\n        self.model = nn.Sequential(\n            nn.Linear(100, 256),\n            nn.BatchNorm1d(256),       \n            nn.LeakyReLU(0.2),\n\n            nn.Linear(256, 512),\n            nn.BatchNorm1d(512),\n            nn.LeakyReLU(0.2),\n\n            nn.Linear(512, 1024),\n            nn.BatchNorm1d(1024),\n            nn.LeakyReLU(0.2),\n\n            nn.Linear(1024, 784),\n            nn.Tanh()\n        )\n\n    def forward(self, x):\n        return self.model(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:23.232558Z","iopub.execute_input":"2026-05-18T22:22:23.233295Z","iopub.status.idle":"2026-05-18T22:22:23.239318Z","shell.execute_reply.started":"2026-05-18T22:22:23.233264Z","shell.execute_reply":"2026-05-18T22:22:23.238602Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Noise","metadata":{}},{"cell_type":"code","source":"def noise(size):\n    return torch.randn(size, 100).to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:40.009342Z","iopub.execute_input":"2026-05-18T22:22:40.010099Z","iopub.status.idle":"2026-05-18T22:22:40.013456Z","shell.execute_reply.started":"2026-05-18T22:22:40.010067Z","shell.execute_reply":"2026-05-18T22:22:40.012792Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Funciones auxiliares","metadata":{}},{"cell_type":"code","source":"def images_to_vectors(images):\n    return images.view(images.size(0), 784)\n\ndef vectors_to_images(vectors):\n    return vectors.view(vectors.size(0), 1, 28, 28)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:42.833302Z","iopub.execute_input":"2026-05-18T22:22:42.834001Z","iopub.status.idle":"2026-05-18T22:22:42.838007Z","shell.execute_reply.started":"2026-05-18T22:22:42.833969Z","shell.execute_reply":"2026-05-18T22:22:42.837188Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inicializacion del modelo","metadata":{}},{"cell_type":"code","source":"discriminator = DiscriminatorNet().to(device)\ngenerator = GeneratorNet().to(device)\n\nlr = 0.0001  \n\nd_optimizer = optim.Adam(discriminator.parameters(), lr=lr, betas=(0.5, 0.999))\ng_optimizer = optim.Adam(generator.parameters(), lr=lr, betas=(0.5, 0.999))\n\nloss = nn.BCELoss()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:45.653865Z","iopub.execute_input":"2026-05-18T22:22:45.654566Z","iopub.status.idle":"2026-05-18T22:22:45.685274Z","shell.execute_reply.started":"2026-05-18T22:22:45.654533Z","shell.execute_reply":"2026-05-18T22:22:45.684734Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Targets","metadata":{}},{"cell_type":"code","source":"def real_data_target(size):\n    return torch.ones(size, 1).to(device)\n\ndef fake_data_target(size):\n    return torch.zeros(size, 1).to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:48.774178Z","iopub.execute_input":"2026-05-18T22:22:48.775139Z","iopub.status.idle":"2026-05-18T22:22:48.779332Z","shell.execute_reply.started":"2026-05-18T22:22:48.775094Z","shell.execute_reply":"2026-05-18T22:22:48.778553Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Entrenamiento del GAN","metadata":{}},{"cell_type":"markdown","source":"## Entrenamiento discriminador","metadata":{}},{"cell_type":"code","source":"def train_discriminator(optimizer, real_data, fake_data):\n\n    optimizer.zero_grad()\n\n    pred_real = discriminator(real_data)\n    loss_real = loss(pred_real, real_data_target(real_data.size(0)))\n\n    pred_fake = discriminator(fake_data)\n    loss_fake = loss(pred_fake, fake_data_target(real_data.size(0)))\n\n    d_loss = loss_real + loss_fake\n    d_loss.backward()\n    optimizer.step()\n\n    return d_loss, pred_real, pred_fake","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:52.294975Z","iopub.execute_input":"2026-05-18T22:22:52.295733Z","iopub.status.idle":"2026-05-18T22:22:52.300165Z","shell.execute_reply.started":"2026-05-18T22:22:52.295702Z","shell.execute_reply":"2026-05-18T22:22:52.299437Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Entrenamiento generador","metadata":{}},{"cell_type":"code","source":"def train_generator(optimizer, fake_data):\n\n    optimizer.zero_grad()\n\n    pred = discriminator(fake_data)\n    g_loss = loss(pred, real_data_target(pred.size(0)))\n\n    g_loss.backward()\n    optimizer.step()\n\n    return g_loss\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:55.383096Z","iopub.execute_input":"2026-05-18T22:22:55.383523Z","iopub.status.idle":"2026-05-18T22:22:55.387791Z","shell.execute_reply.started":"2026-05-18T22:22:55.383491Z","shell.execute_reply":"2026-05-18T22:22:55.386898Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Entrenamieto general","metadata":{}},{"cell_type":"code","source":"num_epochs = 100\noutput_dir = \"output_images\"\nos.makedirs(output_dir, exist_ok=True)\n\ntest_noise = noise(25)\n\nfor epoch in range(num_epochs):\n    for n_batch, (real_batch, _) in enumerate(data_loader):\n\n        real_data = images_to_vectors(real_batch).to(device)\n\n        # ---------------------\n        # Train Discriminator\n        # ---------------------\n        fake_data = generator(noise(real_data.size(0))).detach()\n\n        d_loss, pred_real, pred_fake = train_discriminator(\n            d_optimizer, real_data, fake_data\n        )\n\n        # ---------------------\n        # Train Generator\n        # ---------------------\n        fake_data = generator(noise(real_data.size(0)))\n\n        g_loss = train_generator(g_optimizer, fake_data)\n\n        if n_batch % 150 == 0:\n            print(f\"Epoch {epoch}/{num_epochs} | Batch {n_batch}\")\n            print(f\"D Loss: {d_loss.item():.4f} | G Loss: {g_loss.item():.4f}\")\n            print(f\"D(real): {pred_real.mean().item():.4f} | D(fake): {pred_fake.mean().item():.4f}\")\n\n            test_images = vectors_to_images(generator(test_noise)).data.cpu()\n            save_image(\n                test_images,\n                os.path.join(output_dir, f'epoch_{epoch}_batch_{n_batch}.png'),\n                nrow=5,\n                normalize=True\n            )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:22:57.839803Z","iopub.execute_input":"2026-05-18T22:22:57.840563Z","iopub.status.idle":"2026-05-18T22:47:56.356055Z","shell.execute_reply.started":"2026-05-18T22:22:57.840530Z","shell.execute_reply":"2026-05-18T22:47:56.355319Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Analisis del entrenamiento","metadata":{}},{"cell_type":"markdown","source":"El entrenamiento del modelo GAN muestra un comportamiento característico de la dinámica adversaria entre el generador y el discriminador, donde ambos modelos compiten constantemente por mejorar su desempeño. En las primeras épocas, se observa que el **Discriminator Loss (D Loss)** inicia alrededor de 1.38 y disminuye progresivamente hacia valores cercanos a 1.05–1.15, mientras que el **Generator Loss (G Loss)** aumenta desde valores cercanos a 0.68 hasta superar 1.3 en algunos puntos; esto indica que, al inicio, el discriminador aprende rápidamente a diferenciar entre imágenes reales y falsas, dificultando el aprendizaje del generador. A medida que avanza el entrenamiento, especialmente después de las primeras 10–15 épocas, ambos valores comienzan a estabilizarse en rangos relativamente constantes (D Loss ≈ 1.15–1.30 y G Loss ≈ 0.85–1.10), lo que evidencia que el sistema alcanza un **equilibrio dinámico**, donde ninguno de los dos modelos domina completamente al otro. Este comportamiento es reforzado por las salidas del discriminador, en donde las predicciones para datos reales (**D(real)**) oscilan generalmente entre 0.55 y 0.65, mientras que para datos falsos (**D(fake**) se mantienen entre 0.35 y 0.45; esto confirma que el discriminador no es completamente seguro en sus decisiones, lo cual es deseable en un GAN bien entrenado. Además, las fluctuaciones constantes en ambas pérdidas indican que el entrenamiento no converge de forma monótona, sino que presenta oscilaciones típicas de este tipo de modelos, sin evidencia clara de colapso de modo ni dominancia total de alguna de las redes. En las etapas finales (épocas 50–100), se observa que las pérdidas se mantienen relativamente estables, sugiriendo que el modelo ha alcanzado un punto de convergencia práctica; sin embargo, la ausencia de una disminución progresiva significativa en la pérdida del generador también indica que la calidad de las imágenes podría estar limitada por la arquitectura fully connected utilizada. En conjunto, el entrenamiento puede considerarse **estable y correcto**, logrando un equilibrio adecuado entre el generador y el discriminador, aunque con las limitaciones esperadas para este tipo de arquitectura en un dataset relativamente complejo como FashionMNIST.\n","metadata":{}},{"cell_type":"markdown","source":"# Generacion de imagenes","metadata":{}},{"cell_type":"code","source":"def generate_images(generator, num_images=25):\n\n    generator.eval()\n\n    z = noise(num_images)\n    fake_images = generator(z)\n\n    fake_images = vectors_to_images(fake_images).cpu()\n\n    plt.figure(figsize=(6,6))\n    grid = make_grid(fake_images, nrow=5, normalize=True)\n    plt.imshow(grid.permute(1,2,0))\n    plt.axis('off')\n    plt.show()\n\n    return fake_images","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:49:30.221553Z","iopub.execute_input":"2026-05-18T22:49:30.222003Z","iopub.status.idle":"2026-05-18T22:49:30.227604Z","shell.execute_reply.started":"2026-05-18T22:49:30.221971Z","shell.execute_reply":"2026-05-18T22:49:30.226989Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fake_imgs = generate_images(generator, 25)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:49:33.562466Z","iopub.execute_input":"2026-05-18T22:49:33.563252Z","iopub.status.idle":"2026-05-18T22:49:33.806293Z","shell.execute_reply.started":"2026-05-18T22:49:33.563206Z","shell.execute_reply":"2026-05-18T22:49:33.805734Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Analisis de resultados\n\nLas imágenes generadas por la GAN presentan un **nivel aceptable de realismo global**, siendo capaces de capturar las **formas principales de las prendas**, especialmente en clases con características distintivas como el calzado, donde se observan bordes relativamente definidos y estructuras coherentes. Sin embargo, la calidad disminuye en clases con mayor variabilidad y similitud estructural, como *Shirt*, *T‑shirt* o *Pullover*, en las cuales se evidencian **errores como ruido, borrosidad, pérdida de detalles finos y ambigüedad entre categorías**. Además, al analizar cuidadosamente la cuadrícula generada, se identifica un caso particularmente relevante en la **parte inferior izquierda**, donde aparece una imagen que corresponde a un **bloque o cuadrado sin forma reconocible**, es decir, una generación completamente fallida; este tipo de resultados indica que el generador aún no logra modelar correctamente ciertas regiones del espacio latente, produciendo salidas sin estructura semántica válida. En conjunto, estos resultados muestran que la GAN ha aprendido la distribución general del dataset, pero su capacidad está limitada por la arquitectura utilizada (fully connected), lo que afecta la representación de detalles espaciales complejos y genera variabilidad en la calidad de las muestras, siendo más efectiva en clases simples y más débil en aquellas que requieren mayor precisión estructural.\n","metadata":{}},{"cell_type":"markdown","source":"# Clasificacion con CNN","metadata":{}},{"cell_type":"markdown","source":"## Inicializacion de la CNN","metadata":{}},{"cell_type":"code","source":"cnn_model = CNN()\n\nstate_dict = torch.load(\"/kaggle/working/best_model.pth\", map_location=device)\n\n# eliminar \"module.\" si existe\nnew_state_dict = {}\nfor k, v in state_dict.items():\n    if k.startswith(\"module.\"):\n        new_state_dict[k[7:]] = v  # quitar \"module.\"\n    else:\n        new_state_dict[k] = v\n        \n\ncnn_model.load_state_dict(new_state_dict)\n\ncnn_model.to(device)\ncnn_model.eval()\n\nprint(\"CNN cargada correctamente sin DataParallel\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:57:46.829651Z","iopub.execute_input":"2026-05-18T22:57:46.830079Z","iopub.status.idle":"2026-05-18T22:57:46.847463Z","shell.execute_reply.started":"2026-05-18T22:57:46.830049Z","shell.execute_reply":"2026-05-18T22:57:46.846777Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Comparacion de etiquetas","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nfrom scipy.stats import entropy\n\n# ---------- 1. Generación (igual que ya tienes) ----------\nnum_samples = 1000\n\nz = noise(num_samples)\nfake_vectors = generator(z)\nfake_images = vectors_to_images(fake_vectors)\n\nfake_images = (fake_images + 1) / 2\nfake_images = fake_images.to(device)\n\n# ---------- 2. Clasificación CNN ----------\ncnn_model.eval()\n\nwith torch.no_grad():\n    outputs = cnn_model(fake_images)\n    probs = torch.softmax(outputs, dim=1)\n    confidences, preds = torch.max(probs, 1)\n\npreds = preds.cpu().numpy()\nconfidences = confidences.cpu().numpy()\n\n# ===========================================================\n#  MÉTRICAS\n# ===========================================================\n\n# ---------- 3. Distribución de clases ----------\nunique, counts = np.unique(preds, return_counts=True)\ndist = counts / counts.sum()\n\nprint(\"\\n Distribución de clases:\")\nfor u, c in zip(unique, counts):\n    print(f\"Clase {u}: {c} imágenes\")\n\n# ---------- 4. Entropía (DIVESIDAD) ----------\nent = entropy(dist)\nprint(f\"\\n Entropía de clases: {ent:.3f}\")\n\n# Máximo ideal (10 clases uniformes)\nmax_entropy = np.log(10)\nprint(f\"Entropía máxima teórica: {max_entropy:.3f}\")\n\n# ---------- 5. Confianza global ----------\nmean_conf = np.mean(confidences)\nstd_conf = np.std(confidences)\n\nprint(f\"\\n Confianza promedio global: {mean_conf:.3f}\")\nprint(f\"Desviación estándar: {std_conf:.3f}\")\n\n# ---------- 6. Calidad por clase ----------\nclass_scores = []\n\nprint(\"\\n Confianza promedio por clase:\")\nfor clase in range(10):\n    mask = preds == clase\n    if np.sum(mask) > 0:\n        avg_conf = np.mean(confidences[mask])\n        class_scores.append((clase, avg_conf))\n        print(f\"Clase {clase}: {avg_conf:.3f}\")\n\n# Ranking\nclass_scores = sorted(class_scores, key=lambda x: x[1], reverse=True)\n\nprint(\"\\n Ranking de clases (mejor → peor):\")\nfor c, score in class_scores:\n    print(f\"Clase {c}: {score:.3f}\")\n\n# ---------- 7. Top / Bottom imágenes ----------\ntop_idx = np.argsort(confidences)[-5:]\nworst_idx = np.argsort(confidences)[:5]\n\n# ---------- 8. Visualización mejores ----------\nfig, axes = plt.subplots(1,5, figsize=(5,2))\nfor i, idx in enumerate(top_idx):\n    img = fake_images[idx].detach().cpu().numpy().squeeze()\n    axes[i].imshow(img, cmap=\"gray\")\n    axes[i].set_title(f\"{confidences[idx]:.2f}\")\n    axes[i].axis(\"off\")\nplt.suptitle(\" Mejores imágenes generadas\")\nplt.show()\n\n# ---------- 9. Visualización peores ----------\nfig, axes = plt.subplots(1,5, figsize=(5,2))\nfor i, idx in enumerate(worst_idx):\n    img = fake_images[idx].detach().cpu().numpy().squeeze()\n    axes[i].imshow(img, cmap=\"gray\")\n    axes[i].set_title(f\"{confidences[idx]:.2f}\")\n    axes[i].axis(\"off\")\nplt.suptitle(\" Peores imágenes generadas\")\nplt.show()\n\n# ---------- 10. Histograma de confianza ----------\nplt.figure(figsize=(6,4))\nplt.hist(confidences, bins=20)\nplt.title(\"Distribución de confianza CNN\")\nplt.xlabel(\"Confianza\")\nplt.ylabel(\"Frecuencia\")\nplt.show()\n\n# ---------- 11. Score global de calidad ----------\n# combinación simple: confianza promedio * diversidad relativa\nquality_score = mean_conf * (ent / max_entropy)\nprint(f\"\\n Score global GAN: {quality_score:.3f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T22:57:52.398763Z","iopub.execute_input":"2026-05-18T22:57:52.399516Z","iopub.status.idle":"2026-05-18T22:57:52.884840Z","shell.execute_reply.started":"2026-05-18T22:57:52.399484Z","shell.execute_reply":"2026-05-18T22:57:52.883870Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Analisis de resultados\n","metadata":{}},{"cell_type":"markdown","source":"En primer lugar, la **distribución de clases** muestra que el modelo genera imágenes de todas las categorías, aunque no de manera completamente uniforme. Las clases más frecuentes son la **clase 4 (171 imágenes)** y la **clase 6 (162 imágenes)**, mientras que clases como la **2 (49 imágenes)** y la **8 (58 imágenes)** están menos representadas. No obstante, la **entropía de clases es 2.233**, muy cercana al valor máximo teórico de **2.303**, lo que indica que, a pesar de pequeñas variaciones, la GAN logra una **alta diversidad en la generación**, cubriendo la mayoría de las clases de manera relativamente balanceada.\n\nEn cuanto a la **calidad de las imágenes**, la **confianza promedio global de 0.852** sugiere que, en general, la CNN reconoce las imágenes generadas como pertenecientes a clases específicas con un nivel bastante alto de seguridad. Sin embargo, la **desviación estándar de 0.177** indica que existe **variabilidad considerable en la calidad**, es decir, el modelo produce tanto imágenes muy buenas como otras con menor realismo.\n\nEl análisis por clase confirma estas diferencias: las clases mejor generadas son **Clase 1 (0.955), Clase 8 (0.944) y Clase 9 (0.911)**, lo que indica que la GAN logra representar correctamente sus estructuras visuales. Por el contrario, las clases con peor desempeño son **Clase 2 (0.709), Clase 6 (0.766) y Clase 4 (0.781)**, evidenciando dificultades en la captura de patrones más complejos o ambiguos. Es particularmente importante notar que algunas clases como la **6 y la 4 presentan alta frecuencia pero baja calidad**, lo que demuestra que la cantidad de imágenes generadas no garantiza una buena representación.\n\n***\n\n\nEn la figura se observan dos conjuntos claramente diferenciados: las **mejores imágenes generadas (parte superior)** y las **peores imágenes (parte inferior)**.\n\n*   **Mejores imágenes (confianza ≈ 1.00):**\n    *   Presentan formas claramente definidas (por ejemplo pantalones o calzado).\n    *   Tienen buena simetría y coherencia estructural.\n    *   Poseen bordes relativamente limpios y poco ruido.\n    *   Son fácilmente reconocidas por la CNN con máxima confianza.\n\n*   **Peores imágenes (confianza entre 0.28 y 0.35):**\n    *   Presentan alto nivel de ruido o distorsión.\n    *   Carecen de estructura clara o simetría.\n    *   Algunas parecen combinaciones incoherentes de patrones o texturas.\n    *   Resultan ambiguas incluso visualmente, no solo para el modelo.\n\n***\n\n\nEl histograma muestra que la mayoría de las predicciones se concentran en valores cercanos a **0.9–1.0**, lo que indica que una gran proporción de imágenes generadas tiene **calidad aceptable o alta**. Sin embargo, también existe una cola hacia valores bajos (≈0.3–0.6), lo que corresponde a las imágenes fallidas observadas en el análisis cualitativo.\n\n***\n\n\nEn conjunto, el análisis demuestra que la GAN logra un **buen equilibrio entre diversidad y calidad**, generando imágenes pertenecientes a todas las clases con una entropía cercana al máximo, lo que indica una adecuada cobertura del espacio de datos. Sin embargo, la calidad no es homogénea: mientras que algunas clases con estructuras bien definidas son generadas de manera consistente y reconocidas con alta confianza, otras presentan dificultades debido a su complejidad visual, lo que se traduce en imágenes ruidosas o ambiguas. La presencia simultánea de ejemplos muy bien definidos y otros claramente defectuosos, junto con la variabilidad en la confianza, evidencia que el modelo ha aprendido la distribución general del dataset, pero aún presenta limitaciones en la generación de detalles finos, lo cual es consistente con el uso de una arquitectura fully connected para un problema con fuerte dependencia espacial como FashionMNIST.\n","metadata":{}},{"cell_type":"markdown","source":"**SEGMENTACIÓN SEMÁNTICA CON U-NET EN PYTORCH**","metadata":{}},{"cell_type":"markdown","source":"**DATASET: CARVANA IMAGE MASKING CHALLENGE**","metadata":{}},{"cell_type":"markdown","source":"Contexto general del artículo\n\nEl artículo “PyTorch implementation of Semantic Segmentation for Single class from scratch” explica cómo construir un modelo de segmentación semántica para una sola clase utilizando PyTorch. El objetivo principal es entrenar una red neuronal capaz de identificar y separar un objeto específico dentro de una imagen, en este caso automóviles del conjunto de datos Carvana. La segmentación semántica consiste en clasificar cada píxel de una imagen, indicando si pertenece o no al objeto de interés.\n\nEn este caso, el modelo recibe como entrada imágenes de automóviles y produce como salida una máscara binaria. En esa máscara, los píxeles que pertenecen al automóvil se representan como la clase positiva, mientras que el fondo se representa como la clase negativa. Por eso se considera un problema de segmentación binaria o de una sola clase.","metadata":{}},{"cell_type":"code","source":"import os\nimport zipfile\nimport cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nimport torch\nimport torch.nn as nn\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\nfrom sklearn.model_selection import train_test_split","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-18T23:46:22.125223Z","iopub.execute_input":"2026-05-18T23:46:22.125568Z","iopub.status.idle":"2026-05-18T23:46:22.597916Z","shell.execute_reply.started":"2026-05-18T23:46:22.125538Z","shell.execute_reply":"2026-05-18T23:46:22.597291Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Configuración del entrenamiento\n\nSe definieron los principales hiperparámetros del modelo, incluyendo tamaño de imagen, batch size, número de épocas y learning rate. Además, se verificó el uso de GPU mediante CUDA para acelerar el entrenamiento.","metadata":{}},{"cell_type":"code","source":"DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\nIMAGE_HEIGHT = 128\nIMAGE_WIDTH = 128\n\nBATCH_SIZE = 8\n\nEPOCHS = 10\n\nLEARNING_RATE = 1e-4\n\nprint(\"Dispositivo:\", DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:08.411092Z","iopub.execute_input":"2026-05-19T00:37:08.412056Z","iopub.status.idle":"2026-05-19T00:37:08.417695Z","shell.execute_reply.started":"2026-05-19T00:37:08.412021Z","shell.execute_reply":"2026-05-19T00:37:08.416637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(os.listdir(\"/kaggle/input/competitions\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:11.273319Z","iopub.execute_input":"2026-05-19T00:37:11.274268Z","iopub.status.idle":"2026-05-19T00:37:11.278931Z","shell.execute_reply.started":"2026-05-19T00:37:11.274234Z","shell.execute_reply":"2026-05-19T00:37:11.278157Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**DESCOMPRIMIR DATASET**","metadata":{}},{"cell_type":"markdown","source":"## Conjunto de datos utilizado\n\nEl conjunto de datos utilizado es Carvana, disponible en Kaggle. Este dataset contiene imágenes de automóviles tomadas desde diferentes ángulos junto con sus respectivas máscaras de segmentación.\n\nCada imagen tiene una máscara asociada que indica exactamente qué parte corresponde al vehículo. Para reducir el costo computacional, las imágenes fueron redimensionadas a 128x128 píxeles.","metadata":{}},{"cell_type":"code","source":"import os\nimport zipfile\n\n# Crear carpetas destino\nos.makedirs(\"/kaggle/working/train\", exist_ok=True)\n\nos.makedirs(\"/kaggle/working/train_masks\", exist_ok=True)\n\n# RUTA BASE REAL DEL DATASET\n\nDATASET_PATH = \"/kaggle/input/competitions/carvana-image-masking-challenge\"\n\n# DESCOMPRIMIR IMÁGENES\nwith zipfile.ZipFile(\n    f\"{DATASET_PATH}/train.zip\",\n    'r'\n) as zip_ref:\n\n    zip_ref.extractall(\"/kaggle/working/train\")\n\n# DESCOMPRIMIR MÁSCARAS\n\nwith zipfile.ZipFile(\n    f\"{DATASET_PATH}/train_masks.zip\",\n    'r'\n) as zip_ref:\n\n    zip_ref.extractall(\"/kaggle/working/train_masks\")\n\nprint(\"Dataset descomprimido correctamente\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:13.224493Z","iopub.execute_input":"2026-05-19T00:37:13.224785Z","iopub.status.idle":"2026-05-19T00:37:19.043347Z","shell.execute_reply.started":"2026-05-19T00:37:13.224762Z","shell.execute_reply":"2026-05-19T00:37:19.042345Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"RUTAS DEL DATASET","metadata":{}},{"cell_type":"code","source":"IMAGE_DIR = \"/kaggle/working/train/train\"\n\nMASK_DIR = \"/kaggle/working/train_masks/train_masks\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:26.071581Z","iopub.execute_input":"2026-05-19T00:37:26.072358Z","iopub.status.idle":"2026-05-19T00:37:26.076426Z","shell.execute_reply.started":"2026-05-19T00:37:26.072325Z","shell.execute_reply":"2026-05-19T00:37:26.075676Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"LISTAR IMÁGENES","metadata":{}},{"cell_type":"code","source":"images = os.listdir(IMAGE_DIR)\n#para entrenamiento rapido\nimages = images[:800]\n\nprint(\"Número de imágenes:\", len(images))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:28.049041Z","iopub.execute_input":"2026-05-19T00:37:28.049886Z","iopub.status.idle":"2026-05-19T00:37:28.058592Z","shell.execute_reply.started":"2026-05-19T00:37:28.049851Z","shell.execute_reply":"2026-05-19T00:37:28.057719Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**DIVISIÓN TRAIN / VALIDATION**","metadata":{}},{"cell_type":"markdown","source":"## División de los datos\n\nEl conjunto de datos fue dividido en entrenamiento y validación utilizando train_test_split. El 80% de las imágenes se utilizó para entrenamiento y el 20% para validación, permitiendo evaluar la capacidad de generalización del modelo.","metadata":{}},{"cell_type":"code","source":"train_images, val_images = train_test_split(\n    images,\n    test_size=0.2,\n    random_state=42\n)\n\nprint(\"Train:\", len(train_images))\n\nprint(\"Validation:\", len(val_images))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:29.879784Z","iopub.execute_input":"2026-05-19T00:37:29.880635Z","iopub.status.idle":"2026-05-19T00:37:29.887041Z","shell.execute_reply.started":"2026-05-19T00:37:29.880601Z","shell.execute_reply":"2026-05-19T00:37:29.886082Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**TRANSFORMACIONES**","metadata":{}},{"cell_type":"markdown","source":"## Preprocesamiento de imágenes\n\nLas imágenes fueron normalizadas y convertidas a tensores para ser utilizadas por PyTorch. La normalización ayuda a estabilizar el entrenamiento y mejorar el aprendizaje del modelo.","metadata":{}},{"cell_type":"code","source":"transform = transforms.Compose([\n\n    transforms.ToTensor(),\n\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:31.985484Z","iopub.execute_input":"2026-05-19T00:37:31.986069Z","iopub.status.idle":"2026-05-19T00:37:31.990066Z","shell.execute_reply.started":"2026-05-19T00:37:31.986041Z","shell.execute_reply":"2026-05-19T00:37:31.989266Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**DATASET PERSONALIZADO**","metadata":{}},{"cell_type":"markdown","source":"## Carga de datos con Dataset personalizado\n\nSe implementó una clase personalizada llamada CarvanaDataset encargada de cargar las imágenes y sus máscaras correspondientes. Esta clase aplica el preprocesamiento necesario y retorna las imágenes y máscaras en formato tensor.\n\nLas imágenes tienen 3 canales RGB, mientras que las máscaras utilizan un único canal binario.","metadata":{}},{"cell_type":"code","source":"class CarvanaDataset(Dataset):\n\n    def __init__(self, images, image_dir, mask_dir):\n\n        self.images = images\n\n        self.image_dir = image_dir\n\n        self.mask_dir = mask_dir\n\n    def __len__(self):\n\n        return len(self.images)\n\n    def __getitem__(self, idx):\n\n        image_name = self.images[idx]\n\n        # -------------------------\n        # CARGAR IMAGEN\n        # -------------------------\n\n        image_path = os.path.join(\n            self.image_dir,\n            image_name\n        )\n\n        image = cv2.imread(image_path)\n\n        image = cv2.cvtColor(\n            image,\n            cv2.COLOR_BGR2RGB\n        )\n\n        image = cv2.resize(\n            image,\n            (IMAGE_WIDTH, IMAGE_HEIGHT)\n        )\n\n        image = image / 255.0\n\n        image = image.astype(np.float32)\n\n        image = transform(image)\n\n        # -------------------------\n        # CARGAR MÁSCARA\n        # -------------------------\n\n        mask_name = image_name.replace(\n            \".jpg\",\n            \"_mask.gif\"\n        )\n\n        mask_path = os.path.join(\n            self.mask_dir,\n            mask_name\n        )\n\n        mask = cv2.imread(\n            mask_path,\n            cv2.IMREAD_GRAYSCALE\n        )\n\n        mask = cv2.resize(\n            mask,\n            (IMAGE_WIDTH, IMAGE_HEIGHT)\n        )\n\n        mask = mask / 255.0\n\n        mask = mask.astype(np.float32)\n\n        mask = torch.tensor(\n            mask,\n            dtype=torch.float32\n        )\n\n        mask = mask.unsqueeze(0)\n\n        return image, mask\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:33.698300Z","iopub.execute_input":"2026-05-19T00:37:33.698873Z","iopub.status.idle":"2026-05-19T00:37:33.706763Z","shell.execute_reply.started":"2026-05-19T00:37:33.698842Z","shell.execute_reply":"2026-05-19T00:37:33.705724Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"CREAR DATASETS","metadata":{}},{"cell_type":"code","source":"train_dataset = CarvanaDataset(\n    train_images,\n    IMAGE_DIR,\n    MASK_DIR\n)\n\nval_dataset = CarvanaDataset(\n    val_images,\n    IMAGE_DIR,\n    MASK_DIR\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:36.509559Z","iopub.execute_input":"2026-05-19T00:37:36.510464Z","iopub.status.idle":"2026-05-19T00:37:36.514168Z","shell.execute_reply.started":"2026-05-19T00:37:36.510430Z","shell.execute_reply":"2026-05-19T00:37:36.513462Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"DATALOADERS","metadata":{}},{"cell_type":"code","source":"train_loader = DataLoader(\n    train_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:38.231456Z","iopub.execute_input":"2026-05-19T00:37:38.232116Z","iopub.status.idle":"2026-05-19T00:37:38.236632Z","shell.execute_reply.started":"2026-05-19T00:37:38.232086Z","shell.execute_reply":"2026-05-19T00:37:38.235453Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**VISUALIZAR EJEMPLOS**","metadata":{}},{"cell_type":"markdown","source":"## Visualización de ejemplos\n\nSe visualizaron algunas imágenes junto con sus máscaras reales para verificar que el proceso de carga y segmentación funcionara correctamente.","metadata":{}},{"cell_type":"code","source":"images_batch, masks_batch = next(iter(train_loader))\n\nplt.figure(figsize=(10,5))\n\nfor i in range(2):\n\n    # Imagen\n    plt.subplot(2,2,i+1)\n\n    img = images_batch[i].permute(1,2,0).numpy()\n\n    plt.imshow(img)\n\n    plt.title(\"Imagen\")\n\n    plt.axis(\"off\")\n\n    # Máscara\n    plt.subplot(2,2,i+3)\n\n    plt.imshow(\n        masks_batch[i][0],\n        cmap=\"gray\"\n    )\n\n    plt.title(\"Máscara\")\n\n    plt.axis(\"off\")\n\nplt.tight_layout()\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:39.879673Z","iopub.execute_input":"2026-05-19T00:37:39.880594Z","iopub.status.idle":"2026-05-19T00:37:40.468402Z","shell.execute_reply.started":"2026-05-19T00:37:39.880556Z","shell.execute_reply":"2026-05-19T00:37:40.467500Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**BLOQUE CONVOLUCIONAL**","metadata":{}},{"cell_type":"markdown","source":"## Arquitectura U-Net\n\nLa arquitectura utilizada fue U-Net, una red neuronal muy utilizada en segmentación semántica. Esta arquitectura tiene una estructura encoder-decoder.\n\nEl encoder reduce progresivamente la imagen para extraer características importantes, mientras que el decoder reconstruye la segmentación final.\n\nAdemás, U-Net utiliza skip connections que ayudan a conservar detalles espaciales importantes.","metadata":{}},{"cell_type":"code","source":"class DoubleConv(nn.Module):\n\n    def __init__(self, in_channels, out_channels):\n\n        super().__init__()\n\n        self.conv = nn.Sequential(\n\n            nn.Conv2d(\n                in_channels,\n                out_channels,\n                kernel_size=3,\n                padding=1\n            ),\n\n            nn.ReLU(inplace=True),\n\n            nn.Conv2d(\n                out_channels,\n                out_channels,\n                kernel_size=3,\n                padding=1\n            ),\n\n            nn.ReLU(inplace=True)\n        )\n\n    def forward(self, x):\n\n        return self.conv(x)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:43.238866Z","iopub.execute_input":"2026-05-19T00:37:43.239309Z","iopub.status.idle":"2026-05-19T00:37:43.245028Z","shell.execute_reply.started":"2026-05-19T00:37:43.239280Z","shell.execute_reply":"2026-05-19T00:37:43.244097Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"MODELO U-NET","metadata":{}},{"cell_type":"code","source":"class UNet(nn.Module):\n\n    def __init__(self):\n\n        super().__init__()\n\n        # ENCODER\n\n        self.down1 = DoubleConv(3, 64)\n\n        self.pool1 = nn.MaxPool2d(2)\n\n        self.down2 = DoubleConv(64, 128)\n\n        self.pool2 = nn.MaxPool2d(2)\n\n        # BOTTLENECK\n\n        self.bottleneck = DoubleConv(128, 256)\n        # DECODEr\n\n        self.up1 = nn.ConvTranspose2d(\n            256,\n            128,\n            kernel_size=2,\n            stride=2\n        )\n\n        self.conv1 = DoubleConv(256, 128)\n\n        self.up2 = nn.ConvTranspose2d(\n            128,\n            64,\n            kernel_size=2,\n            stride=2\n        )\n\n        self.conv2 = DoubleConv(128, 64)\n\n        # CAPA FINAL\n        self.final = nn.Conv2d(\n            64,\n            1,\n            kernel_size=1\n        )\n\n    def forward(self, x):\n\n        # ENCODER\n\n        d1 = self.down1(x)\n\n        p1 = self.pool1(d1)\n\n        d2 = self.down2(p1)\n\n        p2 = self.pool2(d2)\n\n        # BOTTLENECK\n\n        bottleneck = self.bottleneck(p2)\n\n        # DECODER\n\n        up1 = self.up1(bottleneck)\n\n        up1 = torch.cat([up1, d2], dim=1)\n\n        up1 = self.conv1(up1)\n\n        up2 = self.up2(up1)\n\n        up2 = torch.cat([up2, d1], dim=1)\n\n        up2 = self.conv2(up2)\n\n        # OUTPUT\n\n        output = self.final(up2)\n\n        return torch.sigmoid(output)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:45.178536Z","iopub.execute_input":"2026-05-19T00:37:45.178846Z","iopub.status.idle":"2026-05-19T00:37:45.187133Z","shell.execute_reply.started":"2026-05-19T00:37:45.178821Z","shell.execute_reply":"2026-05-19T00:37:45.186265Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" CREAR MODELO","metadata":{}},{"cell_type":"code","source":"model = UNet().to(DEVICE)\n\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:48.481197Z","iopub.execute_input":"2026-05-19T00:37:48.481985Z","iopub.status.idle":"2026-05-19T00:37:48.506586Z","shell.execute_reply.started":"2026-05-19T00:37:48.481950Z","shell.execute_reply":"2026-05-19T00:37:48.505697Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**FUNCIÓN DE PÉRDIDA**","metadata":{}},{"cell_type":"markdown","source":"## Función de pérdida\n\nPara el entrenamiento se utilizó Binary Cross Entropy Loss (BCELoss), adecuada para problemas de segmentación binaria donde cada píxel puede pertenecer al fondo o al objeto de interés.","metadata":{}},{"cell_type":"code","source":"criterion = nn.BCELoss()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:51.375755Z","iopub.execute_input":"2026-05-19T00:37:51.376768Z","iopub.status.idle":"2026-05-19T00:37:51.380651Z","shell.execute_reply.started":"2026-05-19T00:37:51.376719Z","shell.execute_reply":"2026-05-19T00:37:51.379926Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"OPTIMIZADOR","metadata":{}},{"cell_type":"markdown","source":"## Optimizador\n\nSe utilizó el optimizador Adam debido a su buen rendimiento y estabilidad durante el entrenamiento de redes neuronales profundas.","metadata":{}},{"cell_type":"code","source":"optimizer = torch.optim.Adam(\n    model.parameters(),\n    lr=LEARNING_RATE\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:52.931555Z","iopub.execute_input":"2026-05-19T00:37:52.931841Z","iopub.status.idle":"2026-05-19T00:37:52.936888Z","shell.execute_reply.started":"2026-05-19T00:37:52.931818Z","shell.execute_reply":"2026-05-19T00:37:52.936119Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**ENTRENAMIENTO**","metadata":{}},{"cell_type":"markdown","source":"## Proceso de entrenamiento\n\nDurante cada época el modelo recibió imágenes de entrada, generó máscaras predichas y calculó la pérdida respecto a las máscaras reales.\n\nPosteriormente se realizó backpropagation para actualizar los pesos de la red neuronal y mejorar progresivamente las predicciones.","metadata":{}},{"cell_type":"code","source":"train_losses = []\n\nfor epoch in range(EPOCHS):\n\n    model.train()\n\n    running_loss = 0\n\n    for images, masks in train_loader:\n\n        images = images.to(DEVICE)\n\n        masks = masks.to(DEVICE)\n\n        # PREDICCIÓN\n\n        outputs = model(images)\n\n        # LOSS\n\n        loss = criterion(outputs, masks)\n\n        # BACKPROPAGATION\n\n        optimizer.zero_grad()\n\n        loss.backward()\n\n        optimizer.step()\n\n        running_loss += loss.item()\n\n    epoch_loss = running_loss / len(train_loader)\n\n    train_losses.append(epoch_loss)\n\n    print(\n        f\"Epoch [{epoch+1}/{EPOCHS}] \"\n        f\"Loss: {epoch_loss:.4f}\"\n    )\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:37:55.012776Z","iopub.execute_input":"2026-05-19T00:37:55.013615Z","iopub.status.idle":"2026-05-19T00:43:34.806298Z","shell.execute_reply.started":"2026-05-19T00:37:55.013576Z","shell.execute_reply":"2026-05-19T00:43:34.805379Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**GRÁFICA DE PÉRDIDA**","metadata":{}},{"cell_type":"markdown","source":"## Análisis del entrenamiento\n\nLa pérdida disminuyó progresivamente durante el entrenamiento, indicando que el modelo aprendió correctamente a segmentar los vehículos.\n\nNo se observaron comportamientos inestables ni aumentos bruscos de la pérdida.","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(8,5))\n\nplt.plot(train_losses)\n\nplt.title(\"Loss durante entrenamiento\")\n\nplt.xlabel(\"Epoch\")\n\nplt.ylabel(\"Loss\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:43:42.018411Z","iopub.execute_input":"2026-05-19T00:43:42.019412Z","iopub.status.idle":"2026-05-19T00:43:42.152103Z","shell.execute_reply.started":"2026-05-19T00:43:42.019356Z","shell.execute_reply":"2026-05-19T00:43:42.151380Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"RESULTADOS DE SEGMENTACIÓN","metadata":{}},{"cell_type":"code","source":"model.eval()\n\nwith torch.no_grad():\n\n    for i, (images, masks) in enumerate(val_loader):\n\n        images = images.to(DEVICE)\n\n        predictions = model(images)\n\n        image = images[0].cpu().permute(1,2,0).numpy()\n\n        mask = masks[0][0].cpu().numpy()\n\n        prediction = predictions[0][0].cpu().numpy()\n\n        prediction = (prediction > 0.5).astype(np.float32)\n\n        plt.figure(figsize=(12,4))\n\n        # -------------------------\n        # IMAGEN ORIGINAL\n        # -------------------------\n\n        plt.subplot(1,3,1)\n\n        plt.imshow(image)\n\n        plt.title(\"Imagen Original\")\n\n        plt.axis(\"off\")\n\n        # -------------------------\n        # MÁSCARA REAL\n        # -------------------------\n\n        plt.subplot(1,3,2)\n\n        plt.imshow(mask, cmap=\"gray\")\n\n        plt.title(\"Máscara Real\")\n\n        plt.axis(\"off\")\n\n        # -------------------------\n        # PREDICCIÓN\n        # -------------------------\n\n        plt.subplot(1,3,3)\n\n        plt.imshow(prediction, cmap=\"gray\")\n\n        plt.title(\"Predicción\")\n\n        plt.axis(\"off\")\n\n        plt.tight_layout()\n\n        plt.show()\n\n        # Mostrar 5 ejemplos\n        if i == 4:\n            break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-19T00:43:45.735459Z","iopub.execute_input":"2026-05-19T00:43:45.735748Z","iopub.status.idle":"2026-05-19T00:43:48.992250Z","shell.execute_reply.started":"2026-05-19T00:43:45.735723Z","shell.execute_reply":"2026-05-19T00:43:48.991428Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Resultados obtenidos\n\nEl modelo logró segmentar correctamente la mayoría de los vehículos del conjunto de validación. Las máscaras predichas muestran una buena coincidencia con las máscaras reales.\n\nSin embargo, todavía existen algunas limitaciones en bordes complejos y detalles pequeños, especialmente en ruedas o regiones difíciles.","metadata":{}},{"cell_type":"markdown","source":"## Limitaciones del enfoque\n\nSin embargo, el enfoque también tiene algunas limitaciones. La primera es que el modelo está diseñado para una sola clase. Esto significa que solo puede segmentar automóviles y no diferentes tipos de objetos al mismo tiempo. Si se quisiera segmentar varias clases, habría que cambiar la cantidad de canales de salida y ajustar la función de pérdida.\n\nOtra limitación es la resolución de entrada. Al trabajar con imágenes de 128 por 128 píxeles, el entrenamiento es más rápido, pero se pierden detalles finos. Esto puede afectar la precisión de los bordes de la máscara, especialmente si se trabaja con objetos pequeños o con imágenes más complejas.\n\nTambién hay que tener en cuenta que el dataset Carvana es relativamente controlado. Las imágenes están centradas en automóviles y los fondos no son tan variados como en escenarios reales. Por eso, aunque el modelo funciona muy bien en este conjunto de datos, su rendimiento podría disminuir si se aplica a imágenes tomadas en condiciones más difíciles, con fondos complejos, iluminación variable u objetos parcialmente ocultos.\n\nOtra limitación es que el artículo reporta principalmente el Dice Score. Aunque esta métrica es muy útil para segmentación, sería recomendable complementarla con otras métricas como IoU, precisión, recall o análisis visual más detallado. Esto permitiría evaluar mejor los errores del modelo.\n\nTambién se puede considerar como limitación que la implementación depende de la librería segmentation_models.pytorch. Aunque esto facilita mucho el desarrollo, también oculta parte del funcionamiento interno de la arquitectura. Para un aprendizaje más profundo, sería útil implementar la U-Net manualmente, capa por capa, para entender mejor cómo funciona el encoder, el decoder y las conexiones intermedias.","metadata":{}},{"cell_type":"markdown","source":"## Posibles mejoras\n\nComo posibles mejoras, se podría aumentar la resolución de las imágenes a 256 por 256 o incluso más, para obtener máscaras con bordes más detallados. También se podrían aplicar más técnicas de aumento de datos, como rotaciones, cambios de brillo, recortes aleatorios o variaciones de contraste. Esto ayudaría a que el modelo generalice mejor.\n\nOtra mejora sería probar arquitecturas más avanzadas, como U-Net++, DeepLabV3+ o modelos con encoders más potentes como ResNet-34, ResNet-50 o EfficientNet. Estas arquitecturas podrían capturar mejor los detalles y mejorar el rendimiento en escenarios más complejos.\n\nTambién sería útil combinar la pérdida Dice con Binary Cross Entropy o Focal Loss. Esto podría mejorar el aprendizaje en zonas difíciles, especialmente en los bordes o en regiones donde el modelo se confunde entre fondo y objeto.\n\nAdemás, se podrían aplicar técnicas de postprocesamiento para refinar las máscaras, como umbralización adaptativa o métodos para suavizar los bordes. Esto permitiría obtener resultados visualmente más limpios y precisos.","metadata":{}},{"cell_type":"markdown","source":"## Conclusión general\n\nEn conclusión, se realizó una implementación práctica de segmentación semántica binaria utilizando PyTorch y una arquitectura U-Net construida manualmente. El modelo fue entrenado con el dataset Carvana Image Masking Challenge, logrando aprender correctamente la segmentación de automóviles a partir de máscaras binarias.\n\nDurante el entrenamiento se observó una disminución progresiva de la función de pérdida, indicando que la red neuronal logró aprender las características necesarias para separar el vehículo del fondo. Los resultados obtenidos muestran que el modelo puede generar máscaras bastante precisas y reconocer adecuadamente la forma general de los automóviles.\n\nA pesar de los buenos resultados, todavía existen algunas limitaciones relacionadas con los bordes y detalles finos de las segmentaciones. Esto puede deberse al tamaño reducido de las imágenes utilizadas y al número limitado de épocas de entrenamiento.\n\nEn general, esta implementación permitió comprender el flujo completo de trabajo en segmentación semántica con PyTorch, incluyendo carga de datos, preprocesamiento, creación del dataset personalizado, arquitectura U-Net, función de pérdida, entrenamiento, validación y visualización de resultados.\n\nComo posibles mejoras futuras, se podrían utilizar imágenes de mayor resolución, entrenar durante más épocas y probar arquitecturas más avanzadas para obtener segmentaciones aún más precisas.","metadata":{}}]}