{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "7374bcb9-58a6-4cad-b50b-2324c7290400",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Installation terminée.\n"
     ]
    }
   ],
   "source": [
    "# Installation des librairies nécessaires\n",
    "!pip install datasets transformers[torch] accelerate -q\n",
    "# Librairie pour la métrique d'évaluation ROUGE\n",
    "!pip install rouge_score -q\n",
    "\n",
    "print(\"Installation terminée.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "02ccff69-deac-4188-9478-3a0eded45a16",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Modèle choisi pour cette session : moussaKam/barthez\n"
     ]
    }
   ],
   "source": [
    "import re\n",
    "import pandas as pd\n",
    "from datasets import load_dataset\n",
    "from transformers import AutoTokenizer\n",
    "\n",
    "# --- PARAMÈTRE PRINCIPAL DU NOTEBOOK ---\n",
    "# Changez cette variable pour tester un autre modèle (ex: \"plguillou/t5-base-fr\")\n",
    "model_checkpoint = \"moussaKam/barthez\" \n",
    "\n",
    "print(f\"Modèle choisi pour cette session : {model_checkpoint}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "1301a599-a67e-4566-875e-64917eaa2f0f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Chargement du jeu de données PleIAs/French-PD-Books...\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "db73dc72d8a74927b2c6267c5783b007",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Resolving data files:   0%|          | 0/145 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c22b630c067e498ca0944b20027d23a4",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Loading dataset shards:   0%|          | 0/145 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Jeu de données chargé.\n",
      "Nombre d'exemples avant nettoyage : 289577\n",
      "\n",
      "Application du nettoyage sur le dataset...\n",
      "Filtrage des données invalides...\n",
      "Nombre d'exemples après nettoyage et filtrage : 1000\n",
      "\n",
      "--- Exemple après nettoyage ---\n",
      "Date: 1923\n",
      "Texte: MINISTÈRE DES AFFAIRES ÉTRANGÈRES DOCUMENTS DIPLOMATIQUES REPONSE DU GOUVERNEMENT FRANÇAIS À LA LETTRE DU GOUVERNEMENT BRITANNIQUE DU il AOÛT 1923 SUR LES RÉPARATIONS 20 AOUT 1923 PARIS IMPRIMERIE NATIONALE MDCGCCXMII MINISTÈRE DES AFFAIRES ÉTRANGÈRES DOCUMENTS DIPLOMATIQUES REPONSE DU GOUVERNEMENT FRANÇAIS À LA LETTRE DU GOUVERNEMENT RRITANNIQUE DU 11 AOÛT 1923 SUR LES RÉPARATIONS 20 AOUT 1923 LI\n"
     ]
    }
   ],
   "source": [
    "# Chargement du jeu de données\n",
    "print(\"Chargement du jeu de données PleIAs/French-PD-Books...\")\n",
    "ds = load_dataset(\"PleIAs/French-PD-Books\", split=\"train\")\n",
    "print(\"Jeu de données chargé.\")\n",
    "print(f\"Nombre d'exemples avant nettoyage : {len(ds)}\")\n",
    "\n",
    "# --- Vos fonctions de nettoyage et de vérification ---\n",
    "def clean_text(example):\n",
    "    text = example[\"complete_text\"]\n",
    "    date = example.get(\"date\", None)\n",
    "\n",
    "    # Si la date contient un \"-\", on essaie d'extraire l'année connue ou moyenne\n",
    "    if date and \"-\" in str(date):\n",
    "        parts = str(date).split(\"-\")\n",
    "        if (parts[1].isdigit() and len(parts[1]) == 4) and parts[0] == \"????\":\n",
    "            date = str(parts[1])\n",
    "        else:\n",
    "            date = str(parts[0])\n",
    "\n",
    "    # Appliquer les nettoyages seulement si le texte est une chaîne de caractères\n",
    "    if isinstance(text, str):\n",
    "        # 1. Retirer les numéros de page\n",
    "        text = re.sub(r\"[—\\-–]\\s*\\d+\\s*[—\\-–]\", \" \", text)\n",
    "        # 2. Corriger les caractères échappés\n",
    "        text = text.replace(\"\\\\'\", \"'\").replace(\"\\\\\\\"\", \"\\\"\").replace(\"\\\\n\", \" \").replace(\"\\\\r\", \" \").replace(\"\\\\t\", \" \")\n",
    "        # 3. & 4. Corriger les mots coupés et espaces multiples (simplifié)\n",
    "        text = re.sub(r'([a-zàâäæçéèêëïîôùûüœ])\\s{2,}([a-zàâäæçéèêëïîôùûüœ])', r'\\1 \\2', text)\n",
    "        # 5. Normaliser les espaces\n",
    "        text = re.sub(r\"\\s+\", \" \", text)\n",
    "        # 6. Nettoyer les caractères spéciaux\n",
    "        text = re.sub(r\"[^\\w\\s\\.,;:\\?!'\\-\\\"«»À-ÖØ-öø-ÿœŒ]\", \" \", text)\n",
    "        # 7. Re-normaliser\n",
    "        text = re.sub(r\"\\s+\", \" \", text)\n",
    "        # 8. Corriger la ponctuation\n",
    "        text = re.sub(r\"\\s+([,.\\?!;:])\", r\"\\1\", text)\n",
    "        text = re.sub(r\"([,.\\?!;:])\\s*([,.\\?!;:])\", r\"\\1\\2\", text)\n",
    "        text = text.strip()\n",
    "    else:\n",
    "        text = \"\" # Si le texte n'est pas une chaîne, le remplacer par une chaîne vide\n",
    "\n",
    "    return {\"text\": text, \"date\": str(date)}\n",
    "\n",
    "# Application de la fonction de nettoyage\n",
    "print(\"\\nApplication du nettoyage sur le dataset...\")\n",
    "reduced_ds = ds.shuffle(seed=42).select(range(1000))\n",
    "ds_cleaned = reduced_ds.map(clean_text, num_proc=4) # Utilise plusieurs processeurs pour accélérer\n",
    "\n",
    "# Filtrage des données invalides (dates incorrectes ou textes vides)\n",
    "print(\"Filtrage des données invalides...\")\n",
    "ds_filtered = ds_cleaned.filter(lambda example: \n",
    "    example['date'].isdigit() and \n",
    "    len(example['date']) == 4 and \n",
    "    len(example['text']) > 50 # Garder des textes d'une longueur minimale\n",
    ")\n",
    "\n",
    "print(f\"Nombre d'exemples après nettoyage et filtrage : {len(ds_filtered)}\")\n",
    "\n",
    "# Affichage d'un exemple pour vérification\n",
    "print(\"\\n--- Exemple après nettoyage ---\")\n",
    "example = ds_filtered[500]\n",
    "print(f\"Date: {example['date']}\")\n",
    "print(f\"Texte: {example['text'][:400]}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c761be3f-049c-4b48-aa37-4e2f445eac6a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Formatage des données pour la tâche sequence-to-sequence...\n",
      "Formatage terminé.\n",
      "\n",
      "--- Exemple au format Seq2Seq ---\n",
      "SOURCE (entrée modèle):\n",
      "réécris ce texte dans le style des années 1920: MINISTÈRE DES AFFAIRES ÉTRANGÈRES DOCUMENTS DIPLOMATIQUES REPONSE DU GOUVERNEMENT FRANÇAIS À LA LETTRE DU GOUVERNEMENT BRITANNIQUE DU il AOÛT 1923 SUR LES RÉPARATIONS 20 AOUT 1923 PARIS IMPRIMERIE NATIONALE MDCGCCXMII MINISTÈRE DES AFFAIRES ÉTRANGÈRES DOCUMENTS DIPLOMATIQUES REPONSE DU GOUVERNEMENT FRANÇAIS À LA LETTRE DU GOUVERNEMENT RRITANNIQUE DU 11 AOÛT 1923 SUR LES RÉPARATIONS 20 AOUT 1923 LIVRE «.UNE. Réponse du Gouv. français. MINISTÈRE DES \n",
      "\n",
      "TARGET (sortie attendue):\n",
      "MINISTÈRE DES AFFAIRES ÉTRANGÈRES DOCUMENTS DIPLOMATIQUES REPONSE DU GOUVERNEMENT FRANÇAIS À LA LETTRE DU GOUVERNEMENT BRITANNIQUE DU il AOÛT 1923 SUR LES RÉPARATIONS 20 AOUT 1923 PARIS IMPRIMERIE NATIONALE MDCGCCXMII MINISTÈRE DES AFFAIRES ÉTRANGÈRES DOCUMENTS DIPLOMATIQUES REPONSE DU GOUVERNEMENT FRANÇAIS À LA LETTRE DU GOUVERNEMENT RRITANNIQUE DU 11 AOÛT 1923 SUR LES RÉPARATIONS 20 AOUT 1923 LI\n"
     ]
    }
   ],
   "source": [
    "# Fonction pour créer les entrées et sorties du modèle\n",
    "def create_seq2seq_format(example):\n",
    "    text = example['text']\n",
    "    year = int(example['date'])\n",
    "    \n",
    "    # Création de la décennie (ex: 1854 -> 1850)\n",
    "    decade = (year // 10) * 10\n",
    "    \n",
    "    # Formatage de l'entrée pour le modèle\n",
    "    # Le préfixe est crucial pour que le modèle comprenne la tâche\n",
    "    prefix = f\"réécris ce texte dans le style des années {decade}: \"\n",
    "    \n",
    "    # Source : ce que le modèle voit en entrée\n",
    "    example['source'] = prefix + text\n",
    "    # Cible : ce que le modèle doit apprendre à prédire\n",
    "    example['target'] = text\n",
    "    \n",
    "    return example\n",
    "\n",
    "print(\"\\nFormatage des données pour la tâche sequence-to-sequence...\")\n",
    "ds_seq2seq = ds_filtered.map(create_seq2seq_format, num_proc=4)\n",
    "\n",
    "print(\"Formatage terminé.\")\n",
    "print(\"\\n--- Exemple au format Seq2Seq ---\")\n",
    "example = ds_seq2seq[500]\n",
    "print(f\"SOURCE (entrée modèle):\\n{example['source'][:500]}\\n\")\n",
    "print(f\"TARGET (sortie attendue):\\n{example['target'][:400]}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "4861e244-c3e1-4f94-9c7f-9afb8cd30028",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "Lancement de la tokenisation avec le tokenizer de 'moussaKam/barthez'...\n",
      "\n",
      "--- Traitement manuel des 1000 premiers exemples ---\n",
      "--- Traitement et tokenisation des 1000 exemples ---\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "0b9ca76da4d040b098455e50f23f1fca",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "  0%|          | 0/1000 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "✅ Tokenisation manuelle terminée.\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "from tqdm.auto import tqdm\n",
    "from datasets import Dataset\n",
    "\n",
    "os.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\"\n",
    "\n",
    "# Chargement du tokenizer\n",
    "tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)\n",
    "\n",
    "# Longueurs maximales pour les séquences source et cible\n",
    "# À ajuster en fonction de la mémoire disponible et des capacités du modèle\n",
    "max_input_length = 512\n",
    "max_target_length = 512\n",
    "\n",
    "def tokenize_function(examples):\n",
    "    # Tokenisation des textes sources\n",
    "    model_inputs = tokenizer(\n",
    "        examples[\"source\"], \n",
    "        max_length=max_input_length, \n",
    "        truncation=True, \n",
    "        padding=\"max_length\"\n",
    "    )\n",
    "\n",
    "    # Tokenisation des textes cibles pour les labels\n",
    "    labels = tokenizer(\n",
    "        text_target=examples[\"target\"], \n",
    "        max_length=max_target_length, \n",
    "        truncation=True, \n",
    "        padding=\"max_length\"\n",
    "    )\n",
    "\n",
    "    # Assigner les labels tokenisés à la clé 'labels' que le modèle attend\n",
    "    model_inputs[\"labels\"] = labels[\"input_ids\"]\n",
    "    \n",
    "    # Remplacer les tokens de padding dans les labels par -100 pour qu'ils soient ignorés dans le calcul de la loss\n",
    "    for i, label_ids in enumerate(model_inputs[\"labels\"]):\n",
    "        model_inputs[\"labels\"][i] = [l if l != tokenizer.pad_token_id else -100 for l in label_ids]\n",
    "\n",
    "    return model_inputs\n",
    "\n",
    "print(f\"\\nLancement de la tokenisation avec le tokenizer de '{model_checkpoint}'...\")\n",
    "\n",
    "# On va tester sur les 1000 premiers exemples\n",
    "num_to_test = 1000\n",
    "problematic_index = -1\n",
    "\n",
    "print(f\"\\n--- Traitement manuel des {num_to_test} premiers exemples ---\")\n",
    "\n",
    "# La barre de progression (tqdm) nous montrera exactement où ça bloque\n",
    "# Liste pour stocker les résultats tokenisés\n",
    "tokenized_results = []\n",
    "\n",
    "# On utilise l'intégralité du dataset\n",
    "num_to_process = len(ds_seq2seq) \n",
    "\n",
    "print(f\"--- Traitement et tokenisation des {num_to_process} exemples ---\")\n",
    "\n",
    "# La boucle parcourt tout le dataset\n",
    "for i in tqdm(range(num_to_process)):\n",
    "    # On récupère un exemple\n",
    "    example = ds_seq2seq[i]\n",
    "    \n",
    "    # On le met dans un format de \"batch\" avec un seul élément\n",
    "    batch = {\n",
    "        \"source\": [example[\"source\"]],\n",
    "        \"target\": [example[\"target\"]]\n",
    "    }\n",
    "    \n",
    "    # On applique la fonction de tokenisation\n",
    "    tokenized_output = tokenize_function(batch)\n",
    "    \n",
    "    # On \"déballe\" le résultat du batch pour n'avoir qu'un seul dictionnaire\n",
    "    # et on l'ajoute à notre liste de résultats\n",
    "    tokenized_results.append({\n",
    "        'input_ids': tokenized_output['input_ids'][0],\n",
    "        'attention_mask': tokenized_output['attention_mask'][0],\n",
    "        'labels': tokenized_output['labels'][0]\n",
    "    })\n",
    "\n",
    "print(\"\\n✅ Tokenisation manuelle terminée.\")\n",
    "\n",
    "# Conversion de la liste de dictionnaires en un objet Dataset de Hugging Face\n",
    "ds_tokenized = Dataset.from_list(tokenized_results)\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "24742f22",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Taille du jeu d'entraînement : 900\n",
      "Taille du jeu de validation : 100\n",
      "DatasetDict({\n",
      "    train: Dataset({\n",
      "        features: ['input_ids', 'attention_mask', 'labels'],\n",
      "        num_rows: 900\n",
      "    })\n",
      "    validation: Dataset({\n",
      "        features: ['input_ids', 'attention_mask', 'labels'],\n",
      "        num_rows: 100\n",
      "    })\n",
      "})\n"
     ]
    }
   ],
   "source": [
    "# Fractionnement du jeu de données : 90% pour l'entraînement, 10% pour la validation\n",
    "ds_final = ds_tokenized.train_test_split(test_size=0.1)\n",
    "\n",
    "# Renommage pour plus de clarté\n",
    "ds_final[\"train\"] = ds_final.pop(\"train\")\n",
    "ds_final[\"validation\"] = ds_final.pop(\"test\")\n",
    "\n",
    "print(\"Taille du jeu d'entraînement :\", len(ds_final[\"train\"]))\n",
    "print(\"Taille du jeu de validation :\", len(ds_final[\"validation\"]))\n",
    "print(ds_final)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b5c47718",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: you may need to restart the kernel to use updated packages.\n",
      "Fonction de calcul des métriques définie.\n"
     ]
    }
   ],
   "source": [
    "%pip install evaluate -q\n",
    "\n",
    "import numpy as np\n",
    "import evaluate # Librairie de Hugging Face pour les métriques\n",
    "\n",
    "# Chargement de la métrique ROUGE\n",
    "rouge_metric = evaluate.load(\"rouge\")\n",
    "\n",
    "def compute_metrics(eval_preds):\n",
    "    preds, labels = eval_preds\n",
    "\n",
    "    # Décodage des prédictions générées par le modèle\n",
    "    # On remplace les tokens -100 (ignorés dans la loss) par le token de padding\n",
    "    preds[preds == -100] = tokenizer.pad_token_id\n",
    "    decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True)\n",
    "    \n",
    "    # Décodage des vrais labels de référence\n",
    "    labels[labels == -100] = tokenizer.pad_token_id\n",
    "    decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)\n",
    "    \n",
    "    # Calcul du score ROUGE\n",
    "    result = rouge_metric.compute(predictions=decoded_preds, references=decoded_labels, use_stemmer=True)\n",
    "    \n",
    "    # Ajout d'une métrique sur la longueur moyenne des prédictions\n",
    "    prediction_lens = [np.count_nonzero(pred != tokenizer.pad_token_id) for pred in preds]\n",
    "    result[\"gen_len\"] = np.mean(prediction_lens)\n",
    "    \n",
    "    return {k: round(v, 4) for k, v in result.items()}\n",
    "\n",
    "print(\"Fonction de calcul des métriques définie.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6d675f3e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[33mWARNING: Ignoring invalid distribution ~orch (/opt/conda/lib/python3.13/site-packages)\u001b[0m\u001b[33m\n",
      "\u001b[0m\u001b[33mWARNING: Ignoring invalid distribution ~orch (/opt/conda/lib/python3.13/site-packages)\u001b[0m\u001b[33m\n",
      "\u001b[0m\u001b[33mWARNING: Ignoring invalid distribution ~orch (/opt/conda/lib/python3.13/site-packages)\u001b[0m\u001b[33m\n",
      "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "# Install/upgrade all libraries in one command for better dependency resolution\n",
    "%pip install -U bitsandbytes accelerate peft transformers torch -q\n",
    "\n",
    "import torch\n",
    "import os\n",
    "from transformers import AutoModelForSeq2SeqLM, DataCollatorForSeq2Seq, Seq2SeqTrainingArguments, Seq2SeqTrainer\n",
    "from transformers import BitsAndBytesConfig\n",
    "from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training\n",
    "\n",
    "# Isolate your script on the GPU 1\n",
    "os.environ[\"CUDA_VISIBLE_DEVICES\"] = \"1\"\n",
    "\n",
    "# --- 1. Configuration de la Quantization (QLoRA) ---\n",
    "quantization_config = BitsAndBytesConfig(\n",
    "    load_in_4bit=True,\n",
    "    bnb_4bit_quant_type=\"nf4\",\n",
    "    bnb_4bit_compute_dtype=torch.bfloat16,\n",
    "    bnb_4bit_use_double_quant=True,\n",
    ")\n",
    "\n",
    "# --- 2. Chargement du Modèle quantizé ---\n",
    "model = AutoModelForSeq2SeqLM.from_pretrained(\n",
    "    model_checkpoint,\n",
    "    quantization_config=quantization_config,\n",
    "    device_map=\"auto\"\n",
    ")\n",
    "\n",
    "print(\"Modèle chargé en 4-bit (QLoRA) sur le GPU 1.\")\n",
    "\n",
    "# --- 3. Préparation du modèle et Configuration de LoRA ---\n",
    "model = prepare_model_for_kbit_training(model)\n",
    "\n",
    "# ## CORRECTIF ##: Ciblez toutes les couches linéaires pour assurer un chemin de gradient complet.\n",
    "lora_config = LoraConfig(\n",
    "    r=16,\n",
    "    lora_alpha=32,\n",
    "    target_modules=[\"q_proj\", \"v_proj\", \"k_proj\", \"out_proj\", \"fc1\", \"fc2\"],\n",
    "    lora_dropout=0.05,\n",
    "    bias=\"none\",\n",
    "    task_type=\"SEQ_2_SEQ_LM\"\n",
    ")\n",
    "\n",
    "model = get_peft_model(model, lora_config)\n",
    "model.print_trainable_parameters()\n",
    "\n",
    "# --- Le reste du code reste identique ---\n",
    "\n",
    "data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer, model=model)\n",
    "model_name = model_checkpoint.split(\"/\")[-1]\n",
    "output_dir = f\"{model_name}-qlora-finetuned-style-transfer\"\n",
    "\n",
    "training_args = Seq2SeqTrainingArguments(\n",
    "    output_dir=output_dir,\n",
    "    learning_rate=2e-4,\n",
    "    per_device_train_batch_size=4,\n",
    "    per_device_eval_batch_size=4,\n",
    "    gradient_accumulation_steps=4,\n",
    "    weight_decay=0.01,\n",
    "    num_train_epochs=3,\n",
    "    eval_strategy=\"epoch\",\n",
    "    save_strategy=\"epoch\",\n",
    "    load_best_model_at_end=True,\n",
    "    predict_with_generate=True,\n",
    "    fp16=False,\n",
    "    bf16=True,\n",
    "    push_to_hub=False,\n",
    ")\n",
    "\n",
    "# On retire l'argument 'tokenizer' qui est obsolète\n",
    "trainer = Seq2SeqTrainer(\n",
    "    model=model,\n",
    "    args=training_args,\n",
    "    train_dataset=ds_final[\"train\"],\n",
    "    eval_dataset=ds_final[\"validation\"],\n",
    "    data_collator=data_collator,\n",
    "    compute_metrics=compute_metrics,\n",
    ")\n",
    "\n",
    "print(\"Entraîneur (Trainer) prêt pour l'entraînement avec QLoRA sur le GPU 1.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "7249d1e7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "🚀 Début de l'entraînement...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/opt/conda/lib/python3.13/site-packages/torch/_dynamo/eval_frame.py:929: UserWarning: torch.utils.checkpoint: the use_reentrant parameter should be passed explicitly. In version 2.5 we will raise an exception if use_reentrant is not passed. use_reentrant=False is recommended, but if you need to preserve the current default behavior, you can pass use_reentrant=True. Refer to docs for more details on the differences between the two variants.\n",
      "  first_ctx: bool = False,\n",
      "/opt/conda/lib/python3.13/site-packages/torch/utils/checkpoint.py:85: UserWarning: None of the inputs have requires_grad=True. Gradients will be None\n",
      "  warnings.warn(\n"
     ]
    },
    {
     "ename": "RuntimeError",
     "evalue": "element 0 of tensors does not require grad and does not have a grad_fn",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mRuntimeError\u001b[39m                              Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[14]\u001b[39m\u001b[32m, line 3\u001b[39m\n\u001b[32m      1\u001b[39m \u001b[38;5;66;03m# Lancement de l'entraînement !\u001b[39;00m\n\u001b[32m      2\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33m🚀 Début de l\u001b[39m\u001b[33m'\u001b[39m\u001b[33mentraînement...\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m----> \u001b[39m\u001b[32m3\u001b[39m \u001b[43mtrainer\u001b[49m\u001b[43m.\u001b[49m\u001b[43mtrain\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m      4\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33m✅ Entraînement terminé !\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m      6\u001b[39m \u001b[38;5;66;03m# Sauvegarde du meilleur modèle dans le dossier de sortie\u001b[39;00m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/opt/conda/lib/python3.13/site-packages/transformers/trainer.py:2325\u001b[39m, in \u001b[36mTrainer.train\u001b[39m\u001b[34m(self, resume_from_checkpoint, trial, ignore_keys_for_eval, **kwargs)\u001b[39m\n\u001b[32m   2323\u001b[39m         hf_hub_utils.enable_progress_bars()\n\u001b[32m   2324\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m2325\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43minner_training_loop\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m   2326\u001b[39m \u001b[43m        \u001b[49m\u001b[43margs\u001b[49m\u001b[43m=\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   2327\u001b[39m \u001b[43m        \u001b[49m\u001b[43mresume_from_checkpoint\u001b[49m\u001b[43m=\u001b[49m\u001b[43mresume_from_checkpoint\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   2328\u001b[39m \u001b[43m        \u001b[49m\u001b[43mtrial\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtrial\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   2329\u001b[39m \u001b[43m        \u001b[49m\u001b[43mignore_keys_for_eval\u001b[49m\u001b[43m=\u001b[49m\u001b[43mignore_keys_for_eval\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m   2330\u001b[39m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/opt/conda/lib/python3.13/site-packages/transformers/trainer.py:2674\u001b[39m, in \u001b[36mTrainer._inner_training_loop\u001b[39m\u001b[34m(self, batch_size, args, resume_from_checkpoint, trial, ignore_keys_for_eval)\u001b[39m\n\u001b[32m   2667\u001b[39m context = (\n\u001b[32m   2668\u001b[39m     functools.partial(\u001b[38;5;28mself\u001b[39m.accelerator.no_sync, model=model)\n\u001b[32m   2669\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m i != \u001b[38;5;28mlen\u001b[39m(batch_samples) - \u001b[32m1\u001b[39m\n\u001b[32m   2670\u001b[39m     \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;28mself\u001b[39m.accelerator.distributed_type != DistributedType.DEEPSPEED\n\u001b[32m   2671\u001b[39m     \u001b[38;5;28;01melse\u001b[39;00m contextlib.nullcontext\n\u001b[32m   2672\u001b[39m )\n\u001b[32m   2673\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m context():\n\u001b[32m-> \u001b[39m\u001b[32m2674\u001b[39m     tr_loss_step = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mtraining_step\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnum_items_in_batch\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m   2676\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m (\n\u001b[32m   2677\u001b[39m     args.logging_nan_inf_filter\n\u001b[32m   2678\u001b[39m     \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m is_torch_xla_available()\n\u001b[32m   2679\u001b[39m     \u001b[38;5;129;01mand\u001b[39;00m (torch.isnan(tr_loss_step) \u001b[38;5;129;01mor\u001b[39;00m torch.isinf(tr_loss_step))\n\u001b[32m   2680\u001b[39m ):\n\u001b[32m   2681\u001b[39m     \u001b[38;5;66;03m# if loss is nan or inf simply add the average of previous logged losses\u001b[39;00m\n\u001b[32m   2682\u001b[39m     tr_loss = tr_loss + tr_loss / (\u001b[32m1\u001b[39m + \u001b[38;5;28mself\u001b[39m.state.global_step - \u001b[38;5;28mself\u001b[39m._globalstep_last_logged)\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/opt/conda/lib/python3.13/site-packages/transformers/trainer.py:4071\u001b[39m, in \u001b[36mTrainer.training_step\u001b[39m\u001b[34m(***failed resolving arguments***)\u001b[39m\n\u001b[32m   4068\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.accelerator.distributed_type == DistributedType.DEEPSPEED:\n\u001b[32m   4069\u001b[39m         kwargs[\u001b[33m\"\u001b[39m\u001b[33mscale_wrt_gas\u001b[39m\u001b[33m\"\u001b[39m] = \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[32m-> \u001b[39m\u001b[32m4071\u001b[39m     \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43maccelerator\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbackward\u001b[49m\u001b[43m(\u001b[49m\u001b[43mloss\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m   4073\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m loss.detach()\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/opt/conda/lib/python3.13/site-packages/accelerate/accelerator.py:2734\u001b[39m, in \u001b[36mAccelerator.backward\u001b[39m\u001b[34m(self, loss, **kwargs)\u001b[39m\n\u001b[32m   2732\u001b[39m     \u001b[38;5;28mself\u001b[39m.lomo_backward(loss, learning_rate)\n\u001b[32m   2733\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m2734\u001b[39m     \u001b[43mloss\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbackward\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/opt/conda/lib/python3.13/site-packages/torch/_tensor.py:647\u001b[39m, in \u001b[36mTensor.backward\u001b[39m\u001b[34m(self, gradient, retain_graph, create_graph, inputs)\u001b[39m\n\u001b[32m    637\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m has_torch_function_unary(\u001b[38;5;28mself\u001b[39m):\n\u001b[32m    638\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m handle_torch_function(\n\u001b[32m    639\u001b[39m         Tensor.backward,\n\u001b[32m    640\u001b[39m         (\u001b[38;5;28mself\u001b[39m,),\n\u001b[32m   (...)\u001b[39m\u001b[32m    645\u001b[39m         inputs=inputs,\n\u001b[32m    646\u001b[39m     )\n\u001b[32m--> \u001b[39m\u001b[32m647\u001b[39m \u001b[43mtorch\u001b[49m\u001b[43m.\u001b[49m\u001b[43mautograd\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbackward\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m    648\u001b[39m \u001b[43m    \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mgradient\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mretain_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcreate_graph\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43minputs\u001b[49m\u001b[43m=\u001b[49m\u001b[43minputs\u001b[49m\n\u001b[32m    649\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/opt/conda/lib/python3.13/site-packages/torch/autograd/__init__.py:354\u001b[39m, in \u001b[36mbackward\u001b[39m\u001b[34m(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)\u001b[39m\n\u001b[32m    349\u001b[39m     retain_graph = create_graph\n\u001b[32m    351\u001b[39m \u001b[38;5;66;03m# The reason we repeat the same comment below is that\u001b[39;00m\n\u001b[32m    352\u001b[39m \u001b[38;5;66;03m# some Python versions print out the first line of a multi-line function\u001b[39;00m\n\u001b[32m    353\u001b[39m \u001b[38;5;66;03m# calls in the traceback and some print out the last line\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m354\u001b[39m \u001b[43m_engine_run_backward\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m    355\u001b[39m \u001b[43m    \u001b[49m\u001b[43mtensors\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    356\u001b[39m \u001b[43m    \u001b[49m\u001b[43mgrad_tensors_\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    357\u001b[39m \u001b[43m    \u001b[49m\u001b[43mretain_graph\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    358\u001b[39m \u001b[43m    \u001b[49m\u001b[43mcreate_graph\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    359\u001b[39m \u001b[43m    \u001b[49m\u001b[43minputs_tuple\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m    360\u001b[39m \u001b[43m    \u001b[49m\u001b[43mallow_unreachable\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m    361\u001b[39m \u001b[43m    \u001b[49m\u001b[43maccumulate_grad\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m    362\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[36mFile \u001b[39m\u001b[32m/opt/conda/lib/python3.13/site-packages/torch/autograd/graph.py:829\u001b[39m, in \u001b[36m_engine_run_backward\u001b[39m\u001b[34m(t_outputs, *args, **kwargs)\u001b[39m\n\u001b[32m    827\u001b[39m     unregister_hooks = _register_logging_hooks_on_whole_graph(t_outputs)\n\u001b[32m    828\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m829\u001b[39m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mVariable\u001b[49m\u001b[43m.\u001b[49m\u001b[43m_execution_engine\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun_backward\u001b[49m\u001b[43m(\u001b[49m\u001b[43m  \u001b[49m\u001b[38;5;66;43;03m# Calls into the C++ engine to run the backward pass\u001b[39;49;00m\n\u001b[32m    830\u001b[39m \u001b[43m        \u001b[49m\u001b[43mt_outputs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\n\u001b[32m    831\u001b[39m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m  \u001b[38;5;66;03m# Calls into the C++ engine to run the backward pass\u001b[39;00m\n\u001b[32m    832\u001b[39m \u001b[38;5;28;01mfinally\u001b[39;00m:\n\u001b[32m    833\u001b[39m     \u001b[38;5;28;01mif\u001b[39;00m attach_logging_hooks:\n",
      "\u001b[31mRuntimeError\u001b[39m: element 0 of tensors does not require grad and does not have a grad_fn"
     ]
    }
   ],
   "source": [
    "# Lancement de l'entraînement !\n",
    "print(\"🚀 Début de l'entraînement...\")\n",
    "trainer.train()\n",
    "print(\"✅ Entraînement terminé !\")\n",
    "\n",
    "# Sauvegarde du meilleur modèle dans le dossier de sortie\n",
    "trainer.save_model()\n",
    "print(f\"Meilleur modèle sauvegardé dans le dossier : {output_dir}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c62301f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Évaluation finale du meilleur modèle sur le jeu de validation\n",
    "print(\"\\nÉvaluation finale du modèle...\")\n",
    "eval_results = trainer.evaluate()\n",
    "\n",
    "print(\"\\n--- Résultats de l'évaluation ---\")\n",
    "for key, value in eval_results.items():\n",
    "    print(f\"{key}: {value}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
