{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Dónde está y qué forma tiene, no solo qué es\n",
    "\n",
    "Detectar es decir dónde. Segmentar es decir qué píxeles. Las dos construidas a mano sobre el clasificador que ya tienes, con IoU y supresión de no máximos.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 12 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/detectar-y-segmentar/\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": [
    "Hasta acá, en el capítulo 11, la red mira una imagen y dice\n",
    "qué es. Una imagen, un número.\n",
    "\n",
    "En un almacén nadie te trae la foto de una caja centrada. Te trae la foto de\n",
    "un pasillo y quiere saber **cuántas cajas hay y dónde**. Eso es\n",
    "otra tarea 📦"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Las tres tareas, en formas"
   ]
  },
  {
   "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",
    "\n",
    "d = load_digits()\n",
    "img = d.images[0]\n",
    "\n",
    "mascara = (img > 5).astype(int)\n",
    "print('clasificar -> 1 numero por imagen')\n",
    "print('detectar   -> por objeto: fila, columna, alto, ancho y clase')\n",
    "print('segmentar  -> una etiqueta por pixel:', mascara.size, 'etiquetas')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De un número a 64. Y en una foto de un celular, de un número a doce millones.\n",
    "Por eso segmentar cuesta tanto más que clasificar 🐣"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Segmentar, en su versión más simple"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(mascara)\n",
    "print('pixeles de tinta:', int(mascara.sum()), 'de', mascara.size)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está el cero, dibujado con unos. Eso es una **máscara**, y\n",
    "segmentar es producirla.\n",
    "\n",
    "Lo hice con un umbral, que es lo que se hacía antes de las redes y sigue\n",
    "funcionando cuando el fondo es limpio. El problema es que el umbral no se\n",
    "sostiene:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for u in (2, 5, 8, 12):\n",
    "    print('  umbral', u, '-> pixeles de tinta', int((img > u).sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De 31 a 7 según dónde lo pongas, y ese número es el que después alguien\n",
    "reporta como \"área del defecto\". Cambia la iluminación de la nave y cambia tu\n",
    "métrica.\n",
    "\n",
    "Una red de segmentación aprende ese umbral sola y por zonas, que es lo único\n",
    "que hace distinto. La idea de \"una etiqueta por píxel\" es la misma."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Detectar: el intento ingenuo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Entreno el clasificador de siempre y monto una escena con dos dígitos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "modelo = LogisticRegression(max_iter=2000).fit(d.data, d.target)\n",
    "\n",
    "lienzo = np.zeros((30, 40))\n",
    "for fila, col, digito in [(3, 5, 0), (14, 22, 1)]:\n",
    "    lienzo[fila:fila+8, col:col+8] = d.images[np.where(d.target == digito)[0][0]]\n",
    "\n",
    "print('lienzo:', lienzo.shape, '| dos digitos puestos')\n",
    "print('el clasificador acierta:', round(float(modelo.score(d.data, d.target)), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un clasificador perfecto. Ahora lo paso por toda la imagen, ventana por\n",
    "ventana:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cajas = []\n",
    "for f in range(lienzo.shape[0] - 7):\n",
    "    for c in range(lienzo.shape[1] - 7):\n",
    "        parche = lienzo[f:f+8, c:c+8]\n",
    "        if parche.sum() == 0:\n",
    "            continue\n",
    "        p = modelo.predict_proba(parche.reshape(1, -1))[0]\n",
    "        if p.max() > 0.9:\n",
    "            cajas.append((float(p.max()), int(p.argmax()), f, c))\n",
    "\n",
    "print('ventanas miradas:', (lienzo.shape[0] - 7) * (lienzo.shape[1] - 7))\n",
    "print('cajas con confianza > 0.9:', len(cajas))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "161 cajas para dos dígitos. Ese es el primer problema, y tiene arreglo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## IoU: cuánto se pisan dos cajas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def iou(a, b):\n",
    "    solape_f = max(0, 8 - abs(a[2] - b[2]))\n",
    "    solape_c = max(0, 8 - abs(a[3] - b[3]))\n",
    "    inter = solape_f * solape_c\n",
    "    return inter / (64 + 64 - inter)\n",
    "\n",
    "print('la misma caja      :', round(iou((0, 0, 3, 5), (0, 0, 3, 5)), 4))\n",
    "print('corrida un pixel   :', round(iou((0, 0, 3, 5), (0, 0, 3, 6)), 4))\n",
    "print('cajas separadas    :', round(iou((0, 0, 3, 5), (0, 0, 14, 22)), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**IoU** es intersección partido unión: lo que comparten dividido\n",
    "entre lo que ocupan juntas. Va de 0 a 1 y es la medida que se usa en todo el\n",
    "oficio, tanto para juntar cajas como para calificar un detector contra las cajas\n",
    "que dibujó una persona."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Supresión de no máximos"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def nms(cajas, umbral=0.3):\n",
    "    quedan = []\n",
    "    for caja in sorted(cajas, reverse=True):\n",
    "        if all(iou(caja, q) < umbral for q in quedan):\n",
    "            quedan.append(caja)\n",
    "    return quedan\n",
    "\n",
    "final = nms(cajas)\n",
    "print('antes:', len(cajas), '-> despues:', len(final))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La receta entera: ordena por confianza, quédate con la mejor, tira todas las\n",
    "que se pisen con ella más de un 30%, repite.\n",
    "\n",
    "De 161 a 13. Y ahora viene lo interesante."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Trece, y solo dos son de verdad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for conf, clase, f, c in sorted(final, reverse=True)[:5]:\n",
    "    print('digito', clase, 'en fila', f, 'col', c, '| confianza', round(conf, 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La segunda es correcta: el cero está en la fila 3, columna 5, que es donde lo\n",
    "puse. Las otras son **trozos de nada con confianza 1,000**.\n",
    "\n",
    "Y esto no se arregla subiendo el umbral, porque ya están al máximo. El\n",
    "problema es de fondo: **mi clasificador nunca vio un recorte que no fuera\n",
    "un dígito centrado**. Le enseñé diez opciones y le estoy preguntando por\n",
    "una undécima que no existe en su mundo, así que contesta la más parecida y lo\n",
    "hace con toda la seguridad.\n",
    "\n",
    "Es el mismo error de la categoría desconocida del capítulo de servir un\n",
    "modelo de mi libro de machine learning: el modelo no sabe decir \"no sé\" si nunca\n",
    "le enseñaste a decirlo 🙃"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y por eso los detectores de verdad hacen otras tres cosas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- **Entrenan con una clase de fondo.** Se le dan miles de\n",
    "recortes que no son nada, etiquetados como nada. Sin eso, todo lo de arriba\n",
    "pasa.\n",
    "\n",
    "- **Predicen la caja, no la buscan.** YOLO parte la imagen en una\n",
    "rejilla y cada celda predice directamente el rectángulo y la clase. Una sola\n",
    "pasada de la red en vez de 759 recortes, y de ahí viene el nombre: *You Only\n",
    "Look Once*.\n",
    "\n",
    "- **Manejan varios tamaños.** Mi ventana es de 8 por 8 y punto.\n",
    "Si el objeto es más grande, no lo veo. Los detectores miran a varias escalas a\n",
    "la vez.\n",
    "\n",
    "El IoU y la supresión de no máximos que acabas de escribir **siguen\n",
    "estando dentro** de YOLO, tal cual. Eso no lo cambió nadie."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora la conversación incómoda"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Entrenar un detector desde cero necesita miles de imágenes con las cajas\n",
    "dibujadas a mano, una por una. Ese trabajo se llama **anotar** y es\n",
    "la parte cara del proyecto, no el modelo.\n",
    "\n",
    "Con lo que hay hoy, para la mayoría de los encargos que me llegan la respuesta\n",
    "correcta es una de estas dos:\n",
    "\n",
    "- **Un modelo ya entrenado, sin tocar.** Si lo que quieres contar\n",
    "son personas, vehículos o cajas, ya está resuelto y se usa como viene.\n",
    "\n",
    "- **Ajuste fino sobre uno entrenado**, con unos cientos de\n",
    "imágenes tuyas. Es el transfer learning del capítulo\n",
    "18, y baja el coste de anotación en un orden de magnitud.\n",
    "\n",
    "Entrenar desde cero se justifica cuando lo que miras no se parece a nada\n",
    "público. Y aun así, primero se prueban las otras dos 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y en un negocio de verdad, ¿dónde entra esto?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Te lo pongo con los datos de siempre, que es lo que hace este libro.\n",
    "\n",
    "En la tabla de ventas hay una columna `unidades` que alguien\n",
    "escribió a mano. Cuando esa columna sale mal, sale mal en la factura y sale mal\n",
    "en el stock, y el problema aparece un mes después cuando el inventario no\n",
    "cuadra 🧾\n",
    "\n",
    "Los tres encargos que me llegan con imágenes son casi siempre estos:\n",
    "\n",
    "- **Contar.** Cuántas cajas hay en el pallet, cuántas unidades\n",
    "entraron al almacén. Es detección: te interesa cuántas y dónde, no la\n",
    "forma. Y la métrica del negocio es si el conteo cuadra con lo que dice el\n",
    "albarán.\n",
    "\n",
    "- **Leer.** El número de una factura, la placa de un camión, el\n",
    "código de un producto. Detección para encontrar el recuadro, y después un\n",
    "clasificador de caracteres dentro. Literalmente lo que acabas de escribir,\n",
    "pero con la ventana bien entrenada.\n",
    "\n",
    "- **Medir.** Qué porcentaje de la pieza tiene óxido, cuánto del\n",
    "lote está maduro. Eso es segmentación, porque la respuesta es un área y una caja\n",
    "no la da.\n",
    "\n",
    "Y una advertencia que doy siempre antes de cotizar: **la foto es el\n",
    "problema, no el modelo**. Si las tomas las hace una persona con el\n",
    "celular, cada una viene con otra luz, otro ángulo y otra distancia, y un\n",
    "detector entrenado con fotos bonitas se cae el primer día.\n",
    "\n",
    "Antes de hablar de arquitecturas, la pregunta es quién saca la foto, con qué\n",
    "y desde dónde. Si esa parte no está resuelta, el proyecto no está listo, por\n",
    "mucho presupuesto que haya 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Clasificar da un número, detectar da cajas, segmentar da una etiqueta por\n",
    "píxel.\n",
    "\n",
    "- Segmentar con umbral fijo depende de la iluminación y por eso no aguanta.\n",
    "\n",
    "- Pasar un clasificador por ventanas da cientos de cajas repetidas.\n",
    "\n",
    "- IoU mide cuánto se pisan dos cajas; NMS se queda con la mejor de cada\n",
    "montón.\n",
    "\n",
    "- Un clasificador sin clase de fondo dice que sí a todo, con confianza\n",
    "máxima.\n",
    "\n",
    "- YOLO predice las cajas en una pasada, y lleva IoU y NMS dentro.\n",
    "\n",
    "- Lo caro es anotar, no entrenar. Empieza por un modelo ya hecho."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Pasas tu clasificador de dígitos por una imagen grande con dos dígitos y te devuelve 13 detecciones, once de ellas con confianza 1,000 sobre trozos vacíos. ¿Qué le falta al clasificador?\n",
    "\n",
    "a) Nunca vio un recorte que no fuera un dígito centrado, así que no tiene forma de decir \"aquí no hay nada\"\n",
    "\n",
    "b) Le falta entrenamiento, con más épocas acertaría\n",
    "\n",
    "c) El umbral de confianza está muy bajo\n",
    "\n",
    "d) Hay que usar una red convolucional en vez de una regresión\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Acierta el 100% en lo suyo. El problema no es que aprenda mal, es que la pregunta que le estás haciendo no es la que le enseñaste a contestar.\n",
    "\n",
    "*c)* Está en 0,9 y las falsas tienen 1,000. Subirlo no quita ninguna.\n",
    "\n",
    "*d)* Una red convolucional entrenada igual, solo con dígitos centrados, hace exactamente lo mismo. Lo que cambia las cosas es qué le enseñas, no la arquitectura.\n",
    "\n",
    "Un clasificador entrenado solo con positivos no sabe decir que no. Por eso los detectores de verdad llevan una clase de fondo 🧾"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. El umbral del NMS decide cuántas te quedan\n",
    "\n",
    "Muévelo y mira el efecto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for u in (0.1, 0.3, 0.5, 0.9):\n",
    "    print('umbral', u, '->', len(nms(cajas, u)), 'cajas')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "umbral 0.1 -> 5 cajas\n",
    "umbral 0.3 -> 13 cajas\n",
    "umbral 0.5 -> 32 cajas\n",
    "umbral 0.9 -> 161 cajas\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con 0,1 te quedas con cinco y te puedes comer objetos que de verdad estaban\n",
    "juntos. Con 0,9 casi no filtras nada. Y con 0,9 salen las 161 de\n",
    "partida, o sea que no filtró ni una. El 0,3 o el 0,5 son lo habitual, y no\n",
    "porque sean mágicos: es lo que se ha visto funcionar. Si tus objetos se tocan\n",
    "mucho, ese número hay que revisarlo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. El modelo no sabe decir \"aquí no hay nada\"\n",
    "\n",
    "Pregúntale por un trozo completamente vacío."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vacio = np.zeros((8, 8))\n",
    "p = modelo.predict_proba(vacio.reshape(1, -1))[0]\n",
    "print('contesta el digito', int(p.argmax()), 'con confianza', round(float(p.max()), 4))\n",
    "print('las probabilidades suman', round(float(p.sum()), 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "contesta el digito 8 con confianza 0.1105\n",
    "las probabilidades suman 1.0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Contesta un 8. Y la línea de abajo explica por qué no puede hacer otra cosa:\n",
    "**las probabilidades tienen que sumar uno**, así que siempre reparte\n",
    "entre las diez clases que conoce. No existe la opción \"ninguna\".\n",
    "\n",
    "Acá la confianza sí es baja, 0,11, así que el umbral la filtraría. El\n",
    "problema son los recortes a medias, que se parecen a algo de verdad y salen con\n",
    "1,000."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Comprueba la detección que sí acertó\n",
    "\n",
    "Mide el IoU contra donde pusiste el dígito."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "verdad = (0, 0, 3, 5)\n",
    "aciertos = [(round(iou(caja, verdad), 3), caja[1], caja[2], caja[3])\n",
    "            for caja in final if iou(caja, verdad) > 0.5]\n",
    "print('cajas que se pisan mas del 50% con el cero real:')\n",
    "for i, clase, f, c in aciertos:\n",
    "    print('  IoU', i, '| dijo que era un', clase, '| fila', f, 'col', c)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "cajas que se pisan mas del 50% con el cero real:\n",
    "  IoU 1.0 | dijo que era un 0 | fila 3 col 5\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "IoU 1,0 y la clase correcta. Así se califica un detector: por cada caja que\n",
    "una persona dibujó, se busca la predicha que más se pise con ella, y se pide un\n",
    "IoU mínimo, normalmente 0,5. De ahí sale la métrica que vas a ver en todos los\n",
    "papers, que se llama mAP."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Cuánto cuesta la ventana deslizante\n",
    "\n",
    "La cuenta que explica por qué se inventó YOLO."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for alto, ancho in ((30, 40), (480, 640), (1080, 1920)):\n",
    "    ventanas = (alto - 7) * (ancho - 7)\n",
    "    print('imagen', alto, 'x', ancho, '->', ventanas, 'ventanas, y eso a UNA escala')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "imagen 30 x 40 -> 759 ventanas, y eso a UNA escala\n",
    "imagen 480 x 640 -> 299409 ventanas, y eso a UNA escala\n",
    "imagen 1080 x 1920 -> 2052649 ventanas, y eso a UNA escala\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dos millones de pasadas del clasificador para una imagen de teléfono, y eso\n",
    "mirando un solo tamaño de objeto. Con cinco escalas son diez millones. YOLO hace\n",
    "**una**. Ahí está toda la diferencia, y no es una mejora de\n",
    "porcentajes: es lo que hace posible detectar en vídeo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. La máscara traducida a negocio\n",
    "\n",
    "Segmentar sirve cuando lo que importa es cuánto, no cuántos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(3):\n",
    "    m = (d.images[i] > 5)\n",
    "    print('digito', d.target[i], '| pixeles de tinta', int(m.sum()),\n",
    "          '| proporcion', round(float(m.mean()), 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "digito 0 | pixeles de tinta 24 | proporcion 0.375\n",
    "digito 1 | pixeles de tinta 23 | proporcion 0.359\n",
    "digito 2 | pixeles de tinta 25 | proporcion 0.391\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esa proporción es la respuesta a \"qué porcentaje de la pieza está oxidado\" o\n",
    "\"cuánto del terreno es cultivo\". Una caja no te lo puede decir: solo dice dónde\n",
    "mirar. Cuando el encargo tenga la palabra *área*, *porcentaje* o\n",
    "*superficie*, es segmentación y no detección."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. El recorte que no tiene el tamaño que espera\n",
    "\n",
    "El error de la primera tarde con imágenes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "parche = lienzo[3:10, 5:12]\n",
    "print('el recorte mide', parche.shape)\n",
    "modelo.predict(parche.reshape(1, -1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "ValueError: X has 49 features, but LogisticRegression is expecting 64 features as input.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Corté 7 por 7 en vez de 8 por 8 y el modelo cuenta 49 en vez de 64. Este error\n",
    "sale el primer día y sale siempre, porque en imágenes todo son índices y un\n",
    "`+1` de más se cuela solo.\n",
    "\n",
    "La costumbre que lo evita es la misma de todo este libro: imprimir\n",
    "`.shape` antes de pasarle nada a un modelo. Y fíjate en que el error\n",
    "habla de *features* y no de píxeles, porque para el modelo una imagen ya\n",
    "dejó de ser una imagen en cuanto la aplastaste 🧷"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 12 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/detectar-y-segmentar/\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
}
