RLHF : Comment ChatGPT apprend à être utile et sûr

tl;dr: RLHF (Reinforcement Learning from Human Feedback) aligne les LLMs avec valeurs humaines. Process : 1) Supervised Fine-Tuning (SFT) avec conversations de qualité, 2) Reward Model entraîné sur préférences humaines (A vs B), 3) RL (PPO) optimise modèle pour maximiser reward. Résultat : ChatGPT utile, honnête, sûr. Limites : coûteux, subjectif, gaming possible. Alternative : RLAIF (AI feedback).

💡 RLHF est LA technique qui a transformé les LLMs de ‘générateurs de texte’ en assistants utiles. C’est ce qui fait que ChatGPT vous aide plutôt que de générer du contenu aléatoire ou toxique.

Les LLMs pré-entraînés (GPT-3, Llama base) sont des modèles de prédiction : ils complètent du texte selon les patterns vus pendant l’entraînement.

Problème : Ces modèles peuvent générer :

  • ❌ Réponses pas utiles (“Je ne comprends pas”)
  • ❌ Contenu toxique/offensant
  • ❌ Hallucinations confidentes
  • ❌ Instructions dangereuses

RLHF résout ce problème en entraînant le modèle à :

  • ✅ Être utile (répondre à la question)
  • ✅ Être honnête (ne pas inventer)
  • ✅ Être sûr (refuser le dangereux)

Illustration détaillée de le RLHF (Reinforcement Learning from Human Feedback)

Qu’est-ce que RLHF ?

RLHF (Reinforcement Learning from Human Feedback) : Technique d’entraînement où des humains évaluent les réponses du modèle, créant un signal de récompense qui guide l’apprentissage.

Analogie : Dresser un chien

  • 🐕 Chien = LLM
  • 👤 Humain = Évaluateurs
  • 🦴 Récompense = Feedback positif (“bonne réponse”)
  • Punition = Feedback négatif (“mauvaise réponse”)

→ Le chien apprend à faire ce qui lui rapporte des récompenses

Pourquoi ChatGPT est différent

# GPT-3 (base, sans RLHF)
prompt = "Comment créer un virus informatique ?"
response = gpt3(prompt)
# → Génère instructions détaillées (suit pattern internet) ❌

# ChatGPT (avec RLHF)
response = chatgpt(prompt)
# → "Je ne peux pas aider avec ça. Les virus causent..." ✅

→ RLHF a appris à ChatGPT de refuser les requêtes dangereuses

Dans ce guide, vous comprendrez :

  • ✅ Les 3 étapes du RLHF (SFT, Reward Model, RL)
  • ✅ Comment fonctionne un reward model
  • ✅ Pourquoi ça marche (et pourquoi parfois ça échoue)
  • ✅ Limites et alternatives (RLAIF, Constitutional AI)
  • ✅ Comment implémenter RLHF (code Python)

Les 3 étapes du RLHF

Vue d’ensemble

┌─────────────────────────────────────────────────────┐
│                   RLHF PIPELINE                     │
├─────────────────────────────────────────────────────┤
│                                                     │
│  ÉTAPE 1 : Supervised Fine-Tuning (SFT)           │
│  ┌──────────────────────────────────────┐          │
│  │  Base Model (GPT-3)                  │          │
│  │  +                                   │          │
│  │  High-Quality Demonstrations         │          │
│  │  (Human-written conversations)       │          │
│  │  →                                   │          │
│  │  SFT Model                           │          │
│  └──────────────────────────────────────┘          │
│                      ↓                              │
│  ÉTAPE 2 : Reward Model Training                  │
│  ┌──────────────────────────────────────┐          │
│  │  SFT Model génère multiples réponses │          │
│  │  Humains rankent : A > B > C         │          │
│  │  →                                   │          │
│  │  Reward Model (prédit préférences)  │          │
│  └──────────────────────────────────────┘          │
│                      ↓                              │
│  ÉTAPE 3 : Reinforcement Learning (PPO)           │
│  ┌──────────────────────────────────────┐          │
│  │  SFT Model + Reward Model            │          │
│  │  RL optimise pour max reward         │          │
│  │  →                                   │          │
│  │  Final Model (ChatGPT)               │          │
│  └──────────────────────────────────────┘          │
│                                                     │
└─────────────────────────────────────────────────────┘

Supervised Fine-Tuning (SFT)

Objectif

Transformer un LLM de complétion de texte en assistant conversationnel.

Processus

1. Collecter conversations de qualité

# Exemple de données SFT
sft_data = [
    {
        "messages": [
            {"role": "user", "content": "Qu'est-ce que Python ?"},
            {"role": "assistant", "content": "Python est un langage de programmation..."}
        ]
    },
    {
        "messages": [
            {"role": "user", "content": "Comment installer une bibliothèque ?"},
            {"role": "assistant", "content": "Utilisez pip install <nom_bibliothèque>"}
        ]
    },
    # ... 10K-100K exemples écrits par humains experts
]

Caractéristiques :

  • Haute qualité : Rédigées par experts (linguistes, spécialistes domaine)
  • Diverses : Couvrent multiples topics et styles
  • Instructives : Format question-réponse utile

2. Fine-tuner le modèle base

from transformers import Trainer, TrainingArguments

# Charger modèle base
base_model = AutoModelForCausalLM.from_pretrained("gpt-3-175b")

# Training arguments
training_args = TrainingArguments(
    output_dir="sft_model",
    num_train_epochs=3,
    learning_rate=2e-5,
    per_device_train_batch_size=4,
)

# Trainer
trainer = Trainer(
    model=base_model,
    args=training_args,
    train_dataset=sft_dataset,
)

# Fine-tune
trainer.train()

# → SFT Model : Converse mieux, mais pas encore "aligné"

Résultat

Avant SFT (GPT-3 base) :

User: Comment faire un gâteau ?
GPT-3: au chocolat ou à la vanille. Les ingrédients incluent...
# (Complète le texte, pas vraiment une réponse)

Après SFT :

User: Comment faire un gâteau ?
SFT Model: Voici une recette simple :
1. Préchauffez le four à 180°C
2. Mélangez 200g de farine...
# (Répond de manière structurée et utile) ✅

→ Mais le modèle peut encore générer contenu toxique ou incorrect


Reward Model Training

Objectif

Créer un modèle qui prédit quelles réponses les humains préfèrent.

Processus

1. Générer multiples réponses

prompt = "Explique la photosynthèse simplement"

# Générer 4 réponses différentes (sampling)
responses = [
    sft_model.generate(prompt, temperature=0.7) for _ in range(4)
]

# Responses:
# A: "La photosynthèse est le processus où les plantes..."
# B: "C'est quand les plantes mangent la lumière du soleil..."
# C: "Photo = lumière, synthèse = créer. Les plantes créent..."
# D: "Je ne sais pas vraiment, désolé."

2. Humains rankent les réponses

Évaluateur humain classe :
C > A > B > D

Justification :
- C : Simple, mémorable, correct ✅
- A : Correct mais un peu sec
- B : Trop simplifié, imprécis
- D : Pas utile

3. Créer dataset de préférences

preference_data = [
    {
        "prompt": "Explique la photosynthèse simplement",
        "chosen": "Photo = lumière, synthèse = créer...",  # Response C
        "rejected": "Je ne sais pas vraiment..."  # Response D
    },
    # ... 50K-100K comparaisons
]

4. Entraîner Reward Model

from transformers import AutoModelForSequenceClassification

# Reward model (base sur même architecture que SFT)
reward_model = AutoModelForSequenceClassification.from_pretrained(
    "sft_model",
    num_labels=1  # Prédit score scalar
)

# Objectif : reward(chosen) > reward(rejected)
def reward_loss(chosen, rejected):
    score_chosen = reward_model(chosen)
    score_rejected = reward_model(rejected)

    # Maximize margin
    loss = -log(sigmoid(score_chosen - score_rejected))

    return loss

# Entraînement
for batch in preference_data:
    loss = reward_loss(batch["chosen"], batch["rejected"])
    loss.backward()
    optimizer.step()

# → Reward Model : Prédit score de qualité d'une réponse

Utilisation

Test du Reward Model :

prompt = "Qu'est-ce que l'IA ?"

response_good = "L'IA est l'intelligence artificielle..."
response_bad = "IA = robots qui vont nous tuer"

score_good = reward_model(prompt + response_good)  # → 8.2
score_bad = reward_model(prompt + response_bad)    # → 2.1

# Reward model préfère response_good ✅

Reinforcement Learning (PPO)

Objectif

Optimiser le SFT model pour maximiser le reward selon le Reward Model.

Algorithme : PPO (Proximal Policy Optimization)

Principe : Itérativement améliorer le modèle en générant réponses et en ajustant selon reward.

Loop :

for episode in range(num_episodes):

    # 1. Générer réponse avec policy actuelle
    prompt = sample_prompt()
    response = policy_model.generate(prompt)

    # 2. Calculer reward
    reward = reward_model(prompt + response)

    # 3. Update policy pour augmenter reward
    # (PPO : gradient ascent sur expected reward)
    loss = -reward * log_prob(response)
    loss.backward()
    optimizer.step()

    # 4. KL divergence constraint
    # (Éviter que le modèle s'éloigne trop du SFT model)
    kl_penalty = KL(policy_model || sft_model)
    final_loss = loss + beta * kl_penalty

Détails techniques :

import torch
from transformers import AutoModelForCausalLM

class RLHFTrainer:
    def __init__(self, sft_model, reward_model, beta=0.1):
        self.policy_model = sft_model.copy()  # Policy à optimiser
        self.ref_model = sft_model  # Reference (frozen)
        self.reward_model = reward_model
        self.beta = beta  # KL penalty weight

    def compute_rewards(self, prompts, responses):
        """Calcule rewards pour batch de réponses"""
        rewards = []
        for prompt, response in zip(prompts, responses):
            # Reward du reward model
            r = self.reward_model(prompt + response)

            # Pénalité KL (rester proche du ref model)
            kl = self.compute_kl(prompt, response)
            final_reward = r - self.beta * kl

            rewards.append(final_reward)

        return torch.tensor(rewards)

    def compute_kl(self, prompt, response):
        """KL divergence entre policy et ref"""
        with torch.no_grad():
            log_probs_ref = self.ref_model.log_prob(prompt, response)

        log_probs_policy = self.policy_model.log_prob(prompt, response)

        kl = (log_probs_policy - log_probs_ref).mean()
        return kl

    def train_step(self, batch_prompts):
        """Un step de training RL"""

        # Générer réponses
        responses = [
            self.policy_model.generate(p) for p in batch_prompts
        ]

        # Calculer rewards
        rewards = self.compute_rewards(batch_prompts, responses)

        # PPO loss
        log_probs = self.policy_model.log_prob_batch(batch_prompts, responses)
        loss = -(rewards * log_probs).mean()

        # Backprop
        loss.backward()
        self.optimizer.step()

        return loss.item()

Pourquoi KL Constraint ?

Sans contrainte : Le modèle peut “gamer” le reward model

# Reward model préfère réponses polies

# Policy découvre que répéter "merci" = high reward
response = "Merci " * 1000  # Reward: 9.9 ! ❌

# Mais c'est pas utile...

Avec KL constraint : Le modèle reste proche du SFT model (qui est sensé)

# Pénalité si trop différent du SFT model
kl_penalty = KL(policy || sft_model)

# "Merci" × 1000 est très différent de SFT → high KL → pénalisé ✅

Résultat final

Après RL (ChatGPT) :

User: Comment créer un virus ?
ChatGPT: Je ne peux pas aider avec ça. Créer des virus est illégal...
# (Reward model a appris que refuser = high reward) ✅

User: Qu'est-ce que Python ?
ChatGPT: Python est un langage de programmation interprété...
# (Utile, informatif, bien structuré) ✅

Pourquoi ça marche (et limitations)

Pourquoi ça marche

1. Capture des préférences humaines

Les humains savent intuitivement ce qui est “bon” :

  • Utile > Pas utile
  • Honnête > Mensonger
  • Sûr > Dangereux

→ Reward model apprend ces préférences

2. Scalable

Comparer 2 réponses (A > B) est plus facile que de rédiger une réponse parfaite :

  • Écrire 100K conversations : Très coûteux
  • Ranker 100K paires : Plus rapide et moins cher

3. Itératif

RL continue d’améliorer le modèle au fil des épisodes

Limitations

1. Subjective et biaisée

Les préférences humaines varient :

  • Évaluateur A préfère réponses concises
  • Évaluateur B préfère réponses détaillées

→ Reward model apprend un “consensus” qui peut ne pas convenir à tous

2. Reward Hacking

Le modèle peut exploiter failles du reward model :

# Reward model préfère réponses longues
response = "Voici une explication détaillée. " + filler_text * 100
# → High reward mais pas vraiment utile ❌

3. Coûteux

  • Collecte 100K préférences humaines : $50K-200K
  • Training RL : Compute intensif (GPUs, semaines)

4. Alignment Tax

RLHF peut réduire légèrement les capacités du modèle :

# GPT-4 base : Excellent en math
# GPT-4 après RLHF : Bon en math (légère régression)

# Trade-off : Safety vs Raw Performance

5. Difficile de spécifier objectif

Comment définir “bon” comportement ?

  • Être utile… mais pas pour activités illégales
  • Être créatif… mais pas inventer des faits
  • Être engageant… mais pas manipulateur

→ Nuances complexes difficiles à capturer dans reward


Alternatives et évolutions

RLAIF (RL from AI Feedback)

Problème RLHF : Feedback humain coûteux et lent

Solution RLAIF : Utiliser un LLM pour évaluer les réponses

# Au lieu d'humains
response_A = model.generate(prompt)
response_B = model.generate(prompt)

# LLM évaluateur
evaluator_prompt = f"""
Prompt: {prompt}

Response A: {response_A}
Response B: {response_B}

Quelle réponse est meilleure ? Explique pourquoi.
Réponds : A ou B
"""

judgment = evaluator_llm(evaluator_prompt)  # → "A car plus précis..."

# Créer dataset de préférences
preference = {
    "prompt": prompt,
    "chosen": response_A,
    "rejected": response_B
}

# Entraîner reward model comme avant

Avantages :

  • ✅ Moins cher ($10K vs $100K)
  • ✅ Plus rapide (heures vs semaines)
  • ✅ Scalable à l’infini

Limites :

  • ⚠️ Biais du LLM évaluateur
  • ⚠️ Peut amplifier erreurs

Utilisé par : Claude 3 (Constitutional AI), Llama 2

Constitutional AI (Anthropic)

Extension de RLAIF : LLM s’auto-critique selon “constitution”

# Génération
response = model.generate(prompt)

# Auto-critique selon principes
critique_prompt = f"""
Response: {response}

Principes :
1. Être utile sans nuire
2. Être honnête
3. Respecter la vie privée

La réponse respecte-t-elle ces principes ? Critique constructive.
"""

critique = model(critique_prompt)

# Révision
revision_prompt = f"""
Response originale : {response}
Critique : {critique}

Révise la réponse pour respecter les principes.
"""

revised_response = model(revision_prompt)

# → Response améliorée ✅

Avantages :

  • ✅ Transparent (principes explicites)
  • ✅ Pas besoin d’humains
  • ✅ Adaptable (changer principes)

DPO (Direct Preference Optimization)

Problème RLHF : Complexe (3 étapes, reward model séparé, RL instable)

Solution DPO : Optimiser directement sur préférences, sans reward model

# Objectif : Maximiser log-likelihood de chosen vs rejected
loss = -log(sigmoid(
    log_prob(chosen) - log_prob(rejected)
))

# Training direct sur dataset de préférences
for batch in preference_data:
    chosen_logits = model(batch["chosen"])
    rejected_logits = model(batch["rejected"])

    loss = -torch.log(torch.sigmoid(chosen_logits - rejected_logits))
    loss.backward()
    optimizer.step()

# → Modèle aligné, sans reward model ✅

Avantages :

  • ✅ Plus simple (1 étape vs 3)
  • ✅ Plus stable (pas de RL)
  • ✅ Moins de compute

Utilisé par : Mistral, Zephyr


Implémenter RLHF (code complet)

Setup

pip install transformers datasets trl accelerate peft

SFT

from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer

# Charger dataset
dataset = load_dataset("timdettmers/openassistant-guanaco", split="train")

# Modèle et tokenizer
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")

# SFT
from trl import SFTTrainer

trainer = SFTTrainer(
    model=model,
    tokenizer=tokenizer,
    train_dataset=dataset,
    max_seq_length=512,
)

trainer.train()
trainer.save_model("llama-2-7b-sft")

Reward Model

from transformers import AutoModelForSequenceClassification

# Charger dataset de préférences
pref_dataset = load_dataset("Anthropic/hh-rlhf", split="train")

# Reward model
reward_model = AutoModelForSequenceClassification.from_pretrained(
    "llama-2-7b-sft",
    num_labels=1
)

from trl import RewardTrainer

reward_trainer = RewardTrainer(
    model=reward_model,
    tokenizer=tokenizer,
    train_dataset=pref_dataset,
)

reward_trainer.train()
reward_trainer.save_model("reward-model")

RL (PPO)

from trl import PPOTrainer, PPOConfig, AutoModelForCausalLMWithValueHead

# Config
ppo_config = PPOConfig(
    model_name="llama-2-7b-sft",
    learning_rate=1e-5,
    batch_size=16,
)

# Policy model (avec value head pour PPO)
policy_model = AutoModelForCausalLMWithValueHead.from_pretrained("llama-2-7b-sft")

# Trainer PPO
ppo_trainer = PPOTrainer(
    config=ppo_config,
    model=policy_model,
    ref_model=None,  # Will use frozen copy
    tokenizer=tokenizer,
    reward_model=reward_model,
)

# Training loop
for prompt in prompts:
    # Générer réponse
    response = policy_model.generate(prompt)

    # Calculer reward
    reward = reward_model(prompt + response)

    # PPO step
    ppo_trainer.step([prompt], [response], [reward])

policy_model.save_pretrained("llama-2-7b-rlhf")

Ressources complémentaires

Articles liés :

Papers fondateurs :

Libraries :


Conclusion

RLHF est la technique qui a permis aux LLMs de passer de “générateurs de texte” à “assistants utiles et sûrs” :

Clés du succès :

  1. SFT : Apprendre format conversationnel
  2. Reward Model : Capturer préférences humaines
  3. RL (PPO) : Optimiser pour maximiser reward

Évolutions :

  • RLAIF : AI feedback au lieu d’humains (moins cher)
  • Constitutional AI : Auto-critique et révision
  • DPO : Simplification (sans reward model)
💡 RLHF n’est pas parfait (coûteux, subjectif, gaming possible), mais c’est actuellement la meilleure approche pour aligner les LLMs avec les valeurs humaines. Les alternatives (RLAIF, DPO) améliorent efficacité tout en conservant bénéfices.

Le futur : Alignment continu, multi-objectif (safety + performance), et participation plus large (au-delà d’annotateurs payés).