{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Entrenar una red\n",
    "\n",
    "Más neuronas, más vueltas y más paso: los tres botones que todo el mundo sube, medidos uno por uno. Los tres la empeoran.\n",
    "\n",
    "Cuaderno de práctica del capítulo 7 de **Deep learning desde cero**, de Miss Yera.\n",
    "\n",
    "Corre de arriba abajo. Si lo abres en Google Colab no necesitas instalar nada.\n",
    "\n",
    "Capítulo completo: https://missyera.com/guias/deep-learning-desde-cero/entrenar-una-red/\n",
    "\n",
    "Los ejercicios están al final y traen una celda vacía debajo de cada uno. Las\n",
    "respuestas viven en el cuaderno de soluciones, y merece la pena pelearse un\n",
    "rato antes de abrirlo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Antes de empezar\n",
    "\n",
    "Esta celda baja el ayudante que corrige tus ejercicios. Después, en cada\n",
    "ejercicio que se pueda corregir solo, vas a ver `%%revisa` arriba de la celda:\n",
    "escribe tu respuesta debajo, ejecuta, y te digo si te salió 💛"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# El ayudante de los cuadernos. Trae la corrección de los ejercicios y, en los\n",
    "# capítulos de consola, la celda mágica que ejecuta los comandos. Se baja en\n",
    "# vez de venir pegado aquí para que siempre sea el último.\n",
    "urllib.request.urlretrieve(\n",
    "    \"https://missyera.com/static/cuadernos/revisa.py\", \"revisa.py\")\n",
    "import revisa\n",
    "revisa.carga({\n",
    "    1: \"Z3VhcmRhbmRvIGVsIG1lam9yIDogMC43MTc2CnF1ZWTDoW5kb3RlIGVsIMO6bHRpbW86IDAuNjU5Nw==\",\n",
    "    2: \"cGFjaWVuY2lhICAgMzogcGFyw7MgZW4gICAzMDAgIHZhbGlkYWNpw7NuIDAuNzA2MSAgcHJ1ZWJhIDAuNzE3NgpwYWNpZW5jaWEgIDEwOiBwYXLDsyBlbiAgIDMwMCAgdmFsaWRhY2nDs24gMC43MDYxICBwcnVlYmEgMC43MTc2CnBhY2llbmNpYSAgMjA6IHBhcsOzIGVuICAgMzAwICB2YWxpZGFjacOzbiAwLjcwNjEgIHBydWViYSAwLjcxNzYKcGFjaWVuY2lhICA1MDogcGFyw7MgZW4gICAzMDAgIHZhbGlkYWNpw7NuIDAuNzA2MSAgcHJ1ZWJhIDAuNzE3Ng==\",\n",
    "    3: \"cmVkIGNvbiBwYXJhZGEgIDogMC43MjA1CmxvZ8Otc3RpY2EgICAgICAgOiAwLjcyMTc=\",\n",
    "    4: \"bWVqb3IgOiAwLjcyMDMKcGVvciAgOiAwLjcwNjQKbWVkaWEgOiAwLjcxNTkKbG9nw61zdGljYTogMC43MjE3\",\n",
    "    5: \"dnVlbHRhICAgICAwICBlbnRyZW5hIDAuNDA0MSAgdmFsaWRhY2nDs24gMC4zODg4CnZ1ZWx0YSAgIDMwMCAgZW50cmVuYSAwLjcyODAgIHZhbGlkYWNpw7NuIDAuNzA2MQp2dWVsdGEgICA2MDAgIGVudHJlbmEgMC43NDk3ICB2YWxpZGFjacOzbiAwLjY5OTcKdnVlbHRhICAgOTAwICBlbnRyZW5hIDAuNzY4MCAgdmFsaWRhY2nDs24gMC42OTMwCnZ1ZWx0YSAgMTIwMCAgZW50cmVuYSAwLjc4NDUgIHZhbGlkYWNpw7NuIDAuNjgwMQp2dWVsdGEgIDE1MDAgIGVudHJlbmEgMC43OTkxICB2YWxpZGFjacOzbiAwLjY2ODU=\",\n",
    "    6: \"ICA0IG5ldXJvbmFzOiAgMTIxIHBlc29zIHBhcmEgMTY4NyBmaWxhcyAgKDAuMDcyIHBvciBmaWxhKQogIDggbmV1cm9uYXM6ICAyNDEgcGVzb3MgcGFyYSAxNjg3IGZpbGFzICAoMC4xNDMgcG9yIGZpbGEpCiAxNiBuZXVyb25hczogIDQ4MSBwZXNvcyBwYXJhIDE2ODcgZmlsYXMgICgwLjI4NSBwb3IgZmlsYSkKIDMyIG5ldXJvbmFzOiAgOTYxIHBlc29zIHBhcmEgMTY4NyBmaWxhcyAgKDAuNTcwIHBvciBmaWxhKQ==\",\n",
    "    7: \"MTAuMDAwIHZ1ZWx0YXMgYSBjaWVnYXMsIDMyIG5ldXJvbmFzOiAwLjU4Nwpjb24gcGFyYWRhIHRlbXByYW5hICAgICAgICAgICAgICAgICA6IDAuNzIwNQ==\",\n",
    "    8: \"VmFsdWVFcnJvcjogRm91bmQgaW5wdXQgdmFyaWFibGVzIHdpdGggaW5jb25zaXN0ZW50IG51bWJlcnMgb2Ygc2FtcGxlczogWzU2MywgMTY4N10=\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya tenemos todas las piezas: la neurona, la activación, las capas, el\n",
    "gradiente y la retropropagación. Toca montarlas bien y soltarlas sobre las\n",
    "ventas de la distribuidora 🚀\n",
    "\n",
    "Y el capítulo va a consistir, básicamente, en subir botones y ver cómo todo\n",
    "empeora. Que suena raro y es lo más útil que te puedo enseñar aquí.\n",
    "\n",
    "Y te pregunto de entrada: **¿qué harías tú si tu modelo acertara el 100% en entrenamiento?** Guárdate la respuesta, que este capítulo va de eso 🎛️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El punto de partida"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "from sklearn.compose import ColumnTransformer\n",
    "from sklearn.impute import SimpleImputer\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.metrics import roc_auc_score\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.pipeline import Pipeline\n",
    "from sklearn.preprocessing import OneHotEncoder, StandardScaler\n",
    "\n",
    "URL = 'https://missyera.com/static/datasets/ventas-miss-yera.csv'\n",
    "\n",
    "def sigmoide(z):\n",
    "    return 1 / (1 + np.exp(-z))\n",
    "\n",
    "def carga_limpia(url):\n",
    "    v = pd.read_csv(url).drop_duplicates()\n",
    "    v['ciudad'] = (v['ciudad'].str.strip().str.lower()\n",
    "                   .str.normalize('NFKD')\n",
    "                   .str.encode('ascii', 'ignore').str.decode('utf-8'))\n",
    "    v['monto'] = pd.to_numeric(v['monto'].str.replace(',', '.'))\n",
    "    for col in ['fecha', 'fecha_ultima_compra']:\n",
    "        f = pd.to_datetime(v[col], format='%Y-%m-%d', errors='coerce')\n",
    "        falta = f.isna() & v[col].notna()\n",
    "        f[falta] = pd.to_datetime(v.loc[falta, col], format='%d/%m/%Y', errors='coerce')\n",
    "        v[col] = f\n",
    "    return v\n",
    "\n",
    "def prepara(v):\n",
    "    v = v.sort_values(['cliente_id', 'fecha']).copy()\n",
    "    v['sin_compra_previa'] = v['fecha_ultima_compra'].isna().astype(int)\n",
    "    v['sin_descuento'] = v['descuento'].isna().astype(int)\n",
    "    v['sin_satisfaccion'] = v['satisfaccion'].isna().astype(int)\n",
    "    v['precio_unitario'] = v['monto'] / v['unidades']\n",
    "    v['visita_numero'] = v.groupby('cliente_id').cumcount() + 1\n",
    "    return v\n",
    "\n",
    "NUMERICAS = ['unidades', 'monto', 'descuento', 'satisfaccion', 'precio_unitario',\n",
    "             'sin_compra_previa', 'sin_descuento', 'sin_satisfaccion', 'visita_numero']\n",
    "CATEGORICAS = ['ciudad', 'segmento', 'canal', 'categoria']\n",
    "\n",
    "datos = prepara(carga_limpia(URL))\n",
    "X = datos[NUMERICAS + CATEGORICAS]\n",
    "y = datos['compro']\n",
    "X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25,\n",
    "                                          random_state=42, stratify=y)\n",
    "\n",
    "logistica = Pipeline([\n",
    "    ('pre', ColumnTransformer([\n",
    "        ('num', Pipeline([('r', SimpleImputer(strategy='median')),\n",
    "                          ('e', StandardScaler())]), NUMERICAS),\n",
    "        ('cat', Pipeline([('r', SimpleImputer(strategy='most_frequent')),\n",
    "                          ('c', OneHotEncoder(handle_unknown='ignore'))]), CATEGORICAS),\n",
    "    ])),\n",
    "    ('mod', LogisticRegression(max_iter=1000, random_state=42)),\n",
    "]).fit(X_tr, y_tr)\n",
    "\n",
    "T_tr = logistica.named_steps['pre'].transform(X_tr)\n",
    "T_te = logistica.named_steps['pre'].transform(X_te)\n",
    "objetivo = y_tr.values.astype(float).reshape(-1, 1)\n",
    "\n",
    "print('entreno con', T_tr.shape[0], 'filas y', T_tr.shape[1], 'columnas')\n",
    "print('el listón a batir:', round(roc_auc_score(\n",
    "    y_te, logistica.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Uso el preprocesamiento de scikit-learn a propósito, porque ya lo tenemos\n",
    "resuelto del libro anterior y lo que interesa aquí es la red. De ahí para\n",
    "adelante, todo es nuestro 🔧"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La red, con los dos números que hay que mirar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#parámetros=∑l(nl−1nl+nl)\n",
    "\n",
    "cada capa aporta un peso por cada par de neuronas conectadas más un sesgo por neurona, que es de dónde salen los millones de los que todo el mundo habla"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def entrena(ocultas=8, paso=0.5, semilla=0, marcas=(0, 500, 2000, 5000, 10000)):\n",
    "    r = np.random.default_rng(semilla)\n",
    "    columnas = T_tr.shape[1]\n",
    "    W1 = r.normal(0, 0.1, (columnas, ocultas)); b1 = np.zeros(ocultas)\n",
    "    W2 = r.normal(0, 0.1, (ocultas, 1)); b2 = np.zeros(1)\n",
    "\n",
    "    for i in range(max(marcas) + 1):\n",
    "        if i in marcas:\n",
    "            en_train = roc_auc_score(y_tr, sigmoide(np.tanh(T_tr @ W1 + b1) @ W2 + b2).ravel())\n",
    "            en_prueba = roc_auc_score(y_te, sigmoide(np.tanh(T_te @ W1 + b1) @ W2 + b2).ravel())\n",
    "            print(f'  vuelta {i:6d}  entrena {en_train:.4f}  prueba {en_prueba:.4f}  '\n",
    "                  f'brecha {en_train - en_prueba:+.4f}')\n",
    "        h = np.tanh(T_tr @ W1 + b1)\n",
    "        p = sigmoide(h @ W2 + b2)\n",
    "        d2 = (p - objetivo) / len(objetivo)\n",
    "        d1 = (d2 @ W2.T) * (1 - h ** 2)\n",
    "        W2 -= paso * (h.T @ d2); b2 -= paso * d2.sum(axis=0)\n",
    "        W1 -= paso * (T_tr.T @ d1); b1 -= paso * d1.sum(axis=0)\n",
    "\n",
    "print('8 neuronas ocultas, paso 0,5')\n",
    "entrena()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Léelo de arriba abajo despacio, porque esta tabla es el capítulo 😨\n",
    "\n",
    "En la vuelta 500 la red va bien: 0,7343 en entrenamiento y **0,7150 en\n",
    "prueba**, con una brecha de dos centésimas.\n",
    "\n",
    "En la vuelta 2.000 el entrenamiento ha subido a 0,7948 y la prueba\n",
    "**ha bajado** a 0,6651. En la 10.000, entrenamiento 0,8328 y prueba\n",
    "0,6174.\n",
    "\n",
    "O sea que a partir de la vuelta 500, cada vuelta que le das **la\n",
    "empeora**. No es que deje de mejorar: es que empeora activamente mientras\n",
    "el número que estabas mirando sube.\n",
    "\n",
    "Eso es sobreajuste, y en el capítulo 6 ya lo habíamos visto en su forma más\n",
    "pura, con etiquetas de moneda. Aquí, con datos de verdad, tiene la misma\n",
    "pinta 🎲"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El botón de más neuronas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('32 neuronas ocultas, paso 0,5')\n",
    "entrena(ocultas=32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con 32 neuronas la red llega a **1,0000 de AUC en entrenamiento**\n",
    "en la vuelta 10.000. Perfecto, sin un solo error. Y su prueba en ese momento es\n",
    "0,5870, o sea casi tirar una moneda.\n",
    "\n",
    "Ahí tienes la memorización en su forma más literal: se aprendió las 2.250\n",
    "filas una por una. La brecha llega a 0,4130 😱\n",
    "\n",
    "Y fíjate en algo importante de las dos tablas: en la vuelta 500 las dos redes\n",
    "van casi igual (0,7150 y 0,7165). **La red grande no aprendió más\n",
    "rápido**, solo se estropeó más rápido."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los tres botones, medidos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos a subirlos uno por uno y quedarnos solo con la prueba:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def solo_prueba(ocultas=8, paso=0.5, vueltas=2000, semilla=0):\n",
    "    r = np.random.default_rng(semilla)\n",
    "    columnas = T_tr.shape[1]\n",
    "    W1 = r.normal(0, 0.1, (columnas, ocultas)); b1 = np.zeros(ocultas)\n",
    "    W2 = r.normal(0, 0.1, (ocultas, 1)); b2 = np.zeros(1)\n",
    "    for _ in range(vueltas):\n",
    "        h = np.tanh(T_tr @ W1 + b1)\n",
    "        p = sigmoide(h @ W2 + b2)\n",
    "        d2 = (p - objetivo) / len(objetivo)\n",
    "        d1 = (d2 @ W2.T) * (1 - h ** 2)\n",
    "        W2 -= paso * (h.T @ d2); b2 -= paso * d2.sum(axis=0)\n",
    "        W1 -= paso * (T_tr.T @ d1); b1 -= paso * d1.sum(axis=0)\n",
    "    return roc_auc_score(y_te, sigmoide(np.tanh(T_te @ W1 + b1) @ W2 + b2).ravel())\n",
    "\n",
    "print('subiendo neuronas:')\n",
    "for ocultas in [4, 8, 16, 32]:\n",
    "    print(f'  {ocultas:3d} neuronas: {solo_prueba(ocultas=ocultas):.4f}')\n",
    "print('subiendo el paso:')\n",
    "for paso in [0.1, 0.5, 2.0]:\n",
    "    print(f'  paso {paso:<4}: {solo_prueba(paso=paso):.4f}')\n",
    "print('subiendo vueltas:')\n",
    "for vueltas in [2000, 5000, 10000]:\n",
    "    print(f'  {vueltas:6d} vueltas: {solo_prueba(vueltas=vueltas):.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los tres botones, y los tres van en la misma dirección: **subirlos\n",
    "empeora** 🎚️\n",
    "\n",
    "Más neuronas: de 0,6934 con cuatro a 0,6210 con treinta y dos. Más paso: de\n",
    "0,7183 con 0,1 a 0,6422 con 2,0. Más vueltas: de 0,6651 con dos mil a 0,6174 con\n",
    "diez mil.\n",
    "\n",
    "Y mira dónde está el mejor de toda la tabla: **paso 0,1, que es el\n",
    "ajuste más conservador de todos**, con 0,7183. Casi el 0,7214 de la\n",
    "logística.\n",
    "\n",
    "Esto va justo en contra de la intuición que vende el marketing de la IA, donde\n",
    "más grande siempre es mejor. Con 2.250 filas de datos tabulares, cada aumento de\n",
    "capacidad es capacidad para memorizar 🧠"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Parar a tiempo, y hacerlo bien"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si en la vuelta 500 la red iba bien y luego se estropea, la solución es\n",
    "evidente: **parar en la 500**.\n",
    "\n",
    "Y aquí viene la trampa que arruina esto en la mitad de los proyectos que veo:\n",
    "si eliges dónde parar mirando la prueba, la prueba deja de ser prueba. Es la fuga\n",
    "del capítulo 6 del libro de machine learning, con otro disfraz.\n",
    "\n",
    "Se hace con un tercer corte, la **validación**, que sale de los\n",
    "datos de entrenamiento:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_ent, X_val, y_ent, y_val = train_test_split(X_tr, y_tr, test_size=0.25,\n",
    "                                              random_state=42, stratify=y_tr)\n",
    "pre = logistica.named_steps['pre']\n",
    "E = pre.transform(X_ent)\n",
    "V = pre.transform(X_val)\n",
    "P = pre.transform(X_te)\n",
    "obj_ent = y_ent.values.astype(float).reshape(-1, 1)\n",
    "\n",
    "def con_parada(ocultas=8, paso=0.5, paciencia=20, maximo=20000, semilla=0):\n",
    "    r = np.random.default_rng(semilla)\n",
    "    W1 = r.normal(0, 0.1, (E.shape[1], ocultas)); b1 = np.zeros(ocultas)\n",
    "    W2 = r.normal(0, 0.1, (ocultas, 1)); b2 = np.zeros(1)\n",
    "    mejor, guardados, espera, cuando = -1, None, 0, 0\n",
    "\n",
    "    for i in range(maximo + 1):\n",
    "        if i % 100 == 0:\n",
    "            auc = roc_auc_score(y_val, sigmoide(np.tanh(V @ W1 + b1) @ W2 + b2).ravel())\n",
    "            if auc > mejor:\n",
    "                mejor, espera, cuando = auc, 0, i\n",
    "                guardados = (W1.copy(), b1.copy(), W2.copy(), b2.copy())\n",
    "            else:\n",
    "                espera += 1\n",
    "                if espera >= paciencia:\n",
    "                    break\n",
    "        h = np.tanh(E @ W1 + b1)\n",
    "        p = sigmoide(h @ W2 + b2)\n",
    "        d2 = (p - obj_ent) / len(obj_ent)\n",
    "        d1 = (d2 @ W2.T) * (1 - h ** 2)\n",
    "        W2 -= paso * (h.T @ d2); b2 -= paso * d2.sum(axis=0)\n",
    "        W1 -= paso * (E.T @ d1); b1 -= paso * d1.sum(axis=0)\n",
    "\n",
    "    W1, b1, W2, b2 = guardados\n",
    "    return cuando, mejor, roc_auc_score(y_te, sigmoide(np.tanh(P @ W1 + b1) @ W2 + b2).ravel())\n",
    "\n",
    "for ocultas in [4, 8, 16, 32]:\n",
    "    cuando, val, prueba = con_parada(ocultas=ocultas)\n",
    "    print(f'{ocultas:3d} neuronas: paró en la vuelta {cuando:5d}  '\n",
    "          f'validación {val:.4f}  prueba {prueba:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y aquí está el final del capítulo, que a mí me parece precioso 💛\n",
    "\n",
    "Los cuatro tamaños paran entre la vuelta 200 y la 300, de 20.000 posibles. Y\n",
    "los cuatro sacan entre 0,7172 y 0,7205, o sea **prácticamente lo mismo**.\n",
    "\n",
    "Con parada temprana, el tamaño de la red dejó de importar. Cuatro neuronas o\n",
    "treinta y dos, da igual: todas encuentran lo mismo, porque lo mismo es lo único\n",
    "que hay en estos datos.\n",
    "\n",
    "Y ese \"lo mismo\" es la solución lineal. Comparado con el 0,7217 de la\n",
    "logística sobre el mismo corte, la red no aporta nada y tampoco pierde nada,\n",
    "siempre que la pares a tiempo 🛑\n",
    "\n",
    "La `paciencia` de esa función es lo que hace `MLPClassifier`\n",
    "con `early_stopping=True`, que en el capítulo 1 subió una red de\n",
    "0,5834 a 0,7018. Ahora ya sabes exactamente qué hace por dentro."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Guardar el mejor y no el último\n",
    "\n",
    "Compara parar y quedarte con los pesos del momento, contra\n",
    "volver a los mejores que viste."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Cuánta paciencia conviene\n",
    "\n",
    "Prueba varias y mira dónde para y qué saca."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. El mismo corte para la logística\n",
    "\n",
    "Compara la red con parada contra la logística entrenada\n",
    "solo con la parte de entrenamiento."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Diez semillas, para saber si el empate es real\n",
    "\n",
    "Repite con arranques distintos y mira cuánto baila."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. La curva de validación, vuelta a vuelta\n",
    "\n",
    "Imprime la validación cada 100 vueltas para ver la forma\n",
    "que tiene."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Cuántos pesos contra cuántas filas\n",
    "\n",
    "Pon los dos números al lado para cada tamaño."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Entrenar sin mirar nada\n",
    "\n",
    "Corre la red 30.000 vueltas sin validación y mira el\n",
    "desastre."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. El error de validar con lo que entrenaste\n",
    "\n",
    "Pásale a `con_parada` el conjunto de\n",
    "entrenamiento como validación."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "La curva de entrenamiento sigue subiendo y la de validación empezó a bajar. ¿Qué haces?\n",
    "\n",
    "a) Paro ahí y me quedo con los pesos de antes de que empezara a bajar\n",
    "\n",
    "b) Sigo, porque el entrenamiento todavía mejora\n",
    "\n",
    "c) Añado más neuronas\n",
    "\n",
    "d) Bajo la tasa de aprendizaje"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La parada temprana que mira donde no debe"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Entrenas vigilando las dos notas como manda el capítulo, guardas la mejor que viste en prueba y esa es la que reportas. Suena a parada temprana.\n",
    "\n",
    "```\n",
    "mejor = 0\n",
    "for vuelta in range(10000):\n",
    "    paso()\n",
    "    auc = evalua(X_prueba, y_prueba)\n",
    "    mejor = max(mejor, auc)\n",
    "\n",
    "print('el mejor AUC fue', mejor)\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 👀 Se entrena vigilando dos números: cómo va en lo que ve y cómo va en lo que\n",
    "no.\n",
    "\n",
    "- 📉 A partir de la vuelta 500 esta red empeora activamente mientras el número\n",
    "que estabas mirando sube.\n",
    "\n",
    "- 😱 Con 32 neuronas llega a 1,0000 de AUC en entrenamiento y 0,5870 en\n",
    "prueba.\n",
    "\n",
    "- 🎚️ Los tres botones (neuronas, paso, vueltas) empeoran al subirlos. El mejor\n",
    "resultado sale del ajuste más conservador.\n",
    "\n",
    "- 🛑 Con parada temprana los cuatro tamaños paran entre la vuelta 200 y la 300\n",
    "y sacan lo mismo: el tamaño deja de importar.\n",
    "\n",
    "- 💾 Hay que guardar los mejores pesos, no quedarse los últimos: 0,7176 contra\n",
    "0,6597.\n",
    "\n",
    "- 🏁 Y aun bien parada, la red empata con la logística (0,7205 contra 0,7217),\n",
    "que tarda dos décimas de segundo y se lee entera.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El número que miras mientras entrenas y el número que puedes prometer nunca son el mismo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo de los tres trozos y la parada temprana bien hecha es la misma disciplina del [libro de machine learning desde cero](https://missyera.com/guias/machine-learning-desde-cero/) 🎯\n",
    "\n",
    "En el capítulo 8 nos dedicamos entero a lo que aquí salió por todas partes: el\n",
    "sobreajuste, y las tres formas de frenarlo.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 7 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/entrenar-una-red/\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
