Curation de Datasets Assistée par LLM
Vue d'ensemble
La curation moderne de datasets utilise les LLMs comme filtres de qualité, réécrits, étiqueteurs et générateurs de données synthétiques. Cette compétence couvre l'hébergement local de modèles avec vLLM/SGLang et leur utilisation pour des travaux de dataset — non pas pour du chat interactif, mais pour des opérations de données batch, structurées et reproductibles.
Quand utiliser
Utilisez cette compétence pour :
- Scorer ou filtrer des exemples de dataset avec un juge de qualité LLM.
- Réécrire en masse du texte bruyant (requêtes, réponses, traces de raisonnement).
- Générer des exemples synthétiques pour équilibrer les classes ou combler les lacunes.
- Extraire des étiquettes structurées à partir de texte non structuré.
- Exécuter un scoring de curriculum (difficulté, complexité, valeur pédagogique).
- Implémenter un scoring de qualité appris de style DataRater (2025).
Ne l'utilisez pas pour :
- Le chat interactif ou l'inspection d'exemples uniques — utilisez une interface.
- La déduplication par correspondance exacte — utilisez le hachage.
- La déduplication basée sur embeddings — utilisez la compétence
embedding-analysis.
Prérequis
Nécessite un serveur vLLM ou SGLang en cours d'exécution. Voir les compétences vllm et sglang pour la configuration du serveur.
# vLLM (débit élevé)
vllm serve Qwen/Qwen2.5-7B-Instruct --port 8000 --max-model-len 8192
# SGLang (sortie structurée)
python -m sglang.launch_server --model-path Qwen/Qwen2.5-7B-Instruct --port 30000
Modèles centraux
1. Scoring de qualité LLM-as-Judge
Scorerez chaque exemple sur la clarté, l'exactitude et l'utilité.
from openai import OpenAI
import json
from datasets import load_dataset
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
QUALITY_PROMPT = """Score the following example on these dimensions (1-5 each):
- clarity: Is the text well-written and understandable?
- correctness: Are the facts accurate?
- usefulness: Would this help someone learn or solve a problem?
Respond with ONLY valid JSON: {"clarity": N, "correctness": N, "usefulness": N}
Example:
{sample}
"""
def score_example(sample: dict) -> dict:
prompt = QUALITY_PROMPT.format(sample=json.dumps(sample))
response = client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=[{"role": "user", "content": prompt}],
temperature=0.0, # deterministic
max_tokens=128,
)
try:
scores = json.loads(response.choices[0].message.content)
except json.JSONDecodeError:
scores = {"clarity": 0, "correctness": 0, "usefulness": 0}
return {**sample, **scores}
# Batch scoring with datasets
dataset = load_dataset("my-dataset", split="train")
scored = dataset.map(score_example)
# Filter low-quality examples
filtered = scored.filter(lambda x: x["clarity"] >= 3 and x["correctness"] >= 3)
2. Filtrage de sortie structurée (SGLang)
Utilisez le décodage contraint de SGLang pour une sortie de schéma JSON garantie.
import sglang as sgl
@sgl.function
def classify_quality(s, text: str):
s += sgl.system("You classify dataset examples. Output ONLY valid JSON.")
s += sgl.user(f"Classify this example:\n\n{text}")
s += sgl.gen("result", max_tokens=256, temperature=0.0, schema=json.dumps({
"type": "object",
"properties": {
"quality": {"type": "string", "enum": ["high", "medium", "low", "noise"]},
"language": {"type": "string", "enum": ["en", "code", "other"]},
"topic": {"type": "string"},
"issues": {"type": "array", "items": {"type": "string"}},
},
"required": ["quality", "language", "topic", "issues"],
}))
state = classify_quality.run(text=example["text"])
result = state["result"] # guaranteed valid JSON
3. Réécriture/Raffinement en batch
Nettoyez les données bruyantes en les réécrivant à travers un LLM.
REWRITE_PROMPT = """Rewrite the following text to be clear, grammatical, and well-structured.
Preserve all factual information. Fix typos, grammar, and awkward phrasing.
Original: {text}
Rewritten:"""
def rewrite_text(sample: dict, client, model: str) -> dict:
prompt = REWRITE_PROMPT.format(text=sample["text"])
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=0.3,
max_tokens=1024,
)
sample["text_rewritten"] = response.choices[0].message.content
return sample
# Process with concurrency
from concurrent.futures import ThreadPoolExecutor, as_completed
def batch_rewrite(dataset, client, model, max_workers=8):
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {
executor.submit(rewrite_text, example, client, model): i
for i, example in enumerate(dataset)
}
results = [None] * len(dataset)
for future in as_completed(futures):
idx = futures[future]
results[idx] = future.result()
return results
4. Génération de données synthétiques
Générez des exemples supplémentaires pour combler les déséquilibres de classes ou couvrir des cas limites.
SYNTHETIC_PROMPT = """Given this REAL example, generate {n} NEW examples that are:
- Semantically different (new variations, not paraphrases)
- Same difficulty level
- Same format and style
- Realistic and useful
REAL example:
{seed}
Generate {n} new examples as a JSON array of objects with the same keys.
Output ONLY the JSON array."""
def generate_synthetic(seed_examples, client, model, n_per_seed=5):
synthetic = []
for seed in seed_examples:
prompt = SYNTHETIC_PROMPT.format(n=n_per_seed, seed=json.dumps(seed))
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
temperature=0.8, # higher for diversity
max_tokens=2048,
)
try:
generated = json.loads(response.choices[0].message.content)
synthetic.extend(generated)
except json.JSONDecodeError:
continue
return synthetic
5. Scoring de difficulté de curriculum
Scorerez les exemples par difficulté pour activer l'apprentissage par curriculum.
DIFFICULTY_PROMPT = """Rate the difficulty of this example on a scale of 1-5:
1 = Trivial, basic knowledge
2 = Easy, common knowledge
3 = Moderate, requires some reasoning
4 = Hard, requires deep understanding
5 = Expert, requires specialized knowledge
Example: {sample}
Difficulty (number only):"""
def score_difficulty(sample, client, model):
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": DIFFICULTY_PROMPT.format(sample=sample["text"])}],
temperature=0.0,
max_tokens=4,
)
try:
return int(response.choices[0].message.content.strip())
except ValueError:
return 3 # default moderate
# Build curriculum: sort by difficulty
scored = dataset.map(lambda x: {"difficulty": score_difficulty(x, client, model)})
curriculum = scored.sort("difficulty")
6. Extraction d'étiquettes basée sur LLM
Extrayez des étiquettes structurées à partir de texte non structuré.
LABELING_PROMPT = """Extract the following labels from this text.
Respond with ONLY valid JSON.
Text: {text}
Labels to extract:
- sentiment: "positive", "negative", or "neutral"
- has_code: true if contains code snippets, false otherwise
- domain: one of ["science", "technology", "business", "arts", "other"]
- entities: list of named entities mentioned
"""
def extract_labels(sample, client, model):
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": LABELING_PROMPT.format(text=sample["text"])}],
temperature=0.0,
max_tokens=256,
)
try:
labels = json.loads(response.choices[0].message.content)
return {**sample, **labels}
except json.JSONDecodeError:
return {**sample, "sentiment": None, "has_code": None, "domain": None, "entities": []}
Modèles d'optimisation
Openai Batch API (vLLM)
# vLLM supports batch API for cost efficiency on large jobs
# Upload a JSONL file of requests
requests = []
for example in dataset:
requests.append({
"custom_id": str(example["id"]),
"method": "POST",
"url": "/v1/chat/completions",
"body": {
"model": "Qwen/Qwen2.5-7B-Instruct",
"messages": [{"role": "user", "content": QUALITY_PROMPT.format(sample=example["text"])}],
"temperature": 0.0,
"max_tokens": 128,
}
})
import tempfile, json
with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=False) as f:
for req in requests:
f.write(json.dumps(req) + "\n")
batch_file = f.name
batch = client.files.create(file=open(batch_file, "rb"), purpose="batch")
job = client.batches.create(input_file_id=batch.id, endpoint="/v1/chat/completions", completion_window="24h")
Intégration de la littérature 2025-2026
Cette compétence intègre des techniques de :
| Article | Lieu | Technique | Application |
|---|---|---|---|
| DataRater (Calian et al.) | NeurIPS 2025 | Scoring de qualité meta-appris | embedding_quality_score() dans embedding-analysis; juge LLM comme proxy |
| Why Less is More (Dohmatob et al.) | 2025 | Théorie des seuils de curation de données | Informe l'agressivité du filtrage |
| GRAPE Score | 2025 | Filtrage basé sur la perplexité | grape_score() dans embedding-analysis |
| NeMo Curator SemDedup | 2024-2025 | Déduplication sémantique basée sur le clustering | semantic_dedup() dans embedding-analysis |
| LSHBloom (Khan et al.) | 2025 | Déduplication de texte à l'échelle Internet | lsh_semantic_dedup() pour >100M d'échelle |
| Blu-WERP (Rupesh et al.) | 2025 | Pipeline de prétraitement scalable | Modèle streaming + map batch |
| TBDFiltering (Busa-Fekete et al.) | 2025 | Filtrage de données basé sur arbre | Score LLM comme condition de nœud d'arbre |
| Ensembled Multimodal Curation (Xu et al.) | 2025 | Fusion de qualité multi-signaux | Combinez scores LLM + scores embedding + perplexité |
Porte de qualité
Une exécution de curation assistée par LLM est complète quand :
- Le serveur LLM (vLLM/SGLang) est sain et accessible.
- Les prompts de scoring sont versionnés et produisent une sortie structurée et analysable.
- Les exemples filtrés sont sauvegardés avec leurs scores pour l'auditabilité.
- Les données synthétiques sont signalées avec un champ
synthetic: true. - Les résultats batch sont reproductibles (temperature=0 pour le scoring, graine fixe pour la génération).
- Une fiche de dataset avant/après documente ce qui a été filtré et pourquoi.