commit 8dfd897cacc44a74dff526f9417a751d52f98275 Author: spham Date: Fri Nov 14 12:13:54 2025 +0100 Ajout du lab de workflows reproductibles avec documentation complète - Implémentation du notebook lab.ipynb avec code complet pour créer des workflows d'IA reproductibles - Ajout d'un README.md pédagogique de 600+ lignes en français - Configuration des graines aléatoires pour la reproductibilité - Implémentation de la génération, division, normalisation et sauvegarde des données - Création et entraînement d'un réseau de neurones avec TensorFlow/Keras - Démonstration du rechargement et de la vérification de la reproductibilité 🤖 Generated with [Claude Code](https://claude.com/claude-code) diff --git a/.instructions/INSTRUCTIONS.mdoc b/.instructions/INSTRUCTIONS.mdoc new file mode 100644 index 0000000..d7c287e --- /dev/null +++ b/.instructions/INSTRUCTIONS.mdoc @@ -0,0 +1,102 @@ +{% steps %} +{% step title="Introduction to Reproducible Workflows" %} + +### Introduction + +Welcome to the "Introduction to Reproducible Workflows" lab! +This lab is designed to give you a foundational understanding of creating reproducible workflows for training an AI model, + its importance, and examples of key parts in model training to define fixed seeds. + +### Learning Objectives + +- Display key areas within AI model workflows to define fixed seeds +- Review saving datasets after train/test splits +- Practice recovering models and training datasets to repeat results + +### Prerequisites + +- A high level understanding of AI neural networks +- Experience in training models. + +{% /step %} + +{% step title="Creating Reproducible Datasets" %} + +### Creating Reproducible Datasets +For you to create a repeatable workflow you will need to start by defining the random seeds. When you define the random seed, it +will determine how data is split, generated, and how these train/test datasets are fed into the model. Your starting seed +helps ensure that all aspects of the workflow are repeatable. The provided code below sets the random seed for several +different libraries used in model development. +```python +random.seed(42) +np.random.seed(42) +tf.random.set_seed(42) +``` +From here you can define the synthetic data generation similar to how it is defined in other labs. +```python +X, y = make_classification( + n_samples=1000, + n_features=20, + n_informative=15, + n_redundant=5, + n_classes=2, + random_state=42 +) +``` +{% /step %} + +{% step title="Training Reproducible Model" %} + +### Training Reproducible Models +For the next step of model creation you will need to split your data into training and test datasets. The code provided below +allows for the initial dataset to be split into training and test sets. The key parameter for you to focus on here is the +```random_state``` which defines the random seed for the split. When you define the random seed, you are +ensuring future instances of the models training will result in the same training dataset being used, and therefore the +same model being created. +```python +X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.2, random_state=42 +) + +scaler = StandardScaler() +X_train = scaler.fit_transform(X_train) +X_test = scaler.transform(X_test) +``` +You can then also save the `train` and `test` data splits to allow for reproducibility in instances where the dataset you are +using may be a subset of a larger dataset. +```python +joblib.dump((X_train, y_train), 'train_data.pkl') +joblib.dump((X_test, y_test), 'test_data.pkl') +``` + +With the datasets saved and reproducible you can move forward with defining and training your model. You will define and train +your model in this lab so you can evaluate it and compare it to a model trained on the same training dataset to ensure it's +properly reproducible. Within the lab all code is provided for +training and evaluating the model. +{% /step %} + +{% step title="Saving Model and Dataset" %} + +### Saving Model and Dataset +Now that your model has been trained and evaluated, you can save it with the following code +```python +model.save('my_model.keras') +``` +From there you can move onto reproducing models which is meant to mimic an entirely new environment of loading a model into. +You can load the previously saved model and datasets using the following code below, including required imports. +```python +from tensorflow.keras.models import load_model +import joblib +modelReloaded = load_model('my_model.keras') +X_train_reloaded, y_train_reloaded = joblib.load('train_data.pkl') +X_test_reloaded, y_test_reloaded = joblib.load('test_data.pkl') +``` +As you revaluate the model on the exact same training and test sets using the previously defined evaluation code, you should +see results that are exactly the same as the previously evaluted model. +```python +loss, accuracy = modelReloaded.evaluate(X_test, y_test) +print(f"Test accuracy: {accuracy:.2f}") +``` + +{% /step %} +{% /steps %} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5d8e9de --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + "terminal.integrated.fontSize": 15, + "editor.fontSize": 15, + "terminal.integrated.defaultProfile.linux": "bash", + "workbench.colorTheme": "Default Dark Modern", + "workbench.startupEditor": "none", + "files.associations": { + "*.md": "markdoc" + }, + "workspace": { + "view": "readme", + "terminals": [ + { + "name": "Terminal", + "active": false + } + ], + "files": [ + "./lab.ipynb" + ], + } + } \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..60eb922 --- /dev/null +++ b/README.md @@ -0,0 +1,1050 @@ +# 🧠 Guide Complet : Workflows Reproductibles pour l'IA + +> **Un tutoriel complet pour apprendre à créer des workflows d'IA reproductibles** + +--- + +## 📋 Table des Matières + +1. [Démarrage Rapide](#-démarrage-rapide) +2. [Introduction aux Concepts](#-introduction-aux-concepts) +3. [Guide Étape par Étape](#-guide-étape-par-étape) +4. [Documentation Technique Détaillée](#-documentation-technique-détaillée) +5. [Points Clés à Retenir](#-points-clés-à-retenir) +6. [Résolution des Problèmes](#-résolution-des-problèmes) +7. [Pour Aller Plus Loin](#-pour-aller-plus-loin) + +--- + +## 🚀 Démarrage Rapide + +### Prérequis +```bash +# Installer les dépendances nécessaires +pip install numpy tensorflow scikit-learn joblib jupyter +``` + +### Lancer le Lab +```bash +# Ouvrir le notebook +jupyter notebook lab.ipynb +``` + +### Exécuter les Cellules +1. Exécutez les cellules **une par une** avec `Shift + Enter` +2. Observez les résultats après chaque étape +3. Vérifiez que la précision finale est identique après rechargement + +**Temps estimé** : 15-20 minutes + +--- + +## 🎓 Introduction aux Concepts + +### Qu'est-ce que la Reproductibilité ? + +La **reproductibilité** signifie obtenir exactement les **mêmes résultats** chaque fois que vous exécutez votre code. C'est essentiel en IA pour : + +| Avantage | Pourquoi c'est important | +|----------|-------------------------| +| 🐛 **Débogage** | Identifier et corriger les erreurs facilement | +| 🤝 **Collaboration** | D'autres peuvent reproduire vos résultats | +| 📊 **Validation** | Prouver la fiabilité de vos modèles | +| 🔬 **Recherche** | Publier des résultats vérifiables | +| 🏭 **Production** | Garantir la cohérence en production | + +### Pourquoi les Résultats Peuvent Varier ? + +Sans reproductibilité, vous pouvez obtenir des résultats différents à cause de : + +1. **Initialisation aléatoire** des poids du réseau de neurones +2. **Division aléatoire** des données train/test +3. **Ordre aléatoire** des données pendant l'entraînement +4. **Dropout aléatoire** dans les réseaux de neurones + +### La Solution : Les Graines Aléatoires (Random Seeds) + +Une **graine aléatoire** est un nombre qui initialise un générateur de nombres pseudo-aléatoires. Avec la même graine, vous obtenez toujours la même séquence de nombres "aléatoires". + +**Analogie** : C'est comme donner le même point de départ à une personne qui mélange un jeu de cartes. Si elle mélange toujours de la même façon depuis le même point, elle obtiendra toujours le même ordre de cartes. + +--- + +## 📖 Guide Étape par Étape + +### Étape 1️⃣ : Configuration de l'Environnement + +#### Code +```python +import numpy as np +import tensorflow as tf +from tensorflow import keras +from sklearn.datasets import make_classification +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import StandardScaler +import random +import joblib +``` + +#### Explication +- **numpy** : Calculs mathématiques et manipulation de tableaux +- **tensorflow/keras** : Framework pour créer des réseaux de neurones +- **sklearn** : Outils pour le machine learning (données, prétraitement, validation) +- **random** : Générateur de nombres aléatoires Python +- **joblib** : Sauvegarde et chargement d'objets Python + +--- + +### Étape 2️⃣ : Définir les Graines Aléatoires + +#### Code +```python +random.seed(42) +np.random.seed(42) +tf.random.set_seed(42) +``` + +#### Explication Détaillée + +| Fonction | Bibliothèque | Ce qu'elle contrôle | +|----------|--------------|---------------------| +| `random.seed(42)` | Python standard | Fonctions aléatoires de base | +| `np.random.seed(42)` | NumPy | Génération de tableaux aléatoires | +| `tf.random.set_seed(42)` | TensorFlow | Initialisation des poids, dropout, etc. | + +**Pourquoi 42 ?** C'est une convention (référence au *Guide du voyageur galactique*). Vous pouvez utiliser n'importe quel nombre, l'important est d'utiliser toujours le **même**. + +#### À Retenir +✅ **Toujours** définir les graines en **premier** +✅ Utiliser la **même valeur** (ici 42) partout +✅ Définir les graines pour **toutes** les bibliothèques utilisées + +--- + +### Étape 3️⃣ : Générer des Données Synthétiques + +#### Code +```python +X, y = make_classification( + n_samples=1000, # 1000 exemples + n_features=20, # 20 caractéristiques + n_informative=15, # 15 caractéristiques utiles + n_redundant=5, # 5 caractéristiques redondantes + n_classes=2, # Classification binaire (2 classes) + random_state=42 # Graine aléatoire +) +``` + +#### Explication des Paramètres + +| Paramètre | Valeur | Signification | +|-----------|--------|---------------| +| `n_samples` | 1000 | Nombre d'exemples générés | +| `n_features` | 20 | Nombre total de caractéristiques (colonnes) | +| `n_informative` | 15 | Caractéristiques réellement utiles pour la prédiction | +| `n_redundant` | 5 | Caractéristiques corrélées aux informatives (bruit) | +| `n_classes` | 2 | Nombre de classes à prédire (0 ou 1) | +| `random_state` | 42 | **CRUCIAL** pour la reproductibilité | + +#### Structure des Données + +``` +X : Matrice de forme (1000, 20) + - 1000 lignes (exemples) + - 20 colonnes (caractéristiques) + +y : Vecteur de forme (1000,) + - Contient 0 ou 1 pour chaque exemple +``` + +#### À Retenir +✅ `X` contient les **caractéristiques** (données d'entrée) +✅ `y` contient les **labels** (ce qu'on veut prédire) +✅ `random_state=42` garantit les **mêmes données** à chaque exécution + +--- + +### Étape 4️⃣ : Diviser les Données (Train/Test Split) + +#### Code +```python +X_train, X_test, y_train, y_test = train_test_split( + X, y, + test_size=0.2, + random_state=42 +) +``` + +#### Explication du Concept + +**Pourquoi diviser les données ?** + +- **Données d'entraînement (80%)** : Le modèle apprend sur ces données +- **Données de test (20%)** : On évalue la performance sur des données **non vues** + +**Analogie** : C'est comme étudier avec des exercices (entraînement) puis passer un examen avec de nouvelles questions (test). + +#### Résultat de la Division + +``` +X_train : 800 exemples pour l'entraînement +y_train : 800 labels correspondants + +X_test : 200 exemples pour le test +y_test : 200 labels correspondants +``` + +#### À Retenir +✅ **80/20** est une proportion standard (peut être 70/30 ou 90/10) +✅ **random_state=42** garantit la **même division** à chaque fois +✅ **Ne jamais** entraîner sur les données de test (c'est de la triche !) + +--- + +### Étape 5️⃣ : Normaliser les Données + +#### Code +```python +scaler = StandardScaler() +X_train = scaler.fit_transform(X_train) +X_test = scaler.transform(X_test) +``` + +#### Explication du StandardScaler + +**Qu'est-ce que la normalisation ?** + +Le `StandardScaler` transforme les données pour avoir : +- **Moyenne = 0** +- **Écart-type = 1** + +**Formule mathématique** : +``` +valeur_normalisée = (valeur - moyenne) / écart-type +``` + +#### Exemple Concret + +``` +Avant normalisation : +caractéristique_1 : [1, 2, 3, 1000] +caractéristique_2 : [0.1, 0.2, 0.3, 0.4] + +Après normalisation : +caractéristique_1 : [-0.5, -0.4, -0.3, 2.1] +caractéristique_2 : [-0.8, -0.2, 0.4, 1.0] +``` + +#### Pourquoi C'est Important ? + +| Sans normalisation | Avec normalisation | +|-------------------|-------------------| +| ❌ Les grandes valeurs dominent | ✅ Toutes les caractéristiques ont la même échelle | +| ❌ Apprentissage lent | ✅ Apprentissage plus rapide | +| ❌ Résultats instables | ✅ Résultats stables | + +#### Différence entre fit_transform et transform + +```python +scaler.fit_transform(X_train) # Apprend ET transforme +scaler.transform(X_test) # Transforme seulement +``` + +**IMPORTANT** : On apprend les statistiques (moyenne, écart-type) **uniquement** sur les données d'entraînement, puis on applique la **même transformation** aux données de test. + +#### À Retenir +✅ Toujours **fit** sur train, **transform** sur test +✅ Évite les **fuites de données** (data leakage) +✅ Améliore les **performances** du modèle + +--- + +### Étape 6️⃣ : Sauvegarder les Données + +#### Code +```python +joblib.dump((X_train, y_train), 'train_data.pkl') +joblib.dump((X_test, y_test), 'test_data.pkl') +``` + +#### Explication + +**Pourquoi sauvegarder les données ?** + +1. **Reproductibilité** : Garantir l'utilisation des mêmes données +2. **Efficacité** : Ne pas recalculer à chaque fois +3. **Traçabilité** : Savoir exactement quelles données ont été utilisées + +**Format .pkl** : Format de sérialisation Python (pickle) + +#### À Retenir +✅ Sauvegarde **après** la division train/test +✅ Permet de recharger les **mêmes données** plus tard +✅ Essentiel pour comparer différents modèles sur les **mêmes données** + +--- + +### Étape 7️⃣ : Construire le Réseau de Neurones + +#### Code +```python +model = keras.Sequential([ + keras.layers.Dense(32, activation='relu', input_shape=(X_train.shape[1],)), + keras.layers.Dense(16, activation='relu'), + keras.layers.Dense(1, activation='sigmoid') +]) +``` + +#### Architecture du Modèle + +``` +Entrée (20 caractéristiques) + ↓ +Couche 1 : 32 neurones + ReLU + ↓ +Couche 2 : 16 neurones + ReLU + ↓ +Couche 3 : 1 neurone + Sigmoid + ↓ +Sortie (probabilité 0-1) +``` + +#### Explication Détaillée de Chaque Couche + +| Couche | Neurones | Activation | Rôle | +|--------|----------|------------|------| +| Couche 1 | 32 | ReLU | Extraction de caractéristiques complexes | +| Couche 2 | 16 | ReLU | Combinaison des caractéristiques | +| Couche 3 | 1 | Sigmoid | Probabilité de la classe 1 (0 à 1) | + +#### Fonctions d'Activation + +**ReLU (Rectified Linear Unit)** +``` +ReLU(x) = max(0, x) + +Exemple : +ReLU(-5) = 0 +ReLU(0) = 0 +ReLU(3) = 3 +``` +- **Avantage** : Rapide, évite le gradient qui disparaît +- **Usage** : Couches cachées + +**Sigmoid** +``` +Sigmoid(x) = 1 / (1 + e^(-x)) + +Exemple : +Sigmoid(-∞) = 0 +Sigmoid(0) = 0.5 +Sigmoid(+∞) = 1 +``` +- **Avantage** : Sortie entre 0 et 1 (probabilité) +- **Usage** : Couche de sortie pour classification binaire + +#### À Retenir +✅ **Sequential** = couches empilées les unes après les autres +✅ **Dense** = couche entièrement connectée +✅ **ReLU** pour les couches cachées, **Sigmoid** pour la sortie binaire + +--- + +### Étape 8️⃣ : Compiler le Modèle + +#### Code +```python +model.compile( + optimizer='adam', + loss='binary_crossentropy', + metrics=['accuracy'] +) +``` + +#### Explication des Paramètres + +**Optimizer : Adam** +- **Rôle** : Algorithme qui ajuste les poids du réseau +- **Pourquoi Adam ?** + - Adapte automatiquement le taux d'apprentissage + - Fonctionne bien dans la plupart des cas + - Combine les avantages de RMSprop et Momentum + +**Loss : Binary Crossentropy** +- **Rôle** : Mesure l'erreur du modèle +- **Formule** : `-[y*log(p) + (1-y)*log(1-p)]` +- **Pourquoi ?** Adapté à la classification binaire (0 ou 1) + +**Metrics : Accuracy** +- **Rôle** : Métrique de performance +- **Formule** : `(Nombre de prédictions correctes) / (Nombre total)` +- **Exemple** : 95% d'accuracy = 95 prédictions correctes sur 100 + +#### À Retenir +✅ **Adam** est un excellent optimiseur par défaut +✅ **binary_crossentropy** pour la classification binaire +✅ **Compiler** avant d'entraîner le modèle + +--- + +### Étape 9️⃣ : Entraîner le Modèle + +#### Code +```python +model.fit(X_train, y_train, epochs=20, batch_size=32, validation_split=0.1) +``` + +#### Explication des Paramètres + +| Paramètre | Valeur | Signification | +|-----------|--------|---------------| +| `epochs` | 20 | Nombre de passages complets sur les données | +| `batch_size` | 32 | Nombre d'exemples traités avant mise à jour des poids | +| `validation_split` | 0.1 | 10% des données d'entraînement pour la validation | + +#### Comprendre l'Entraînement + +**Qu'est-ce qu'une Epoch ?** +- 1 epoch = le modèle voit **tous** les exemples d'entraînement une fois +- 20 epochs = le modèle voit 20 fois chaque exemple + +**Qu'est-ce qu'un Batch ?** +``` +800 exemples d'entraînement / batch_size 32 = 25 batches par epoch + +Epoch 1: + Batch 1 : exemples 1-32 → mise à jour des poids + Batch 2 : exemples 33-64 → mise à jour des poids + ... + Batch 25 : exemples 769-800 → mise à jour des poids +``` + +**Validation Split** +- Prend 10% de X_train pour validation +- **Entraînement** : 720 exemples (90% de 800) +- **Validation** : 80 exemples (10% de 800) +- Permet de surveiller le **surapprentissage** (overfitting) + +#### Qu'Observer Pendant l'Entraînement ? + +``` +Epoch 1/20 +25/25 [======] - loss: 0.6932 - accuracy: 0.5000 - val_loss: 0.6931 - val_accuracy: 0.5000 + +Epoch 20/20 +25/25 [======] - loss: 0.1234 - accuracy: 0.9500 - val_loss: 0.1456 - val_accuracy: 0.9300 +``` + +| Métrique | Signification | +|----------|---------------| +| `loss` | Erreur sur données d'entraînement (plus bas = mieux) | +| `accuracy` | Précision sur données d'entraînement | +| `val_loss` | Erreur sur données de validation | +| `val_accuracy` | Précision sur données de validation | + +#### À Retenir +✅ **Loss diminue** = le modèle apprend +✅ **val_loss >> loss** = surapprentissage (overfitting) +✅ Surveiller les deux métriques pour détecter les problèmes + +--- + +### Étape 🔟 : Évaluer le Modèle + +#### Code +```python +loss, accuracy = model.evaluate(X_test, y_test) +print(f"Test accuracy: {accuracy:.2f}") +``` + +#### Explication + +**Pourquoi évaluer sur les données de test ?** + +| Données | Utilisées pour | Ce qu'elles mesurent | +|---------|----------------|----------------------| +| Entraînement | Apprendre | Performance sur données connues | +| Validation | Ajuster | Performance pendant l'entraînement | +| **Test** | **Évaluer** | **Performance sur données inconnues** | + +**L'accuracy de test est la métrique finale** qui compte ! + +#### Interpréter les Résultats + +```python +Test accuracy: 0.95 # 95% de précision +``` + +Cela signifie : +- Sur 200 exemples de test +- Le modèle prédit correctement 190 exemples +- Et se trompe sur 10 exemples + +#### À Retenir +✅ **Jamais** entraîner sur les données de test +✅ L'accuracy de test mesure la **vraie performance** +✅ C'est le résultat qu'on doit **reproduire** + +--- + +### Étape 1️⃣1️⃣ : Sauvegarder le Modèle + +#### Code +```python +model.save('my_model.keras') +``` + +#### Explication + +**Que sauvegarde-t-on ?** +1. **Architecture** du modèle (nombre de couches, neurones) +2. **Poids** entraînés de tous les neurones +3. **Configuration** de compilation (optimizer, loss) +4. **État** de l'optimizer + +**Format .keras** : Format moderne de Keras (recommandé vs .h5) + +#### À Retenir +✅ Sauvegarde **après** l'entraînement +✅ Contient **tout** pour réutiliser le modèle +✅ Permet de faire des prédictions sans ré-entraîner + +--- + +### Étape 1️⃣2️⃣ : Recharger et Vérifier la Reproductibilité + +#### Code +```python +from tensorflow.keras.models import load_model + +modelReloaded = load_model('my_model.keras') +X_train_reloaded, y_train_reloaded = joblib.load('train_data.pkl') +X_test_reloaded, y_test_reloaded = joblib.load('test_data.pkl') + +loss_reloaded, accuracy_reloaded = modelReloaded.evaluate(X_test_reloaded, y_test_reloaded) +print(f"Test accuracy: {accuracy_reloaded:.2f}") +``` + +#### Vérification de la Reproductibilité + +**Test de Reproductibilité Réussi ✅** +``` +Modèle original : Test accuracy: 0.95 +Modèle rechargé : Test accuracy: 0.95 +``` + +**Si les valeurs sont différentes ❌** +- Vérifier que toutes les graines aléatoires sont définies +- Vérifier que les mêmes données sont utilisées +- Vérifier les versions des bibliothèques + +#### À Retenir +✅ Les deux accuracies doivent être **identiques** +✅ C'est la preuve que le workflow est **reproductible** +✅ Vous pouvez partager votre travail en toute confiance + +--- + +## 🔧 Documentation Technique Détaillée + +### Architecture Complète du Workflow + +``` +┌─────────────────────────────────────────────────────┐ +│ 1. INITIALISATION │ +│ • random.seed(42) │ +│ • np.random.seed(42) │ +│ • tf.random.set_seed(42) │ +└─────────────────┬───────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────┐ +│ 2. GÉNÉRATION DES DONNÉES │ +│ • make_classification(random_state=42) │ +│ • X: (1000, 20), y: (1000,) │ +└─────────────────┬───────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────┐ +│ 3. DIVISION TRAIN/TEST │ +│ • train_test_split(random_state=42) │ +│ • Train: 800, Test: 200 │ +└─────────────────┬───────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────┐ +│ 4. NORMALISATION │ +│ • StandardScaler.fit_transform(X_train) │ +│ • StandardScaler.transform(X_test) │ +└─────────────────┬───────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────┐ +│ 5. SAUVEGARDE DES DONNÉES │ +│ • joblib.dump(train_data.pkl) │ +│ • joblib.dump(test_data.pkl) │ +└─────────────────┬───────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────┐ +│ 6. CONSTRUCTION DU MODÈLE │ +│ • Input(20) → Dense(32) → Dense(16) → Dense(1) │ +│ • ReLU, ReLU, Sigmoid │ +└─────────────────┬───────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────┐ +│ 7. COMPILATION │ +│ • optimizer='adam' │ +│ • loss='binary_crossentropy' │ +└─────────────────┬───────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────┐ +│ 8. ENTRAÎNEMENT │ +│ • model.fit(epochs=20, batch_size=32) │ +│ • validation_split=0.1 │ +└─────────────────┬───────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────┐ +│ 9. ÉVALUATION │ +│ • model.evaluate(X_test, y_test) │ +└─────────────────┬───────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────┐ +│ 10. SAUVEGARDE DU MODÈLE │ +│ • model.save('my_model.keras') │ +└─────────────────┬───────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────┐ +│ 11. RECHARGEMENT ET VÉRIFICATION │ +│ • load_model(), joblib.load() │ +│ • Vérification: accuracy identique ✓ │ +└─────────────────────────────────────────────────────┘ +``` + +### Paramètres Importants + +#### make_classification + +```python +X, y = make_classification( + n_samples=1000, # Nombre d'exemples + n_features=20, # Nombre de caractéristiques + n_informative=15, # Caractéristiques informatives + n_redundant=5, # Caractéristiques redondantes + n_classes=2, # Nombre de classes + random_state=42, # CRUCIAL: graine aléatoire + flip_y=0.0, # Pourcentage de labels bruités (défaut: 0.01) + class_sep=1.0, # Séparation entre les classes (défaut: 1.0) + shuffle=True # Mélanger les exemples (défaut: True) +) +``` + +#### train_test_split + +```python +X_train, X_test, y_train, y_test = train_test_split( + X, y, + test_size=0.2, # 20% pour le test + train_size=None, # Automatique (1 - test_size) + random_state=42, # CRUCIAL: graine aléatoire + shuffle=True, # Mélanger avant division (défaut: True) + stratify=None # Préserver la distribution des classes (None par défaut) +) +``` + +#### StandardScaler + +```python +scaler = StandardScaler( + copy=True, # Copier les données (défaut: True) + with_mean=True, # Centrer autour de 0 (défaut: True) + with_std=True # Mettre à l'échelle à 1 (défaut: True) +) +``` + +#### model.fit + +```python +history = model.fit( + X_train, y_train, + epochs=20, # Nombre d'epochs + batch_size=32, # Taille des batchs + validation_split=0.1, # % pour validation + validation_data=None, # Ou tuple (X_val, y_val) + verbose=1, # Affichage (0: rien, 1: barre, 2: epoch) + shuffle=True, # Mélanger à chaque epoch (défaut: True) + callbacks=None # Callbacks (early stopping, etc.) +) +``` + +### Versions Recommandées + +``` +numpy==1.24.3 +tensorflow==2.15.0 +scikit-learn==1.3.2 +joblib==1.3.2 +``` + +Installer avec : +```bash +pip install numpy==1.24.3 tensorflow==2.15.0 scikit-learn==1.3.2 joblib==1.3.2 +``` + +--- + +## 💡 Points Clés à Retenir + +### Les 10 Commandements de la Reproductibilité + +1. ✅ **Tu définiras les graines aléatoires** au début de ton code +2. ✅ **Tu utiliseras random_state** dans toutes les fonctions qui l'acceptent +3. ✅ **Tu sauvegarderas tes données** après division train/test +4. ✅ **Tu ne fit que sur train** et transform sur test +5. ✅ **Tu ne regarderas jamais** les données de test avant l'évaluation finale +6. ✅ **Tu sauvegarderas ton modèle** après entraînement +7. ✅ **Tu documenteras les versions** de tes bibliothèques +8. ✅ **Tu vérifieras la reproductibilité** en rechargeant tout +9. ✅ **Tu utiliseras le même environnement** (virtualenv, conda) +10. ✅ **Tu partageras ton code** avec les instructions complètes + +### Checklist de Reproductibilité + +Avant de partager votre travail, vérifiez : + +- [ ] Toutes les graines aléatoires sont définies +- [ ] `random_state=42` est utilisé partout +- [ ] Les données train/test sont sauvegardées +- [ ] Le modèle est sauvegardé +- [ ] Les versions des bibliothèques sont documentées +- [ ] Le code est testé sur une machine différente +- [ ] Les résultats sont identiques après rechargement +- [ ] Un README explique comment exécuter le code + +### Erreurs Courantes à Éviter + +| Erreur | Conséquence | Solution | +|--------|-------------|----------| +| ❌ Oublier `random_state` | Résultats différents à chaque fois | Toujours définir `random_state=42` | +| ❌ Fit sur test | Data leakage | Fit sur train uniquement | +| ❌ Ne pas sauvegarder les données | Impossible de reproduire exactement | Sauvegarder après split | +| ❌ Mélanger train et test | Évaluation biaisée | Garder séparés | +| ❌ Différentes versions de bibliothèques | Résultats différents | Documenter les versions | + +--- + +## 🐛 Résolution des Problèmes + +### Problème 1 : Résultats Différents à Chaque Exécution + +**Symptôme** : L'accuracy change à chaque fois + +**Solutions** : +```python +# 1. Vérifier que TOUTES les graines sont définies +random.seed(42) +np.random.seed(42) +tf.random.set_seed(42) + +# 2. Vérifier random_state dans train_test_split +X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42) + +# 3. Vérifier random_state dans make_classification +X, y = make_classification(..., random_state=42) +``` + +### Problème 2 : Erreur de Shape + +**Symptôme** : `ValueError: Input shape mismatch` + +**Solutions** : +```python +# Vérifier les shapes +print(f"X_train shape: {X_train.shape}") +print(f"y_train shape: {y_train.shape}") + +# S'assurer que input_shape correspond +input_shape=(X_train.shape[1],) # Pas X_train.shape ! +``` + +### Problème 3 : Loss ne Diminue Pas + +**Symptôme** : La loss reste élevée ou augmente + +**Solutions** : +```python +# 1. Vérifier que les données sont normalisées +scaler = StandardScaler() +X_train = scaler.fit_transform(X_train) + +# 2. Essayer un taux d'apprentissage plus petit +from tensorflow.keras.optimizers import Adam +optimizer = Adam(learning_rate=0.001) +model.compile(optimizer=optimizer, ...) + +# 3. Augmenter le nombre d'epochs +model.fit(..., epochs=50) +``` + +### Problème 4 : Overfitting + +**Symptôme** : `train_accuracy >> val_accuracy` + +**Solutions** : +```python +# 1. Ajouter du Dropout +model = keras.Sequential([ + keras.layers.Dense(32, activation='relu'), + keras.layers.Dropout(0.3), # Dropout 30% + keras.layers.Dense(16, activation='relu'), + keras.layers.Dropout(0.3), + keras.layers.Dense(1, activation='sigmoid') +]) + +# 2. Réduire la complexité du modèle +model = keras.Sequential([ + keras.layers.Dense(16, activation='relu'), # Moins de neurones + keras.layers.Dense(1, activation='sigmoid') +]) + +# 3. Ajouter de la régularisation L2 +from tensorflow.keras.regularizers import l2 +keras.layers.Dense(32, activation='relu', kernel_regularizer=l2(0.01)) +``` + +### Problème 5 : Erreur lors du Rechargement + +**Symptôme** : `FileNotFoundError` ou modèle ne charge pas + +**Solutions** : +```python +# 1. Vérifier que le fichier existe +import os +print(os.path.exists('my_model.keras')) # Doit être True + +# 2. Utiliser le chemin absolu +import os +model_path = os.path.abspath('my_model.keras') +model = load_model(model_path) + +# 3. Vérifier la version de TensorFlow +import tensorflow as tf +print(tf.__version__) # Doit être >= 2.12 pour .keras +``` + +--- + +## 📚 Pour Aller Plus Loin + +### Améliorations Possibles + +#### 1. Ajouter des Callbacks + +```python +from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint + +# Arrêt anticipé si pas d'amélioration +early_stop = EarlyStopping( + monitor='val_loss', + patience=5, # Attendre 5 epochs + restore_best_weights=True +) + +# Sauvegarder le meilleur modèle +checkpoint = ModelCheckpoint( + 'best_model.keras', + monitor='val_accuracy', + save_best_only=True +) + +model.fit( + X_train, y_train, + epochs=100, + callbacks=[early_stop, checkpoint] +) +``` + +#### 2. Hyperparamètres Tuning + +```python +# Tester différentes architectures +architectures = [ + [32, 16], + [64, 32, 16], + [128, 64, 32] +] + +for arch in architectures: + model = build_model(arch) + model.fit(...) + # Comparer les résultats +``` + +#### 3. Cross-Validation + +```python +from sklearn.model_selection import KFold + +kfold = KFold(n_splits=5, shuffle=True, random_state=42) + +for fold, (train_idx, val_idx) in enumerate(kfold.split(X)): + X_train_fold = X[train_idx] + y_train_fold = y[train_idx] + # Entraîner et évaluer +``` + +#### 4. Logging et Tracking + +```python +import mlflow + +mlflow.start_run() +mlflow.log_params({ + "epochs": 20, + "batch_size": 32, + "architecture": "32-16-1" +}) + +history = model.fit(...) + +mlflow.log_metrics({ + "accuracy": accuracy, + "loss": loss +}) +mlflow.end_run() +``` + +### Concepts Avancés + +#### Stratified Split + +Pour les datasets déséquilibrés : +```python +X_train, X_test, y_train, y_test = train_test_split( + X, y, + stratify=y, # Préserve la distribution des classes + random_state=42 +) +``` + +#### K-Fold Cross-Validation + +Pour une évaluation plus robuste : +```python +from sklearn.model_selection import cross_val_score +from sklearn.neural_network import MLPClassifier + +clf = MLPClassifier(random_state=42) +scores = cross_val_score(clf, X, y, cv=5) +print(f"Accuracy: {scores.mean():.2f} (+/- {scores.std():.2f})") +``` + +#### Data Augmentation + +Pour augmenter la taille du dataset : +```python +from sklearn.utils import resample + +# Bootstrap sampling +X_resampled, y_resampled = resample( + X_train, y_train, + n_samples=len(X_train) * 2, + random_state=42 +) +``` + +### Ressources Supplémentaires + +#### Documentation Officielle +- [TensorFlow/Keras](https://www.tensorflow.org/guide/keras) +- [Scikit-learn](https://scikit-learn.org/stable/documentation.html) +- [NumPy](https://numpy.org/doc/) + +#### Tutoriels Recommandés +- [Machine Learning Mastery](https://machinelearningmastery.com/) +- [Towards Data Science](https://towardsdatascience.com/) +- [Fast.ai](https://www.fast.ai/) + +#### Livres +- "Hands-On Machine Learning" - Aurélien Géron +- "Deep Learning with Python" - François Chollet +- "Python Machine Learning" - Sebastian Raschka + +--- + +## 📝 Résumé en 5 Minutes + +### Ce que Vous Avez Appris + +1. **La reproductibilité** est essentielle en IA +2. **Les graines aléatoires** permettent d'obtenir les mêmes résultats +3. **Diviser train/test** correctement est crucial +4. **Normaliser les données** améliore l'apprentissage +5. **Sauvegarder modèle et données** permet de reproduire + +### Code Minimal Reproductible + +```python +import numpy as np +import tensorflow as tf +from sklearn.datasets import make_classification +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import StandardScaler +import random + +# 1. Graines aléatoires +random.seed(42) +np.random.seed(42) +tf.random.set_seed(42) + +# 2. Données +X, y = make_classification(n_samples=1000, n_features=20, random_state=42) + +# 3. Division et normalisation +X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42) +scaler = StandardScaler() +X_train = scaler.fit_transform(X_train) +X_test = scaler.transform(X_test) + +# 4. Modèle +model = tf.keras.Sequential([ + tf.keras.layers.Dense(32, activation='relu', input_shape=(20,)), + tf.keras.layers.Dense(1, activation='sigmoid') +]) +model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) + +# 5. Entraînement +model.fit(X_train, y_train, epochs=20, batch_size=32) + +# 6. Évaluation +loss, accuracy = model.evaluate(X_test, y_test) +print(f"Accuracy: {accuracy:.2f}") + +# 7. Sauvegarde +model.save('model.keras') +``` + +--- + +## 🎯 Conclusion + +Félicitations ! Vous avez maintenant une compréhension complète des workflows reproductibles en IA. + +**N'oubliez pas** : +- ✅ Toujours définir les graines aléatoires +- ✅ Utiliser `random_state` partout +- ✅ Sauvegarder modèles et données +- ✅ Vérifier la reproductibilité + +**Prochaines étapes** : +1. Pratiquez avec vos propres datasets +2. Expérimentez avec différentes architectures +3. Explorez les techniques avancées +4. Partagez votre travail reproductible ! + +--- + +## 📞 Support + +Si vous rencontrez des problèmes : +1. Relisez la section **Résolution des Problèmes** +2. Vérifiez les versions de vos bibliothèques +3. Consultez la documentation officielle +4. Recherchez sur Stack Overflow + +Bon apprentissage ! 🚀 diff --git a/lab.ipynb b/lab.ipynb new file mode 100644 index 0000000..2a5a18a --- /dev/null +++ b/lab.ipynb @@ -0,0 +1,166 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import tensorflow as tf\n", + "from tensorflow import keras\n", + "from sklearn.datasets import make_classification\n", + "from sklearn.model_selection import train_test_split\n", + "from sklearn.preprocessing import StandardScaler\n", + "import random\n", + "import joblib" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating Reproducible Datasets" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# 1. Set random seeds for reproducibility\n# Définir les graines aléatoires pour la reproductibilité\nrandom.seed(42) # Pour le module random\nnp.random.seed(42) # Pour NumPy\ntf.random.set_seed(42) # Pour TensorFlow\n\nprint(\"✓ Graines aléatoires définies avec succès !\")" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# 2. Generate synthetic data\n# Générer des données synthétiques pour la classification\nX, y = make_classification(\n n_samples=1000, # 1000 exemples\n n_features=20, # 20 caractéristiques\n n_informative=15, # 15 caractéristiques utiles\n n_redundant=5, # 5 caractéristiques redondantes\n n_classes=2, # 2 classes (classification binaire)\n random_state=42 # Graine aléatoire pour reproductibilité\n)\n\nprint(f\"✓ Données générées : {X.shape[0]} exemples, {X.shape[1]} caractéristiques\")" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Training Reproducible Model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# 3. Split and scale the data\n# Diviser les données en ensembles d'entraînement (80%) et de test (20%)\nX_train, X_test, y_train, y_test = train_test_split(\n X, y, \n test_size=0.2, # 20% pour le test\n random_state=42 # Important pour la reproductibilité !\n)\n\n# Normaliser les données (StandardScaler centre les données autour de 0)\nscaler = StandardScaler()\nX_train = scaler.fit_transform(X_train) # Apprendre et transformer les données d'entraînement\nX_test = scaler.transform(X_test) # Transformer les données de test\n\nprint(f\"✓ Données divisées :\")\nprint(f\" - Entraînement : {X_train.shape[0]} exemples\")\nprint(f\" - Test : {X_test.shape[0]} exemples\")" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Sauvegarder les données d'entraînement et de test pour la reproductibilité\njoblib.dump((X_train, y_train), 'train_data.pkl')\njoblib.dump((X_test, y_test), 'test_data.pkl')\n\nprint(\"✓ Données sauvegardées :\")\nprint(\" - train_data.pkl\")\nprint(\" - test_data.pkl\")" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Model Initalization and Training" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 4. Build a neural network\n", + "model = keras.Sequential([\n", + " keras.layers.Dense(32, activation='relu', input_shape=(X_train.shape[1],)),\n", + " keras.layers.Dense(16, activation='relu'),\n", + " keras.layers.Dense(1, activation='sigmoid')\n", + "])\n", + "\n", + "model.compile(\n", + " optimizer='adam',\n", + " loss='binary_crossentropy',\n", + " metrics=['accuracy']\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 5. Train the model\n", + "model.fit(X_train, y_train, epochs=20, batch_size=32, validation_split=0.1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# 6. Evaluate the model\n", + "loss, accuracy = model.evaluate(X_test, y_test)\n", + "print(f\"Test accuracy: {accuracy:.2f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Saving Models" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# 7. Save the model and scaler\n# Sauvegarder le modèle entraîné\nmodel.save('my_model.keras')\n\nprint(\"✓ Modèle sauvegardé : my_model.keras\")" + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reproducing Models" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "#8. Reloading model later\n# Recharger le modèle et les données sauvegardées\nfrom tensorflow.keras.models import load_model\n\nmodelReloaded = load_model('my_model.keras')\nX_train_reloaded, y_train_reloaded = joblib.load('train_data.pkl')\nX_test_reloaded, y_test_reloaded = joblib.load('test_data.pkl')\n\nprint(\"✓ Modèle et données rechargés avec succès !\")" + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": "# Vérifier que le modèle rechargé donne les mêmes résultats\nloss_reloaded, accuracy_reloaded = modelReloaded.evaluate(X_test_reloaded, y_test_reloaded)\nprint(f\"\\n🎯 Précision du modèle rechargé : {accuracy_reloaded:.2f}\")\nprint(\"\\n💡 Si la précision est identique à celle obtenue plus haut,\")\nprint(\" votre workflow est reproductible ! ✓\")" + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file