🎉 init project for french pd books analysis

This commit is contained in:
2025-10-23 15:02:10 +02:00
commit a59e48a869
35 changed files with 2072 additions and 0 deletions
+153
View File
@@ -0,0 +1,153 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# gitignore template for Jupyter Notebooks
# website: http://jupyter.org/
.ipynb_checkpoints
*/.ipynb_checkpoints/*
# IPython
profile_default/
ipython_config.py
# Remove previous ipynb_checkpoints
# git rm -r .ipynb_checkpoints/
# End of https://mrkandreev.name/snippets/gitignore-generator/#Python,JupyterNotebooks
+34
View File
@@ -0,0 +1,34 @@
*Réaliser par Mathéo GUILBERT*
*-- Octobre 2025 --*
# Journal de bord
## Introduction
Le jeu de données utilisé est `PleIAs/French-PD-Books`.
## Partie 1 : Classification
**Deux approches :**
1. Entrainement du modèle sur des données brutes vectorisées
2. Entrainement sur des données avec analyse de patrons sémantiques et stylistique préalable
**Pronostiques :**
Je pense que l'approche 2 donnera de meilleurs résultat que la première. Cepandant la deuxième nécessitera un travail d'exploration des données plus important.
Je pense également que la première approche peut facilement poser un problème de surapprentissage sur les indicateurs temporelles explicites tel que les dates ou les nom d'époque (renaissance, moyen age, ...).
Pour la méthode de vectorisation j'essaie deux méthodes :
1. TF-IDF
2. Embeddings
Pour ce qui est des modèles utilisé :
TF-IDF --> c'est le classifier
Embeddings --> dangvantuan/sentence-camembert-base <-- Pour mon PC
dangvantuan/sentence-camembert-large <-- Pour Morgoth
## Partie 2 : Génération
Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

+1253
View File
File diff suppressed because one or more lines are too long
+632
View File
@@ -0,0 +1,632 @@
{
"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
}