{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# La neurona\n",
    "\n",
    "Multiplicar, sumar, aplastar. Y comprobar que la regresión logística que ya conoces es exactamente esto.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 2 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/la-neurona/\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": [
    "Todo el deep learning sale de repetir una pieza muy simple. Vamos a\n",
    "escribirla 🔧\n",
    "\n",
    "Antes, una pregunta: **¿qué te imaginas exactamente que hay dentro de una neurona artificial?** Piénsalo un segundo, porque al final del capítulo vas a poder compararlo con lo que hay de verdad, que es bastante menos de lo que parece 🐣\n",
    "\n",
    "Y te adelanto el final del capítulo, porque es el que quita el misterio: la\n",
    "regresión logística del libro anterior **es** una neurona. No se\n",
    "parece, no es análoga: es la misma cuenta, y lo vamos a comprobar hasta la\n",
    "diezmilbillonésima."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las tres cosas que hace"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "a=f(b+∑j=1mwjxj)\n",
    "\n",
    "una neurona multiplica cada entrada por su peso, lo suma todo, le añade un sesgo y pasa el resultado por una función"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una neurona recibe números y devuelve un número. Por dentro:\n",
    "\n",
    "- ✖️ **Multiplica** cada entrada por su peso. El peso dice cuánto\n",
    "importa esa entrada.\n",
    "\n",
    "- ➕ **Suma** todos esos productos, más un sesgo, que es el número\n",
    "que tiene puesto de arranque.\n",
    "\n",
    "- 🫓 **Aplasta** el resultado a algo entre 0 y 1 con una función\n",
    "de activación."
   ]
  },
  {
   "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",
    "entradas = np.array([2.0, -0.5, 1.0])       # tres cosas que sabemos del cliente\n",
    "pesos = np.array([0.8, -0.3, 0.2])          # cuánto importa cada una\n",
    "sesgo = -0.1\n",
    "\n",
    "z = entradas @ pesos + sesgo\n",
    "print('la suma da   :', round(z, 4))\n",
    "print('aplastada da :', round(sigmoide(z), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso es una neurona. En serio, no hay más 😌\n",
    "\n",
    "El `@` de numpy es la multiplicación de matrices, y aquí hace de\n",
    "golpe lo mismo que `2.0*0.8 + (-0.5)*(-0.3) + 1.0*0.2`. Escribirlo así\n",
    "no es por elegancia: es que cuando sean 28 entradas y 3.000 filas, la diferencia\n",
    "de velocidad es de varios órdenes.\n",
    "\n",
    "Y el resultado, 0,8641, se lee como \"86% de que sí\". Aunque todavía no\n",
    "significa nada, porque esos pesos me los inventé yo ✋"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Muchas filas a la vez"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "𝐚(l)=f(W(l)𝐚(l−1)+𝐛(l))\n",
    "\n",
    "lo mismo para una capa entera, donde la multiplicación de todas las neuronas por todas las entradas cabe en un solo producto de matrices"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La misma línea, sin tocarla, funciona para una tabla entera:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "clientes = np.array([\n",
    "    [2.0, -0.5,  1.0],\n",
    "    [-1.0,  0.5, -2.0],\n",
    "    [0.0,  0.0,  0.0],\n",
    "    [3.0,  1.0,  0.5],\n",
    "])\n",
    "\n",
    "print('las sumas   :', np.round(clientes @ pesos + sesgo, 4))\n",
    "print('aplastadas  :', np.round(sigmoide(clientes @ pesos + sesgo), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuatro clientes, cuatro probabilidades, y el código es idéntico. Eso se llama\n",
    "vectorizar y es la razón de que numpy exista.\n",
    "\n",
    "Fíjate en el tercero: entradas todas cero, así que la suma es solo el sesgo\n",
    "(-0,1) y la probabilidad sale 0,475. **El sesgo es lo que la neurona\n",
    "contesta cuando no sabe nada**, y por eso hace falta 🎈"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Por qué aplastar, y por qué con esa función"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La suma puede dar cualquier cosa: -47, 0,3, 1.200. Y nosotras queremos una\n",
    "probabilidad, o sea algo entre 0 y 1.\n",
    "\n",
    "La sigmoide hace exactamente eso, y además de una forma con una propiedad que\n",
    "va a ser clave en el capítulo 5:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for z in [-20, -6, -2, 0, 2, 6, 20]:\n",
    "    s = sigmoide(z)\n",
    "    print(f'z={z:4d}   sigmoide={s:.6f}   pendiente={s * (1 - s):.6f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la columna de la derecha, que es cuánto cambia la salida si mueves un\n",
    "poquito la entrada.\n",
    "\n",
    "En el centro (z=0) la pendiente vale 0,25, que es su máximo. En los extremos\n",
    "vale **cero**: en z=20 la neurona dice 1,000000 y da igual lo que le\n",
    "muevas, va a seguir diciendo 1.\n",
    "\n",
    "Eso se llama **saturación**, y es un problema muy serio, porque\n",
    "una neurona saturada deja de aprender. Aparece con nombre propio en el\n",
    "capítulo 7, cuando apilemos capas y esos ceros se\n",
    "multipliquen entre sí 😰"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La comprobación que quita el misterio"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora lo prometido. Entrenamos una regresión logística con scikit-learn, le\n",
    "sacamos los pesos, y hacemos la cuenta a mano."
   ]
  },
  {
   "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.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",
    "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",
    "])\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",
    "modelo.fit(X_tr, y_tr)\n",
    "\n",
    "W = modelo.named_steps['mod'].coef_[0]          # los pesos que aprendió\n",
    "B = modelo.named_steps['mod'].intercept_[0]     # y su sesgo\n",
    "print('pesos:', W.shape, '  sesgo:', round(B, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Veintiocho pesos y un sesgo. Exactamente lo que pide una neurona con 28\n",
    "entradas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "T = modelo.named_steps['pre'].transform(X_te)   # las columnas ya preparadas\n",
    "\n",
    "a_mano = sigmoide(T @ W + B)\n",
    "libreria = modelo.predict_proba(X_te)[:, 1]\n",
    "\n",
    "print('a mano  :', np.round(a_mano[:4], 6))\n",
    "print('librería:', np.round(libreria[:4], 6))\n",
    "print('la mayor diferencia entre las dos:', float(np.abs(a_mano - libreria).max()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Iguales hasta donde llega el doble de precisión: la mayor diferencia es\n",
    "1,1e-16, o sea el error de redondeo de la computadora 🎯\n",
    "\n",
    "Ahí está lo que quería que vieras. **No hay una caja negra**. Lo\n",
    "que hace `predict_proba` es esa línea de numpy, ni una operación más.\n",
    "\n",
    "Y la única diferencia entre esto y una red neuronal de verdad es que la red\n",
    "tiene varias de estas apiladas, cada una comiéndose la salida de la anterior. En\n",
    "el capítulo 4 apilamos la primera."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dónde deja de aprender la sigmoide"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La función que aplasta tiene un problema serio, y se ve mirando su pendiente\n",
    "en vez de su valor 📉"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def derivada(z):\n",
    "    return sigmoide(z) * (1 - sigmoide(z))\n",
    "\n",
    "\n",
    "for z in (0, 1, 3, 6, 10, 20):\n",
    "    print(f'z={z:>3}   sigmoide {sigmoide(z):.6f}   pendiente {derivada(z):.8f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira la columna de la derecha bajando: 0,25 en el centro, 0,0000454 en z=10,\n",
    "y en z=20 **cero** 🪫\n",
    "\n",
    "La pendiente es lo que le dice a la red cuánto mover cada peso. Si la pendiente\n",
    "es cero, el peso no se mueve. Y si el peso no se mueve,\n",
    "**esa neurona dejó de aprender**, aunque se esté equivocando.\n",
    "\n",
    "Se llama saturación, y es lo que pasa cuando la neurona está muy segura de\n",
    "algo: da igual que esté muy segura y equivocada, porque ya no puede corregirse.\n",
    "\n",
    "Fíjate además en la columna del medio: en z=6 la sigmoide ya vale 0,9975, o\n",
    "sea que para la respuesta da lo mismo z=6 que z=20. Pero para el aprendizaje no\n",
    "da nada igual: en uno la pendiente es 0,0025 y en el otro es cero 🧊\n",
    "\n",
    "De aquí salen dos cosas que vas a ver en el resto del libro. La primera es por\n",
    "qué se escalan las entradas: con columnas sin escalar, la suma ponderada se va a\n",
    "números enormes y la neurona nace saturada. La segunda es por qué casi nadie usa\n",
    "sigmoide en las capas de en medio, y qué se usa en su lugar, que es el capítulo\n",
    "3 🔀"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El problema que una neurona no puede resolver"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora el límite de verdad, con el ejemplo histórico que paró la\n",
    "investigación en redes neuronales durante años 🕰️\n",
    "\n",
    "Cuatro filas, dos columnas, y la respuesta es \"una u otra, pero no las dos\":"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])\n",
    "y_xor = np.array([0, 1, 1, 0])\n",
    "\n",
    "una = LogisticRegression().fit(X_xor, y_xor)\n",
    "\n",
    "print('lo que quiero :', y_xor)\n",
    "print('lo que predice:', una.predict(X_xor))\n",
    "print('acierta       :', round(una.score(X_xor, y_xor), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Predice cero para las cuatro y acierta la mitad, que es tirar una moneda 🪙\n",
    "\n",
    "Y no es que le falte entrenamiento ni datos: **es imposible**. Una\n",
    "neurona traza una línea recta y separa lo que queda a cada lado. Dibuja esos\n",
    "cuatro puntos en un papel y prueba a separar los unos de los ceros con una sola\n",
    "raya: no hay manera, están en diagonal.\n",
    "\n",
    "Ahora con una capa de cuatro neuronas en medio:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.neural_network import MLPClassifier\n",
    "\n",
    "dos = MLPClassifier(hidden_layer_sizes=(4,), max_iter=5000,\n",
    "                    random_state=0).fit(X_xor, y_xor)\n",
    "\n",
    "print('con una capa oculta:', dos.predict(X_xor))\n",
    "print('acierta            :', round(dos.score(X_xor, y_xor), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las cuatro bien 🎉\n",
    "\n",
    "Eso es exactamente para lo que sirven las capas, y es toda la idea del libro\n",
    "en cuatro filas: **una neurona traza una raya, y varias neuronas en capas\n",
    "trazan la combinación de varias rayas**, que ya no es una raya.\n",
    "\n",
    "Este ejemplo tiene además una historia detrás que explica por qué las redes\n",
    "neuronales estuvieron muertas casi veinte años. Se publicó en 1969, en un libro\n",
    "que demostraba que una sola capa no podía con esto, y la financiación se cortó.\n",
    "Lo que faltaba no era la idea de apilar capas, que ya se conocía: era saber cómo\n",
    "entrenarlas, y eso llega en el capítulo 6 📚"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que la neurona NO hace"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Como ya sabes lo que hay dentro, dos cosas que se dicen mucho y son falsas:\n",
    "\n",
    "- 🧠 **No \"entiende\" nada.** Multiplica y suma. La palabra\n",
    "\"neurona\" viene de una analogía de 1943 y ha hecho más daño que bien.\n",
    "\n",
    "- 🎲 **No hay nada aleatorio en predecir.** Los mismos pesos y la\n",
    "misma entrada dan siempre el mismo número. Lo aleatorio está en el arranque del\n",
    "entrenamiento, y por eso ponemos `random_state`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Qué peso le puso a cada cosa\n",
    "\n",
    "Mira los cinco pesos más grandes de la neurona que\n",
    "entrenamos, con el nombre de su columna."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "nombres = modelo.named_steps['pre'].get_feature_names_out()\n",
    "for i in np.argsort(-np.abs(W))[:5]:\n",
    "    print(f'{nombres[i]:32} {W[i]:+.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "cat__segmento_Bodega             -0.6832\n",
    "cat__segmento_Mayorista          +0.4725\n",
    "cat__canal_Marketplace           -0.4651\n",
    "num__satisfaccion                +0.4152\n",
    "num__sin_compra_previa           -0.3747\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ser Bodega es el peso más negativo y ser Mayorista el más positivo, que es lo\n",
    "mismo que salía en el capítulo 12 del libro de machine learning.\n",
    "\n",
    "Una neurona entrenada es una lista de números con un nombre al lado. Cuando\n",
    "sean cuatro capas ya no vas a poder leerla así, y esa pérdida es real: es el\n",
    "precio que se paga por la capacidad 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Mover un peso a mano y ver qué pasa\n",
    "\n",
    "Duplica el peso de la satisfacción y mira cuánto se mueven\n",
    "las predicciones."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "i = list(nombres).index('num__satisfaccion')\n",
    "W2 = W.copy()\n",
    "W2[i] = W[i] * 2\n",
    "\n",
    "antes = sigmoide(T @ W + B)\n",
    "despues = sigmoide(T @ W2 + B)\n",
    "print('peso original :', round(W[i], 4), ' duplicado:', round(W2[i], 4))\n",
    "print('se movió en promedio:', round(float(np.abs(despues - antes).mean()), 4))\n",
    "print('el que más se movió :', round(float(np.abs(despues - antes).max()), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "peso original : 0.4152  duplicado: 0.8303\n",
    "se movió en promedio: 0.0671\n",
    "el que más se movió : 0.1518\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Duplicar un peso mueve las predicciones 0,0671 en promedio y hasta 0,1518 en\n",
    "el caso más extremo.\n",
    "\n",
    "Este ejercicio es el que hace tangible qué es entrenar: **buscar la\n",
    "combinación de 28 números que menos se equivoca**. Nada más. En el\n",
    "capítulo 5 dejamos de moverlos a mano 🎚️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. La neurona sin aplastar\n",
    "\n",
    "Quita la sigmoide y mira qué sale."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "crudo = T @ W + B\n",
    "print('sin aplastar, va de', round(float(crudo.min()), 4),\n",
    "      'a', round(float(crudo.max()), 4))\n",
    "print('aplastado, va de', round(float(sigmoide(crudo).min()), 4),\n",
    "      'a', round(float(sigmoide(crudo).max()), 4))\n",
    "print('¿ordenan igual?', bool((np.argsort(crudo) == np.argsort(sigmoide(crudo))).all()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "sin aplastar, va de -2.4445 a 2.8774\n",
    "aplastado, va de 0.0798 a 0.9467\n",
    "¿ordenan igual? True\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Van de -2,4445 a 2,8774 sin aplastar, y de 0,0798 a 0,9467 aplastado. Y\n",
    "ordenan exactamente igual.\n",
    "\n",
    "Eso es útil de saber: si solo te importa el ranking, como en el capítulo 22\n",
    "del libro anterior, la sigmoide no cambia nada. Importa cuando quieres leer el\n",
    "número como probabilidad, y cuando entrenas 📏"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Un umbral es una raya en la suma\n",
    "\n",
    "Comprueba que cortar en 0,5 de probabilidad es lo mismo que\n",
    "cortar en 0 de la suma."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('sigmoide(0) =', sigmoide(0))\n",
    "print('¿coinciden los dos cortes?',\n",
    "      bool(((sigmoide(crudo) >= 0.5) == (crudo >= 0)).all()))\n",
    "print('¿y 0,14 con -1,8153?',\n",
    "      bool(((sigmoide(crudo) >= 0.14) == (crudo >= -1.8153)).all()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "sigmoide(0) = 0.5\n",
    "¿coinciden los dos cortes? True\n",
    "¿y 0,14 con -1,8153? True\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El famoso 0,5 del capítulo 11 del libro anterior es, por dentro, cortar la\n",
    "suma en cero. Y el 0,14 que salía óptimo es cortar en -1,8153.\n",
    "\n",
    "No cambia nada práctico y sí cambia cómo lo piensas: **el umbral no es\n",
    "una propiedad del modelo, es dónde pones la raya después** ✂️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Cuántas cuentas hace de verdad\n",
    "\n",
    "Cuenta las multiplicaciones que hace para predecir las 750\n",
    "filas de prueba."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "filas, columnas = T.shape\n",
    "print(f'{filas} filas x {columnas} pesos = {filas * columnas:,} multiplicaciones')\n",
    "print(f'y {filas:,} sumas del sesgo, y {filas:,} exponenciales')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "750 filas x 28 pesos = 21,000 multiplicaciones\n",
    "y 750 sumas del sesgo, y 750 exponenciales\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Veintiún mil multiplicaciones para predecir 750 clientes, y tarda menos de un\n",
    "parpadeo.\n",
    "\n",
    "Guárdate ese número, porque en el capítulo 16 vamos a ver que un modelo de\n",
    "lenguaje hace del orden de **miles de millones** de estas por cada\n",
    "palabra que escribe. La cuenta es la misma; lo que cambia es cuántas 🔢"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. La neurona con los pesos al revés\n",
    "\n",
    "Ponle los pesos con el signo cambiado y mira qué predice."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "alreves = sigmoide(T @ (-W) + (-B))\n",
    "from sklearn.metrics import roc_auc_score\n",
    "print('normal  :', round(roc_auc_score(y_te, sigmoide(T @ W + B)), 4))\n",
    "print('al revés:', round(roc_auc_score(y_te, alreves), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "normal  : 0.7214\n",
    "al revés: 0.2786\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "0,7214 y 0,2786, que suman exactamente 1.\n",
    "\n",
    "Un modelo con AUC de 0,28 no es un modelo malo: es un modelo bueno leído al\n",
    "revés. Si alguna vez te sale un AUC muy por debajo de 0,5, casi siempre hay una\n",
    "etiqueta invertida en alguna parte, y es de los errores más fáciles de\n",
    "arreglar 🔄"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. El error que vas a ver mil veces\n",
    "\n",
    "Dale a la neurona menos pesos que columnas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "T @ W[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 5 is different from 28)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí está el error más común de todo el deep learning, y no exagero. Una\n",
    "matriz de 750 por 28 no se puede multiplicar por un vector de 5.\n",
    "\n",
    "El mensaje es feo pero dice todo lo que necesitas: **\"size 5 is\n",
    "different from 28\"**. Cuando te salga, no leas el resto: busca esos dos\n",
    "números y mira qué forma esperabas tú 📐\n",
    "\n",
    "Mi truco de toda la vida es imprimir `.shape` de todo lo que entra\n",
    "en un `@` antes de ejecutarlo. Suena de principiante y lo sigo\n",
    "haciendo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Una neurona multiplica, suma y aplasta. ¿Qué pasa si le quitas el aplastar?\n",
    "\n",
    "a) Que deja de poder aprender cualquier cosa que no sea una recta\n",
    "\n",
    "b) Que va más rápido pero aprende igual\n",
    "\n",
    "c) Que hay que usar más neuronas para compensar\n",
    "\n",
    "d) Nada, es un detalle de implementación\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Va más rápido, sí. Pero mira qué queda si encadenas dos capas sin aplastar.\n",
    "\n",
    "*c)* Por muchas que pongas, la suma de rectas sigue siendo una recta.\n",
    "\n",
    "*d)* Es la diferencia entre una red y una regresión lineal cara.\n",
    "\n",
    "Sin activación, dos capas son exactamente una: 4,4e-16 de diferencia."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y el arranque más ordenado del mundo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Inicializas los pesos a cero, que es lo ordenado y lo que haría cualquiera que venga de programar. El entrenamiento corre, la pérdida baja un poco y se queda quieta.\n",
    "\n",
    "```\n",
    "W1 = np.zeros((n_entradas, n_ocultas))\n",
    "W2 = np.zeros((n_ocultas, 1))\n",
    "\n",
    "# ...tras 2.000 vueltas:\n",
    "# perdida 0.6930   acierto 0.5075\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "Con todos los pesos iguales, **todas las neuronas de una capa calculan exactamente lo mismo**. Y si calculan lo mismo, reciben la misma corrección, así que siguen siendo idénticas para siempre 👯 Tienes ocho neuronas y una sola neurona repetida ocho veces.\n",
    "\n",
    "Los dos números lo delatan y hay que saber leerlos. Una pérdida de **0,6930** es exactamente el logaritmo de 2, o sea lo que sale cuando el modelo dice \"no tengo ni idea\" en todas las filas. Y ese acierto de **0,5075** es clavado el porcentaje de la clase mayoritaria: está diciendo siempre lo mismo.\n",
    "\n",
    "Con pesos al azar, el mismo código llega a pérdida 0,0314 y acierto 0,9950. La asimetría del arranque no es un detalle de implementación: es lo único que hace que las neuronas se repartan el trabajo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- ✖️ Una neurona multiplica, suma un sesgo y aplasta. Se escribe en una línea\n",
    "de numpy.\n",
    "\n",
    "- 🎯 La regresión logística *es* una neurona: haciendo la cuenta a mano\n",
    "sale lo mismo que la librería con 1,1e-16 de diferencia.\n",
    "\n",
    "- 🎈 El sesgo es lo que contesta cuando todas las entradas son cero.\n",
    "\n",
    "- 😰 La sigmoide se satura: en z=20 su pendiente es cero y la neurona deja de\n",
    "aprender. Vuelve en el capítulo 7.\n",
    "\n",
    "- ✂️ Un umbral de probabilidad es una raya en la suma: 0,5 es cortar en cero.\n",
    "\n",
    "- 🔄 Un AUC de 0,28 es un 0,72 con la etiqueta invertida.\n",
    "\n",
    "- 📐 El error que más vas a ver es de formas, y su mensaje trae los dos\n",
    "números que necesitas.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dentro de una red no hay magia. Hay una multiplicación, una suma y un aplastón, repetidos muchas veces."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si el numpy de este capítulo te costó más que la idea, eso es de Python y se arregla en el [libro de Python desde cero](https://missyera.com/guias/python-desde-cero/) 🐍\n",
    "\n",
    "En el capítulo 3 vemos por qué la sigmoide no es la única función de\n",
    "activación, y por qué casi nadie la usa ya en las capas del medio.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 2 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/la-neurona/\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
}
