Chapitre 3 : Oubli Catastrophique
Préserver les Connaissances Pendant le Fine-Tuning
L'oubli catastrophique est l'un des plus grands défis du fine-tuning. Le comprendre vous aide à concevoir de meilleures stratégies d'entraînement.
Qu'est-ce que l'Oubli Catastrophique ?
L'oubli catastrophique se produit lorsqu'un réseau neuronal perd des connaissances précédemment acquises après avoir été entraîné sur de nouvelles données.
Scénario d'Exemple
Un LLM pré-entraîné sait :
- Comment traduire des langues
- Connaissances générales du monde
- Raisonnement mathématique
- Génération de code
Après un fine-tuning sur des données de support client :
- ✅ Excellent en support client
- ❌ Peut avoir des difficultés avec la traduction
- ❌ Peut perdre certaines connaissances générales
- ❌ La qualité de la génération de code peut diminuer
Pourquoi Cela Se Produit-il ?
Interférence des Poids
Les réseaux neuronaux stockent les connaissances dans leurs poids. Lorsque vous vous entraînez sur de nouvelles données :
Poids originaux (W) → encodent des connaissances générales
Nouvel entraînement → met à jour W en (W')
W' optimise pour la nouvelle tâche → peut casser les représentations des anciennes tâches
Changement de Distribution
Le modèle s'adapte à votre distribution de données spécifique, perdant potentiellement en généralisation.
Mesurer l'Oubli Catastrophique
Évaluation Avant/Après
from lm_eval import evaluator
from lm_eval.models.huggingface import HFLM
def evaluate_model(model_path, tasks=["hellaswag", "arc_easy", "mmlu"]):
"""Évaluer le modèle sur des benchmarks standard."""
model = HFLM(pretrained=model_path)
results = evaluator.simple_evaluate(
model=model,
tasks=tasks,
num_fewshot=0,
)
return results
# Avant le fine-tuning
baseline_results = evaluate_model("meta-llama/Llama-3.1-8B")
print("Baseline:", baseline_results)
# Après le fine-tuning
finetuned_results = evaluate_model("./my-finetuned-model")
print("Fine-tuned:", finetuned_results)
# Comparer
for task in ["hellaswag", "arc_easy", "mmlu"]:
baseline = baseline_results["results"][task]["acc"]
finetuned = finetuned_results["results"][task]["acc"]
diff = finetuned - baseline
print(f"{task}: {diff:+.2%} ({'↑' if diff > 0 else '↓'})")
Tests Spécifiques aux Tâches
# Créer une suite de tests pour les capacités que vous souhaitez préserver
test_cases = [
{"prompt": "Translate to French: Hello, how are you?",
"expected_contains": "Bonjour"},
{"prompt": "What is 15 * 17?",
"expected_contains": "255"},
{"prompt": "Write a Python function to reverse a string",
"expected_contains": "def"},
]
def test_capabilities(model, tokenizer, test_cases):
"""Tester si le modèle conserve des capacités spécifiques."""
for test in test_cases:
output = model.generate(
tokenizer.encode(test["prompt"], return_tensors="pt"),
max_new_tokens=100
)
response = tokenizer.decode(output[0], skip_special_tokens=True)
passed = test["expected_contains"] in response
print(f"{'✅' if passed else '❌'} {test['prompt'][:50]}...")
Stratégies d'Atténuation
1. Utiliser des Taux d'Apprentissage Plus Bas
# Fine-tuning complet : un LR plus bas préserve plus de connaissances
training_args = TrainingArguments(
learning_rate=1e-5, # Plus bas que par défaut
# vs 2e-5 ou plus pour PEFT
)
2. Moins d'Époques d'Entraînement
# Moins d'entraînement = moins d'oubli
training_args = TrainingArguments(
num_train_epochs=1, # Commencer avec 1
# Surveiller la performance de validation
# N'augmenter que si sous-apprentissage
)
3. Techniques de Régularisation
Décroissance des Poids
training_args = TrainingArguments(
weight_decay=0.01, # Pénaliser les grands changements de poids
)
Consolidation Élastique des Poids (EWC)
# EWC ajoute une pénalité pour changer les poids importants
# (Technique de recherche, pas directement dans HF Trainer)
loss = cross_entropy_loss + lambda * sum(
importance[i] * (theta[i] - theta_star[i])^2
for i in parameters
)
4. Mélange / Relecture de Données
# Mélanger les données de style pré-entraînement originales avec les données de fine-tuning
from datasets import concatenate_datasets
# Vos données spécifiques
finetuning_data = load_dataset("your/dataset")
# Données d'instruction générales pour maintenir les capacités
general_data = load_dataset("databricks/dolly-15k", split="train[:1000]")
# Les mélanger (par exemple, 80% spécifiques, 20% générales)
mixed_dataset = concatenate_datasets([
finetuning_data,
general_data.shuffle().select(range(len(finetuning_data) // 4))
])
5. Utiliser PEFT à la Place
La mitigation la plus efficace : ne mettez pas à jour tous les poids
from peft import LoraConfig, get_peft_model
# Ne mettre à jour que de petits adaptateurs, préservant le modèle de base
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
)
model = get_peft_model(base_model, lora_config)
# Poids du modèle de base gelés → pas d'oubli catastrophique !
Avantage de PEFT
Pourquoi PEFT Empêche l'Oubli
Preuves Empiriques
| Méthode | Performance de la Tâche | Rétention du Benchmark |
|---|---|---|
| Fine-tuning Complet | 95% | 70-85% |
| LoRA | 93% | 95-99% |
| QLoRA | 92% | 95-99% |
Les méthodes PEFT conservent généralement 95%+ de la performance de base sur des tâches non liées.
Stratégie d'Évaluation
Protocole d'Évaluation Complet
def comprehensive_evaluation(model_path, baseline_path):
"""Évaluer à la fois la performance de la tâche et la rétention des capacités."""
# 1. Évaluation spécifique à la tâche (votre objectif de fine-tuning)
task_score = evaluate_on_task(model_path, your_test_set)
# 2. Benchmarks de capacités générales
benchmarks = ["hellaswag", "arc_easy", "mmlu", "gsm8k"]
baseline_scores = {}
finetuned_scores = {}
for bench in benchmarks:
baseline_scores[bench] = evaluate_benchmark(baseline_path, bench)
finetuned_scores[bench] = evaluate_benchmark(model_path, bench)
# 3. Calculer la rétention
retention = {}
for bench in benchmarks:
retention[bench] = finetuned_scores[bench] / baseline_scores[bench]
avg_retention = sum(retention.values()) / len(retention)
print(f"Performance de la Tâche : {task_score:.2%}")
print(f"Rétention Moyenne des Capacités : {avg_retention:.2%}")
# 4. Analyse des compromis
if task_score > 0.90 and avg_retention > 0.95:
print("✅ Bon équilibre atteint")
elif task_score > 0.90 and avg_retention < 0.90:
print("⚠️ Haute performance de tâche mais oubli significatif")
else:
print("❌ Envisagez d'ajuster l'entraînement")
return {
"task_score": task_score,
"retention": retention,
"avg_retention": avg_retention
}
Résumé des Meilleures Pratiques
Pour Minimiser l'Oubli Catastrophique :
| Stratégie | Difficulté | Efficacité |
|---|---|---|
| Utiliser PEFT (LoRA/QLoRA) | Facile | ⭐⭐⭐⭐⭐ |
| Taux d'apprentissage plus bas | Facile | ⭐⭐⭐ |
| Moins d'époques | Facile | ⭐⭐⭐ |
| Mélange de données | Moyen | ⭐⭐⭐⭐ |
| Décroissance des poids | Facile | ⭐⭐ |
| Arrêt précoce | Facile | ⭐⭐⭐ |
Approche Recommandée
- Commencez avec PEFT (LoRA ou QLoRA)
- Si PEFT est insuffisant, essayez le fine-tuning complet avec :
- Taux d'apprentissage bas (1e-5)
- Époques limitées (1-2)
- Mélange de données
- Évaluation complète
Points Clés à Retenir
✅ L'oubli catastrophique est lorsque les modèles perdent des connaissances après le fine-tuning
✅ Cela se produit parce que les poids sont modifiés pour s'adapter à une nouvelle distribution de données
✅ Les méthodes PEFT évitent largement l'oubli en gardant les poids de base gelés
✅ Pour le fine-tuning complet : utilisez un LR bas, peu d'époques, mélange de données
✅ Évaluez toujours à la fois la performance de la tâche ET la rétention des capacités
✅ Le compromis est : spécialisation de la tâche vs capacité générale
Prochaines Étapes
Testez vos connaissances avec le Quiz du Module 2, puis continuez vers Module 3 : Fine-Tuning Efficace en Paramètres.
Vérification Rapide
Avant de continuer, assurez-vous de pouvoir répondre :
- Qu'est-ce que l'oubli catastrophique et pourquoi se produit-il ?
- Comment les méthodes PEFT aident-elles à prévenir l'oubli catastrophique ?
- Quelles sont trois stratégies pour minimiser l'oubli dans le fine-tuning complet ?