{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cómo sabe la red hacia dónde corregir\n",
    "\n",
    "La pérdida, la pendiente y el paso. Y por qué sin escalar no existe ningún paso que funcione, que era lo que quedó pendiente.\n",
    "\n",
    "Cuaderno de práctica del capítulo 5 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/descenso-de-gradiente/\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: \"dnVlbHRhICAgMCAgcMOpcmRpZGEgMC42OTMxCnZ1ZWx0YSAgNDAgIHDDqXJkaWRhIDAuNTg1OAp2dWVsdGEgIDgwICBww6lyZGlkYSAwLjU2MzkKdnVlbHRhIDEyMCAgcMOpcmRpZGEgMC41NTc0CnZ1ZWx0YSAxNjAgIHDDqXJkaWRhIDAuNTU1Mgp2dWVsdGEgMjAwICBww6lyZGlkYSAwLjU1NDQ=\",\n",
    "    2: \"cGVzb3MgZW5jb250cmFkb3M6IFswLjY5OTcgMS4wMTE1XQpzZXNnbyAgICAgICAgICAgIDogMC4xNzEKCnlvIGdlbmVyw6kgY29uIDAuMDAyIHBvciBzb2wgZGUgbW9udG8geSAwLjggcG9yIHB1bnRvIGRlIHNhdGlzZmFjY2nDs24KZW4gdW5pZGFkZXMgZXNjYWxhZGFzIGVzbyBlczogWzAuNTcyOCAwLjgyMzRd\",\n",
    "    3: \"ICAgMjAwIHZ1ZWx0YXM6IHBlc29zIFswLjY5OSAgMS4wMTA2XSAgcMOpcmRpZGEgMC41NTQ0CiAgMTAwMCB2dWVsdGFzOiBwZXNvcyBbMC43NjMgIDEuMDkyNF0gIHDDqXJkaWRhIDAuNTUzOAogIDUwMDAgdnVlbHRhczogcGVzb3MgWzAuNzYzICAxLjA5MjVdICBww6lyZGlkYSAwLjU1MzgKIDIwMDAwIHZ1ZWx0YXM6IHBlc29zIFswLjc2MyAgMS4wOTI1XSAgcMOpcmRpZGEgMC41NTM4\",\n",
    "    4: \"ICAgNTAwIGZpbGFzOiBlbmNvbnRyYWRvIFswLjU3OTYgMC42NDk1XSAgdmVyZGFkZXJvIFswLjYwODIgMC43NTA0XQogIDUwMDAgZmlsYXM6IGVuY29udHJhZG8gWzAuNjM1ICAwLjgwNzhdICB2ZXJkYWRlcm8gWzAuNTk3MiAwLjgwMDVdCiA1MDAwMCBmaWxhczogZW5jb250cmFkbyBbMC41OTQgIDAuODE3OV0gIHZlcmRhZGVybyBbMC42MDA4IDAuNzk5MV0=\",\n",
    "    5: \"ICAxMDAgdnVlbHRhczogQVVDIDAuNzE4OQogIDUwMCB2dWVsdGFzOiBBVUMgMC43MjEzCiAxNTAwIHZ1ZWx0YXM6IEFVQyAwLjcyMTYKIDMwMDAgdnVlbHRhczogQVVDIDAuNzIxNg==\",\n",
    "    6: \"YWNpZXJ0YSAgICAgIHDDqXJkaWRhIDAuMDYyOCAgZ3JhZGllbnRlIFstMC4wMDY3IC0wLjAwMjggIDAuMDA0MV0Kc2UgZXF1aXZvY2EgIHDDqXJkaWRhIDguMDI3NCAgZ3JhZGllbnRlIFstMC43OTYzICAwLjAxNTUgIDAuMDcwNF0=\",\n",
    "    7: \"Y29uIGxvdGVzIGRlIDMyIDogWzAuNzA1NCAxLjAyODVdIHDDqXJkaWRhIDAuNTU0Nwpjb24gbGFzIDUwMCAgICAgOiBbMC42OTk3IDEuMDExNV0gcMOpcmRpZGEgMC41NTQz\",\n",
    "    8: \"VmFsdWVFcnJvcjogQ2Fubm90IHRha2UgYSBsYXJnZXIgc2FtcGxlIHRoYW4gcG9wdWxhdGlvbiB3aGVuIHJlcGxhY2UgaXMgRmFsc2U=\",\n",
    "    9: \"cG9yIGbDs3JtdWxhOiAwLjAzNzQzMDQxCm1vdmllbmRvICAgOiAwLjAzNzQzMDQxCmRpZmVyZW5jaWEgOiAwLjA=\",\n",
    "    10: \"dnVlbHRhIDA6IHDDqXJkaWRhICAgICAwLjY5MzEgIHBlc29zIFswLiAwLl0KdnVlbHRhIDE6IHDDqXJkaWRhICAgICAyLjEzNjIgIHBlc29zIFsgNy44OTcgMTEuOTkxXQp2dWVsdGEgMjogcMOpcmRpZGEgICAgIDEuMDE5OSAgcGVzb3MgWzQuMjU3IDQuMjc2XQp2dWVsdGEgMzogcMOpcmRpZGEgICAgIDEuNjU0OCAgcGVzb3MgWy0yLjgxNiAgMC4zNjNdCnZ1ZWx0YSA0OiBww6lyZGlkYSAgICAgNC43ODQ4ICBwZXNvcyBbMjQuOTczICA4LjYzNV0KdnVlbHRhIDU6IHDDqXJkaWRhICAgICAyLjYxOTkgIHBlc29zIFsxMC42OTQgMTQuNzc2XQp2dWVsdGEgNjogcMOpcmRpZGEgICAgIDEuNTA4OSAgcGVzb3MgWzYuMDQ3IDcuNDMxXQp2dWVsdGEgNzogcMOpcmRpZGEgICAgIDAuNjgzMiAgcGVzb3MgWzAuNzA3IDEuNjU5XQ==\",\n",
    "    11: \"bmFu\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 4 la red aprendió y yo te dije que no miraras las líneas del\n",
    "\"hacia atrás\". Aquí las miramos 🔍\n",
    "\n",
    "Antes de entrar: **¿alguna vez has dejado algo entrenando toda la noche para descubrir por la mañana que no había aprendido nada?** Aquí está la razón número uno de que pase 📉\n",
    "\n",
    "Y de paso pago lo que quedé debiendo en el capítulo 1, cuando dije que la\n",
    "razón de escalar estaba en el gradiente. Está, y se ve clarísima."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Primero: cuánto te equivocas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "L=1n∑i=1n(yi−y^i)2\n",
    "\n",
    "el error medio al cuadrado, que castiga mucho más un fallo grande que dos medianos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para corregir hace falta un número que diga cómo de mal vas. Ese número es la\n",
    "**pérdida**, y para un sí o no se usa esta:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "def sigmoide(z):\n",
    "    return 1 / (1 + np.exp(-z))\n",
    "\n",
    "def perdida(p, y):\n",
    "    p = np.clip(p, 1e-12, 1 - 1e-12)      # para que log(0) no reviente\n",
    "    return float(-np.mean(y * np.log(p) + (1 - y) * np.log(1 - p)))\n",
    "\n",
    "y = np.array([1., 1., 0., 0.])\n",
    "print('acertando de lleno  :', round(perdida(np.array([.99, .99, .01, .01]), y), 4))\n",
    "print('sin idea (0,5 a todo):', round(perdida(np.array([.5, .5, .5, .5]), y), 4))\n",
    "print('equivocada de lleno :', round(perdida(np.array([.01, .01, .99, .99]), y), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se llama entropía cruzada y funciona así: **castiga estar seguro y\n",
    "equivocado mucho más que estar dudando**.\n",
    "\n",
    "Fíjate en los números. Dudar cuesta 0,6931, que ya salió en el capítulo 4 y es\n",
    "ln(2). Y estar seguro y equivocado cuesta 4,6052, o sea casi siete veces más.\n",
    "\n",
    "Esa asimetría es a propósito. Un modelo que dice \"70%\" y falla es un modelo\n",
    "que se puede arreglar; uno que dice \"99%\" y falla está roto 😬"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Segundo: hacia dónde crece el error"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El **gradiente** es una lista de números, uno por peso, que dice\n",
    "cuánto sube la pérdida si subes ese peso un poquito.\n",
    "\n",
    "Para una neurona con sigmoide y entropía cruzada, sale una fórmula\n",
    "sorprendentemente corta:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rng = np.random.default_rng(0)\n",
    "X = rng.normal(0, 1, (200, 3))\n",
    "y = (rng.random(200) < sigmoide(X @ np.array([1., -0.5, 0.3]))).astype(float)\n",
    "\n",
    "w = np.array([0.2, -0.1, 0.4])\n",
    "b = 0.05\n",
    "\n",
    "error = sigmoide(X @ w + b) - y\n",
    "gradiente = X.T @ error / len(y)\n",
    "print('gradiente:', np.round(gradiente, 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esa línea, `X.T @ (predicho - real) / n`, es todo el cálculo. Sale\n",
    "de derivar la pérdida y se simplifica muchísimo porque la sigmoide y la entropía\n",
    "cruzada están hechas la una para la otra.\n",
    "\n",
    "Ahora, ¿cómo sabemos que esa fórmula está bien? Se comprueba **moviendo\n",
    "el peso a mano** y midiendo cuánto cambia la pérdida:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def perdida_de(w, b):\n",
    "    return perdida(sigmoide(X @ w + b), y)\n",
    "\n",
    "h = 1e-5\n",
    "numerico = np.zeros(3)\n",
    "for i in range(3):\n",
    "    arriba = w.copy(); arriba[i] += h\n",
    "    abajo = w.copy(); abajo[i] -= h\n",
    "    numerico[i] = (perdida_de(arriba, b) - perdida_de(abajo, b)) / (2 * h)\n",
    "\n",
    "print('por fórmula:', np.round(gradiente, 8))\n",
    "print('moviendo    :', np.round(numerico, 8))\n",
    "print('diferencia  :', float(np.abs(gradiente - numerico).max()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "6,7e-12 de diferencia, o sea que la fórmula está bien 🎯\n",
    "\n",
    "Esto tiene nombre, **gradient checking**, y es lo que se hace\n",
    "cuando escribes una capa nueva a mano y no estás segura de la derivada. Es lento\n",
    "(hay que evaluar la pérdida dos veces por peso) así que solo se usa para\n",
    "comprobar, nunca para entrenar.\n",
    "\n",
    "Te lo enseño porque es la herramienta que convierte \"creo que la derivada está\n",
    "bien\" en \"está bien\", y esa diferencia vale mucho."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tercero: dar el paso"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "w←w−η∂L∂w\n",
    "\n",
    "mueves cada peso un poquito en la dirección contraria a la pendiente, y ese poquito es la tasa de aprendizaje"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El gradiente apunta hacia donde el error *crece*. Así que para mejorar\n",
    "hay que ir al revés:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('pérdida ahora     :', round(perdida_de(w, b), 6))\n",
    "print('un paso a favor   :', round(perdida_de(w - 0.5 * gradiente, b), 6))\n",
    "print('un paso al revés  :', round(perdida_de(w + 0.5 * gradiente, b), 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Restando baja de 0,6483 a 0,6285 y sumando sube a 0,6707. Por eso todas las\n",
    "líneas de corrección del libro llevan un menos 🔽\n",
    "\n",
    "Y ese 0,5 que multiplica es el **paso**, o tasa de aprendizaje,\n",
    "que en el capítulo 4 ya vimos que puede arruinarlo todo por los dos lados.\n",
    "\n",
    "Entrenar es repetir esas tres cosas hasta que deje de mejorar. Nada más."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que quedó pendiente en el capítulo 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora sí. Armo un problema con dos columnas de escalas muy distintas, que es\n",
    "lo que pasa siempre en datos de verdad: un monto en cientos de soles y una\n",
    "satisfacción del 1 al 5."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "n = 500\n",
    "monto = rng.normal(800, 300, n)\n",
    "satisfaccion = rng.normal(3.5, 1.0, n)\n",
    "z = 0.002 * (monto - 800) + 0.8 * (satisfaccion - 3.5)\n",
    "objetivo = (rng.random(n) < sigmoide(z)).astype(float)\n",
    "\n",
    "crudo = np.column_stack([monto, satisfaccion])\n",
    "escalado = (crudo - crudo.mean(axis=0)) / crudo.std(axis=0)\n",
    "\n",
    "def baja(datos, paso, vueltas=200):\n",
    "    w = np.zeros(datos.shape[1])\n",
    "    b = 0.0\n",
    "    inicial = perdida(sigmoide(datos @ w + b), objetivo)\n",
    "    for _ in range(vueltas):\n",
    "        e = sigmoide(datos @ w + b) - objetivo\n",
    "        w -= paso * (datos.T @ e) / len(objetivo)\n",
    "        b -= paso * e.mean()\n",
    "    return inicial, perdida(sigmoide(datos @ w + b), objetivo)\n",
    "\n",
    "for nombre, datos in [('sin escalar', crudo), ('escalado', escalado)]:\n",
    "    for paso in [0.00001, 0.0001, 0.01, 0.1]:\n",
    "        ini, fin = baja(datos, paso)\n",
    "        estado = 'DIVERGE' if fin > ini else f'{fin:.4f}'\n",
    "        print(f'{nombre:12} paso {paso:<8} {estado}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está la respuesta 😳\n",
    "\n",
    "**Sin escalar, desde 0,0001 la pérdida explota.** El único paso\n",
    "que sobrevive es 0,00001, y con ese la pérdida baja de 0,6931 a 0,6806 en 200\n",
    "vueltas.\n",
    "\n",
    "Con las columnas escaladas, ese mismo paso de 0,00001 apenas mueve nada\n",
    "(0,6930), pero como ahí sí puedes usar 0,1, la pérdida llega a\n",
    "**0,5544**.\n",
    "\n",
    "Compara el avance: sin escalar ganó 0,0125 de pérdida y escalado ganó 0,1387,\n",
    "o sea **once veces más**, en las mismas 200 vueltas.\n",
    "\n",
    "El mecanismo es este. El gradiente lleva la escala de la columna dentro: la\n",
    "del monto sale unas trescientas veces más grande que la de satisfacción. Un paso\n",
    "suficientemente chico para no hacer explotar el peso del monto es\n",
    "**trescientas veces demasiado chico** para mover el de satisfacción.\n",
    "Y no existe un número que sirva para los dos.\n",
    "\n",
    "Escalar es poner las dos columnas en la misma escala para que un solo paso les\n",
    "valga a las dos. Por eso no es una buena práctica: es la condición para que el\n",
    "descenso de gradiente funcione ⚖️\n",
    "\n",
    "Y por eso también el `StandardScaler` va dentro del pipeline desde\n",
    "el capítulo 1."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora sobre las ventas de verdad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo lo anterior fue con datos que fabriqué yo, para poder comparar contra la\n",
    "respuesta verdadera. Vamos a soltar el mismo bucle sobre el CSV de la\n",
    "distribuidora 🐔"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "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 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",
    "ventas = prepara(carga_limpia(URL))\n",
    "Xv = ventas[NUMERICAS + CATEGORICAS]\n",
    "yv = ventas['compro']\n",
    "Xv_tr, Xv_te, yv_tr, yv_te = train_test_split(Xv, yv, test_size=0.25,\n",
    "                                              random_state=42, stratify=yv)\n",
    "\n",
    "sklearn_modelo = 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(Xv_tr, yv_tr)\n",
    "\n",
    "T_tr = sklearn_modelo.named_steps['pre'].transform(Xv_tr)\n",
    "T_te = sklearn_modelo.named_steps['pre'].transform(Xv_te)\n",
    "objetivo_v = yv_tr.values.astype(float)\n",
    "\n",
    "w_mano = np.zeros(T_tr.shape[1])\n",
    "b_mano = 0.0\n",
    "for _ in range(3000):\n",
    "    e = sigmoide(T_tr @ w_mano + b_mano) - objetivo_v\n",
    "    w_mano -= 0.5 * (T_tr.T @ e) / len(objetivo_v)\n",
    "    b_mano -= 0.5 * e.mean()\n",
    "\n",
    "print('nuestro bucle:', round(roc_auc_score(yv_te, sigmoide(T_te @ w_mano + b_mano)), 4))\n",
    "print('scikit-learn :', round(roc_auc_score(\n",
    "    yv_te, sklearn_modelo.predict_proba(Xv_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "0,7216 el bucle que acabamos de escribir, contra 0,7214 la librería. Con\n",
    "veintiocho columnas de ventas peruanas de verdad, con su suciedad, sus nulos y su\n",
    "OneHotEncoder 🎉\n",
    "\n",
    "Y no es que hayamos empatado por poco: las dos están resolviendo exactamente\n",
    "el mismo problema con el mismo método. La diferencia de dos diezmilésimas es que\n",
    "scikit-learn usa un optimizador más listo que el nuestro y llega a un sitio\n",
    "ligerísimamente distinto.\n",
    "\n",
    "Ese es el punto de haber escrito todo esto a mano. **Ya no hay ninguna\n",
    "parte de una regresión logística que no sepas hacer tú**, y lo que viene\n",
    "en el capítulo 6 es la misma idea con capas 💪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Ver la pérdida bajar vuelta a vuelta\n",
    "\n",
    "Guarda la pérdida cada 20 vueltas y mírala."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Cuánto vale cada peso al final\n",
    "\n",
    "Compara los pesos que encontró con los que usé para\n",
    "fabricar los datos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Cuánto se tarda en llegar de verdad\n",
    "\n",
    "Dale muchas más vueltas y mira si alcanza los pesos\n",
    "reales."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Con más filas, ¿se acerca a la verdad?\n",
    "\n",
    "Fabrica el mismo problema con más datos y mira si los pesos\n",
    "encontrados se pegan a los verdaderos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Cuántas vueltas hacen falta con datos de verdad\n",
    "\n",
    "Mira el AUC sobre las ventas según las vueltas que le\n",
    "des."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. El gradiente cuando ya acertaste\n",
    "\n",
    "Mira cuánto vale el gradiente en un modelo que acierta y en\n",
    "uno que no."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Por lotes, que es como se hace de verdad\n",
    "\n",
    "En vez de usar las 500 filas cada vuelta, usa 32 al azar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. El error de pedir un lote más grande que los datos\n",
    "\n",
    "Pide 600 filas de un conjunto de 500."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. Comprobar el gradiente del sesgo\n",
    "\n",
    "Haz el gradient checking para `b`, que arriba lo\n",
    "salté."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 9\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. El paso que se va de largo, visto por dentro\n",
    "\n",
    "Imprime la pérdida vuelta a vuelta con un paso enorme."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 10\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. El nan silencioso del log de cero\n",
    "\n",
    "Quita el `clip` de la función de pérdida y dale\n",
    "una predicción perfecta."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 11\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "El descenso de gradiente da pasos cada vez más cortos según se acerca al mínimo. ¿Por qué?\n",
    "\n",
    "a) Porque el paso es proporcional a la pendiente, y la pendiente se aplana\n",
    "\n",
    "b) Porque la tasa de aprendizaje va bajando sola\n",
    "\n",
    "c) Porque el modelo detecta que está cerca\n",
    "\n",
    "d) Porque se va quedando sin datos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La pérdida plana que no convergió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Entrenas 20.000 vueltas, imprimes la pérdida cada mil y ves que se queda plana. Convergió, así que paras y apuntas el número.\n",
    "\n",
    "```\n",
    "for i in range(20000):\n",
    "    perdida = paso(lr=0.00001)\n",
    "    if i % 5000 == 0:\n",
    "        print(i, round(perdida, 4))\n",
    "\n",
    "# 0      0.6931\n",
    "# 5000   0.6923\n",
    "# 10000  0.6919\n",
    "# 15000  0.6916\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": [
    "- 📏 La pérdida de entropía cruzada castiga estar seguro y equivocado (4,6052)\n",
    "mucho más que dudar (0,6931).\n",
    "\n",
    "- 🧮 El gradiente de una neurona cabe en una línea:\n",
    "`X.T @ (predicho - real) / n`.\n",
    "\n",
    "- 🔬 Se comprueba moviendo cada peso a mano, y coincide con 6,7e-12 de\n",
    "diferencia.\n",
    "\n",
    "- 🔽 El gradiente apunta hacia donde el error crece, así que se resta.\n",
    "\n",
    "- ⚖️ Sin escalar no hay ningún paso que sirva: desde 0,0001 explota, y con\n",
    "0,00001 la pérdida se mueve una diezmilésima en 200 vueltas.\n",
    "\n",
    "- ⏱️ El 99% del trabajo se hace al principio: entre 5.000 y 20.000 vueltas la\n",
    "pérdida solo baja 0,0005.\n",
    "\n",
    "- 🚀 Con lotes de 32 filas se llega casi al mismo sitio mirando una quinceava\n",
    "parte de los datos.\n",
    "\n",
    "- 🪃 Pesos que saltan de signo son la firma de un paso demasiado grande.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una pérdida plana no dice que llegaste. Dice que no te estás moviendo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las derivadas y las pendientes que salen aquí vienen de la parte matemática, y la trato despacio en el [libro de estadística desde cero](https://missyera.com/guias/estadistica-desde-cero/) 📐\n",
    "\n",
    "En el capítulo 6 llevamos esto a una red con capas, que es donde el cálculo se\n",
    "complica y aparece la retropropagación.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 5 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/descenso-de-gradiente/\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
}
