{
 "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 práctica 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",
    "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: \"dW1icmFsIDAuMSAtPiA1IGNhamFzCnVtYnJhbCAwLjMgLT4gMTMgY2FqYXMKdW1icmFsIDAuNSAtPiAzMiBjYWphcwp1bWJyYWwgMC45IC0+IDE2MSBjYWphcw==\",\n",
    "    2: \"Y29udGVzdGEgZWwgZGlnaXRvIDggY29uIGNvbmZpYW56YSAwLjExMDUKbGFzIHByb2JhYmlsaWRhZGVzIHN1bWFuIDEuMA==\",\n",
    "    3: \"Y2FqYXMgcXVlIHNlIHBpc2FuIG1hcyBkZWwgNTAlIGNvbiBlbCBjZXJvIHJlYWw6CiAgSW9VIDEuMCB8IGRpam8gcXVlIGVyYSB1biAwIHwgZmlsYSAzIGNvbCA1\",\n",
    "    4: \"aW1hZ2VuIDMwIHggNDAgLT4gNzU5IHZlbnRhbmFzLCB5IGVzbyBhIFVOQSBlc2NhbGEKaW1hZ2VuIDQ4MCB4IDY0MCAtPiAyOTk0MDkgdmVudGFuYXMsIHkgZXNvIGEgVU5BIGVzY2FsYQppbWFnZW4gMTA4MCB4IDE5MjAgLT4gMjA1MjY0OSB2ZW50YW5hcywgeSBlc28gYSBVTkEgZXNjYWxh\",\n",
    "    5: \"ZGlnaXRvIDAgfCBwaXhlbGVzIGRlIHRpbnRhIDI0IHwgcHJvcG9yY2lvbiAwLjM3NQpkaWdpdG8gMSB8IHBpeGVsZXMgZGUgdGludGEgMjMgfCBwcm9wb3JjaW9uIDAuMzU5CmRpZ2l0byAyIHwgcGl4ZWxlcyBkZSB0aW50YSAyNSB8IHByb3BvcmNpb24gMC4zOTE=\",\n",
    "    6: \"VmFsdWVFcnJvcjogWCBoYXMgNDkgZmVhdHVyZXMsIGJ1dCBMb2dpc3RpY1JlZ3Jlc3Npb24gaXMgZXhwZWN0aW5nIDY0IGZlYXR1cmVzIGFzIGlucHV0Lg==\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "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"
   ]
  },
  {
   "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": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "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": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "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
}
