{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# La convolución\n",
    "\n",
    "Un filtro de nueve números que recorre la imagen. Escrito a mano y medido contra los píxeles sueltos, moviendo los dígitos.\n",
    "\n",
    "Cuaderno de práctica del capítulo 11 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/convolucion/\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: \"ZGlhZ29uYWw6IHZhIGRlIC0yLjY5IGEgMi4xMgpwcm9tZWRpbzogdmEgZGUgMC4yMSBhIDAuNTcKCmVsIHByb21lZGlvIHNvYnJlIGVsIHByaW1lciBkw61naXRvLCBxdWUgZXMgZGVzZW5mb2NhcmxvOgpbWzAuMjUgMC40NiAwLjU3IDAuNTMgMC40MSAwLjI4XQogWzAuMzMgMC40NCAwLjQ3IDAuNDIgMC40NSAwLjM4XQogWzAuMzMgMC4zNCAwLjI2IDAuMjEgMC4zNiAwLjM2XQogWzAuMzEgMC4zMSAwLjIyIDAuMjEgMC4zNyAwLjM2XQogWzAuMzEgMC4zNCAwLjM0IDAuMzQgMC40MSAwLjMzXQogWzAuMjYgMC4zOCAwLjQ5IDAuNDQgMC4zNiAwLjIyXV0=\",\n",
    "    2: \"ZmlsdHJvIDN4MyBzb2JyZSA4eDggZGVqYSAoNiwgNikKZmlsdHJvIDV4NSBzb2JyZSA4eDggZGVqYSAoNCwgNCkKZmlsdHJvIDd4NyBzb2JyZSA4eDggZGVqYSAoMiwgMik=\",\n",
    "    3: \"c2luIGJvcmRlOiAoNiwgNikKY29uIGJvcmRlOiAoOCwgOCk=\",\n",
    "    4: \"ZGVzcGxhemFtaWVudG8gICAgICBub3JtYWwgIGNvbiBhdW1lbnRvCigwLDApICAgICAgICAgICAgICAgMC45NjIyICAgICAgIDAuOTA2NwooMSwwKSAgICAgICAgICAgICAgIDAuNDA4OSAgICAgICAwLjg4MjIKKDEsMSkgICAgICAgICAgICAgICAwLjExNTYgICAgICAgMC40NjAw\",\n",
    "    5: \"ZMOtZ2l0byAwOiB2ZXJ0aWNhbCBtw6F4IDIuNTAgIGhvcml6b250YWwgbcOheCAyLjAwICBtw6FzIHZlcnRpY2FsCmTDrWdpdG8gMTogdmVydGljYWwgbcOheCAzLjAwICBob3Jpem9udGFsIG3DoXggMS42OSAgbcOhcyB2ZXJ0aWNhbApkw61naXRvIDI6IHZlcnRpY2FsIG3DoXggMi42MiAgaG9yaXpvbnRhbCBtw6F4IDIuMzggIG3DoXMgdmVydGljYWw=\",\n",
    "    6: \"bWlsZXMgZGUgc29sZXMgcG9yIG1lczogWzEyOC44IDEyOS41IDEzOS45IDExNS41IDE1NS41IDEzOC4xIDE0MS42IDEyMi40IDE0Mi40IDEzOS42IDEzMi40IDEyNS45CiAxMjkuOCAxMjEuNSAxNjQuMyAxMjcuNCAxNTMuNCAxMDMuNV0Kc3Vhdml6YWRhICAgICAgICAgICAgIDogWzEzMi43IDEyOC4zIDEzNi45IDEzNi4zIDE0NS4gIDEzNC4gIDEzNS40IDEzNC44IDEzOC4xIDEzMi42IDEyOS40IDEyNS43CiAxMzguNSAxMzcuNyAxNDguMyAxMjguMV0KY2FtYmlvcyAgICAgICAgICAgICAgIDogWyAxMS4xIC0xNC4gICAxNS41ICAyMi42IC0xMy44IC0xNS43ICAgMC43ICAxNy4zIC0xMC4gIC0xMy43ICAtMi41ICAtNC41CiAgMzQuNSAgIDUuOSAtMTAuOSAtMjMuOV0=\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ocho capítulos diciendo que la red pierde. Aquí cambiamos de tipo de dato y\n",
    "por fin se entiende para qué existe todo esto 🖼️\n",
    "\n",
    "Una pregunta para arrancar: **¿qué tienen los píxeles de una foto que no tienen las columnas de un Excel?** Ahí está la respuesta entera de por qué existe el deep learning 🧩\n",
    "\n",
    "Y vamos a usar imágenes de verdad, que vienen dentro de scikit-learn: 1.797\n",
    "dígitos escritos a mano en 8 por 8 píxeles."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Una imagen es una matriz de números"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from sklearn.datasets import load_digits\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.metrics import accuracy_score\n",
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "digitos = load_digits()\n",
    "imagenes = digitos.images / 16.0          # de 0 a 1\n",
    "etiquetas = digitos.target\n",
    "\n",
    "print('tenemos', imagenes.shape[0], 'imágenes de', imagenes.shape[1], 'por',\n",
    "      imagenes.shape[2])\n",
    "print('la primera es un', etiquetas[0], 'y por dentro es esto:')\n",
    "print((imagenes[0] * 16).astype(int))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí lo tienes: un cero dibujado con números. Se ve el agujero del medio en la\n",
    "franja de ceros de las filas 3, 4 y 5 👀\n",
    "\n",
    "Y aquí está la propiedad que lo cambia todo, la que anuncié en el capítulo 1:\n",
    "en esta matriz **la posición significa algo**. El píxel de arriba a\n",
    "la izquierda y el de abajo a la derecha no son dos columnas intercambiables como\n",
    "`monto` y `ciudad`: son vecinos o no lo son, y eso es\n",
    "información."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La convolución, escrita"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "S(i,j)=∑u∑vI(i+u,j+v)K(u,v)\n",
    "\n",
    "deslizas un filtro chiquito por toda la imagen y en cada posición sumas el producto punto a punto, que es cómo se detecta un borde sin decirle dónde mirar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un filtro es una matriz chiquita. Se apoya en una esquina de la imagen, se\n",
    "multiplican los números que coinciden, se suman, y ese número va a la salida.\n",
    "Luego se corre una posición y se repite."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def convolucion(imagen, filtro):\n",
    "    alto, ancho = filtro.shape\n",
    "    salida = np.zeros((imagen.shape[0] - alto + 1, imagen.shape[1] - ancho + 1))\n",
    "    for i in range(salida.shape[0]):\n",
    "        for j in range(salida.shape[1]):\n",
    "            trozo = imagen[i:i + alto, j:j + ancho]\n",
    "            salida[i, j] = (trozo * filtro).sum()\n",
    "    return salida\n",
    "\n",
    "VERTICAL = np.array([[-1., 0, 1],\n",
    "                     [-1., 0, 1],\n",
    "                     [-1., 0, 1]])\n",
    "HORIZONTAL = VERTICAL.T\n",
    "\n",
    "print('el filtro vertical sobre el primer dígito:')\n",
    "print(np.round(convolucion(imagenes[0], VERTICAL), 1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira lo que hizo ese filtro sin que nadie le explicara nada 🤯\n",
    "\n",
    "La primera columna sale toda positiva (2,1 2,5 2,2 1,9 2,1 1,9) y la última\n",
    "toda negativa. Positivo significa \"aquí la imagen se aclara de izquierda a\n",
    "derecha\" y negativo lo contrario.\n",
    "\n",
    "O sea que **encontró los dos bordes verticales del cero**: el de\n",
    "la izquierda, donde el trazo empieza, y el de la derecha, donde termina. Con\n",
    "nueve números y una resta.\n",
    "\n",
    "Ese filtro se llama detector de bordes y lleva inventado desde antes que las\n",
    "redes neuronales. Lo que hace una red convolucional es **aprender los\n",
    "filtros en vez de que se los escribas tú**, con la retropropagación del\n",
    "capítulo 6 exactamente igual que cualquier otro peso."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Por qué un filtro y no una capa normal"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Podrías conectar cada píxel a cada neurona, como venimos haciendo. La cuenta\n",
    "explica por qué no se hace:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for lado in [8, 28, 224]:\n",
    "    pixeles = lado * lado\n",
    "    densa = pixeles * 100                    # 100 neuronas conectadas a todo\n",
    "    convo = 9 * 100                          # 100 filtros de 3 por 3\n",
    "    print(f'imagen de {lado}x{lado}: capa densa {densa:9,} pesos   '\n",
    "          f'convolucional {convo:6,} pesos')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para una foto de 224 por 224, que es el tamaño típico, una capa densa\n",
    "necesitaría cinco millones de pesos y la convolucional novecientos. **Cinco\n",
    "mil veces menos** 😳\n",
    "\n",
    "Y no es solo ahorro. Los mismos nueve números se usan en las 50.000 posiciones\n",
    "de la imagen, así que **lo que el filtro aprende en una esquina sirve en\n",
    "todas**. Una capa densa tendría que aprender por separado a reconocer un\n",
    "borde arriba a la izquierda y el mismo borde abajo a la derecha.\n",
    "\n",
    "Eso se llama compartir parámetros y es la idea central del capítulo 🔑"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ¿Y funciona mejor?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos a construir rasgos con los dos filtros y compararlos contra los píxeles\n",
    "crudos. Después de convolucionar, se resume cada zona con su valor máximo, que se\n",
    "llama **pooling**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def agrupa(mapa):\n",
    "    return np.array([mapa[i:i + 2, j:j + 2].max()\n",
    "                     for i in range(0, 6, 2) for j in range(0, 6, 2)])\n",
    "\n",
    "def rasgos(imgs):\n",
    "    salida = []\n",
    "    for im in imgs:\n",
    "        v = np.abs(convolucion(im, VERTICAL))\n",
    "        h = np.abs(convolucion(im, HORIZONTAL))\n",
    "        salida.append(np.concatenate([agrupa(v), agrupa(h)]))\n",
    "    return np.array(salida)\n",
    "\n",
    "indices = np.arange(len(imagenes))\n",
    "i_tr, i_te = train_test_split(indices, test_size=0.25, random_state=42,\n",
    "                              stratify=etiquetas)\n",
    "I_tr, I_te = imagenes[i_tr], imagenes[i_te]\n",
    "y_tr, y_te = etiquetas[i_tr], etiquetas[i_te]\n",
    "\n",
    "pixeles_tr = I_tr.reshape(len(I_tr), -1)\n",
    "conv_tr = rasgos(I_tr)\n",
    "print('columnas con píxeles     :', pixeles_tr.shape[1])\n",
    "print('columnas con convolución :', conv_tr.shape[1])\n",
    "\n",
    "m_pix = LogisticRegression(max_iter=2000).fit(pixeles_tr, y_tr)\n",
    "m_conv = LogisticRegression(max_iter=2000).fit(conv_tr, y_tr)\n",
    "print('acierto con píxeles    :', round(accuracy_score(\n",
    "    y_te, m_pix.predict(I_te.reshape(len(I_te), -1))), 4))\n",
    "print('acierto con convolución:', round(accuracy_score(\n",
    "    y_te, m_conv.predict(rasgos(I_te))), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pues no: 0,9622 con los píxeles y 0,9156 con la convolución 🙃\n",
    "\n",
    "Y tiene una explicación aburrida y correcta: 64 píxeles llevan más información\n",
    "que 18 rasgos. Al agrupar tiré datos, y en imágenes tan chiquitas y tan\n",
    "centraditas eso no compensa.\n",
    "\n",
    "Así que hasta aquí la convolución tampoco gana. Pero espera al siguiente\n",
    "apartado, que es donde se ve para qué existe de verdad."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La prueba de verdad: mover la imagen"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las fotos reales no vienen centradas. Vamos a desplazar los dígitos de prueba\n",
    "uno o dos píxeles y ver quién aguanta:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def desplaza(imgs, dx, dy):\n",
    "    return np.array([np.roll(np.roll(im, dy, axis=0), dx, axis=1) for im in imgs])\n",
    "\n",
    "print(f'{\"desplazamiento\":16} {\"píxeles\":>9} {\"convolución\":>12}')\n",
    "for dx, dy in [(0, 0), (1, 0), (0, 1), (1, 1), (2, 0)]:\n",
    "    movidas = desplaza(I_te, dx, dy)\n",
    "    a_pix = accuracy_score(y_te, m_pix.predict(movidas.reshape(len(movidas), -1)))\n",
    "    a_conv = accuracy_score(y_te, m_conv.predict(rasgos(movidas)))\n",
    "    print(f'({dx},{dy}){\"\":11} {a_pix:9.4f} {a_conv:12.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está 🎯\n",
    "\n",
    "Con un píxel de desplazamiento a la derecha, el modelo de píxeles se desploma\n",
    "de 0,9622 a **0,4089**, o sea que pierde más de la mitad de sus\n",
    "aciertos. La convolución baja a 0,5689, que también es una caída pero\n",
    "**dieciséis puntos por encima**.\n",
    "\n",
    "La razón es directa: para el modelo de píxeles, la columna \"píxel número 27\"\n",
    "pasó a contener lo que antes estaba en la 26. Todo lo que aprendió sobre esa\n",
    "columna dejó de valer.\n",
    "\n",
    "Para la convolución, el borde vertical *sigue estando ahí*, un poquito\n",
    "más allá, y como después agrupamos por zonas, muchas veces cae en la misma\n",
    "casilla. El filtro no pregunta dónde: pregunta si 🔍\n",
    "\n",
    "Y ahora la parte honesta: las dos se desploman. Con dos píxeles, 0,0756 y\n",
    "0,1067, o sea que las dos son inservibles.\n",
    "\n",
    "Dos filtros escritos a mano y un pooling de 2 por 2 son un juguete. Una red\n",
    "convolucional de verdad tiene decenas de filtros aprendidos por capa, varias\n",
    "capas apiladas y encima aumento de datos (el freno 3 del capítulo 8, que aquí\n",
    "sería entrenar con las imágenes ya movidas). Con eso sí se consigue aguantar de\n",
    "verdad."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El compromiso del pooling"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si agrupar da resistencia al movimiento, ¿por qué no agrupar más?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def agrupa3(mapa):\n",
    "    return np.array([mapa[i:i + 3, j:j + 3].max()\n",
    "                     for i in range(0, 6, 3) for j in range(0, 6, 3)])\n",
    "\n",
    "def rasgos3(imgs):\n",
    "    return np.array([np.concatenate([agrupa3(np.abs(convolucion(im, VERTICAL))),\n",
    "                                     agrupa3(np.abs(convolucion(im, HORIZONTAL)))])\n",
    "                     for im in imgs])\n",
    "\n",
    "m_grueso = LogisticRegression(max_iter=2000).fit(rasgos3(I_tr), y_tr)\n",
    "print(f'{\"desplazamiento\":16} {\"pool 2x2\":>9} {\"pool 3x3\":>9}')\n",
    "for dx, dy in [(0, 0), (1, 0), (1, 1), (2, 0)]:\n",
    "    movidas = desplaza(I_te, dx, dy)\n",
    "    print(f'({dx},{dy}){\"\":11} '\n",
    "          f'{accuracy_score(y_te, m_conv.predict(rasgos(movidas))):9.4f} '\n",
    "          f'{accuracy_score(y_te, m_grueso.predict(rasgos3(movidas))):9.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Porque se paga. Con pooling de 3 por 3, en las imágenes quietas el acierto cae\n",
    "de 0,9156 a 0,7067, y a cambio con dos píxeles de desplazamiento sube de 0,1067 a\n",
    "**0,2844**, casi el triple.\n",
    "\n",
    "Ese es el compromiso de todo el asunto: **agrupar da resistencia y quita\n",
    "precisión**. Cuánto agrupar es la decisión de diseño más repetida en las\n",
    "redes de imágenes, y no tiene una respuesta buena, tiene la respuesta que\n",
    "corresponda a tus datos 🎚️"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Inventar tus propios filtros\n",
    "\n",
    "Prueba un filtro que detecte diagonales y otro que\n",
    "promedie."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Cuánto encoge la imagen\n",
    "\n",
    "Mira qué tamaño sale con filtros de distinto tamaño."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. El padding, en tres líneas\n",
    "\n",
    "Escribe el relleno de ceros y comprueba que conserva el\n",
    "tamaño."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Entrenar con las imágenes movidas\n",
    "\n",
    "Aplica el aumento de datos del capítulo 8: entrena con\n",
    "copias desplazadas."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Dónde se activa el filtro\n",
    "\n",
    "Busca en qué zona del dígito responde más fuerte cada\n",
    "filtro."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Convolución sobre una fila de datos, que también existe\n",
    "\n",
    "Aplica un filtro de 1 dimensión a las ventas mensuales de\n",
    "la distribuidora, que son las del CSV de los otros capítulos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. El filtro más grande que la imagen, que no avisa\n",
    "\n",
    "Pásale a una imagen de 8 por 8 un filtro de 9 por 9."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Sobre dígitos limpios los píxeles crudos ganan a la convolución (0,9622 contra 0,9156), y al mover la imagen un píxel se invierte. ¿Qué demuestra?\n",
    "\n",
    "a) Que la convolución no sirve para ver mejor, sirve para no depender de la posición\n",
    "\n",
    "b) Que la convolución está mal implementada\n",
    "\n",
    "c) Que los dígitos son demasiado fáciles\n",
    "\n",
    "d) Que hacen falta más filtros"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La normalización que se adelanta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Normalizas las imágenes antes de partir, que es lo que se hace siempre con píxeles, y luego repartes en entrenamiento y prueba.\n",
    "\n",
    "```\n",
    "X = (X - X.mean()) / X.std()\n",
    "\n",
    "X_tr, X_te, y_tr, y_te = train_test_split(\n",
    "    X, y, test_size=0.25)\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": [
    "- 🖼️ En una imagen la posición significa algo, y por eso no se trata como\n",
    "columnas sueltas.\n",
    "\n",
    "- 🔍 Un filtro de nueve números encuentra los bordes verticales del dígito sin\n",
    "que nadie le explique qué es un borde.\n",
    "\n",
    "- 🔑 Comparte los mismos nueve números en toda la imagen: para 224 por 224 son\n",
    "900 pesos contra 5 millones de una capa densa.\n",
    "\n",
    "- 🙃 En dígitos limpios y centrados los píxeles crudos ganan (0,9622 contra\n",
    "0,9156), porque agrupar tira información.\n",
    "\n",
    "- 🎯 Con un píxel de desplazamiento los píxeles caen a 0,4089 y la convolución\n",
    "aguanta en 0,5689. Ahí está para qué existe.\n",
    "\n",
    "- 😅 Y las dos se desploman con dos píxeles. Dos filtros a mano son un juguete;\n",
    "las redes de verdad aprenden decenas por capa.\n",
    "\n",
    "- 🎚️ Agrupar más da resistencia y quita precisión: pool 3x3 baja el acierto\n",
    "limpio a 0,7067 y triplica el aguante al desplazamiento.\n",
    "\n",
    "- 💪 Y el aumento de datos resuelve aquí más que la convolución: de 0,4089 a\n",
    "0,8822, a cambio de bajar de 0,9622 a 0,9067 en las imágenes quietas.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La convolución no es una capa más lista. Es una capa que sabe algo del dato antes de verlo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y si quieres el vocabulario de todo esto suelto y en dos líneas por término, lo tengo en el [glosario de IA](https://missyera.com/glosario-ia/) 📖\n",
    "\n",
    "En el capítulo 13 pasamos al texto, que tiene otra forma de estructura y otra\n",
    "manera de convertirse en números.\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 11 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/convolucion/\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
}
