Ir al contenido
wannacode

Dividir documentos en fragmentos (chunking)

En resumen: El chunking divide un documento en fragmentos por palabras o por frases, con un solapamiento, para que cada fragmento tenga su propio vector y la respuesta no se corte por la mitad.

Problema

La página entera en un vector

La tienda tiene una política de devoluciones. Es una página con seis frases:

// la política de devoluciones: seis frases
string[] policy =
[
"puedes devolver el producto hasta 14 días tras la entrega",
"el producto tiene que estar en su embalaje original",
"un mensajero lo recoge sin costo",
"el dinero llega a tu tarjeta en cinco días",
"los productos con tu inscripción no se devuelven",
"si llega dañado escríbenos antes de tres días",
];
// la pregunta del cliente
var question = "cuándo me devuelven el dinero";

El cliente pregunta «cuándo me devuelven el dinero». La respuesta es la cuarta frase. Si la página entera está en el índice como un solo texto, es un vector:

Console.WriteLine($"palabras: {words.Length}");
var doc = string.Join(" ", policy);
Console.WriteLine($"documento completo: {Cosine(v, space.OfText(doc)):F3}");
Console.WriteLine($"la frase con la respuesta: {Cosine(v, space.OfText(policy[3])):F3}");
Salida
palabras: 50
documento completo: 0.609
la frase con la respuesta: 0.716

La página entera tiene 0,609, y la frase con la respuesta sola tiene 0,716. El vector de la página es una mezcla de seis temas: plazo, embalaje, mensajero, dinero, excepciones, daños. Cuanto más larga es la página, más se difumina. En una base con muchos documentos, una página así pierde frente a textos cortos que no responden la pregunta en absoluto.

Y si aun así se encuentra, las 50 palabras van al prompt por culpa de 9. Las políticas reales tienen miles de palabras, y tres páginas así llenan la ventana de contexto y la factura.

La idea

Fragmentos

El documento se corta en fragmentos (chunks) antes de entrar en el índice. Cada fragmento es una entrada aparte: su propio texto, su propio vector y de qué documento viene. La búsqueda de la lección anterior encuentra el fragmento, no la página entera.

El corte más simple es por número de palabras: las primeras 10 palabras, las siguientes 10, y así hasta el final. Para los vectores de la lección usamos el mismo modelo que en la lección sobre vectores, con dos diferencias: también aprende del texto de la política (si no, sus palabras serían desconocidas), y tiene 16 ejes en vez de 8. Todos los fragmentos hablan de devoluciones, y con 8 ejes se distinguen poco.

var space = new Space([.. text, .. policy], stop, 16);
var words = string.Join(" ", policy).Split(' ');
var v = space.OfText(question);

Míralo

Fragmentos de 10 palabras

La función corta las palabras en fragmentos. Por ahora el solapamiento es 0: cada fragmento empieza donde termina el anterior.

// fragmentos de size palabras; el paso es size - overlap
static List<string> Chunk(
string[] words, int size, int overlap)
{
var pieces = new List<string>();
int step = size - overlap;
for (int from = 0; from < words.Length; from += step)
{
int end = Math.Min(from + size, words.Length);
pieces.Add(string.Join(" ", words[from..end]));
if (end == words.Length)
break;
}
return pieces;
}

Otra función imprime cada fragmento con su similitud con la pregunta y cuál es el más cercano:

// cada fragmento con su similitud; el más cercano al final
static void Show(Space space, int[] v, List<string> pieces)
{
int best = 0;
double top = -1;
for (int i = 0; i < pieces.Count; i++)
{
double c = Cosine(v, space.OfText(pieces[i]));
Console.WriteLine($"{i + 1} {c:F3} {pieces[i]}");
if (c > top)
{
top = c;
best = i;
}
}
Console.WriteLine($"→ {best + 1}");
}
Show(space, v, Chunk(words, 10, 0));
Salida
1 0.590 puedes devolver el producto hasta 14 días tras la entrega
2 0.254 el producto tiene que estar en su embalaje original un
3 0.772 mensajero lo recoge sin costo el dinero llega a tu
4 0.471 tarjeta en cinco días los productos con tu inscripción no
5 0.583 se devuelven si llega dañado escríbenos antes de tres días
→ 3

El más cercano es el fragmento 3 (0,772). Termina con «el dinero llega a tu», y «tarjeta en cinco días» está en el fragmento 4. El modelo leerá «el dinero llega a tu» y no sabrá adónde llega ni cuándo. El corte cayó justo en medio de la respuesta.

La idea

Solapamiento

El corte es ciego: no sabe dónde termina la frase. La solución es un solapamiento: cada fragmento empieza un poco antes del final del anterior. Con fragmentos de 10 palabras y un solapamiento de 4, cada fragmento empieza 6 palabras después del anterior, y sus últimas 4 palabras son las primeras 4 del siguiente.

Así los bordes se repiten, y una frase cortada en un fragmento está completa en el vecino. El precio son más fragmentos y palabras repetidas.

Míralo

Con un solapamiento de 4

Show(space, v, Chunk(words, 10, 4));
Salida
1 0.590 puedes devolver el producto hasta 14 días tras la entrega
2 0.457 días tras la entrega el producto tiene que estar en
3 0.226 tiene que estar en su embalaje original un mensajero lo
4 0.688 original un mensajero lo recoge sin costo el dinero llega
5 0.714 costo el dinero llega a tu tarjeta en cinco días
6 0.471 tarjeta en cinco días los productos con tu inscripción no
7 0.610 con tu inscripción no se devuelven si llega dañado escríbenos
8 0.583 si llega dañado escríbenos antes de tres días
→ 5

Ahora el más cercano es el fragmento 5 (0,714), y contiene la frase entera: «el dinero llega a tu tarjeta en cinco días». Hay 8 fragmentos en vez de 5.

// fragmentos de size palabras; el paso es size - overlap
static List<string> Chunk(
string[] words, int size, int overlap)
{
var pieces = new List<string>();
int step = size - overlap;
for (int from = 0; from < words.Length; from += step)
{
int end = Math.Min(from + size, words.Length);
pieces.Add(string.Join(" ", words[from..end]));
if (end == words.Length)
break;
}
return pieces;
}
var v = space.OfText(question);
Show(space, v, Chunk(words, 10, 0));
Paso a paso, en palabras
  1. La política de devoluciones: 50 palabras. En naranja está la frase con la respuesta: «el dinero llega a tu tarjeta en cinco días».
  2. Fragmentos de 10 palabras sin solapamiento: 5 fragmentos, uno tras otro.
  3. La similitud de cada fragmento con la pregunta. El más cercano es el fragmento 3 (0,772).
  4. Pero el fragmento 3 termina en «llega a tu»: «tarjeta en cinco días» está en el siguiente fragmento. La respuesta queda cortada por la mitad.
  5. Solapamiento 4: cada fragmento empieza 6 palabras después del anterior, así que los bordes se repiten. 8 fragmentos.
  6. El más cercano es el fragmento 5 (0,714), y contiene la frase completa.
  7. Sin solapamiento la respuesta queda repartida entre dos fragmentos; con solapamiento un fragmento la contiene entera. El precio: 8 fragmentos en vez de 5, y palabras repetidas.

Pruébalo

Predice la salida

Una frase de 8 palabras en fragmentos de 4 con un solapamiento de 1. ¿Cuántos fragmentos salen?

var ws = "la entrega a la oficina cuesta 3 euros".Split(' ');
Console.WriteLine(Chunk(ws, 4, 1).Count);
Respuesta

Pruébalo

Completa

¿Cuántas palabras después empieza cada fragmento siguiente? ¿Qué va en el lugar de ___?

// fragmentos de size palabras; el paso es size - overlap
static List<string> Chunk(
    string[] words, int size, int overlap)
{
    var pieces = new List<string>();
    int step = size - ;
    for (int from = 0; from < words.Length; from += step)
    {
        int end = Math.Min(from + size, words.Length);
        pieces.Add(string.Join(" ", words[from..end]));
        if (end == words.Length)
            break;
    }
    return pieces;
}

Pruébalo

Encuentra el error

Un compañero corta con un solapamiento de 4, pero una parte de la política no está en ningún fragmento. ¿En qué línea está el error?

Elige la línea con el error.

La idea

Por frases

Un número de palabras es algo tosco. Los documentos tienen límites naturales: frases, párrafos, títulos. Si un fragmento es una frase entera, la respuesta no se corta:

Show(space, v, [.. policy]);
Salida
1 0.590 puedes devolver el producto hasta 14 días tras la entrega
2 0.254 el producto tiene que estar en su embalaje original
3 0.000 un mensajero lo recoge sin costo
4 0.716 el dinero llega a tu tarjeta en cinco días
5 0.000 los productos con tu inscripción no se devuelven
6 0.583 si llega dañado escríbenos antes de tres días
→ 4

Gana la frase con la respuesta (0,716). Pero mira la frase 3: «un mensajero lo recoge sin costo», 0,000. «lo» es el producto de la frase anterior. Por sí solo, el fragmento no dice qué recoge el mensajero ni que se trata de devoluciones. Si el cliente pregunta «viene un mensajero por la devolución», este fragmento ha perdido parte de su significado.

Por eso los sistemas reales conservan el vínculo: ponen el título del documento y de la sección delante de cada fragmento, añaden una frase de cada vecino o cortan por párrafos. Y guardan de cada fragmento de dónde viene: documento, sección, lugar. Así la respuesta cita su fuente.

La idea

Cuándo duele

  • Fragmentos demasiado pequeños pierden el significado: «hasta 14 días» sin la palabra «devolver» no dice para qué son los 14 días.
  • Fragmentos demasiado grandes se difuminan como la página entera y llenan el prompt.
  • El solapamiento trae repeticiones. Los fragmentos 4 y 5 tienen las mismas palabras. Si ambos están entre los primeros k, texto repetido va al prompt. Los fragmentos vecinos de un documento se unen antes de ir al modelo.
  • Tablas, listas y código no se cortan por número de palabras. Una fila cortada de una tabla de precios no tiene sentido: esos documentos se cortan por su estructura.
  • Si cambias el corte, cambias el índice. Todos los fragmentos se cortan y se calculan de nuevo.

No hay un tamaño que sirva para todos los documentos. El tamaño y el solapamiento se eligen con un conjunto de pruebas de preguntas, como k en la lección anterior: ver la evaluación.

Más precisamente, para curiosos

El tamaño en los sistemas reales. Los fragmentos se miden en tokens, no en palabras: a menudo de 200 a 800 tokens con un solapamiento del 10 al 20 % (cifras de ejemplo). Cuántos tokens tiene un texto está en la lección sobre tokens. Aquí los fragmentos son de 10 palabras y las frases son casi igual de largas, por eso el solapamiento es grande.

Cuándo el solapamiento mantiene la respuesta completa. Una frase de L palabras está completa en algún fragmento si L ≤ overlap + 1. Con un solapamiento de 4, son las frases de hasta 5 palabras. Una frase más larga está completa solo si algún corte cae justo antes de ella: el ejercicio muestra que eso depende de dónde caen los cortes.

Cortar por significado. Algunos sistemas cortan donde los vectores de frases vecinas se separan: un cambio de tema es un límite. Eso cuesta una llamada al modelo de vectores por frase.

Ejercicio

Una comprobación para el corte

Resuélvelo en tu editor.

Escribe una comprobación: para fragmentos de 10 palabras y un solapamiento de 0 a 4, busca el fragmento más cercano a la pregunta e imprime cuántos fragmentos hay, la similitud y si el fragmento contiene la frase completa con la respuesta.

Salida
solapamiento 0: 5 fragmentos, 0.772, respuesta completa: no
solapamiento 1: 6 fragmentos, 0.688, respuesta completa: no
solapamiento 2: 6 fragmentos, 0.714, respuesta completa: sí
solapamiento 3: 7 fragmentos, 0.771, respuesta completa: no
solapamiento 4: 8 fragmentos, 0.714, respuesta completa: sí

Los solapamientos 2 y 4 mantienen la respuesta completa, y 0, 1 y 3 no. La similitud más alta es la del solapamiento 0 (0,772), y la del 3 es casi igual (0,771), pero con 0 el fragmento termina en «a tu» y se pierde «tarjeta en cinco días», y con 3 termina en «a tu tarjeta» y se pierde «en cinco días». Una similitud más alta no significa una respuesta completa. Que la respuesta esté completa depende de dónde caen los cortes, por eso una comprobación así se ejecuta cada vez que cambia el corte.

Más tareas

  • Frases de hasta 16 palabras. Une frases enteras en un fragmento hasta pasar de 16 palabras. ¿Cuál es el fragmento más cercano?
  • Sin repeticiones. Si dos de los primeros k fragmentos se solapan, únelos en uno.

Test

Ponte a prueba

1. ¿Por qué un documento largo no se mete en el índice como un solo vector?
2. Fragmentos de 10 palabras con un solapamiento de 4. ¿Cuántas palabras hay entre sus comienzos?
3. ¿Qué se pierde cuando cada frase es un fragmento aparte?
4. El solapamiento es 4, y los k = 3 fragmentos más cercanos son 5, 4 y 7. ¿Cuál es el problema?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·