{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Las activaciones\n",
    "\n",
    "Sin función de activación, diez capas son una. Lo comprobamos, y de paso vemos por qué casi nadie usa ya la sigmoide en el medio.\n",
    "\n",
    "Cuaderno de práctica del capítulo 3 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/activaciones/\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: \"Y2luY28gY2FwYXMgPSAoNSwgMikgICB1bmEgc29sYSBtYXRyaXogPSAoMywgMikKwr9kYW4gbG8gbWlzbW8/IFRydWU=\",\n",
    "    2: \"IDEgY2FwYXMgICBzaWdtb2lkZSAyLjUwMGUtMDEgICB0YW5oIDEuMDAwMCAgIHJlbHUgMS4wMDAwCiAyIGNhcGFzICAgc2lnbW9pZGUgNi4yNTBlLTAyICAgdGFuaCAxLjAwMDAgICByZWx1IDEuMDAwMAogNSBjYXBhcyAgIHNpZ21vaWRlIDkuNzY2ZS0wNCAgIHRhbmggMS4wMDAwICAgcmVsdSAxLjAwMDAKMTAgY2FwYXMgICBzaWdtb2lkZSA5LjUzN2UtMDcgICB0YW5oIDEuMDAwMCAgIHJlbHUgMS4wMDAwCjIwIGNhcGFzICAgc2lnbW9pZGUgOS4wOTVlLTEzICAgdGFuaCAxLjAwMDAgICByZWx1IDEuMDAwMA==\",\n",
    "    3: \"aWRlbnRpdHkgICAwLjUwNjMKdGFuaCAgICAgICAwLjk5OTgKcmVsdSAgICAgICAwLjk5OTU=\",\n",
    "    4: \"Y2Vyb3MgZW4gbGEgY2FwYSBvY3VsdGE6IDAuNDkzMwpuZXVyb25hcyBtdWVydGFzIGRlbCB0b2RvOiAwIGRlIDE2\",\n",
    "    5: \"TWF5b3Jpc3RhICAwLjgyMjMKSG9yZWNhICAgICAwLjE1MDIKQm9kZWdhICAgICAwLjAyNzQKc3VtYW46IDEuMA==\",\n",
    "    6: \"eiAgICAgICAgICAgWy02LiAgLTIuICAtMC41ICAwLiAgIDAuNSAgMi4gICA2LiBdCnJlbHUgICAgICAgIFswLiAgMC4gIDAuICAwLiAgMC41IDIuICA2LiBdCmxlYWt5ICAgICAgIFstNi5lLTAyIC0yLmUtMDIgLTUuZS0wMyAgMC5lKzAwICA1LmUtMDEgIDIuZSswMCAgNi5lKzAwXQpwZW5kaWVudGUgICBbMC4wMSAwLjAxIDAuMDEgMC4wMSAxLiAgIDEuICAgMS4gIF0=\",\n",
    "    7: \"VmFsdWVFcnJvcjogVGhlIHRydXRoIHZhbHVlIG9mIGFuIGFycmF5IHdpdGggbW9yZSB0aGFuIG9uZSBlbGVtZW50IGlzIGFtYmlndW91cy4gVXNlIGEuYW55KCkgb3IgYS5hbGwoKQ==\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 2 la neurona aplastaba con una sigmoide y no dije por qué. Es\n",
    "que hace falta explicarlo con dos capas delante, y ahora ya podemos 🔧\n",
    "\n",
    "Y te lanzo la pregunta del capítulo por adelantado: **¿qué crees que pasa si apilas cien capas sin nada entre ellas?** Apuesta mentalmente, que se comprueba en dos líneas 🔀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La demostración de que sin activación no sirve de nada"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una red es capas apiladas: la salida de una entra en la siguiente. Si\n",
    "quitamos la activación, cada capa es solo una multiplicación de matrices."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "rng = np.random.default_rng(0)\n",
    "X = rng.normal(0, 1, (5, 3))          # 5 filas con 3 columnas\n",
    "W1 = rng.normal(0, 1, (3, 4))         # primera capa: 3 entradas, 4 neuronas\n",
    "W2 = rng.normal(0, 1, (4, 2))         # segunda capa: 4 entradas, 2 salidas\n",
    "\n",
    "dos_capas = (X @ W1) @ W2\n",
    "una_capa = X @ (W1 @ W2)              # las dos matrices multiplicadas de antemano\n",
    "\n",
    "print('¿dan lo mismo?', np.allclose(dos_capas, una_capa))\n",
    "print('mayor diferencia:', float(np.abs(dos_capas - una_capa).max()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está, y es toda la razón de ser de este capítulo 💡\n",
    "\n",
    "Sin activación, **dos capas son exactamente una**. Y no es que se\n",
    "parezcan: la diferencia es 4,4e-16, o sea el redondeo de la máquina. Puedes\n",
    "apilar cincuenta capas y seguirás teniendo un modelo lineal, con más gasto de\n",
    "electricidad y ninguna capacidad nueva.\n",
    "\n",
    "Lo que rompe esa cadena es meter una función no lineal entre capa y capa. Eso\n",
    "es una **activación**, y su único trabajo es doblar 🌀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las tres que hay que conocer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "σ(z)=11+e−z,σ\\′(z)=σ(z)(1−σ(z))\n",
    "\n",
    "aplasta cualquier número entre 0 y 1, y su derivada nunca pasa de 0,25, que es exactamente por lo que el gradiente se desvanece al apilar capas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sigmoide(z):\n",
    "    return 1 / (1 + np.exp(-z))\n",
    "\n",
    "def tanh(z):\n",
    "    return np.tanh(z)\n",
    "\n",
    "def relu(z):\n",
    "    return np.maximum(0, z)           # ojo: np.maximum, no max\n",
    "\n",
    "z = np.array([-6.0, -2.0, -0.5, 0.0, 0.5, 2.0, 6.0])\n",
    "print('z       ', z)\n",
    "print('sigmoide', np.round(sigmoide(z), 4))\n",
    "print('tanh    ', np.round(tanh(z), 4))\n",
    "print('relu    ', np.round(relu(z), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Léelas por lo que hacen con los números:\n",
    "\n",
    "- 🫓 **Sigmoide** aplasta todo entre 0 y 1. Sirve para una salida\n",
    "que quieras leer como probabilidad.\n",
    "\n",
    "- ⚖️ **Tanh** aplasta entre -1 y 1, o sea que queda centrada en\n",
    "cero. Es la sigmoide estirada.\n",
    "\n",
    "- ✂️ **ReLU** es lo más tonto que te puedas imaginar: si es\n",
    "negativo, cero; si no, tal cual. Y es la que se usa en casi todo.\n",
    "\n",
    "Que la más simple sea la que ganó no es casualidad, y se ve mirando otra\n",
    "cosa 👀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que de verdad decide: la pendiente"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ReLU(z)=max(0,z),ReLU\\′(z)={1z>00z≤0\n",
    "\n",
    "deja pasar lo positivo y corta lo negativo, y su derivada vale uno o cero, que es lo que impide que el gradiente se encoja al bajar de capa"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Del capítulo 2 nos quedó pendiente que una neurona saturada deja de aprender.\n",
    "Aquí está el porqué, con las tres al lado:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('pendiente de la sigmoide', np.round(sigmoide(z) * (1 - sigmoide(z)), 4))\n",
    "print('pendiente de tanh       ', np.round(1 - tanh(z) ** 2, 4))\n",
    "print('pendiente de relu       ', np.round((z > 0).astype(float), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta tabla explica veinte años de historia del deep learning, así que vale la\n",
    "pena pararse 🛑\n",
    "\n",
    "**La sigmoide no pasa nunca de 0,25**, y eso es en su mejor\n",
    "momento, justo en el centro. En z=6 vale 0,0025.\n",
    "\n",
    "**La de ReLU vale 1** en toda la mitad positiva. Ni encoge ni\n",
    "crece.\n",
    "\n",
    "Y ahora la parte que importa: en el capítulo 5 vamos a ver que para entrenar\n",
    "una red, las pendientes de todas las capas **se multiplican entre\n",
    "sí**. Con sigmoides, diez capas te dan como mucho 0,25 elevado a diez, que\n",
    "es 0,00000095. El aviso de corrección que llega a la primera capa es\n",
    "prácticamente cero y esa capa no aprende nunca.\n",
    "\n",
    "Eso tiene nombre, **gradiente desvanecido**, y fue lo que tuvo\n",
    "parado el campo durante años. Con ReLU se multiplica por 1 diez veces y no pasa\n",
    "nada 🎉"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que ReLU rompe a cambio"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nada es gratis. Mira su pendiente en la mitad negativa: cero, y no cero\n",
    "pequeño sino cero exacto.\n",
    "\n",
    "Una neurona cuya suma sale siempre negativa devuelve siempre cero, y como su\n",
    "pendiente también es cero, no recibe ninguna corrección. Está muerta y no\n",
    "revive:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_g = rng.normal(0, 1, (200, 4))\n",
    "W = rng.normal(0, 1, (4, 8))\n",
    "\n",
    "for sesgo in [0.0, -5.0]:\n",
    "    H = relu(X_g @ W + np.full(8, sesgo))\n",
    "    muertas = int((H.sum(axis=0) == 0).sum())\n",
    "    print(f'sesgo {sesgo:5}: {muertas} de 8 neuronas nunca se activan, '\n",
    "          f'{(H == 0).mean():.4f} de la capa son ceros')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con el sesgo en cero, ninguna muere y el 49,94% de la capa son ceros, que es\n",
    "lo normal y hasta conveniente. Con el sesgo en -5, **mueren 6 de 8**\n",
    "y el 99,69% de la capa es cero. Esa capa ya no hace nada 💀\n",
    "\n",
    "Por eso importa cómo se arrancan los pesos, que es un tema entero, y por eso\n",
    "existen variantes como `LeakyReLU`, que en vez de cero devuelve\n",
    "`0,01 * z` en el lado negativo para dejar un hilito de pendiente."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La de la salida es otra cosa"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "pk=ezk∑jezj\n",
    "\n",
    "convierte una lista de números en probabilidades que suman uno, exagerando las diferencias porque la exponencial crece rapidísimo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí se confunde mucha gente. La activación de las capas del medio y la de la\n",
    "salida se eligen por razones distintas:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Dónde | Qué se usa | Por qué |\n",
    "|---|---|---|\n",
    "| Capas del medio | ReLU | Para que la pendiente no se apague al multiplicarse |\n",
    "| Salida, sí o no | Sigmoide | Para leer el número como probabilidad |\n",
    "| Salida, varias clases | Softmax | Para que las probabilidades sumen 1 |\n",
    "| Salida, un número | Ninguna | Un precio o una cantidad no se aplasta |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La softmax es la sigmoide cuando hay más de dos opciones, y tiene una trampa\n",
    "que vas a encontrarte:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def softmax_ingenua(z):\n",
    "    e = np.exp(z)\n",
    "    return e / e.sum()\n",
    "\n",
    "def softmax(z):\n",
    "    z = z - z.max()                   # esta línea es toda la diferencia\n",
    "    e = np.exp(z)\n",
    "    return e / e.sum()\n",
    "\n",
    "grandes = np.array([1000.0, 1.0, 2.0])\n",
    "print('ingenua:', softmax_ingenua(grandes))\n",
    "print('estable:', np.round(softmax(grandes), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La ingenua devuelve `nan`, porque `np.exp(1000)` se sale\n",
    "del rango de un número de computadora y luego infinito dividido infinito no es\n",
    "nada.\n",
    "\n",
    "Restar el máximo antes no cambia el resultado matemático (compruébalo con\n",
    "lápiz: se cancela arriba y abajo) y hace que el mayor exponente sea siempre\n",
    "`exp(0) = 1`. Todas las librerías lo hacen por dentro, y si alguna vez\n",
    "escribes la tuya, acuérdate 🧯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y en nuestros datos, ¿cuál gana?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya sabes que aquí las redes pierden, pero la comparación entre activaciones\n",
    "sigue siendo interesante:"
   ]
  },
  {
   "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.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",
    "\n",
    "for activacion in ['logistic', 'tanh', 'relu', 'identity']:\n",
    "    m = arma(MLPClassifier(hidden_layer_sizes=(16,), activation=activacion,\n",
    "                           max_iter=300, random_state=42)).fit(X_tr, y_tr)\n",
    "    print(f'{activacion:10} {roc_auc_score(y_te, m.predict_proba(X_te)[:, 1]):.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y aquí está el remate del capítulo, que no me lo esperaba cuando lo corrí 😳\n",
    "\n",
    "`identity` significa **sin activación ninguna**, o sea\n",
    "justo lo que acabamos de demostrar que colapsa en un modelo lineal. Y gana:\n",
    "0,7230, por encima de las tres que doblan.\n",
    "\n",
    "No se contradice con nada. Lo que dice es que **en estos datos la\n",
    "relación es lineal**, así que doblar la frontera no ayuda y solo añade\n",
    "formas de equivocarse. Y 0,7230 es prácticamente el 0,7214 de la regresión\n",
    "logística, que es exactamente lo que tiene que pasar: son el mismo modelo.\n",
    "\n",
    "Doblar es una capacidad, no una mejora 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Comprobar el colapso con más capas\n",
    "\n",
    "Apila cinco capas sin activación y mira si sigue siendo\n",
    "una."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Cuánto se apaga la pendiente al apilar\n",
    "\n",
    "Multiplica la mejor pendiente posible de cada activación\n",
    "tantas veces como capas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Dibujar la frontera que dobla\n",
    "\n",
    "Vuelve al problema del círculo del capítulo 1 y compara con\n",
    "activación y sin ella."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. ReLU deja medio cerebro apagado y está bien\n",
    "\n",
    "Cuenta qué porcentaje de la capa son ceros en una red\n",
    "entrenada de verdad."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. La softmax de verdad, con tres opciones\n",
    "\n",
    "Úsala sobre un caso con la forma de un problema real."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Escribir LeakyReLU en una línea\n",
    "\n",
    "Hazla tú y compárala con la ReLU normal."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. El error de escribir ReLU con el max de Python\n",
    "\n",
    "Prueba a usar `max` en vez de\n",
    "`np.maximum`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "En el capítulo 3 gana identity, o sea ninguna activación, con 0,7230. ¿Qué significa eso?\n",
    "\n",
    "a) Que en esta tabla no hay nada no lineal que aprender\n",
    "\n",
    "b) Que identity es la mejor activación\n",
    "\n",
    "c) Que la prueba está mal hecha\n",
    "\n",
    "d) Que hay que probar más activaciones"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La activación de salida que no reparte nada"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Te suena que la capa de salida lleva softmax, así que lo pones. La red entrena, no da ningún error y el acierto sale razonable.\n",
    "\n",
    "```\n",
    "salida = softmax(h @ W2 + b2)\n",
    "\n",
    "print('salida.shape:', salida.shape)\n",
    "print('primeras:', salida[:3].ravel())\n",
    "\n",
    "# salida.shape: (750, 1)\n",
    "# primeras: [1. 1. 1.]\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": [
    "- 🌀 Sin activación, dos capas son exactamente una: la diferencia es\n",
    "4,4e-16.\n",
    "\n",
    "- 📉 La pendiente de la sigmoide no pasa de 0,25 y se multiplica entre capas.\n",
    "Veinte capas la dejan en 9,095e-13.\n",
    "\n",
    "- ✂️ ReLU tiene pendiente 1 en todo el lado positivo, y por eso las redes\n",
    "profundas se pudieron entrenar.\n",
    "\n",
    "- 💀 Y a cambio mata neuronas: con un sesgo de -5 murieron 6 de 8.\n",
    "\n",
    "- 🎚️ La activación del medio se elige por el gradiente; la de la salida, por\n",
    "lo que quieres leer.\n",
    "\n",
    "- 🧯 La softmax se escribe restando el máximo, o devuelve `nan` con\n",
    "números grandes.\n",
    "\n",
    "- 😳 En nuestros datos gana `identity`, o sea ninguna activación,\n",
    "con 0,7230. Doblar es una capacidad y no una mejora.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sin la parte que dobla, cien capas son una sola. Y eso se comprueba en dos líneas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y si quieres el vocabulario suelto de todo esto, lo tengo definido en dos líneas por término en el [glosario de IA](https://missyera.com/glosario-ia/) 📖\n",
    "\n",
    "En el capítulo 4 apilamos la primera red de verdad y le damos el problema que\n",
    "una sola neurona no puede resolver.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 3 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/activaciones/\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
}
