{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# PyTorch hace en cuatro líneas lo que escribiste a mano\n",
    "\n",
    "La misma red de los capítulos anteriores, ahora con la librería que se usa en el trabajo. Y la comprobación de que sale lo mismo.\n",
    "\n",
    "Cuaderno de práctica del capítulo 9 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/pytorch/\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": [
    "Llevas siete capítulos escribiendo redes con numpy. Ahora te voy a enseñar la\n",
    "librería con la que se hacen en el trabajo, y quiero que veas que no te van a\n",
    "enseñar nada nuevo: te van a ahorrar teclas 🧵\n",
    "\n",
    "Ese orden importa y por eso el capítulo está aquí y no al principio. Quien\n",
    "empieza por la librería aprende a llamar funciones. Quien empieza por la cuenta\n",
    "aprende qué hacen esas funciones, y cuando algo se rompe sabe dónde mirar.\n",
    "\n",
    "Antes de correr nada, quédate con esta pregunta: **¿qué parte de lo que\n",
    "escribiste a mano crees que PyTorch hace distinto?** Al final del\n",
    "capítulo la contestamos con números 🧠"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los mismos datos y el mismo listón"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto es el arranque del capítulo 7, sin tocar una\n",
    "coma, más las tres líneas que convierten las matrices en tensores."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import torch\n",
    "from torch import nn\n",
    "from torch.utils.data import DataLoader, TensorDataset\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",
    "# Con datos de este tamano los cuatro hilos de torch se estorban entre ellos y\n",
    "# tarda mas que con uno. Con datos de verdad esta linea se quita.\n",
    "torch.set_num_threads(1)\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",
    "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",
    "# Las tres lineas nuevas: una matriz de numpy se vuelve tensor y ya esta.\n",
    "E_tr = torch.tensor(T_tr)\n",
    "E_te = torch.tensor(T_te)\n",
    "O_tr = torch.tensor(objetivo)\n",
    "\n",
    "print('entreno con', tuple(E_tr.shape), 'y pruebo con', tuple(E_te.shape))\n",
    "print('el liston a batir:', round(roc_auc_score(\n",
    "    y_te, logistica.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un tensor es una matriz de numpy que además se acuerda de las operaciones que\n",
    "le hicieron. Eso es todo lo que es, y es lo que hace posible lo que viene\n",
    "ahora 📦"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los gradientes que escribiste son los que saca autograd"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 6 repartiste la culpa hacia atrás a\n",
    "mano, con la regla de la cadena, y te salieron cuatro matrices de derivadas.\n",
    "Aquí voy a calcular esas mismas cuatro de las dos maneras, con los mismos pesos\n",
    "de partida, y a compararlas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sigmoide(z):\n",
    "    return 1 / (1 + np.exp(-z))\n",
    "\n",
    "r = np.random.default_rng(0)\n",
    "W1 = r.normal(0, 0.1, (T_tr.shape[1], 8)); b1 = np.zeros(8)\n",
    "W2 = r.normal(0, 0.1, (8, 1)); b2 = np.zeros(1)\n",
    "\n",
    "# 1) A mano, como en el capitulo de retropropagacion.\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",
    "mios = {'W1': T_tr.T @ d1, 'b1': d1.sum(axis=0),\n",
    "        'W2': h.T @ d2, 'b2': d2.sum(axis=0)}\n",
    "\n",
    "# 2) Con autograd: los mismos numeros, marcados con requires_grad.\n",
    "sueltos = {n: torch.tensor(v, requires_grad=True)\n",
    "           for n, v in [('W1', W1), ('b1', b1), ('W2', W2), ('b2', b2)]}\n",
    "z = (torch.tanh(E_tr @ sueltos['W1'] + sueltos['b1'])\n",
    "     @ sueltos['W2'] + sueltos['b2'])\n",
    "nn.functional.binary_cross_entropy_with_logits(z, O_tr).backward()\n",
    "\n",
    "for nombre in ['W1', 'b1', 'W2', 'b2']:\n",
    "    suyo = sueltos[nombre].grad.numpy()\n",
    "    print('%-3s de %-8s  el mio y el de torch son iguales: %s'\n",
    "          % (nombre, str(np.shape(mios[nombre])), np.allclose(mios[nombre], suyo)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuatro veces True 🎉\n",
    "\n",
    "Esa línea de `backward()` es el capítulo entero de\n",
    "retropropagación. PyTorch fue apuntando cada operación que hiciste con los\n",
    "tensores, y al llamar a `backward()` recorrió esa lista al revés\n",
    "aplicando la regla de la cadena, que es exactamente lo que hiciste tú con lápiz.\n",
    "No es otra cuenta: es la misma cuenta, hecha por un programa.\n",
    "\n",
    "Y fíjate en el detalle que hace que salga igual:\n",
    "`binary_cross_entropy_with_logits`. Tu `d2` era\n",
    "`(p - objetivo) / n`, y esa resta tan limpia es la derivada de la\n",
    "entropía cruzada con sigmoide. Si le hubiera puesto otra pérdida, las cuatro\n",
    "habrían dado False, y con razón."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La red entera, en cuatro líneas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora la red completa. Le copio los mismos pesos de partida para que la\n",
    "comparación sea justa, y le pido las mismas cinco fotos del capítulo\n",
    "7."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "red = nn.Sequential(nn.Linear(28, 8), nn.Tanh(), nn.Linear(8, 1)).double()\n",
    "with torch.no_grad():\n",
    "    red[0].weight.copy_(torch.tensor(W1.T)); red[0].bias.copy_(torch.tensor(b1))\n",
    "    red[2].weight.copy_(torch.tensor(W2.T)); red[2].bias.copy_(torch.tensor(b2))\n",
    "\n",
    "optimizador = torch.optim.SGD(red.parameters(), lr=0.5)\n",
    "perdida = nn.BCEWithLogitsLoss()\n",
    "\n",
    "def mide(modelo):\n",
    "    modelo.eval()\n",
    "    with torch.no_grad():\n",
    "        return (roc_auc_score(y_tr, modelo(E_tr).numpy().ravel()),\n",
    "                roc_auc_score(y_te, modelo(E_te).numpy().ravel()))\n",
    "\n",
    "marcas = (0, 500, 2000, 5000, 10000)\n",
    "for vuelta in range(max(marcas) + 1):\n",
    "    if vuelta in marcas:\n",
    "        entrena, prueba = mide(red)\n",
    "        print('  vuelta %6d  entrena %.4f  prueba %.4f  brecha %+.4f'\n",
    "              % (vuelta, entrena, prueba, entrena - prueba))\n",
    "    red.train()\n",
    "    optimizador.zero_grad()\n",
    "    perdida(red(E_tr), O_tr).backward()\n",
    "    optimizador.step()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ve a buscar la tabla del capítulo 7 y ponla al\n",
    "lado 😄\n",
    "\n",
    "Es la misma, cifra por cifra, en las quince casillas. Y no es que se parezca:\n",
    "es que es la misma cuenta con los mismos pesos, así que tenía que salir igual.\n",
    "Cuando alguien te venda una librería como si fuera inteligencia, acuérdate de\n",
    "esta tabla.\n",
    "\n",
    "Las tres líneas del medio son el bucle entero:\n",
    "\n",
    "- `optimizador.zero_grad()` borra los gradientes de la vuelta\n",
    "anterior, porque PyTorch los suma en vez de reemplazarlos.\n",
    "\n",
    "- `perdida(...).backward()` es tu retropropagación.\n",
    "\n",
    "- `optimizador.step()` es tu `W -= paso * gradiente`,\n",
    "que en el capítulo 5 escribiste a mano.\n",
    "\n",
    "Y hay dos que no hacen nada aquí y sí harán falta enseguida:\n",
    "`red.train()` y `red.eval()`. Le dicen a la red si está\n",
    "entrenando o contestando, y las capas que se comportan distinto en cada caso las\n",
    "miran. Ahora mismo esta red no tiene ninguna de esas capas, así que las dos dan\n",
    "lo mismo. En cuanto le metamos dropout, dejan de darlo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora lo que a mano no ibas a escribir"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hasta aquí PyTorch solo ha empatado. Lo que gana es lo que viene: los tres\n",
    "frenos del capítulo 8 ya están escritos, los lotes\n",
    "también, y hay optimizadores que a mano no te apetece programar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "torch.manual_seed(0)\n",
    "red2 = nn.Sequential(nn.Linear(28, 16), nn.ReLU(), nn.Dropout(0.2),\n",
    "                     nn.Linear(16, 1)).double()\n",
    "carga = DataLoader(TensorDataset(E_tr, O_tr), batch_size=64, shuffle=True,\n",
    "                   generator=torch.Generator().manual_seed(0))\n",
    "opt = torch.optim.Adam(red2.parameters(), lr=0.01, weight_decay=1e-3)\n",
    "\n",
    "for epoca in range(1, 31):\n",
    "    red2.train()\n",
    "    for filas, etiquetas in carga:\n",
    "        opt.zero_grad()\n",
    "        perdida(red2(filas), etiquetas).backward()\n",
    "        opt.step()\n",
    "    if epoca in (1, 2, 3, 5, 10, 20, 30):\n",
    "        entrena, prueba = mide(red2)\n",
    "        print('  epoca %3d  entrena %.4f  prueba %.4f  brecha %+.4f'\n",
    "              % (epoca, entrena, prueba, entrena - prueba))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El mejor resultado del capítulo está en la primera línea 😅\n",
    "\n",
    "La época 1 da 0,7233 en prueba, que es el mejor número que ha sacado ninguna\n",
    "red en este libro sobre estos datos, y le pasa por delante al listón de la\n",
    "regresión logística, que era 0,7214. A partir de ahí solo empeora. Treinta\n",
    "épocas de entrenamiento para que la buena fuera la primera.\n",
    "\n",
    "Y antes de que lo cuentes por ahí: esa ventaja es de 19 diezmilésimas, con\n",
    "una semilla y una partición. El capítulo 5 ya dejó\n",
    "escrito que una corrida no es un resultado, y esto es exactamente eso.\n",
    "**La conclusión del libro no cambia: en tabla, la red empata con la\n",
    "logística en el mejor de los casos y cuesta cien veces más.** Lo que sí\n",
    "es nuevo aquí es que empatar le costó una época en vez de quinientas\n",
    "vueltas 🐢\n",
    "\n",
    "Eso no es un fallo de PyTorch ni de Adam. Es la misma lección del capítulo\n",
    "7 con otra ropa: **estos datos dan para lo que\n",
    "dan**, y una red con 481 números que ajustar se los aprende de memoria en\n",
    "cuanto la dejas. Lo que cambió es la velocidad a la que llega: donde el descenso\n",
    "a mano necesitaba 500 vueltas por todas las filas, Adam con lotes de 64 llegó\n",
    "más alto en una sola pasada.\n",
    "\n",
    "Los nombres nuevos, uno por uno, y todos son cosas que ya conoces:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Lo que pone | Qué es, en lo que ya sabes |\n",
    "|---|---|\n",
    "| `nn.Dropout(0.2)` | El freno 2 del capítulo 8, apagar neuronas al azar |\n",
    "| `weight_decay=1e-3` | El freno 1, encoger los pesos grandes |\n",
    "| `DataLoader` | Partir las filas en lotes y barajarlas en cada época |\n",
    "| `Adam` | Descenso de gradiente con un paso distinto para cada peso, ajustado sobre la marcha |\n",
    "| `época` | Una pasada por todas las filas, o sea 36 lotes de 64 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora sí, `red2.train()` y `red2.eval()` hacen algo:\n",
    "el dropout apaga neuronas mientras entrena y no las apaga al medir. Si se te\n",
    "olvida el `eval()`, tus números de prueba salen peores de lo que son\n",
    "y encima cambian en cada corrida."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Los dos errores que salen la primera vez"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Estos dos me los sé de memoria porque los he visto en todos los cursos que he\n",
    "dado. El primero sale al mezclar precisiones."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    capa = nn.Linear(28, 8)     # nace en float32\n",
    "    capa(E_tr)                  # y mis datos son float64\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "RuntimeError: mat1 and mat2 must have the same dtype, but got Double and Float\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Double es float64 y Float es float32, que es un vocabulario de C asomando por\n",
    "debajo. PyTorch crea las capas en float32 porque es la mitad de memoria y en GPU\n",
    "va al doble de rápido, y numpy trabaja en float64 por costumbre. Se arregla\n",
    "eligiendo un lado: `.double()` a la red, que es lo que hice arriba\n",
    "para poder comparar con numpy, o `dtype=torch.float32` a los tensores,\n",
    "que es lo que harías con datos de verdad.\n",
    "\n",
    "El segundo sale al querer sacar un número de un tensor que todavía está\n",
    "enganchado al grafo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    suelto = torch.tensor([2.0], requires_grad=True)\n",
    "    (suelto * 3).numpy()\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "RuntimeError: Can't call numpy() on Tensor that requires grad. Use tensor.detach().numpy() instead.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este error es un favor. Ese tensor lleva colgando el historial entero de\n",
    "operaciones que hacen falta para el `backward()`, y si lo pasaras a\n",
    "numpy sin avisar, ese historial se quedaría vivo en memoria pegado a algo que ya\n",
    "no lo usa. Por eso te obliga a decirlo en voz alta con `detach()`, o\n",
    "a envolverlo en `with torch.no_grad():` como hice en\n",
    "`mide`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo de la GPU, que es una línea y no es magia"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo lo de este capítulo corrió en el procesador de tu computadora y tardó\n",
    "segundos. Cuando los datos crecen, se mueve a la tarjeta gráfica así:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "aparato = 'cuda' if torch.cuda.is_available() else 'cpu'\n",
    "red2.to(aparato)\n",
    "E_tr = E_tr.to(aparato)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ya. Lo importante es lo que **no** hace: una GPU no mejora tu\n",
    "modelo ni un decimal. Hace la misma cuenta más rápido, porque puede multiplicar\n",
    "matrices grandes en paralelo. Con 2.250 filas y 28 columnas no vas a notar nada,\n",
    "y muy probablemente vaya más lento, porque mover los datos a la tarjeta cuesta\n",
    "más que la cuenta que ahorras.\n",
    "\n",
    "La regla que uso: **GPU cuando el modelo no entra o el entrenamiento no\n",
    "cabe en una tarde**. Antes de eso es un gasto y una fuente de errores\n",
    "nuevos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Una analista entrena su primera red en PyTorch, copia el bucle de un tutorial y lo adapta. La pérdida empieza en 0,69, baja un poco y después se dispara hasta dar nan. Prueba con menos paso, prueba con menos neuronas y sigue igual.\n",
    "\n",
    "```\n",
    "for epoca in range(30):\n",
    "    for filas, etiquetas in carga:\n",
    "        salida = red(filas)\n",
    "        error = perdida(salida, etiquetas)\n",
    "        error.backward()\n",
    "        opt.step()\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Seis. El 4 es el que hago yo antes de creerme cualquier resultado 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Cuatro optimizadores con el mismo paso\n",
    "\n",
    "Entrena la misma red diez épocas con SGD, SGD con momento,\n",
    "RMSprop y Adam, todos con paso 0,01, y compara."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El ancho de la capa oculta\n",
    "\n",
    "Prueba 4, 16, 64 y 256 neuronas ocultas y saca también\n",
    "cuántos números tiene que ajustar cada red."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. El tamaño del lote\n",
    "\n",
    "Entrena con lotes de 16, 64, 256 y con las 2.250 filas de\n",
    "golpe."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Guarda la red y vuelve a cargarla\n",
    "\n",
    "Entrena tres épocas, guarda los pesos, cárgalos en una red\n",
    "recién hecha y comprueba que contesta exactamente igual."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. La matriz que PyTorch guarda al revés\n",
    "\n",
    "Mira la forma de `weight` en una capa\n",
    "`Linear` y reproduce su salida multiplicando tú."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Cuenta los parámetros y comprueba la cuenta\n",
    "\n",
    "Lista los pesos con nombre y compara el total contra la\n",
    "fórmula escrita a mano."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Entrenas una red en PyTorch, la pérdida baja bien pero al medir en prueba los números salen distintos en cada corrida. ¿Qué miras primero?\n",
    "\n",
    "a) Si al medir llamaste a red.eval() antes\n",
    "\n",
    "b) Si el DataLoader está barajando las filas\n",
    "\n",
    "c) Si te falta fijar la semilla del optimizador\n",
    "\n",
    "d) Si el paso es demasiado grande"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🔦 `backward()` es el capítulo de retropropagación entero, y con\n",
    "los mismos pesos saca los mismos gradientes que sacaste tú.\n",
    "\n",
    "- 📋 La red en PyTorch reprodujo la tabla del capítulo\n",
    "7 casilla por casilla, las quince.\n",
    "\n",
    "- 🔁 El bucle son tres líneas: `zero_grad()`,\n",
    "`backward()` y `step()`. Si te falta la primera, los\n",
    "gradientes se suman y acabas en nan.\n",
    "\n",
    "- 🏁 Con Adam y lotes de 64, el mejor resultado fue la época 1 con 0,7233. Más\n",
    "entrenamiento solo empeoró.\n",
    "\n",
    "- ⚖️ En los ejercicios, el SGD con momento le gana a Adam en prueba, y la red\n",
    "de 121 parámetros le gana a la de 7.681.\n",
    "\n",
    "- 🖥️ La GPU hace lo mismo más rápido. No mejora el modelo ni un decimal.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una librería no sabe más que tú. Escribe más rápido lo que tú ya entiendes."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si quieres ver el mismo salto en el otro sentido, en el\n",
    "[libro de machine learning desde\n",
    "cero](https://missyera.com/guias/machine-learning-desde-cero/) está scikit-learn desde el primer capítulo, y ahí el pipeline hace el\n",
    "mismo papel que aquí hace `nn.Sequential` 🧰. El vocabulario suelto\n",
    "está definido en el [glosario de IA](https://missyera.com/glosario-ia/), y si el código\n",
    "de este capítulo te costó más que la idea, eso es de Python y se arregla en el\n",
    "[libro de Python desde cero](https://missyera.com/guias/python-desde-cero/) 🐍\n",
    "\n",
    "En el capítulo 10 viene la pieza que hace posible\n",
    "entrenar redes profundas, y ahora ya sabes qué es ese `eval()` del\n",
    "que va a hablar.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 9 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/pytorch/\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
}
