1053 lines
32 KiB
Markdown
1053 lines
32 KiB
Markdown
# 🧠 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 ! 🚀
|