{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "m4-s02-01",
   "metadata": {},
   "source": "# M4 · Sesión 02 — Retropropagación y optimización del entrenamiento\n\n**Deep Learning y Visión Artificial** — 6 ECTS  \nMáster en Inteligencia Artificial, Automatización y Agentes Digitales · Next Educación · Próxima edición\n\n---\n\n## Práctica de la sesión: Diagnóstico de tres entrenamientos averiados\n\nIdentificación y corrección de tres configuraciones de entrenamiento con fallos deliberados de tasa de aprendizaje, inicialización y normalización.\n\n| | |\n|---|---|\n| **Stack** | PyTorch, TensorBoard |\n| **Entregable** | Cuaderno con las tres correcciones y el registro de curvas. |\n\n### Objetivos que se evalúan\n\n1. Explicar el cálculo del gradiente mediante retropropagación.\n2. Seleccionar optimizador y tasa de aprendizaje con criterio.\n3. Diagnosticar un entrenamiento que no converge.\n\n> **Cómo se corrige.** No se evalúa solo el resultado: se evalúan la justificación escrita de cada decisión, la honestidad de la validación y que el cuaderno se ejecute de principio a fin en un equipo distinto al tuyo."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-02",
   "metadata": {},
   "source": "---\n## 0. Entorno\n\nEjecuta esta celda antes que ninguna otra. Fija la semilla y el estilo de figuras que se usa en todo el máster, de modo que los resultados sean comparables entre alumnos."
  },
  {
   "cell_type": "code",
   "id": "m4-s02-03",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "import platform\nimport numpy as np\nimport pandas as pd\nimport matplotlib\nimport matplotlib.pyplot as plt\n\nRANDOM_STATE = 42\nrng = np.random.default_rng(RANDOM_STATE)\nnp.random.seed(RANDOM_STATE)\n\npd.set_option(\"display.width\", 118)\npd.set_option(\"display.max_columns\", 40)\nplt.rcParams.update({\n    \"figure.figsize\": (9, 4.2), \"figure.dpi\": 110, \"axes.grid\": True,\n    \"grid.alpha\": .25, \"axes.spines.top\": False, \"axes.spines.right\": False,\n    \"font.size\": 10, \"axes.titlesize\": 12, \"axes.titleweight\": \"bold\",\n    \"axes.titlecolor\": \"#12395e\", \"axes.labelcolor\": \"#34495e\",\n})\nAZUL, OSCURO, ROJO = \"#2a76dd\", \"#12395e\", \"#e74c3c\"\n\nprint(\"Python\", platform.python_version(), \"| pandas\", pd.__version__,\n      \"| numpy\", np.__version__, \"| matplotlib\", matplotlib.__version__)"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-04",
   "metadata": {},
   "source": "---\n## 1. Datos de la sesión: imágenes sintéticas etiquetadas\n\nSe generan formas geométricas de 32×32 píxeles con ruido controlado. El conjunto permite entrenar y depurar sin descargas, y traslada los mismos problemas que un dataset real: desbalanceo, ruido y ambigüedad entre clases."
  },
  {
   "cell_type": "code",
   "id": "m4-s02-05",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "TAM, N = 32, 1200\nCLASES = [\"circulo\", \"cuadrado\", \"franja\"]\n\ndef dibuja(clase, ruido=.16):\n    img = np.zeros((TAM, TAM), dtype=float)\n    yy, xx = np.mgrid[0:TAM, 0:TAM]\n    cx, cy = rng.integers(11, 21, 2)\n    if clase == \"circulo\":\n        img[(xx - cx) ** 2 + (yy - cy) ** 2 <= rng.integers(5, 9) ** 2] = 1\n    elif clase == \"cuadrado\":\n        l = rng.integers(9, 15); img[cy - l // 2:cy + l // 2, cx - l // 2:cx + l // 2] = 1\n    else:\n        g = rng.integers(4, 8); img[:, cx - g // 2:cx + g // 2] = 1\n    return np.clip(img + rng.normal(0, ruido, (TAM, TAM)), 0, 1)\n\netiquetas = rng.choice(CLASES, N, p=[.45, .35, .20])       # desbalanceo deliberado\nX = np.stack([dibuja(c) for c in etiquetas])\ny = pd.Series(etiquetas, name=\"clase\")\n\nprint(\"X:\", X.shape, \"| rango [%.2f, %.2f]\" % (X.min(), X.max()))\ny.value_counts().rename(\"imágenes\").to_frame()"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-06",
   "metadata": {},
   "source": "### 1.1 Perfilado\n\nAntes de tocar nada, mirar el dato. Este bloque es el mínimo exigible en cualquier entrega del módulo."
  },
  {
   "cell_type": "code",
   "id": "m4-s02-07",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "estad = pd.DataFrame({\n    \"media_intensidad\": X.reshape(len(X), -1).mean(1),\n    \"desv_intensidad\": X.reshape(len(X), -1).std(1),\n    \"pixeles_activos\": (X > .5).reshape(len(X), -1).sum(1),\n    \"clase\": y,\n})\nprint(\"Estadísticos por clase (útiles para detectar fuga por atajo visual):\")\nestad.groupby(\"clase\").agg([\"mean\", \"std\"]).round(3)"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-08",
   "metadata": {},
   "source": "### 1.2 Lectura visual\n\nUna figura que responde a una pregunta concreta, no un gráfico decorativo."
  },
  {
   "cell_type": "code",
   "id": "m4-s02-09",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "fig, axes = plt.subplots(3, 6, figsize=(10, 5.4))\nfor fila, clase in enumerate(CLASES):\n    idx = np.flatnonzero(y == clase)[:6]\n    for col, i in enumerate(idx):\n        axes[fila, col].imshow(X[i], cmap=\"gray\", vmin=0, vmax=1)\n        axes[fila, col].set_xticks([]); axes[fila, col].set_yticks([]); axes[fila, col].grid(False)\n    axes[fila, 0].set_ylabel(clase, color=OSCURO, fontweight=\"bold\")\nfig.suptitle(\"Muestra del conjunto por clase\", color=OSCURO, fontweight=\"bold\")\nplt.tight_layout(); plt.show()"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-10",
   "metadata": {},
   "source": "---\n## 2. Ejemplo trabajado: una línea base sin red neuronal\n\nAntes de entrenar nada profundo, conviene saber cuánto se consigue con estadísticos elementales. Ese número es la referencia contra la que se juzga después la red."
  },
  {
   "cell_type": "code",
   "id": "m4-s02-11",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "from collections import Counter\n\ncarac = np.c_[X.reshape(len(X), -1).mean(1), (X > .5).reshape(len(X), -1).sum(1)]\ncorte = int(.7 * len(X))\nXtr, Xte, ytr, yte = carac[:corte], carac[corte:], y[:corte].to_numpy(), y[corte:].to_numpy()\n\ncentroides = {c: Xtr[ytr == c].mean(0) for c in CLASES}\npred = np.array([min(centroides, key=lambda c: np.linalg.norm(x - centroides[c])) for x in Xte])\n\ntrivial = Counter(ytr).most_common(1)[0][0]\nprint(f\"Línea base trivial (siempre '{trivial}') : {(yte == trivial).mean():.1%}\")\nprint(f\"Centroide sobre 2 características        : {(pred == yte).mean():.1%}\")\nprint(\"\\nCualquier modelo posterior tiene que superar claramente estas dos cifras para justificarse.\")"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-12",
   "metadata": {},
   "source": "---\n## 3. Tu turno: Diagnóstico de tres entrenamientos averiados\n\nA partir de aquí trabajas tú. Cada paso tiene una celda de código y una celda de justificación: **las dos se corrigen**. Si adaptas la práctica a tu propio proyecto, indícalo en el primer paso."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-13",
   "metadata": {},
   "source": "### 3.1 Ejecutar las tres configuraciones y registrar las curvas."
  },
  {
   "cell_type": "code",
   "id": "m4-s02-14",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Paso 1: Ejecutar las tres configuraciones y registrar las curvas.\n# TODO\n"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-15",
   "metadata": {},
   "source": "> **Justificación del paso 1.** _(dos o tres frases)_ Qué has decidido, con qué criterio, y qué alternativa has descartado y por qué."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-16",
   "metadata": {},
   "source": "### 3.2 Diagnosticar la causa de cada fallo."
  },
  {
   "cell_type": "code",
   "id": "m4-s02-17",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Paso 2: Diagnosticar la causa de cada fallo.\n# TODO\n"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-18",
   "metadata": {},
   "source": "> **Justificación del paso 2.** _(dos o tres frases)_ Qué has decidido, con qué criterio, y qué alternativa has descartado y por qué."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-19",
   "metadata": {},
   "source": "### 3.3 Aplicar la corrección mínima en cada caso."
  },
  {
   "cell_type": "code",
   "id": "m4-s02-20",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Paso 3: Aplicar la corrección mínima en cada caso.\n# TODO\n"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-21",
   "metadata": {},
   "source": "> **Justificación del paso 3.** _(dos o tres frases)_ Qué has decidido, con qué criterio, y qué alternativa has descartado y por qué."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-22",
   "metadata": {},
   "source": "### 3.4 Documentar la relación entre síntoma y causa."
  },
  {
   "cell_type": "code",
   "id": "m4-s02-23",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "# Paso 4: Documentar la relación entre síntoma y causa.\n# TODO\n"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-24",
   "metadata": {},
   "source": "> **Justificación del paso 4.** _(dos o tres frases)_ Qué has decidido, con qué criterio, y qué alternativa has descartado y por qué."
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-25",
   "metadata": {},
   "source": "---\n## 4. Cierre y entrega\n\n**Entregable:** Cuaderno con las tres correcciones y el registro de curvas.\n\nAntes de subir el cuaderno al Campus Virtual, repasa la lista. La celda de abajo verifica la parte que puede verificarse sola; el resto lo marcas tú al completarlo."
  },
  {
   "cell_type": "code",
   "id": "m4-s02-26",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": "comprobaciones = {\n    \"Semilla aleatoria fijada\": \"RANDOM_STATE\" in dir(),\n    \"Sin rutas absolutas del equipo\": True,\n    \"Justificacion escrita en todos los pasos\": False,\n    \"Ejecutado de arriba abajo sin errores\": False,\n    \"Sin datos personales ni credenciales\": True,\n}\nfor k, v in comprobaciones.items():\n    print((\"  OK   \" if v else \" FALTA \") + \"| \" + k)\n\nprint()\nprint(\"Listo para entregar.\" if all(comprobaciones.values())\n      else \"Completa lo que falta antes de entregar.\")"
  },
  {
   "cell_type": "markdown",
   "id": "m4-s02-27",
   "metadata": {},
   "source": "---\n*M4 · Sesión 02 — Retropropagación y optimización del entrenamiento · Máster en Inteligencia Artificial, Automatización y Agentes Digitales · Next Educación*  \n*Material docente elaborado por Sami Halawa Ribas. Pendiente de validación por la Dirección del programa.*"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11",
   "mimetype": "text/x-python",
   "file_extension": ".py",
   "pygments_lexer": "ipython3",
   "nbconvert_exporter": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}