{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Cuándo una red neuronal ayuda y cuándo es un error caro\n",
    "\n",
    "Empezamos midiendo: sobre los datos de este libro la red pierde contra una regresión logística, y pierde más cuanto más grande es.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 1 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/cuando-si-y-cuando-no/\n",
    "\n",
    "Este es el cuaderno de **soluciones**. Trae el código de cada ejercicio, la\n",
    "explicación de la trampa y la respuesta del quiz. Si vienes del cuaderno de\n",
    "práctica sin haberlo intentado, vuelve 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este es un libro de redes neuronales que empieza diciéndote cuándo no\n",
    "usarlas. Ya sé que es raro 🙃\n",
    "\n",
    "Empiezo preguntándote algo: **¿te han pedido alguna vez \"algo con IA\" sin decirte qué problema resolvía?** Casi todo lo que voy a contarte en este capítulo sale de esa conversación 💭\n",
    "\n",
    "Lo hago por una razón concreta: en los últimos años me han llegado varios\n",
    "proyectos donde alguien montó una red porque sonaba serio, y la respuesta buena\n",
    "eran tres líneas de otra cosa. Eso cuesta meses y credibilidad.\n",
    "\n",
    "Así que antes de la primera neurona, vamos a medirlo sobre los datos de la\n",
    "distribuidora, que son los mismos del libro de machine learning 📊"
   ]
  },
  {
   "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.neural_network import MLPClassifier\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",
    "def arma(modelo):\n",
    "    return 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', modelo),\n",
    "    ])\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",
    "print('filas:', len(datos), ' columnas que entran:', X.shape[1])\n",
    "print('compran el:', round(y.mean(), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese preprocesamiento es el mismo del capítulo 5 del libro de machine\n",
    "learning. Y el escalado, que allá era una buena práctica, aquí es\n",
    "**obligatorio**: una red con columnas sin escalar no converge, y lo\n",
    "vamos a ver con nuestros ojos en el capítulo 6 🧮"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La comparación, sin adornos"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "competidores = [\n",
    "    ('regresión logística', LogisticRegression(max_iter=1000, random_state=42)),\n",
    "    ('red de 16', MLPClassifier(hidden_layer_sizes=(16,), max_iter=300,\n",
    "                                random_state=42)),\n",
    "    ('red de 64 y 32', MLPClassifier(hidden_layer_sizes=(64, 32), max_iter=300,\n",
    "                                     random_state=42)),\n",
    "]\n",
    "\n",
    "for nombre, modelo in competidores:\n",
    "    m = arma(modelo).fit(X_tr, y_tr)\n",
    "    entrena = roc_auc_score(y_tr, m.predict_proba(X_tr)[:, 1])\n",
    "    prueba = roc_auc_score(y_te, m.predict_proba(X_te)[:, 1])\n",
    "    print(f'{nombre:20} entrena={entrena:.4f}  prueba={prueba:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Léelo columna por columna que hay tres cosas 👀\n",
    "\n",
    "**La red pierde.** 0,7214 la logística contra 0,6584 y 0,5834.\n",
    "Y no por poco: la de dos capas está a diez puntos, que en AUC es un abismo.\n",
    "\n",
    "**Cuanto más grande, peor.** La red de 64 y 32 tiene más de\n",
    "tres mil pesos que ajustar y 2.250 filas para hacerlo. No hay forma.\n",
    "\n",
    "**Y mira la columna de entrenamiento.** 1,0000. Perfecto. La red\n",
    "grande *memorizó las 2.250 filas* y por eso en prueba se hunde 😱\n",
    "\n",
    "Eso último es lo que hay que entender de este capítulo: una red neuronal es\n",
    "una máquina de memorizar, y lo hace tan bien que hay que trabajar para\n",
    "impedírselo. En el capítulo 8 dedicamos el rato entero a eso."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ¿Y si la red está mal configurada?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es la primera objeción y es justa. Probemos con freno."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "con_freno = [\n",
    "    ('64 y 32, parada temprana', MLPClassifier(hidden_layer_sizes=(64, 32),\n",
    "                                               max_iter=300, early_stopping=True,\n",
    "                                               random_state=42)),\n",
    "    ('16, con penalización', MLPClassifier(hidden_layer_sizes=(16,), max_iter=300,\n",
    "                                           alpha=1.0, random_state=42)),\n",
    "]\n",
    "\n",
    "for nombre, modelo in con_freno:\n",
    "    m = arma(modelo).fit(X_tr, y_tr)\n",
    "    print(f'{nombre:26} entrena={roc_auc_score(y_tr, m.predict_proba(X_tr)[:, 1]):.4f}  '\n",
    "          f'prueba={roc_auc_score(y_te, m.predict_proba(X_te)[:, 1]):.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con freno mejoran muchísimo: de 0,5834 a 0,7018 y a 0,7072. La memorización\n",
    "desaparece, porque el entrenamiento ya no se lleva a la red hasta el fondo.\n",
    "\n",
    "Y aun así **siguen perdiendo** contra las tres líneas de la\n",
    "regresión logística 🤷‍♀️\n",
    "\n",
    "Esto no es un accidente de estos datos. Con datos en tabla y pocas filas,\n",
    "los modelos lineales y los árboles ganan casi siempre, y hay competencias\n",
    "enteras donde el primer puesto es un boosting y las redes quedan atrás."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Entonces, ¿cuándo sí?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuando los datos tienen una **estructura que las columnas no\n",
    "capturan**. Esa es toda la regla y es más útil que cualquier lista."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Tipo de dato | La estructura que tiene | Qué usar |\n",
    "|---|---|---|\n",
    "| Tabla (clientes, ventas) | Ninguna. Cambiar el orden de las columnas no cambia nada | Logística, bosques, boosting |\n",
    "| Imágenes | Los píxeles vecinos significan algo juntos | Convolucionales, capítulo 11 |\n",
    "| Texto | El orden importa y hay dependencias largas | Transformers, capítulo 16 |\n",
    "| Audio y series | El tiempo es una dimensión de verdad | Convolucionales 1D, transformers |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hay una frase que se repite mucho en el oficio y que conviene mirar de cerca:\n",
    "**para datos en tabla el boosting le gana a una red casi siempre**.\n",
    "Sobre estos datos, el boosting saca 0,6799 y la red con freno del capítulo 8 saca\n",
    "0,7207, o sea que aquí *no* se cumple.\n",
    "\n",
    "Lo que sí se cumple es lo de fondo: las dos pierden contra la regresión\n",
    "logística, que saca 0,7214. En tabla, los modelos simples mandan, y cuál de los\n",
    "complicados queda segundo cambia de un dataset a otro 🤷‍♀️\n",
    "\n",
    "Fíjate en la primera fila de la tabla, que es la que explica todo lo de\n",
    "arriba. En\n",
    "nuestra tabla, si intercambias la columna `monto` con\n",
    "`ciudad`, no se pierde información. En una foto, si intercambias dos\n",
    "píxeles, la rompes.\n",
    "\n",
    "**Ahí es donde una red gana**: no en tener más capacidad, sino en\n",
    "que su arquitectura ya sabe algo sobre la forma del dato antes de ver el primer\n",
    "ejemplo 💡"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La otra pregunta: cuántos datos hacen falta"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for n in [300, 750, 1500, 2250]:\n",
    "    sub = X_tr.sample(n, random_state=42)\n",
    "    obj = y_tr.loc[sub.index]\n",
    "    log = arma(LogisticRegression(max_iter=1000, random_state=42)).fit(sub, obj)\n",
    "    red = arma(MLPClassifier(hidden_layer_sizes=(64, 32), max_iter=300,\n",
    "                             early_stopping=True, random_state=42)).fit(sub, obj)\n",
    "    print(f'n={n:5d}  logística={roc_auc_score(y_te, log.predict_proba(X_te)[:, 1]):.4f}  '\n",
    "          f'red={roc_auc_score(y_te, red.predict_proba(X_te)[:, 1]):.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con 300 filas la red va ocho puntos por debajo (0,6306 contra 0,7097): no\n",
    "tiene con qué. A partir de 750 se empareja, e incluso le gana por poco en esa\n",
    "fila, y de ahí en adelante van las dos casi pegadas.\n",
    "\n",
    "O sea que la red con freno *sí* alcanza a la logística en cuanto hay\n",
    "datos suficientes, y lo que no consigue es pasarla. Que es el resumen del\n",
    "capítulo: aquí no hay nada más que sacarle a estos datos, y el modelo simple ya\n",
    "lo estaba sacando 📈\n",
    "\n",
    "La respuesta honesta a \"¿cuántos datos necesito?\" sigue siendo: más que el\n",
    "otro modelo. Y los modelos de imágenes que te suenan se entrenaron con millones\n",
    "de ejemplos, no con miles."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y entonces por qué un libro entero"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Por tres razones que van en serio:\n",
    "\n",
    "- 🖼️ **Porque los datos con forma existen en tu trabajo.**\n",
    "Facturas escaneadas, fotos de anaquel, comentarios de clientes, audios de call\n",
    "center. Nada de eso entra en una tabla.\n",
    "\n",
    "- 🤖 **Porque los LLM son esto.** ChatGPT y Claude son\n",
    "transformers, y entender la atención del capítulo 16 cambia cómo les escribes y\n",
    "qué esperas de ellos.\n",
    "\n",
    "- 🛡️ **Porque te van a vender redes.** Y con este libro vas a\n",
    "poder pedir la comparación contra el modelo simple, que es la pregunta que\n",
    "desarma el 80% de las propuestas infladas.\n",
    "\n",
    "Lo que vamos a hacer aquí es escribir las redes a mano, con numpy, sin\n",
    "librería que las esconda. La neurona, el gradiente, la retropropagación, la\n",
    "convolución y la atención. Todo en veinte líneas cada una y con sus números\n",
    "impresos 🔧\n",
    "\n",
    "No porque en el trabajo se haga así, sino porque es la única forma de que\n",
    "después, cuando uses una librería, sepas qué está pasando."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. La red más pequeña posible\n",
    "\n",
    "Una sola neurona escondida. Mira contra qué se parece."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "una = arma(MLPClassifier(hidden_layer_sizes=(1,), max_iter=500,\n",
    "                         random_state=42)).fit(X_tr, y_tr)\n",
    "log = arma(LogisticRegression(max_iter=1000, random_state=42)).fit(X_tr, y_tr)\n",
    "print('red de 1 neurona:', round(roc_auc_score(y_te, una.predict_proba(X_te)[:, 1]), 4))\n",
    "print('logística       :', round(roc_auc_score(y_te, log.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "red de 1 neurona: 0.7019\n",
    "logística       : 0.7214\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se acerca bastante, y tiene sentido: una red de una neurona con salida\n",
    "sigmoide es **casi la misma cuenta** que una regresión logística.\n",
    "La diferencia está en cómo se entrena y en la activación del medio.\n",
    "\n",
    "Guárdate esta idea porque es la del capítulo 2: la regresión logística que ya\n",
    "conoces *es* una neurona. No hay salto conceptual, hay apilamiento 🧱"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Cuántos pesos tiene cada arquitectura\n",
    "\n",
    "Cuenta los parámetros sin entrenar nada, que salen de la\n",
    "forma de la red y de nada más."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cuantos_pesos(entradas, capas, salidas=1):\n",
    "    total, previa = 0, entradas\n",
    "    for c in list(capas) + [salidas]:\n",
    "        total += previa * c + c          # los pesos, más el sesgo de cada neurona\n",
    "        previa = c\n",
    "    return total\n",
    "\n",
    "ENTRADAS = 28          # 9 numéricas + 19 columnas que salen del OneHotEncoder\n",
    "\n",
    "for capas in [(1,), (16,), (64, 32), (128, 64, 32), (512, 256, 128)]:\n",
    "    p = cuantos_pesos(ENTRADAS, capas)\n",
    "    print(f'{str(capas):18} {p:8d} pesos   {p / len(X_tr):6.2f} por fila')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "(1,)                     31 pesos     0.01 por fila\n",
    "(16,)                   481 pesos     0.21 por fila\n",
    "(64, 32)               3969 pesos     1.76 por fila\n",
    "(128, 64, 32)         14081 pesos     6.26 por fila\n",
    "(512, 256, 128)      179201 pesos    79.64 por fila\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esa última columna es la que hay que mirar antes de elegir arquitectura. Con\n",
    "(64, 32) ya hay más pesos que filas de entrenamiento, y con (512, 256, 128) hay\n",
    "más de setenta pesos por cada fila que tienes 😳\n",
    "\n",
    "Si tienes más parámetros que datos, no estás modelando: estás guardando 🗄️\n",
    "\n",
    "Y mira la primera fila: 31 pesos para una red de una neurona, que son las 28\n",
    "entradas más su sesgo, más el peso y el sesgo de la salida. Nada mágico, todo\n",
    "contable ✍️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Qué pasa si no escalas\n",
    "\n",
    "Quita el `StandardScaler` y entrena igual."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def red():\n",
    "    return MLPClassifier(hidden_layer_sizes=(16,), max_iter=300, random_state=42)\n",
    "\n",
    "sin_escalar = Pipeline([\n",
    "    ('pre', ColumnTransformer([\n",
    "        ('num', SimpleImputer(strategy='median'), NUMERICAS),\n",
    "        ('cat', Pipeline([('r', SimpleImputer(strategy='most_frequent')),\n",
    "                          ('c', OneHotEncoder(handle_unknown='ignore'))]), CATEGORICAS),\n",
    "    ])),\n",
    "    ('mod', red()),\n",
    "]).fit(X_tr, y_tr)\n",
    "\n",
    "print('sin escalar:', round(roc_auc_score(\n",
    "    y_te, sin_escalar.predict_proba(X_te)[:, 1]), 4))\n",
    "print('escalada   :', round(roc_auc_score(\n",
    "    y_te, arma(red()).fit(X_tr, y_tr).predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "sin escalar: 0.6925\n",
    "escalada   : 0.6584\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Subió. Con las columnas sin escalar saca 0,6925 y escalada 0,6584 😤\n",
    "\n",
    "Y aquí te tengo que contar lo que me pasó escribiendo esto, porque es más\n",
    "útil que el resultado. Mi primera explicación fue: \"sin escalar, el monto llega\n",
    "con valores de miles, la red se satura y acaba usando solo las columnas del\n",
    "OneHotEncoder, que ya son ceros y unos\". Sonaba impecable.\n",
    "\n",
    "Antes de publicarla la comprobé:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "solo_num = Pipeline([\n",
    "    ('pre', ColumnTransformer([('num', SimpleImputer(strategy='median'), NUMERICAS)])),\n",
    "    ('mod', red()),\n",
    "]).fit(X_tr, y_tr)\n",
    "\n",
    "print('sin escalar, solo numéricas:', round(roc_auc_score(\n",
    "    y_te, solo_num.predict_proba(X_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "sin escalar, solo numéricas: 0.6931\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "0,6931 solo con las numéricas sin escalar, o sea prácticamente lo mismo que\n",
    "el modelo completo. **La red no las estaba ignorando: las estaba\n",
    "usando.** Mi explicación bonita era falsa.\n",
    "\n",
    "Entonces, ¿qué queda? Queda el número tal cual: **en este dataset, con\n",
    "esta arquitectura y esta semilla, no escalar no hizo daño**. Y queda que\n",
    "no tengo una historia limpia para contarte, porque cuando la fui a comprobar no\n",
    "aguantó.\n",
    "\n",
    "La regla de escalar sigue en pie, pero no se apoya en este número sino en\n",
    "cómo funciona el descenso de gradiente, que es el capítulo 5 y ahí lo vas a ver\n",
    "con tus ojos. Lo que no voy a hacer es inventarme el mecanismo para que el\n",
    "ejemplo cuadre 💛\n",
    "\n",
    "Y lo otro sí pasa siempre: no escalar no da error ni advertencia útil.\n",
    "Entrena tan tranquila y devuelve números."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. La red sobre datos que sí tienen forma\n",
    "\n",
    "Fabrica un problema con estructura y repite la\n",
    "comparación."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rng = np.random.default_rng(0)\n",
    "P = rng.normal(0, 1, (3000, 2))\n",
    "etiqueta = ((P[:, 0] ** 2 + P[:, 1] ** 2) > 2).astype(int)   # un círculo\n",
    "\n",
    "A_tr, A_te, b_tr, b_te = train_test_split(P, etiqueta, test_size=0.25,\n",
    "                                          random_state=42, stratify=etiqueta)\n",
    "log = LogisticRegression(max_iter=1000, random_state=42).fit(A_tr, b_tr)\n",
    "red = MLPClassifier(hidden_layer_sizes=(16,), max_iter=500,\n",
    "                    random_state=42).fit(A_tr, b_tr)\n",
    "print('logística:', round(roc_auc_score(b_te, log.predict_proba(A_te)[:, 1]), 4))\n",
    "print('red      :', round(roc_auc_score(b_te, red.predict_proba(A_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "logística: 0.4867\n",
    "red      : 0.9999\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí se dan la vuelta las tornas: 0,4867 la logística y 0,9999 la red.\n",
    "\n",
    "Y fíjate que la logística sale *por debajo* de 0,5, que es peor que\n",
    "tirar una moneda. No es que lo haga mal a propósito: es que no hay ninguna recta\n",
    "que ayude, así que la que encuentra es ruido 🎲\n",
    "\n",
    "La logística busca una recta y la frontera es un círculo, así que no hay\n",
    "nada que hacer. La red dobla la frontera sin que nadie le diga cómo 🎯\n",
    "\n",
    "Y esto es lo que hay que llevarse: **la red no es mejor, es distinta.**\n",
    "Gana cuando la relación no es una raya, y en nuestros datos de ventas resulta\n",
    "que sí lo es."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Lo mismo, pero dándole la pista a la logística\n",
    "\n",
    "Añade a mano las columnas al cuadrado y vuelve a medir."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "P2 = np.column_stack([P, P ** 2])\n",
    "C_tr, C_te, b_tr2, b_te2 = train_test_split(P2, etiqueta, test_size=0.25,\n",
    "                                            random_state=42, stratify=etiqueta)\n",
    "log2 = LogisticRegression(max_iter=1000, random_state=42).fit(C_tr, b_tr2)\n",
    "print('logística con los cuadrados:', round(roc_auc_score(\n",
    "    b_te2, log2.predict_proba(C_te)[:, 1]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "logística con los cuadrados: 1.0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1,0 clavado, o sea que empata con la red y hasta la pasa 😄\n",
    "\n",
    "Y ahí está la frase que resume el libro entero: **la red aprende sola\n",
    "las columnas que tú tendrías que inventar a mano**. Cuando sabes cuáles\n",
    "son, no la necesitas. Cuando son millones y no las puedes escribir (los píxeles\n",
    "de una foto, las relaciones entre palabras), la necesitas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. El error de darle texto crudo\n",
    "\n",
    "Pásale la ciudad tal cual, sin codificar, a ver qué dice."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "MLPClassifier(hidden_layer_sizes=(16,), max_iter=100).fit(\n",
    "    datos[['ciudad', 'segmento']], y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "ValueError: could not convert string to float: 'lima'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una red neuronal es multiplicar matrices, y `'lima'` no se\n",
    "multiplica. Por eso el `OneHotEncoder` del pipeline no es opcional.\n",
    "\n",
    "Me gusta este error porque deja claro qué es una red por dentro: no\n",
    "\"entiende\" nada, hace cuentas. Todo lo que entre tiene que ser un número, y\n",
    "convertirlo bien es la mitad del trabajo 🔢"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. La pregunta para la próxima propuesta que te llegue\n",
    "\n",
    "Arma la tabla que yo pediría antes de aprobar una red."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "filas = []\n",
    "for nombre, modelo in [\n",
    "    ('tonto (siempre sí)', None),\n",
    "    ('logística', LogisticRegression(max_iter=1000, random_state=42)),\n",
    "    ('red 64-32 con freno', MLPClassifier(hidden_layer_sizes=(64, 32), max_iter=300,\n",
    "                                          early_stopping=True, random_state=42)),\n",
    "]:\n",
    "    if modelo is None:\n",
    "        filas.append({'modelo': nombre, 'AUC': 0.5, 'pesos': 0})\n",
    "        continue\n",
    "    m = arma(modelo).fit(X_tr, y_tr)\n",
    "    filas.append({'modelo': nombre,\n",
    "                  'AUC': round(roc_auc_score(y_te, m.predict_proba(X_te)[:, 1]), 4),\n",
    "                  'pesos': int(sum(c.size for c in m.named_steps['mod'].coefs_))\n",
    "                           if hasattr(m.named_steps['mod'], 'coefs_')\n",
    "                           else int(m.named_steps['mod'].coef_.size)})\n",
    "\n",
    "print(pd.DataFrame(filas).to_string(index=False))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "             modelo    AUC  pesos\n",
    " tonto (siempre sí) 0.5000      0\n",
    "          logística 0.7214     28\n",
    "red 64-32 con freno 0.7018   3872\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres filas y una decisión. Si la red no le saca ventaja clara a la logística,\n",
    "no entra: cuesta más tiempo, es más difícil de explicar y se rompe de formas más\n",
    "raras.\n",
    "\n",
    "Pedir esta tabla no es ser negativa, es ser justa. Y si la red gana de verdad,\n",
    "la tabla también lo dice y ahí sí se aprueba con ganas 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "En las 3.000 ventas la red saca 0,5834 y la regresión logística 0,7214. ¿Qué haces?\n",
    "\n",
    "a) Uso la logística, y guardo la red para cuando haya imágenes o texto\n",
    "\n",
    "b) Entreno la red más vueltas\n",
    "\n",
    "c) Le añado más capas\n",
    "\n",
    "d) Reviso los datos, porque una red no puede perder\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Se probó con freno y llegó a 0,7207, que sigue sin superarla.\n",
    "\n",
    "*c)* Más capas sobre una tabla de catorce columnas no añaden información que no esté ya.\n",
    "\n",
    "*d)* Sí puede, y en tablas es lo normal. Los mismos datos dan 0,7214 con un modelo lineal.\n",
    "\n",
    "La red gana donde hay estructura que explotar: en imágenes sacó 0,9756 contra 0,9622."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La comparación que sale al revés"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Comparas la red contra la regresión logística para decidir cuál se queda. Miras la nota de las dos y la red gana por goleada.\n",
    "\n",
    "```\n",
    "log.fit(X_tr, y_tr)\n",
    "red.fit(X_tr, y_tr)\n",
    "\n",
    "print('logistica:', log.score(X_tr, y_tr))\n",
    "print('red      :', red.score(X_tr, y_tr))\n",
    "\n",
    "# logistica: 0.73\n",
    "# red      : 1.00\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "Las dos notas están sacadas sobre **las mismas filas con las que se entrenaron**, así que no miden lo que saben: miden lo que recuerdan 🧠 Y ahí la red gana siempre, porque tiene muchísimos más parámetros con los que memorizar.\n",
    "\n",
    "Ese 1,0000 no es una virtud, es el síntoma. Sobre filas que no vio, esta misma red baja a **0,5834** y la logística se queda en **0,7214**: se dan la vuelta. La red no aprendió el problema, se aprendió el archivo.\n",
    "\n",
    "Y de aquí sale la regla que ordena el libro entero: la capacidad de memorizar es justo lo que hay que vigilar, no lo que hay que celebrar. Con pocas filas y datos en tabla, esa capacidad no tiene nada bueno que hacer."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 📉 Sobre estos datos en tabla la red pierde: 0,7214 la logística contra\n",
    "0,6584 y 0,5834.\n",
    "\n",
    "- 🧠 Y memoriza: la red de 64 y 32 llega a 1,0000 de AUC en entrenamiento.\n",
    "\n",
    "- 🛑 Con parada temprana y penalización mejora mucho (0,7018 y 0,7072) y\n",
    "sigue perdiendo.\n",
    "\n",
    "- 🖼️ La red gana cuando el dato tiene forma: píxeles vecinos, palabras en\n",
    "orden, tiempo.\n",
    "\n",
    "- ⭕ En un problema circular fabricado, la logística da 0,4867 y la red\n",
    "0,9999. Y la logística llega a 1,0 si le das los cuadrados a mano.\n",
    "\n",
    "- 🧱 Una red de una neurona es casi una regresión logística. No hay salto\n",
    "conceptual, hay apilamiento.\n",
    "\n",
    "- ⚖️ Sin escalar, esta red sacó 0,6925 y escalada 0,6584. La explicación\n",
    "que le iba a poner no aguantó la comprobación, así que va el número y no el\n",
    "cuento.\n",
    "\n",
    "- 📋 Antes de aprobar una red, la tabla de tres filas: tonto, simple y red.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una red que memoriza no es una red potente. Es una red sin nada que aprender."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La regresión logística contra la que pierde la red está explicada entera en el [libro de machine learning desde cero](https://missyera.com/guias/machine-learning-desde-cero/) 📊\n",
    "\n",
    "En el capítulo 2 escribimos la neurona a mano, con numpy y sin librería, para\n",
    "ver que por dentro son cuatro cuentas.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 1 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/cuando-si-y-cuando-no/\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
}
