{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Reutilizar lo que otra red ya aprendió\n",
    "\n",
    "Congelar la capa oculta de una red entrenada y medir qué pasa. Salió lo contrario de lo que yo esperaba.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 19 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/transferencia/\n",
    "\n",
    "Este es el cuaderno de **soluciones**. Trae el código de cada ejercicio, la\n",
    "explicación de la trampa y la respuesta del quiz. Si vienes del cuaderno de\n",
    "práctica sin haberlo intentado, vuelve 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Este capítulo lo escribí esperando una cosa y salió otra. Te cuento las dos,\n",
    "porque la segunda enseña más 🔄\n",
    "\n",
    "La idea de partida es la que sostiene casi todo el deep learning que se usa\n",
    "hoy: **nadie entrena desde cero**. Se agarra una red que ya aprendió\n",
    "sobre millones de imágenes y se le enseña lo tuyo encima, con cien fotos en vez\n",
    "de un millón.\n",
    "\n",
    "Antes de medirlo, piénsalo tú: **¿qué es exactamente lo que una red\n",
    "aprende y se puede prestar?** No son las respuestas, porque las clases son\n",
    "otras. Es la capa del medio 🧠"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Qué es lo que se presta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 4 vimos que una capa oculta transforma el\n",
    "espacio hasta que el problema se pueda separar. Esa transformación es lo\n",
    "valioso: aprender a ver bordes, curvas y trazos cuesta muchos datos, y una vez\n",
    "aprendida sirve para reconocer cosas que la red nunca vio.\n",
    "\n",
    "Vamos a montarlo con los dígitos del capítulo 11, partidos\n",
    "en dos mitades: la red aprende con los dígitos del 0 al 4, y después le pedimos\n",
    "que reconozca del 5 al 9 con poquísimos ejemplos."
   ]
  },
  {
   "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",
    "from sklearn.neural_network import MLPClassifier\n",
    "\n",
    "d = load_digits()\n",
    "X = d.data / 16.0\n",
    "y = d.target\n",
    "X_viejas, y_viejas = X[y <= 4], y[y <= 4]        # las que tienen muchas fotos\n",
    "X_nuevas, y_nuevas = X[y >= 5], y[y >= 5]        # las que casi no tienen\n",
    "print('fotos de las clases viejas:', len(X_viejas))\n",
    "print('fotos de las clases nuevas:', len(X_nuevas))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esa partición imita la situación real: tienes un montón de datos de una cosa\n",
    "y cuatro fotos de la que te interesa 📸"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "base = MLPClassifier(hidden_layer_sizes=(64,), max_iter=600,\n",
    "                     random_state=0).fit(X_viejas, y_viejas)\n",
    "print('acierto de la base en sus propias clases:', round(base.score(X_viejas, y_viejas), 4))\n",
    "print('pesos de la capa oculta:', base.coefs_[0].shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La base aprendió sus cinco dígitos perfecto. Y lo que nos vamos a llevar\n",
    "prestado es esa matriz de 64 por 64, que es su capa del medio."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Prestarla son dos líneas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def rasgos(M):\n",
    "    # La capa oculta de la base, usada como extractor de caracteristicas.\n",
    "    return np.maximum(0, M @ base.coefs_[0] + base.intercepts_[0])\n",
    "\n",
    "print('64 pixeles se vuelven', rasgos(X_nuevas[:1]).shape[1], 'numeros aprendidos')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso es todo. Multiplicar por los pesos que la base aprendió y aplicar su\n",
    "activación, o sea la misma cuenta del capítulo 2 pero parando a\n",
    "mitad de camino 🔧\n",
    "\n",
    "El `np.maximum(0, ...)` es la ReLU del capítulo\n",
    "3: `MLPClassifier` la usa por defecto, así que hay\n",
    "que aplicar la misma o los números no significarían lo mismo.\n",
    "\n",
    "A esto se le llama **congelar** la base: sus pesos no se vuelven\n",
    "a tocar, solo se usan. Encima ponemos un modelo barato, que con pocos datos es\n",
    "justo lo que hay que poner."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora la medición"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f'{\"por clase\":>10} {\"pixeles\":>10} {\"transferencia\":>15}')\n",
    "for pocas in [2, 3, 5, 10]:\n",
    "    pix, tra = [], []\n",
    "    for semilla in range(5):\n",
    "        i_tr, i_te = train_test_split(np.arange(len(X_nuevas)), train_size=pocas * 5,\n",
    "                                      random_state=semilla, stratify=y_nuevas)\n",
    "        Xt, yt = X_nuevas[i_tr], y_nuevas[i_tr]\n",
    "        Xe, ye = X_nuevas[i_te], y_nuevas[i_te]\n",
    "        pix.append(accuracy_score(ye, LogisticRegression(max_iter=2000)\n",
    "                                  .fit(Xt, yt).predict(Xe)))\n",
    "        tra.append(accuracy_score(ye, LogisticRegression(max_iter=2000)\n",
    "                                  .fit(rasgos(Xt), yt).predict(rasgos(Xe))))\n",
    "    print(f'{pocas:10} {np.mean(pix):10.4f} {np.mean(tra):15.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pierde. En los cuatro tamaños 🫠\n",
    "\n",
    "Con dos fotos por clase, los píxeles crudos sacan 0,8032 y la transferencia\n",
    "0,6781. Doce puntos de diferencia, y encima la brecha no se cierra al darle más\n",
    "datos.\n",
    "\n",
    "Y no es de una partición con suerte: cada fila es el promedio de cinco\n",
    "particiones distintas, que es la lección del capítulo 4\n",
    "aplicada aquí."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La explicación fácil, y por qué es falsa"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La primera explicación que se le ocurre a cualquiera, y a mí la primera, es\n",
    "esta: *la base se entrenó para distinguir del 0 al 4, así que tiró todo lo\n",
    "que no le servía para eso, incluido justo lo que separa al 5 del 8*.\n",
    "\n",
    "Suena impecable. Vamos a comprobarla, que para eso están los datos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.feature_selection import mutual_info_classif\n",
    "\n",
    "i_pix = mutual_info_classif(X_nuevas, y_nuevas, random_state=0).sum()\n",
    "i_ras = mutual_info_classif(rasgos(X_nuevas), y_nuevas, random_state=0).sum()\n",
    "print(f'informacion sobre la clase nueva en los 64 pixeles : {i_pix:.4f}')\n",
    "print(f'informacion en los 64 rasgos de la base            : {i_ras:.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es falsa 😮\n",
    "\n",
    "Los rasgos de la base tienen **más** información sobre las clases\n",
    "nuevas que los píxeles crudos, no menos. 11,83 contra 11,16.\n",
    "\n",
    "O sea que el problema no es que se perdiera nada. Es que la información quedó\n",
    "**ordenada para la tarea vieja**: repartida entre las 64 neuronas de\n",
    "una forma que le sirve a una recta para separar del 0 al 4, y que a una recta\n",
    "para separar del 5 al 9 no le sirve igual.\n",
    "\n",
    "Y esto explica de paso por qué en la práctica **casi nadie congela la\n",
    "base entera**: se ajusta, o sea se sigue entrenando desde esos pesos en\n",
    "vez de arrancar de cero. Eso permite reordenar la representación en lugar de\n",
    "tener que apañárselas con el orden viejo 🔧"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La transferencia no falla por perder información. Falla cuando la información queda ordenada para otra pregunta."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El error que sale al cambiar la entrada"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y antes de cerrar, el error que te va a salir el primer día. Imagina que\n",
    "tienes fotos de productos del almacén y decides recortarlas a otro tamaño para\n",
    "que pesen menos:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    recortadas = X_nuevas[:, :30]      # nos quedamos con 30 pixeles en vez de 64\n",
    "    rasgos(recortadas)\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 64 is different from 30)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*size 64 is different from 30*. La base aprendió una matriz de 64 por\n",
    "64 y no sabe multiplicar otra cosa.\n",
    "\n",
    "Esto sí revienta, y por eso es el bueno de los dos errores. El peligroso es el\n",
    "de la trampa de más abajo, que **no** revienta 🚩"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y en un negocio de verdad, ¿dónde entra esto?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Te lo aterrizo con la distribuidora del libro, que es donde a mí me llega esta\n",
    "pregunta 💼"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Lo que te piden | Qué haría yo |\n",
    "|---|---|\n",
    "| Clasificar fotos de productos del stock por categoría | Modelo preentrenado congelado, y encima una regresión. Es el caso de manual y ahí sí gana |\n",
    "| Sacar el monto y el número de pedido de facturas escaneadas | Empieza por un lector de texto ya hecho. Entrenar uno tuyo es meses |\n",
    "| Predecir qué cliente va a comprar, con tu tabla de ventas | Nada de esto. Vuelve al capítulo 1 y usa una logística |\n",
    "| Clasificar los reclamos que llegan por WhatsApp | Un modelo de lenguaje ya entrenado. Aquí prestar es casi obligatorio |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en la tercera fila, que es la que más veces me toca decir en voz alta:\n",
    "si tu dato es una tabla de ventas con ciudad, canal y segmento, la transferencia\n",
    "no aplica porque no hay nada que prestar 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Entonces, ¿cuándo sí sirve?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La condición es una y se deduce de todo lo anterior: **la transferencia\n",
    "gana cuando aprender los rasgos es la parte cara**.\n",
    "\n",
    "Aquí no lo es. Una imagen de 8 por 8 son 64 números y ya son una\n",
    "representación decente: cada píxel dice algo directo. No hay gran cosa que\n",
    "aprender antes de clasificar, así que prestada no aporta y encima estorba."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Tu dato | ¿Sirve prestar? |\n",
    "|---|---|\n",
    "| Imágenes de 8x8 como estas | No. Los píxeles crudos ya sirven, medido arriba |\n",
    "| Fotos de verdad, 224x224 a color | Sí, y muchísimo. Son 150.000 números y aprender a verlos cuesta millones de ejemplos |\n",
    "| Texto | Sí. Aprender qué significa una palabra es el trabajo entero, y es lo que hace el capítulo 13 |\n",
    "| Tu tabla de ventas | No. Vuelve al capítulo 1 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y una cosa que hay que decir con todas sus letras: **este libro no puede\n",
    "enseñarte la parte donde la transferencia gana**. Para eso hace falta un\n",
    "modelo preentrenado sobre millones de fotos, que pesa cientos de megas y hay que\n",
    "descargarlo. Lo que sí puedes hacer tú, cuando tengas internet y ganas, es\n",
    "buscar `timm` o los modelos de\n",
    "[Hugging Face](https://huggingface.co/models) y repetir exactamente este\n",
    "montaje con uno de ellos 🌐\n",
    "\n",
    "Lo que sí te llevas de aquí es lo que no cambia con el modelo: qué es lo que\n",
    "se presta, cómo se enchufa, y cómo se comprueba si sirvió."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## El caso donde esto se usa todos los días: el sentimiento de un texto"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De la tabla de arriba, la fila del texto es la que más veces vas a ver\n",
    "aplicada, y tiene nombre propio: **análisis de sentimiento**. Saber\n",
    "si un reclamo llega enojado, si una reseña es buena o mala, si el comentario del\n",
    "cliente pide una llamada urgente.\n",
    "\n",
    "Y se resuelve exactamente con el montaje de este capítulo, cambiando las fotos\n",
    "por frases. La base preentrenada, en vez de una red que vio millones de imágenes,\n",
    "es una que leyó millones de textos: la más nombrada se llama\n",
    "**BERT**. Encima se le pone un clasificador chiquito con tus\n",
    "etiquetas, que suelen ser unas cientos, y listo.\n",
    "\n",
    "La diferencia con lo que acabamos de medir es la que decide todo, y ya está\n",
    "dicha arriba: **acá aprender los rasgos sí es la parte cara**. Una\n",
    "imagen de 8 por 8 son 64 números que ya significan algo por sí solos. Una frase\n",
    "es una fila de palabras que no significan nada hasta que alguien aprendió con\n",
    "quién anda cada una, que es el trabajo entero del capítulo\n",
    "13 y el que no puedes pagar con doscientos reclamos\n",
    "tuyos.\n",
    "\n",
    "Por eso en imágenes de 8 por 8 la transferencia estorbó y en texto es casi\n",
    "obligatoria. No son dos resultados contradictorios: es la misma regla aplicada a\n",
    "dos datos distintos 🔄\n",
    "\n",
    "Dos avisos si te toca evaluarlo en el trabajo. El primero es que el\n",
    "sentimiento es más difícil de lo que suena, porque la ironía y la queja educada\n",
    "se escriben con palabras amables, y eso no lo arregla un modelo más grande.\n",
    "\n",
    "El segundo es de la casa: si la base leyó sobre todo inglés y tus reclamos\n",
    "llegan en castellano peruano con jerga y sin tildes, la parte prestada sirve\n",
    "menos de lo que promete el folleto. Se comprueba igual que todo lo de este\n",
    "capítulo, midiendo contra la versión sin prestar nada 📋"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Un equipo reutiliza una red preentrenada para clasificar fotos de productos. Preparan las imágenes así y el modelo sale con un acierto malísimo que nadie sabe explicar.\n",
    "\n",
    "```\n",
    "# la base se entreno con imagenes normalizadas a media 0 y desviacion 1\n",
    "base = carga_modelo_preentrenado()\n",
    "\n",
    "fotos = carga_fotos()          # valores de 0 a 255\n",
    "rasgos = base.extrae(fotos)    # se le pasan tal cual\n",
    "\n",
    "modelo = LogisticRegression().fit(rasgos, etiquetas)\n",
    "```\n",
    "\n",
    "**Qué está mal**\n",
    "\n",
    "La base espera las imágenes con el **mismo preprocesamiento** con el que se entrenó, y aquí le están llegando valores de 0 a 255 cuando ella aprendió con valores centrados en cero. Sus neuronas se saturan enteras, que es lo del capítulo de las activaciones, y los rasgos que devuelve son casi todos el mismo número. No da ningún error: da un modelo mediocre. Es el error número uno de la transferencia y por eso todo modelo publicado viene con su función de preprocesamiento al lado, que hay que usar. En el montaje de este capítulo se ve en una línea: la base se entrenó con `X = d.data / 16.0`, así que `rasgos()` tiene que recibir datos divididos entre 16 también, y si le pasas `d.data` crudo el acierto se cae sin avisar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Seis. El 3 es el que contesta la pregunta que quedó abierta 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Rómpelo con el preprocesamiento\n",
    "\n",
    "Pásale a `rasgos()` los píxeles sin dividir entre\n",
    "16 y mira cuánto cae."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "crudos = d.data[y >= 5].astype(float)      # de 0 a 255, sin dividir\n",
    "i_tr, i_te = train_test_split(np.arange(len(X_nuevas)), train_size=50,\n",
    "                              random_state=0, stratify=y_nuevas)\n",
    "bien = LogisticRegression(max_iter=2000).fit(rasgos(X_nuevas[i_tr]), y_nuevas[i_tr])\n",
    "mal = LogisticRegression(max_iter=2000).fit(rasgos(crudos[i_tr]), y_nuevas[i_tr])\n",
    "print('con el preprocesamiento correcto:',\n",
    "      round(bien.score(rasgos(X_nuevas[i_te]), y_nuevas[i_te]), 4))\n",
    "print('con los pixeles crudos          :',\n",
    "      round(mal.score(rasgos(crudos[i_te]), y_nuevas[i_te]), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es la trampa de arriba, ejecutada. Y fíjate en que no revienta: devuelve un\n",
    "número peor y ya."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Una base más chica\n",
    "\n",
    "Repite todo con `hidden_layer_sizes=(16,)` en la\n",
    "base y compara."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con 16 neuronas la capa oculta comprime mucho más, así que la representación\n",
    "prestada es más pobre y la caída es mayor. Es el mismo compromiso del pooling\n",
    "del capítulo 11: resumir da robustez y cuesta información."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Ajustar en vez de congelar\n",
    "\n",
    "La pregunta que el capítulo deja abierta. En vez de congelar\n",
    "la base, arranca de sus pesos y sigue entrenando."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se escribe con la red a mano del capítulo 4, pasándole\n",
    "`W1` y `b1` de la base en vez de inicializarlos al azar, y\n",
    "dejando que el entrenamiento los mueva. Tarda un par de minutos.\n",
    "\n",
    "Esto es lo que me salió a mí, promediando cinco particiones:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    " por clase   desde cero   desde la base\n",
    "         2       0.7679          0.7580\n",
    "         3       0.8277          0.8070\n",
    "         5       0.9084          0.8916\n",
    "        10       0.9291          0.9248\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "También pierde, aunque por mucho menos que congelando. Tres montajes\n",
    "distintos y los tres dicen lo mismo: sobre estos dígitos no hay nada que\n",
    "prestar. Cuando un resultado aguanta tres formas de medirlo, ya no es un\n",
    "accidente del montaje 🧪"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Cambia qué mitad es la vieja\n",
    "\n",
    "Entrena la base con el 5 al 9 y transfiere al 0 al 4."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si la conclusión fuera un accidente de qué dígitos tocaron, aquí se vería.\n",
    "Comprobar que un resultado no depende de una elección arbitraria es de las cosas\n",
    "más baratas que puedes hacer y de las que más credibilidad dan."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. ¿Y si la base ve todas las clases?\n",
    "\n",
    "Entrena la base con los diez dígitos y después transfiere al\n",
    "5 al 9."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "todos = MLPClassifier(hidden_layer_sizes=(64,), max_iter=600,\n",
    "                      random_state=0).fit(X, y)\n",
    "def rasgos_todos(M):\n",
    "    return np.maximum(0, M @ todos.coefs_[0] + todos.intercepts_[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahora sí debería ir mejor, y hay que entender por qué: la base ya vio esas\n",
    "clases, así que su representación está ordenada también para ellas. Ojo con lo\n",
    "que esto significa de verdad, que es la lección escondida del ejercicio: ya no\n",
    "es transferencia, es entrenar con los datos de prueba. En un proyecto real eso\n",
    "sería fuga de información, que en el libro de machine learning tiene su capítulo\n",
    "entero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Cuántas fotos necesitas para empatar\n",
    "\n",
    "Sigue subiendo el número de fotos por clase y busca dónde\n",
    "las dos curvas se cruzan."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for pocas in [10, 20, 40, 80]:\n",
    "    i_tr, i_te = train_test_split(np.arange(len(X_nuevas)), train_size=pocas * 5,\n",
    "                                  random_state=0, stratify=y_nuevas)\n",
    "    p = LogisticRegression(max_iter=2000).fit(X_nuevas[i_tr], y_nuevas[i_tr])\n",
    "    t = LogisticRegression(max_iter=2000).fit(rasgos(X_nuevas[i_tr]), y_nuevas[i_tr])\n",
    "    print(f'{pocas:3}: pixeles {p.score(X_nuevas[i_te], y_nuevas[i_te]):.4f}   '\n",
    "          f'transferencia {t.score(rasgos(X_nuevas[i_te]), y_nuevas[i_te]):.4f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La pregunta que importa detrás del ejercicio: si con muchas fotos empatan y\n",
    "con pocas pierde, ¿en qué situación de tu trabajo la ibas a usar?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Tienes 300 fotos de piezas de tu almacén y quieres clasificarlas. ¿Por dónde empiezas?\n",
    "\n",
    "a) Por un modelo preentrenado de imágenes, congelado, y encima una regresión\n",
    "\n",
    "b) Por entrenar una red desde cero con las 300 fotos\n",
    "\n",
    "c) Por los píxeles crudos y una regresión, como en este capítulo\n",
    "\n",
    "d) Por etiquetar 10.000 fotos más\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Con 300 fotos de verdad, a color y grandes, una red desde cero no tiene de dónde aprender a ver.\n",
    "\n",
    "*c)* Eso funcionó con imágenes de 8 por 8. Una foto de verdad son cientos de miles de píxeles y ahí sí hace falta que alguien ya haya aprendido a verlos.\n",
    "\n",
    "*d)* Puede que haga falta y es lo más caro de todo. Antes de pagarlo, mide qué sacas con lo que ya tienes.\n",
    "\n",
    "Y la primera medición del proyecto es siempre la misma: qué saca lo barato, para tener contra qué comparar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🔄 Prestar una red es usar su capa oculta como entrada de otro modelo, y son\n",
    "dos líneas.\n",
    "\n",
    "- 📉 Sobre estos dígitos pierde en los cuatro tamaños, y con dos fotos por\n",
    "clase pierde por doce puntos.\n",
    "\n",
    "- 🔬 Y no por perder información: los rasgos prestados tienen más (11,83\n",
    "contra 11,16). Queda ordenada para la pregunta vieja.\n",
    "\n",
    "- 🎯 Gana cuando aprender a ver es la parte cara. Con 64 píxeles no lo es.\n",
    "\n",
    "- ⚠️ Y el error que no avisa es olvidarse del preprocesamiento con el que se\n",
    "entrenó la base.\n",
    "\n",
    "Si quieres el vocabulario suelto de todo esto, está definido en dos líneas por\n",
    "término en el [glosario de IA](https://missyera.com/glosario-ia/) 📖\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 19 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/transferencia/\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
}
