# Los cuatro errores que arruinan un dataset hecho con tus documentos

**Respuesta corta.** Los cuatro errores son: dejar texto duplicado, que el modelo
memoriza en vez de generalizar; contaminar el conjunto de evaluación con
fragmentos del de entrenamiento; trocear los documentos antes de repartirlos, que
filtra la misma plantilla a entrenamiento y a prueba; y dejar dentro datos
personales, que el modelo puede acabar recitando. El tercero es el más peligroso
porque no da error: sube la métrica y parece que el modelo aprendió.

---

Los cuatro están documentados. Los cuatro tienen arreglo. Y los cuatro aparecen
casi siempre que alguien construye por primera vez un conjunto de datos con
documentos de su empresa, porque los pipelines públicos están pensados para
corpus web y los documentos de oficina se comportan distinto: más plantilla, más
repetición, más datos personales por página.

## 1. Duplicados: el modelo memoriza en vez de aprender

Lee y otros, *Deduplicating Training Data Makes Language Models Better*
(ACL 2022, [arXiv:2107.06499](https://arxiv.org/abs/2107.06499)):

> «over 1% of the unprompted output of language models trained on these datasets
> is copied verbatim from the training data […] Deduplication allows us to train
> models that emit memorized text ten times less frequently and require fewer
> train steps to achieve the same or better accuracy.»

Dos cifras para dimensionarlo. En C4 encontraron **una frase de 61 palabras
repetida más de 60.000 veces**. CCNet ([arXiv:1911.00359](https://ar5iv.labs.arxiv.org/html/1911.00359))
midió que los párrafos duplicados eran **el 70 % del texto** de su corpus.

Y Carlini y otros ([arXiv:2202.07646](https://arxiv.org/abs/2202.07646)) muestran
que la memorización crece log-linealmente con el número de veces que aparece un
ejemplo. Deduplicar no es solo calidad: es privacidad.

**En documentos de empresa el problema es peor**, no mejor. La misma plantilla de
contrato, el mismo membrete, el mismo informe en dos versiones, el mismo párrafo
de tratamiento de datos en cada anexo.

### Qué parámetros usa la gente

El consenso es *shingles de 5-gramas de palabras*. El resto varía:

| Pipeline | Hashes | Umbral |
|---|---|---|
| The Pile | 10 | 0,5 |
| FineWeb | 112 (14 bandas × 8) | 0,75 |
| text-dedup (por defecto) | 240 | 0,7 |
| RefinedWeb / Lee et al. | 9.000 (20 × 450) | 0,8 |

RefinedWeb ([arXiv:2306.01116](https://arxiv.org/html/2306.01116v1)) critica
explícitamente los 10 hashes de The Pile: dieron «lower deduplication rates and
worse model performance».

**El detalle que cuesta una tarde:** bajar el umbral no basta si el bandeado LSH
no acompaña. El bandeado determina qué pares se comparan siquiera; si está fijo,
puedes bajar el umbral todo lo que quieras y no encontrarás un duplicado más,
porque esos pares nunca llegan a compararse.

## 2. Contaminación: la métrica deja de medir

Si un fragmento del conjunto de evaluación está también en el de entrenamiento,
la puntuación no mide lo que el modelo aprendió sino lo que recordó.

GPT-3 ([arXiv:2005.14165](https://ar5iv.labs.arxiv.org/html/2005.14165), apéndice
C) fijó el método:

> «We attempted to remove text occurring in benchmarks from training data by
> searching for **13-gram overlaps** […] We ignored 13-grams that matched more
> than 10 training documents, as inspection showed the majority of these to
> contain common cultural phrases, legal boilerplate, or similar content.»

Esa segunda frase es la que importa en documentos de empresa, donde el formulario
domina el texto.

Llama 3 ([arXiv:2407.21783](https://arxiv.org/html/2407.21783v3)) usa hoy
8-gramas con umbral de fracción de tokens, y publicó cuánta contaminación tienen
los benchmarks conocidos: AGIEval 98 %, BIG-Bench Hard 95 %, HellaSwag 85 %,
GSM8K 41 %. Tulu 3 encontró NuminaMath-TIR contaminado un 11,3 % contra MATH.

**Límite conocido:** el solapamiento de n-gramas no ve una paráfrasis. Un modelo
entrenado con un benchmark reescrito puntúa alto y pasa el filtro.

## 3. Fuga por troceado: el error silencioso

Este es el que más veces se comete y el que menos se nota, porque el resultado es
una métrica **mejor**.

Si troceas los documentos primero y repartes los trozos después, dos fragmentos
del mismo contrato acaban uno en entrenamiento y otro en prueba. El modelo ya vio
la plantilla, el vocabulario y el formato. No ha generalizado: ha reconocido.

El principio está en la documentación de scikit-learn sobre validación cruzada:

> «we need to ensure that all the samples in the validation fold come from groups
> that are not represented at all in the paired training fold»

Kapoor y Narayanan, *Leakage and the Reproducibility Crisis in ML-based Science*
([arXiv:2207.07048](https://ar5iv.labs.arxiv.org/html/2207.07048)), lo catalogan
como fuga tipo L3.2 y contabilizan **329 artículos afectados en 17 campos**. Un
trabajo sobre ventanas deslizantes cuantifica el efecto: el reparto ingenuo «can
inflate classification accuracy from a genuine ~20–60 % to an artefactual
99.9 %», con los mismos datos y el mismo modelo.

Hasta The Pile lo admite de sí mismo: «it is still possible that some documents
are duplicated across the train/validation/test splits».

**El arreglo es simple:** reparte por documento, no por trozo. Y que el reparto
sea estable, para poder regenerar el conjunto sin invalidar la evaluación
anterior: misma semilla, mismo reparto, y añadir un documento nuevo no mueve de
partición a los que ya estaban.

**Lo que el reparto por documento no arregla:** dos documentos *distintos* pueden
ser casi iguales, como dos versiones del mismo contrato. Por eso hacen falta
también los pasos 1 y 2.

## 4. Datos personales que el modelo acaba recitando

Carlini y otros, *Extracting Training Data from Large Language Models*
(USENIX Security 2021, [arXiv:2012.07805](https://arxiv.org/abs/2012.07805)),
extrajeron de GPT-2 cientos de secuencias literales, incluidos «names, phone
numbers, and email addresses», **de secuencias que aparecían en un solo
documento** del corpus. Y los modelos grandes son más vulnerables que los
pequeños.

Lo que hacen los pipelines públicos es menos de lo que parece:

- **Dolma** ([arXiv:2402.00159](https://arxiv.org/html/2402.00159v2)) se limita a
  «three kinds of PII that are detectable with high precision: email addresses,
  IP addresses and phone numbers». Si un documento tiene más de cinco
  apariciones, lo descarta entero.
- **FineWeb** anonimiza solo correos e IP públicas.
- **Llama 3** elimina dominios enteros con mucha PII, no la PII en sí.
- **StarCoder** ([arXiv:2305.06161](https://ar5iv.labs.arxiv.org/html/2305.06161))
  entrenó un detector con 22.950 entidades anotadas a mano: EMAIL 99 %, NAME
  89 %, y **identificador genérico 53 %, tan malo que lo excluyeron**.

Esa última cifra es la lección honesta: **nadie detecta identificadores genéricos
de forma fiable**, ni con anotadores humanos. Cualquier herramienta que te
prometa anonimización perfecta te está vendiendo algo.

**Lo razonable:** anonimizar por defecto, contar cuántas sustituciones se
hicieron, avisar si lo desactivas, y que sea reversible con un mapa que se queda
contigo. Una sustitución irreversible por un token fijo destruye información que
quizá necesites; una reversible te deja volver.

## El orden importa

Los cuatro pasos no son independientes:

1. **Deduplicar antes de repartir.** Si repartes primero, los casi-duplicados se
   reparten a los dos lados y el paso 3 no te salva.
2. **Anonimizar antes de exportar**, pero **clasificar antes de anonimizar**: los
   marcadores borran justo las palabras que delatan de qué tipo es el documento.
3. **Medir contaminación al final**, contra el conjunto de evaluación real, y con
   el fragmento literal que coincidió en el informe, para poder comprobarlo en
   vez de creerlo.

## Cómo comprobarlo en tus documentos

Sin instalar nada, en el navegador: [PDF a datos de IA](/es/pdf-a-jsonl/) enseña
cuántos duplicados salieron, cuántos datos personales se sustituyeron y cómo
quedó el reparto.

En terminal, cada paso por separado y con salida JSON para scripts:

```bash
npx sirdas dedupe docs/*.pdf --threshold 0.8 --json
npx sirdas boilerplate docs/*.pdf
npx sirdas contamination train/*.pdf --against eval/*.pdf --n 13
```
