{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Apilar capas\n",
    "\n",
    "El XOR, que paró el campo veinte años. Y la diferencia entre poder representar algo y llegar a encontrarlo.\n",
    "\n",
    "Cuaderno de práctica del capítulo 4 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/apilar-capas/\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: \"YmFzZSAgICAgICAgICAgIFs3LjAwMGUtMDQgNC45OTVlLTAxIDkuOTk1ZS0wMSA1LjAwNWUtMDFdCjQgdmVjZXMgdnVlbHRhcyBbMS4wMDBlLTA0IDQuOTk5ZS0wMSA5Ljk5OWUtMDEgNS4wMDFlLTAxXQpwYXNvIG3DoXMgZ3JhbmRlIFsyLjAwMGUtMDQgNC45OTllLTAxIDkuOTk5ZS0wMSA1LjAwMWUtMDFdCjggbmV1cm9uYXMgICAgICBbMi4wMDBlLTA0IDkuOTk0ZS0wMSA5Ljk5NmUtMDEgNi4wMDBlLTA0XQ==\",\n",
    "    2: \"Y2FwYSBvY3VsdGEsIHVuYSBmaWxhIHBvciBjYXNvOgpbWyAwLjAxMiAtMC4wNiAgLTAuNjg0IC0wLjc3N10KIFsgMC45NzIgIDAuOTcyIC0xLiAgICAgMC45NTVdCiBbIDAuOTc1ICAwLjk3NyAgMC45NDkgLTEuICAgXQogWyAxLiAgICAgMS4gICAgLTAuOTggIC0wLjk4MV1d\",\n",
    "    3: \"Y29uIGxhIGNvbHVtbmEgZXh0cmE6IFswLjAxNDggMC45OTAxIDAuOTkwMSAwLjAwNjZdCmFjaWVydGEgNCBkZSA0\",\n",
    "    4: \"IDIgb2N1bHRhczogICA5IHBlc29zIHBhcmEgYXByZW5kZXIgNCBmaWxhcwogNCBvY3VsdGFzOiAgMTcgcGVzb3MgcGFyYSBhcHJlbmRlciA0IGZpbGFzCiA4IG9jdWx0YXM6ICAzMyBwZXNvcyBwYXJhIGFwcmVuZGVyIDQgZmlsYXMKMTYgb2N1bHRhczogIDY1IHBlc29zIHBhcmEgYXByZW5kZXIgNCBmaWxhcw==\",\n",
    "    5: \"cGFzbyAgMC4wMDE6IHDDqXJkaWRhIDAuNjM4MiAgWzAuNDM4IDAuNjMxIDAuNDUxIDAuNTEzXQpwYXNvICAgMC4wNTogcMOpcmRpZGEgMC4wMTE0ICBbMC4wMDIgMC45ODggMC45ODcgMC4wMThdCnBhc28gICAgMC41OiBww6lyZGlkYSAwLjAwMDkgIFswLiAgICAwLjk5OSAwLjk5OSAwLjAwMl0KcGFzbyAgICA1LjA6IHDDqXJkaWRhIDAuMDAwMCAgWzAuIDEuIDEuIDAuXQpwYXNvICAgNTAuMDogcMOpcmRpZGEgNTUuODIwMiAgWzAuIDAuIDAuIDAuXQ==\",\n",
    "    6: \"Y29uIHRyZXMgY2FwYXMgY29udmVyZ2VuIDEwIGRlIDEw\",\n",
    "    7: \"cCB0aWVuZSBmb3JtYSAgOiAoNCwgMSkKWV9YT1IgdGllbmUgICAgOiAoNCwpCmxhIHJlc3RhIHNhbGUgIDogKDQsIDQp\",\n",
    "    8: \"dmlzaXRhX3JlcGV0aWRhICAgIGVsIGRlc2N1ZW50byBzdW1hICswLjExNzUgY3VhbmRvIGVzIDAgeSArMC4xMjM1IGN1YW5kbyBlcyAxCm1heW9yaXN0YSAgICAgICAgICBlbCBkZXNjdWVudG8gc3VtYSArMC4xMzY4IGN1YW5kbyBlcyAwIHkgKzAuMDY4MiBjdWFuZG8gZXMgMQ==\",\n",
    "    9: \"VmFsdWVFcnJvcjogbWF0bXVsOiBJbnB1dCBvcGVyYW5kIDEgaGFzIGEgbWlzbWF0Y2ggaW4gaXRzIGNvcmUgZGltZW5zaW9uIDAsIHdpdGggZ3VmdW5jIHNpZ25hdHVyZSAobj8sayksKGssbT8pLT4obj8sbT8pIChzaXplIDQgaXMgZGlmZmVyZW50IGZyb20gOCk=\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hasta aquí tenemos una neurona (capítulo 2) y sabemos por qué hace falta\n",
    "doblar (capítulo 3). Toca apilar 🧱\n",
    "\n",
    "Una pregunta para ti: **¿cuántas veces has probado algo una vez, te ha salido bien y lo has dado por bueno?** Este capítulo tiene una medición que me curó de eso 🎲\n",
    "\n",
    "Y para que se vea la diferencia hace falta un problema que una sola neurona\n",
    "**no pueda**. El clásico se llama XOR, tiene cuatro filas y paró la\n",
    "investigación en redes neuronales durante casi veinte años."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tres problemas de cuatro filas"
   ]
  },
  {
   "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",
    "X = np.array([[0., 0.],\n",
    "              [0., 1.],\n",
    "              [1., 0.],\n",
    "              [1., 1.]])\n",
    "\n",
    "Y_AND = np.array([0., 0., 0., 1.])      # los dos\n",
    "Y_OR = np.array([0., 1., 1., 1.])       # alguno de los dos\n",
    "Y_XOR = np.array([0., 1., 1., 0.])      # uno pero no los dos\n",
    "\n",
    "for nombre, objetivo in [('AND', Y_AND), ('OR', Y_OR), ('XOR', Y_XOR)]:\n",
    "    print(nombre, objetivo)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Llévalo a algo tuyo para que no sea abstracto: imagina que la primera columna\n",
    "es \"le hicimos descuento\" y la segunda \"lo visitó un vendedor\".\n",
    "\n",
    "El AND sería \"compra solo si le hacemos las dos cosas\". El OR, \"compra si le\n",
    "hacemos cualquiera de las dos\". Y el XOR sería \"compra si le haces una, pero si\n",
    "le haces las dos se siente presionado y no compra\" 🤔\n",
    "\n",
    "Los tres suenan igual de razonables. Y solo dos se pueden aprender con una\n",
    "neurona."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Una neurona, entrenada 5.000 vueltas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def una_neurona(objetivo, vueltas=5000, paso=0.5):\n",
    "    rng = np.random.default_rng(0)\n",
    "    w = rng.normal(0, 0.5, 2)\n",
    "    b = 0.0\n",
    "    for _ in range(vueltas):\n",
    "        p = sigmoide(X @ w + b)\n",
    "        error = p - objetivo\n",
    "        w -= paso * X.T @ error / len(X)\n",
    "        b -= paso * error.mean()\n",
    "    return sigmoide(X @ w + b)\n",
    "\n",
    "for nombre, objetivo in [('AND', Y_AND), ('OR', Y_OR), ('XOR', Y_XOR)]:\n",
    "    p = una_neurona(objetivo)\n",
    "    aciertos = int(((p >= 0.5) == objetivo).sum())\n",
    "    print(f'{nombre:4} {np.round(p, 4)}  acierta {aciertos} de 4')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "AND y OR salen perfectos. Y el XOR devuelve **0,5 en los cuatro\n",
    "casos**, que es la forma que tiene una neurona de decir \"no tengo ni\n",
    "idea\" 🤷‍♀️\n",
    "\n",
    "Y no es que le faltaran vueltas. Puedes dejarla un millón y va a seguir\n",
    "diciendo 0,5, porque el problema no es de esfuerzo.\n",
    "\n",
    "Una neurona traza una recta y decide de qué lado cae cada punto. Coloca los\n",
    "cuatro puntos del XOR en un papel: los que valen 1 están en las esquinas (0,1) y\n",
    "(1,0), o sea en diagonal. No hay ninguna recta que deje esas dos de un lado y las\n",
    "otras dos del otro. Ninguna 📐\n",
    "\n",
    "Eso lo demostraron Minsky y Papert en 1969, y el campo se quedó helado. La\n",
    "salida ya se conocía, pero faltaba saber cómo entrenarla, que es el capítulo 6."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Dos capas, escritas enteras"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Metemos una capa en medio: las entradas van a cuatro neuronas ocultas, y esas\n",
    "cuatro van a la salida."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def red_dos_capas(semilla, ocultas=4, vueltas=5000, paso=0.5):\n",
    "    rng = np.random.default_rng(semilla)\n",
    "    W1 = rng.normal(0, 1, (2, ocultas))\n",
    "    b1 = np.zeros(ocultas)\n",
    "    W2 = rng.normal(0, 1, (ocultas, 1))\n",
    "    b2 = np.zeros(1)\n",
    "    objetivo = Y_XOR.reshape(-1, 1)\n",
    "\n",
    "    for _ in range(vueltas):\n",
    "        # hacia adelante\n",
    "        h = np.tanh(X @ W1 + b1)\n",
    "        p = sigmoide(h @ W2 + b2)\n",
    "        # hacia atrás (el capítulo 6 explica de dónde sale cada línea)\n",
    "        d2 = (p - objetivo) / len(X)\n",
    "        dW2 = h.T @ d2\n",
    "        db2 = d2.sum(axis=0)\n",
    "        d1 = (d2 @ W2.T) * (1 - h ** 2)\n",
    "        dW1 = X.T @ d1\n",
    "        db1 = d1.sum(axis=0)\n",
    "        # corregir\n",
    "        W2 -= paso * dW2\n",
    "        b2 -= paso * db2\n",
    "        W1 -= paso * dW1\n",
    "        b1 -= paso * db1\n",
    "\n",
    "    perdida = -np.mean(objetivo * np.log(p) + (1 - objetivo) * np.log(1 - p))\n",
    "    return p.ravel(), float(perdida)\n",
    "\n",
    "p, perdida = red_dos_capas(semilla=1)\n",
    "print('la red dice :', np.round(p, 4))\n",
    "print('lo que quería:', Y_XOR)\n",
    "print('pérdida      :', round(perdida, 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está 🎉\n",
    "\n",
    "0,0001 y 0,0016 donde tenía que decir 0, y 0,9989 en los dos que tenían que\n",
    "ser 1. Con cuatro neuronas en medio y veinte líneas de numpy.\n",
    "\n",
    "Lo que hicieron esas cuatro neuronas es doblar el espacio hasta que los cuatro\n",
    "puntos sí se puedan separar con una recta. Cada una traza su propia raya, y la\n",
    "capa de salida combina las cuatro. Eso es todo lo que hace una capa oculta.\n",
    "\n",
    "No te preocupes por las líneas del \"hacia atrás\", que son el capítulo 6\n",
    "entero. Por ahora quédate con que hay una parte que calcula y otra que corrige 🔄"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora lo que casi nadie cuenta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese resultado salió con la semilla 1. Probemos con diez arranques distintos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for semilla in range(10):\n",
    "    p, perdida = red_dos_capas(semilla)\n",
    "    aciertos = int(((p >= 0.5).astype(int) == Y_XOR).sum())\n",
    "    print(f'semilla {semilla}: {np.round(p, 4)}  pérdida {perdida:.4f}  '\n",
    "          f'acierta {aciertos}/4')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nueve de diez lo resuelven. Y **la semilla 0 se queda atascada**:\n",
    "devuelve 0,4995 y 0,5005 en dos de los cuatro, acierta 2 de 4, y su pérdida se\n",
    "queda en 0,3474 en vez de bajar a 0,0009.\n",
    "\n",
    "Y no le faltan vueltas ni le falta capacidad. La misma red, el mismo código,\n",
    "las mismas 5.000 vueltas. Lo único distinto son los números con los que\n",
    "arrancó 🎲\n",
    "\n",
    "Esto tiene nombre: se quedó en un **mínimo local**. Encontró una\n",
    "combinación de pesos desde la que cualquier cambio pequeño empeora, así que se\n",
    "queda ahí aunque exista una solución mucho mejor en otro sitio.\n",
    "\n",
    "Guárdate esta distinción porque vale para todo el libro: **que una red\n",
    "pueda representar la solución no significa que la vaya a encontrar**. Son\n",
    "dos cosas distintas y la segunda es la difícil."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Más neuronas no es más capacidad: es más suerte"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En teoría con dos neuronas ocultas basta para el XOR. Vamos a ver qué pasa en\n",
    "la práctica:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for ocultas in [2, 4, 8, 16]:\n",
    "    convergen = sum(\n",
    "        1 for s in range(10)\n",
    "        if ((red_dos_capas(s, ocultas=ocultas)[0] >= 0.5).astype(int) == Y_XOR).sum() == 4\n",
    "    )\n",
    "    print(f'{ocultas:2d} neuronas ocultas: convergen {convergen} de 10 arranques')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y aquí está el resultado que a mí me parece el más bonito del capítulo 💡\n",
    "\n",
    "Con dos neuronas convergen 5 de 10, con cuatro 9 de 10, y con ocho o dieciséis\n",
    "las diez.\n",
    "\n",
    "Con dos ya alcanza para representar el XOR. Lo que cambia al poner más no es\n",
    "lo que la red *puede*: es la probabilidad de que el entrenamiento\n",
    "**encuentre** una solución en vez de atascarse. Más neuronas son más\n",
    "caminos hacia abajo.\n",
    "\n",
    "Por eso en la práctica las redes se hacen más grandes de lo que la teoría\n",
    "pide. No es derroche: es comprar probabilidad de converger 🛣️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ¿Y hay algún XOR en los datos de la distribuidora?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Buena pregunta, porque si no lo hay, este capítulo es una clase de teoría y ya\n",
    "está. Vamos a mirarlo 🔎\n",
    "\n",
    "El equivalente sería: que hacer descuento ayude cuando el cliente está poco\n",
    "satisfecho y **estorbe** cuando está contento. O sea que el efecto\n",
    "del descuento cambie de signo según la otra columna."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\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",
    "ventas = carga_limpia(URL)\n",
    "ventas['con_descuento'] = (ventas['descuento'].fillna(0) > 0).astype(int)\n",
    "ventas['satisfecho'] = (ventas['satisfaccion']\n",
    "                        .fillna(ventas['satisfaccion'].median()) >= 4).astype(int)\n",
    "\n",
    "print(ventas.groupby(['con_descuento', 'satisfecho'])['compro']\n",
    "      .agg(['size', 'mean']).round(4).to_string())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí están las cuatro casillas, igual que el XOR pero con 3.000 filas\n",
    "detrás.\n",
    "\n",
    "Y **no hay XOR**. Mira los dos saltos: sin satisfacción, el\n",
    "descuento sube la compra de 0,4031 a 0,5535, o sea quince puntos. Con\n",
    "satisfacción, la sube de 0,6250 a 0,6813, o sea cinco puntos y medio.\n",
    "\n",
    "El descuento ayuda *siempre*. Ayuda más cuando el cliente está poco\n",
    "contento, que tiene todo el sentido comercial, pero nunca cambia de signo. Eso no\n",
    "es un XOR: es un efecto que suma con distinta fuerza.\n",
    "\n",
    "Y ahí tienes explicado, por fin, todo lo que veníamos midiendo. En el\n",
    "capítulo 1 la red perdía y en el\n",
    "3 ganaba `identity` porque **en estos\n",
    "datos no hay ninguna esquina cruzada que doblar**. La capacidad de la red\n",
    "está ahí y no hay dónde gastarla 💡\n",
    "\n",
    "Esta comprobación de cuatro casillas es la que yo haría antes de proponer una\n",
    "red para datos en tabla. Si las cuatro casillas suman, la logística te vale."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Rescatar la semilla atascada\n",
    "\n",
    "Dale a la semilla 0 más vueltas, más paso y más neuronas, y\n",
    "mira cuál la salva."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Qué aprendió cada neurona oculta\n",
    "\n",
    "Mira lo que sale de la capa del medio para los cuatro\n",
    "casos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Una neurona con la pista dada a mano\n",
    "\n",
    "Añade tú la columna que hace falta y usa una sola\n",
    "neurona."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Cuántos pesos hay en juego\n",
    "\n",
    "Cuenta los parámetros de cada versión."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. La red que se rinde por el paso\n",
    "\n",
    "Prueba pasos muy chicos y muy grandes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Tres capas en vez de dos\n",
    "\n",
    "Mete una capa más y mira si ayuda."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. El error de olvidar el reshape\n",
    "\n",
    "Pásale el objetivo plano en vez de en columna."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Buscar la esquina cruzada en tres pares de columnas\n",
    "\n",
    "Repite la comprobación de las cuatro casillas con otros\n",
    "pares y mira si alguno cruza."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. El error de cambiar el tamaño en un solo sitio\n",
    "\n",
    "Pon ocho neuronas en la primera capa y déjate las cuatro de\n",
    "la segunda, que es lo que pasa cuando editas rápido."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 9\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "El XOR converge en 9 de 10 arranques con 4 neuronas, en 5 de 10 con 2 y en 10 de 10 con 8. ¿Qué dan las neuronas de más?\n",
    "\n",
    "a) Caminos: más arranques distintos acaban encontrando la solución\n",
    "\n",
    "b) Capacidad: pueden aprender funciones más complicadas\n",
    "\n",
    "c) Velocidad de entrenamiento\n",
    "\n",
    "d) Precisión en la respuesta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y la arquitectura que eliges por suerte"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Pruebas dos arquitecturas para el XOR, la de dos neuronas ocultas y la de ocho, y te quedas con la de dos porque es más pequeña y resolvió igual.\n",
    "\n",
    "```\n",
    "red2 = entrena(ocultas=2, semilla=0)\n",
    "red8 = entrena(ocultas=8, semilla=0)\n",
    "\n",
    "print('2 ocultas:', red2.acierto)   # 1.0\n",
    "print('8 ocultas:', red8.acierto)   # 1.0\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": [
    "- 📐 Una neurona traza una recta. El XOR no se separa con una recta, así que\n",
    "devuelve 0,5 en los cuatro casos por muchas vueltas que le des.\n",
    "\n",
    "- 🧱 Una capa oculta de cuatro neuronas lo resuelve, y se escribe en veinte\n",
    "líneas de numpy.\n",
    "\n",
    "- 🎲 Pero no siempre: de diez arranques convergen nueve, y el que falla se\n",
    "queda en un mínimo local con pérdida 0,3468.\n",
    "\n",
    "- 🛣️ Con dos neuronas convergen 5 de 10, con cuatro 9 y con ocho las diez. Más\n",
    "neuronas no dan más capacidad, dan más caminos.\n",
    "\n",
    "- 🧩 La capa oculta es feature engineering automático: si le das a mano la\n",
    "columna que falta, una sola neurona resuelve el XOR.\n",
    "\n",
    "- 🎯 Una pérdida de 0,6931 es ln(2), o sea el modelo diciendo 0,5 a todo.\n",
    "\n",
    "- 😨 Restar un (4, 1) menos un (4,) devuelve un (4, 4) sin avisar.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una corrida no es un resultado. Y en redes, dos corridas tampoco."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo de medir con varias semillas en vez de con una es la misma disciplina de la validación cruzada, y está en el [libro de machine learning desde cero](https://missyera.com/guias/machine-learning-desde-cero/) 🎲\n",
    "\n",
    "En el capítulo 5 dejamos de mover los pesos a mano: descenso de gradiente, que\n",
    "es cómo la red decide hacia dónde corregir.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 4 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/apilar-capas/\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
}
