{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Buscar por significado entre un millón de trozos\n",
    "\n",
    "Del coseno a fuerza bruta al índice que mira un rincón. Y por qué juntar dos búsquedas a veces empeora.\n",
    "\n",
    "Cuaderno de soluciones del capítulo 14 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/buscar-en-vectores/\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": [
    "El capítulo 13 terminó con cada palabra convertida en\n",
    "cinco números. Bien. Ahora la pregunta que queda colgando y que nadie contesta:\n",
    "**¿qué haces con eso cuando no son 41 palabras sino un millón de párrafos\n",
    "de tus contratos?** 🔤\n",
    "\n",
    "Ese es el trabajo de una base de datos vectorial, y es el motor de cualquier\n",
    "sistema que diga \"responde con tus documentos\". Vamos a construir una chiquita y\n",
    "a medirla, incluida la parte donde sale mal."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Primero, partir el texto"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No se guarda el documento entero: se parte en trozos, porque lo que se compara\n",
    "después es cada trozo por separado. Acá el manual de atención de una tienda, una\n",
    "regla por línea, que es el caso fácil:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from sklearn.feature_extraction.text import TfidfVectorizer\n",
    "from sklearn.decomposition import TruncatedSVD\n",
    "\n",
    "MANUAL = '''El plazo de entrega en Lima es de 48 horas desde confirmado el pedido.\n",
    "Para provincia el plazo de entrega es de 5 dias habiles.\n",
    "El cliente puede anular el pedido sin costo dentro de las 2 horas siguientes.\n",
    "Pasadas las 2 horas la anulacion tiene un cargo del 10 por ciento.\n",
    "La garantia de los productos electronicos es de 12 meses.\n",
    "La garantia no cubre danio por mal uso ni por humedad.\n",
    "Para hacer efectiva la garantia se necesita la boleta o factura original.\n",
    "El cambio por talla se acepta dentro de los 15 dias, con la prenda sin uso.\n",
    "No se aceptan cambios de ropa interior ni de trajes de banio.\n",
    "La devolucion del dinero se hace por el mismo medio de pago.\n",
    "La devolucion del dinero demora entre 7 y 10 dias habiles.\n",
    "El codigo SKU-4471 corresponde al modelo descontinuado del ventilador.\n",
    "Los pedidos mayores a 400 soles tienen envio gratuito a Lima.\n",
    "El horario de atencion del canal telefonico es de 9 a 18 horas.\n",
    "Los reclamos se registran en el libro de reclamaciones virtual.\n",
    "El plazo legal para responder un reclamo es de 30 dias calendario.\n",
    "Las facturas se emiten solo si el cliente entrega su RUC al comprar.\n",
    "Una boleta no se puede convertir en factura despues de emitida.\n",
    "El descuento por volumen aplica desde 50 unidades del mismo producto.\n",
    "El descuento por volumen es del 12 por ciento y no se acumula con otros.'''\n",
    "\n",
    "trozos = [t.strip() for t in MANUAL.split('\\n') if t.strip()]\n",
    "print('trozos:', len(trozos))\n",
    "print('el primero:', trozos[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en dos parejas que puse a propósito: los dos trozos de anulación y los\n",
    "dos de devolución. **Cada uno solo cuenta media verdad**, y esa es\n",
    "la trampa del troceo: si el corte separa la regla de su excepción, el sistema\n",
    "puede encontrar una y contestar sin la otra 📋"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## De trozos a vectores"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mismo truco del capítulo 13: contar palabras y\n",
    "comprimir con SVD. En producción esto lo hace una red entrenada, que entiende\n",
    "bastante más; acá basta para ver el mecanismo, y tiene la ventaja de que corre en\n",
    "tu máquina sin descargar nada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "vec = TfidfVectorizer()\n",
    "X = vec.fit_transform(trozos)\n",
    "svd = TruncatedSVD(n_components=8, random_state=0)\n",
    "E = svd.fit_transform(X)\n",
    "E = E / (np.linalg.norm(E, axis=1, keepdims=True) + 1e-12)   # todos de largo 1\n",
    "print('palabras distintas :', X.shape[1])\n",
    "print('numeros por trozo  :', E.shape[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De 123 columnas a 8. Eso es lo que hace que la búsqueda sea rápida y también\n",
    "lo que hace que a veces se equivoque: al comprimir se pierde algo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Buscar es comparar ángulos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los vectores están normalizados a largo 1, así que el producto punto entre dos\n",
    "es directamente el coseno del ángulo: 1 es misma dirección, 0 es nada que ver."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def busca(pregunta, k=3):\n",
    "    q = svd.transform(vec.transform([pregunta]))\n",
    "    q = q / (np.linalg.norm(q) + 1e-12)\n",
    "    puntajes = (E @ q.T).ravel()\n",
    "    return [(round(float(puntajes[i]), 3), trozos[i])\n",
    "            for i in np.argsort(-puntajes)[:k]]\n",
    "\n",
    "for p in ['cuanto demora en llegar a provincia', 'me devuelven la plata?']:\n",
    "    print('pregunta:', p)\n",
    "    for s, t in busca(p):\n",
    "        print(f'  {s}  {t}')\n",
    "    print()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La primera sale perfecta y ninguna palabra de la pregunta está en la\n",
    "respuesta: ni \"demora\", ni \"llegar\". Eso es exactamente lo que compras al buscar\n",
    "por significado 🔤\n",
    "\n",
    "La segunda sale **mal**, y la dejo publicada porque es la mitad\n",
    "del capítulo. \"Me devuelven la plata\" tenía que traer la devolución del dinero y\n",
    "trae la garantía, con 0,759 de confianza. **El puntaje alto no significa\n",
    "que acertó**: significa que de lo que había, eso fue lo más parecido."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Entonces, ¿el vector le gana a buscar por palabra?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vamos a medirlo en vez de suponerlo. Seis preguntas, cada una con el trozo que\n",
    "debería salir, y las dos búsquedas compitiendo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "palabras = vec.build_analyzer()\n",
    "\n",
    "def literal(pregunta, k=3):\n",
    "    ps = set(palabras(pregunta))\n",
    "    return sorted(((len(ps & set(palabras(t))), t) for t in trozos),\n",
    "                  reverse=True)[:k]\n",
    "\n",
    "PRUEBAS = [\n",
    "    ('cuanto demora en llegar a provincia', 1),\n",
    "    ('me devuelven la plata?', 10),\n",
    "    ('SKU-4471', 11),\n",
    "    ('puedo anular?', 2),\n",
    "    ('hasta cuando cambio una polo', 7),\n",
    "    ('cuanto tiempo tengo para reclamar', 15),\n",
    "]\n",
    "\n",
    "print(f'{\"pregunta\":36} {\"vector\":>7} {\"palabra\":>8}')\n",
    "for p, correcto in PRUEBAS:\n",
    "    v = busca(p, 1)[0][1] == trozos[correcto]\n",
    "    l = literal(p, 1)[0][1] == trozos[correcto]\n",
    "    print(f'{p:36} {\"si\" if v else \"NO\":>7} {\"si\" if l else \"NO\":>8}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuatro contra dos. Y mira las dos filas donde los dos fallan: *me devuelven\n",
    "la plata* y *hasta cuando cambio una polo*. En la segunda la palabra\n",
    "\"polo\" no está en ningún trozo del manual, que habla de \"prenda\" y de \"talla\".\n",
    "Ningún método inventa lo que no existe en el texto 📋\n",
    "\n",
    "El SKU lo encuentran los dos, y eso merece una nota honesta: con un modelo de\n",
    "embeddings de verdad, los códigos exactos suelen diluirse y ahí la búsqueda por\n",
    "palabra gana claro. Acá no pasa porque nuestro vector se construye contando\n",
    "palabras, así que por dentro sigue siendo un poco literal."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Traer más y ordenar después"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hasta ahora miramos solo el primer resultado. ¿Y si traemos tres, o cinco?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for k in (1, 3, 5):\n",
    "    v = sum(trozos[c] in [t for _, t in busca(p, k)] for p, c in PRUEBAS)\n",
    "    l = sum(trozos[c] in [t for _, t in literal(p, k)] for p, c in PRUEBAS)\n",
    "    print(f'en los primeros {k}:  vector {v}/6   palabra {l}/6')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está el hallazgo que cambia cómo se arma un sistema de estos:\n",
    "**trayendo cinco, el vector encuentra las seis**. La respuesta\n",
    "correcta ya estaba, solo que no arriba.\n",
    "\n",
    "Eso es lo que justifica un **re-ranker**: traer 20 o 50 baratos y\n",
    "después reordenarlos con algo más caro y más fino, que puede ser un modelo que\n",
    "mira pregunta y trozo juntos. No busca más: ordena mejor lo que ya se encontró\n",
    "🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La híbrida, que debería ayudar y aquí no"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Lo estándar es juntar las dos búsquedas sumando posiciones, algo así como\n",
    "darle a cada trozo puntos por lo alto que quedó en cada lista:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def hibrida(pregunta, k=3):\n",
    "    rank = {}\n",
    "    for f in (busca, literal):\n",
    "        for pos, (_, t) in enumerate(f(pregunta, len(trozos))):\n",
    "            rank[t] = rank.get(t, 0) + 1 / (10 + pos)\n",
    "    return sorted(((s, t) for t, s in rank.items()), reverse=True)[:k]\n",
    "\n",
    "for k in (1, 3):\n",
    "    h = sum(trozos[c] in [t for _, t in hibrida(p, k)] for p, c in PRUEBAS)\n",
    "    v = sum(trozos[c] in [t for _, t in busca(p, k)] for p, c in PRUEBAS)\n",
    "    print(f'en los primeros {k}:  hibrida {h}/6   vector solo {v}/6')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sale **peor** arriba del todo: 3 contra 4.\n",
    "\n",
    "Y tiene explicación, no es ruido. Juntar dos buscadores ayuda cuando los dos\n",
    "son parecidos de buenos y se equivocan en cosas distintas. Acá uno acierta 4 y el\n",
    "otro 2, así que promediar con el flojo **arrastra hacia abajo**. La\n",
    "híbrida no es gratis: hay que medirla, y si no gana, no va.\n",
    "\n",
    "Te lo dejo publicado con el número feo a propósito. En las propuestas la\n",
    "búsqueda híbrida se vende como una mejora automática y no lo es 📋"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y ahora el problema de verdad: un millón de trozos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Todo lo anterior compara la pregunta contra los 20 trozos, uno por uno. Con un\n",
    "millón de trozos son un millón de comparaciones por cada pregunta, y ahí se acabó\n",
    "la fiesta.\n",
    "\n",
    "La salida es agrupar primero y mirar solo el grupo que toca. Es la idea de los\n",
    "índices que usan las bases vectoriales de verdad, con más maña; con k-means se ve\n",
    "el mecanismo entero:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.cluster import KMeans\n",
    "\n",
    "km = KMeans(n_clusters=4, n_init=10, random_state=0).fit(E)\n",
    "print('tamanio de cada grupo:', [int((km.labels_ == g).sum()) for g in range(4)])\n",
    "\n",
    "def busca_indice(pregunta, k=3, grupos=1):\n",
    "    q = svd.transform(vec.transform([pregunta]))\n",
    "    q = q / (np.linalg.norm(q) + 1e-12)\n",
    "    cerca = np.argsort(((km.cluster_centers_ - q) ** 2).sum(axis=1))[:grupos]\n",
    "    cand = np.where(np.isin(km.labels_, cerca))[0]\n",
    "    puntajes = (E[cand] @ q.T).ravel()\n",
    "    return len(cand), [trozos[i] for i in cand[np.argsort(-puntajes)[:k]]]\n",
    "\n",
    "for grupos in (1, 2, 4):\n",
    "    comp = sum(busca_indice(p, 3, grupos)[0] for p, _ in PRUEBAS) / len(PRUEBAS)\n",
    "    ok = sum(trozos[c] in busca_indice(p, 3, grupos)[1] for p, c in PRUEBAS)\n",
    "    print(f'{grupos} grupo(s): {comp:.1f} comparaciones de {len(trozos)}, acierta {ok}/6')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De 20 comparaciones a 4,8, **con los mismos aciertos**. Mirando\n",
    "los cuatro grupos se vuelve a comparar contra todo, que es la fuerza bruta del\n",
    "principio: el índice no es magia, es elegir cuánto mirar.\n",
    "\n",
    "Con 20 trozos no se pierde nada y por eso el ejemplo es cómodo. Lo que se paga\n",
    "en serio aparece con volumen: **si la respuesta correcta cae en el grupo de\n",
    "al lado, no la vas a ver nunca**, porque su grupo ni se abrió. Por eso a\n",
    "esto se le llama búsqueda aproximada, y por eso las bases vectoriales te dejan\n",
    "subir cuántos grupos mirar cuando notas que se te escapan cosas."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. La regla partida de su excepción\n",
    "\n",
    "Pregunta por la anulación de un pedido y mira los dos\n",
    "primeros resultados. ¿Qué problema ves para quien va a contestarle al\n",
    "cliente?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Los dos trozos de anulación dicen cosas opuestas según el momento: sin costo\n",
    "dentro de las 2 horas, con 10% después. Si la búsqueda trae solo uno, la\n",
    "respuesta es falsa aunque el trozo sea correcto.\n",
    "\n",
    "Es el argumento de fondo para que los trozos se solapen un poco entre ellos,\n",
    "o para pedirle siempre al menos tres al sistema en vez de uno."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Sube los números por trozo\n",
    "\n",
    "Cambia `n_components=8` por 4 y por 15, y vuelve a\n",
    "correr la tabla de las seis preguntas. ¿Mejora siempre con más?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con 4 se comprime demasiado y trozos distintos empiezan a parecerse. Con 15 se\n",
    "guarda casi toda la matriz original y la búsqueda se vuelve más literal, así que\n",
    "se pierde parte de la gracia de encontrar sin compartir palabras.\n",
    "\n",
    "No hay un número bueno universal: se prueba con tus preguntas, que es\n",
    "exactamente lo que estás haciendo acá."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Una pregunta que no tiene respuesta\n",
    "\n",
    "Pregunta algo que el manual no cubre, como \"aceptan pago con\n",
    "Yape\". Mira el puntaje del primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Devuelve algo igual, porque la búsqueda siempre devuelve lo más parecido. Lo\n",
    "interesante es el número: suele quedar bastante más bajo que el de una pregunta\n",
    "que sí tiene respuesta.\n",
    "\n",
    "Ese umbral es la defensa práctica contra inventar: si el mejor puntaje no\n",
    "llega a cierto valor, el sistema debería contestar que no lo tiene en vez de\n",
    "armar algo con el trozo más cercano."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. El error de pasarle una frase suelta\n",
    "\n",
    "Búscale el parecido a una pregunta pasándola tal cual, sin\n",
    "meterla en una lista."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "vec.transform('cuanto demora en llegar a provincia')\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "ValueError: Iterable over raw text documents expected, string object received.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El vectorizador espera una colección de documentos, no uno. Y una cadena de\n",
    "texto sí es iterable, así que la confusión es razonable: iteraría letra por\n",
    "letra, que no es lo que quieres, y por eso sklearn corta antes con este\n",
    "mensaje.\n",
    "\n",
    "Es el error más repetido de toda la librería y se arregla con dos corchetes:\n",
    "`vec.transform(['cuanto demora...'])`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. El otro error, el de las formas\n",
    "\n",
    "Multiplica los vectores por la pregunta sin transponerla."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "E @ svd.transform(vec.transform(['cuanto demora']))\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 1 is different from 8)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "E es de 20 por 8 y la pregunta sale de 1 por 8. Para multiplicarlas, la\n",
    "pregunta tiene que quedar de 8 por 1, que es lo que hace `.T`.\n",
    "\n",
    "Lee el mensaje despacio porque siempre dice lo mismo: 1 es distinto de 8. Casi\n",
    "todos los errores de matrices se arreglan mirando las dos formas antes de\n",
    "adivinar."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. El re-ranker más tonto posible\n",
    "\n",
    "Trae los 5 primeros por vector y reordénalos por cuántas\n",
    "palabras comparten con la pregunta. ¿Sube de 4 aciertos?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es la idea del re-ranking con la herramienta más barata que hay. Sabiendo que\n",
    "a 5 el vector acierta 6 de 6, todo lo que haga falta es que el reordenamiento\n",
    "suba la correcta al primer puesto.\n",
    "\n",
    "Y sirve para ver lo otro: reordenar con algo tan pobre como contar palabras\n",
    "compartidas también puede empeorar, igual que pasó con la híbrida. Mídelo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Una base vectorial guarda trozos como vectores y busca por coseno. Los\n",
    "vectores normalizados hacen que el producto punto sea el parecido.\n",
    "\n",
    "- El puntaje alto no significa que acertó: significa que fue lo más parecido de\n",
    "lo que había. Acá 0,759 salió equivocado.\n",
    "\n",
    "- Buscar por significado le ganó a buscar por palabra 4 a 2, y con un modelo de\n",
    "embeddings de verdad la diferencia en códigos exactos va al revés.\n",
    "\n",
    "- La respuesta correcta suele estar entre las primeras aunque no sea la\n",
    "primera. A 5 aciertos 6 de 6. Por eso existe el re-ranking.\n",
    "\n",
    "- La búsqueda híbrida no mejora sola: acá salió 3 contra 4 porque uno de los\n",
    "dos buscadores era bastante peor.\n",
    "\n",
    "- El índice cambia exactitud por velocidad. De 20 comparaciones a 4,8 sin\n",
    "perder nada acá, y con volumen sí se pierde lo que cae en el grupo de al\n",
    "lado.\n",
    "\n",
    "- Si el corte separa una regla de su excepción, la búsqueda encuentra media\n",
    "verdad y la contesta entera."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Mides tu buscador con seis preguntas cuya respuesta conoces. Trayendo un solo resultado acierta 4 de 6, y trayendo cinco acierta 6 de 6. ¿Qué te dice eso del sistema?\n",
    "\n",
    "a) Que la respuesta correcta casi siempre está entre las primeras, y lo que falla es el orden\n",
    "\n",
    "b) Que hay que cambiar el modelo de embeddings\n",
    "\n",
    "c) Que faltan documentos por cargar\n",
    "\n",
    "d) Que el sistema está bien y 4 de 6 es un buen número\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Puede ayudar, y antes de eso hay algo más barato: si la respuesta ya viene entre las cinco, el problema no es encontrarla sino ordenarla.\n",
    "\n",
    "*c)* Si faltaran, no aparecerían ni trayendo cinco. Aparecen, así que están cargados.\n",
    "\n",
    "*d)* Es el número que ve quien pregunta, y dos de cada seis se van con la respuesta equivocada arriba del todo.\n",
    "\n",
    "Traer más y reordenar después es lo primero que conviene probar, y es lo que hace un re-ranker 🔍"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con esto ya sabes cómo se encuentra el trozo. El capítulo\n",
    "18 contesta la otra mitad: cuándo te conviene buscar\n",
    "así y cuándo te conviene otra cosa 🐥"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 14 de **Deep learning desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/deep-learning-desde-cero/buscar-en-vectores/\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
}
