---
# System prepended metadata

title: 'Projet : Classification d''images avec Fashion MNIST'

---

# Projet : Classification d'images avec Fashion MNIST

Ce projet a pour objectif de démontrer un flux de travail complet (end-to-end) pour l'entraînement, le déploiement et le test d'un modèle de Machine Learning (ML) en utilisant Python. Le projet se concentre sur la classification d'images du dataset Fashion MNIST, qui contient des images de vêtements en niveaux de gris de 28x28 pixels. Le projet est divisé en trois parties principales :

1. **Entraînement des modèles** : Utilisation de `train_models.py` pour entraîner plusieurs modèles de classification.
2. **Déploiement de l'API** : Utilisation de Flask pour créer une API qui permet de faire des prédictions avec les modèles entraînés.
3. **Interface utilisateur** : Utilisation de Streamlit pour créer une interface utilisateur simple qui permet de télécharger des images et d'obtenir des prédictions via l'API Flask.

## Étapes du projet
### Prérequis
1. Créer un environement `ml` avec 
    * La version python `3.10`
    * Télécharger Anaconda à partir de ce [lien](https://www.anaconda.com/download/success)
    * Lors de l'installation cocher " ajouter anaconda aux variables d'environements "

![Capture](https://hackmd.io/_uploads/SJF6vKHiye.png)


* Voici la liste des dépendances nécessaires pour ce projet :
```file=requirements.txt
#requirements.txt
numpy==1.26.4
pandas==2.2.3
scikit-learn==1.6.1
scipy==1.12.0
matplotlib==3.10.0
seaborn==0.13.2
#tensorflow==2.18.0
#tensorflow-addons==0.22.0
keras==3.8.0
jupyterlab==4.3.5
ipython==8.21.0
ipykernel==6.29.5
ipywidgets==8.1.5
openml==0.15.1
mlxtend==0.23.4
imbalanced-learn==0.13.0
category_encoders==2.8.0
gdown==5.2.0
GPy==1.13.2
graphviz==0.20.3
streamlit==1.42.1
Flask==3.1.0
```
* Créer un fichier requirements.txt contenant les packages ci-dessus.
* Ouvrir anaconda prompt dans le dossier contenenant le fichier `requirements.txt`
![createML](https://hackmd.io/_uploads/SkGkqtroyx.png)

```bash 
# Créer l'environnement , Taper ceci dans anaconda prompt
# si vous avez déjà créer cet environement avec anaconda-navigator passer à la commande suivante
conda create --name ml python=3.10

# Activer l'environnement
conda activate ml

# Installer les dépendances
pip install -r requirements.txt

```
2. Initialisation de `git`

* Télécharger `git` à partir de ce [lien](https://git-scm.com/downloads/win)
* Avant de commencer, configure ton nom d'utilisateur et ton email si ce n'est pas encore fait sur ta machine :

```bash
git config --global user.name "nevermind78"
git config --global user.email "ton-email@example.com"
```
* Vérifie la configuration avec :
```bash
git config --global --list
```
2️⃣ Créer un nouveau dossier `projet` , ouvrir un terminal git et se placer dans le dossier du projet qui a été déjà créer
```bash
cd projet
```
* Placer les fichiers : 
    - [`api.py`](#api-py) ( api *Flask*)
    - [`app.py`](#app-py) ( application streamlit front)
    - [`train_models.py`](#train-models-py) ( Création des modèles)
    - [`download_test.py`](#download-test-py) ( Télécharger des images de test)

3️⃣ Initialiser un dépôt Git dans ce dossier
```bash
git init
```
4️⃣ Ajouter tous les fichiers du projet au suivi Git
```bash
git add .
```
5️⃣ Créer un commit avec un message explicatif
```bash
git commit -m "Initial commit"
```
6️⃣ Créer un dépôt sur GitHub
Va sur GitHub
* Crée un dépôt projet25 (⚠️ sans initialiser avec un README)

7️⃣ Lier le dépôt local avec le dépôt GitHub
```bash
git remote add origin https://github.com/nevermind78/projet25.git
```
* Vérifie si le lien est bien ajouté :

```bash
git remote -v
```
8️⃣ Définir la branche principale (main)
```bash
git branch -M main
```
9️⃣ Envoyer le projet sur GitHub
```bash
git push -u origin main
```
✅ Ton projet est maintenant synchronisé avec GitHub ! 🎉🚀

🔄 Pour les prochaines modifications :
* À chaque changement, utilise :

```bash
git add .
git commit -m "Description des modifications"
git push origin main
```

### 1. Entraînement des modèles (`train_models.py`)

Le script `train_models.py` est responsable de l'entraînement des modèles de classification. Voici les étapes principales :

- **Chargement des données** : Le dataset Fashion MNIST est chargé à l'aide de la bibliothèque `openml`.
- **Préprocessing** : Les images sont normalisées en divisant les valeurs des pixels par 255 pour les ramener dans l'intervalle [0, 1].
- **Division des données** : Les données sont divisées en ensembles d'entraînement et de test avec une proportion de 10% pour l'entraînement.
- **Entraînement des modèles** : Trois modèles sont entraînés : 
  - Régression logistique (`LogisticRegression`)
  - SVM linéaire (`LinearSVC`)
  - K-Nearest Neighbors (`KNeighborsClassifier`)
- **Sauvegarde des modèles** : Les modèles entraînés sont sauvegardés en utilisant `joblib` pour être utilisés ultérieurement dans l'API.
    ### train_models.py
```python=
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split, cross_validate
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score
import joblib
import openml as oml
# Charger les données
fmnist = oml.datasets.get_dataset(40996)
X, y, _, _ = fmnist.get_data(target=fmnist.default_target_attribute)

# Preprocessing
X = X / 255.0  # Normalisation
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, train_size=0.1)

# Définir les modèles
classifiers = [LogisticRegression(max_iter=1000), LinearSVC(max_iter=1000), KNeighborsClassifier()]

# Entraîner les modèles
for clf in classifiers:
    clf.fit(X_train, y_train)

# Sauvegarder les modèles
joblib.dump(classifiers[0], 'logistic_regression.pkl')
joblib.dump(classifiers[1], 'linear_svc.pkl')
joblib.dump(classifiers[2], 'knn.pkl')

print("Les modèles ont été entraînés et sauvegardés.")
```
### 2. Déploiement de l'API Flask (`api.py`)

L'API Flask est utilisée pour servir les modèles entraînés et permettre des prédictions en temps réel. Voici les étapes principales :

- **Chargement des modèles** : Les modèles sauvegardés sont chargés en mémoire.
- **Endpoint de prédiction** : Un endpoint `/predict` est créé pour recevoir des images et renvoyer des prédictions.
- **Traitement de l'image** : L'image téléchargée est convertie en niveaux de gris, redimensionnée à 28x28 pixels, et normalisée.
- **Prédiction** : Le modèle sélectionné est utilisé pour prédire la classe de l'image.
- **Réponse** : La prédiction est renvoyée sous forme de JSON.
    ### `api.py`
```python=
from flask import Flask, request, jsonify
import joblib
import numpy as np
from PIL import Image
import io

app = Flask(__name__)

# Charger les modèles
logistic_regression = joblib.load('logistic_regression.pkl')
linear_svc = joblib.load('linear_svc.pkl')
knn = joblib.load('knn.pkl')

@app.route('/predict', methods=['POST'])
def predict():
    if 'file' not in request.files:
        return jsonify({"error": "No file provided"}), 400

    file = request.files['file']
    
    # Réinitialiser le pointeur du fichier
    file.seek(0)
    
    try:
        # Lire le fichier en mémoire
        file_data = file.read()
        
        # Ouvrir l'image à partir des données en mémoire
        image = Image.open(io.BytesIO(file_data)).convert('L').resize((28, 28))
    except Exception as e:
        return jsonify({"error": f"Failed to process image: {str(e)}"}), 400

    # Convertir l'image en tableau NumPy
    image_array = np.array(image).reshape(1, -1) / 255.0

    # Récupérer le modèle sélectionné
    model_name = request.form.get('model')
    if model_name == "Logistic Regression":
        prediction = logistic_regression.predict(image_array)
    elif model_name == "Linear SVC":
        prediction = linear_svc.predict(image_array)
    elif model_name == "KNN":
        prediction = knn.predict(image_array)
    else:
        return jsonify({"error": "Invalid model name"}), 400

    # Renvoyer uniquement la prédiction
    return jsonify({
        "prediction": int(prediction[0])  # Convertir en entier pour éviter les problèmes de sérialisation
    })

if __name__ == '__main__':
    app.run(debug=True)
```

### 3. Interface utilisateur avec Streamlit (`app.py`)

L'interface utilisateur est créée avec Streamlit pour permettre aux utilisateurs de télécharger des images et d'obtenir des prédictions. Voici les étapes principales :

- **Configuration de l'entraînement** : L'utilisateur peut sélectionner la proportion des données à utiliser pour l'entraînement.
- **Lancement de l'entraînement** : Un bouton permet de lancer l'entraînement des modèles (simulé dans ce cas).
- **Téléchargement d'image** : L'utilisateur peut télécharger une image pour la classification.
- **Sélection du modèle** : L'utilisateur peut choisir parmi les trois modèles entraînés.
- **Prédiction** : Un bouton permet de lancer la prédiction en envoyant l'image à l'API Flask.
- **Affichage du résultat** : La prédiction est affichée sous forme de nom de classe et de numéro de classe.
    ### `app.py`
```python=
import streamlit as st
from PIL import Image
import requests

# Titre de l'application
st.title("Classification d'images avec Fashion MNIST")

# Classes Fashion MNIST
fmnist_classes = [
    "T-shirt/top",  # Classe 0
    "Trouser",      # Classe 1
    "Pullover",     # Classe 2
    "Dress",        # Classe 3
    "Coat",         # Classe 4
    "Sandal",       # Classe 5
    "Shirt",        # Classe 6
    "Sneaker",      # Classe 7
    "Bag",          # Classe 8
    "Ankle boot"    # Classe 9
]

# URL de l'API Flask (remplacez par l'URL de votre API si nécessaire)
API_URL = "http://127.0.0.1:5000/predict"

# Téléchargement de l'image
uploaded_file = st.file_uploader("Téléchargez une image (28x28 pixels)", type=["png", "jpg", "jpeg"])

# Afficher l'image téléchargée une seule fois
if uploaded_file is not None:
    # Réinitialiser le pointeur du fichier pour éviter l'erreur PIL
    uploaded_file.seek(0)
    
    # Ouvrir l'image
    image = Image.open(uploaded_file).convert('L')  # Convertir en niveaux de gris
    
    # Afficher l'image avec une taille réduite
    col1, col2, col3 = st.columns([1, 2, 1])

    with col2:  # Utilise la colonne du milieu pour centrer l'image
        st.image(image, caption="Image téléchargée", width=150)
# Sélection du modèle
model_name = st.selectbox(
    "Sélectionnez un modèle",
    ["Logistic Regression", "Linear SVC", "KNN"]
)

# Bouton pour lancer la prédiction
if st.button("Prédire"):
    if uploaded_file is not None:
        # Réinitialiser le pointeur du fichier
        uploaded_file.seek(0)
        
        # Envoyer la requête à l'API Flask
        files = {"file": uploaded_file}
        data = {"model": model_name}
        response = requests.post(API_URL, files=files, data=data)

        # Afficher le résultat
        if response.status_code == 200:
            result = response.json()
            prediction = result.get("prediction")  # Correction : "prediction" -> "prediction"
            
            # Afficher la prédiction
            class_name = fmnist_classes[prediction]  # Convertir le numéro en nom de classe
            st.success(f"**Prédiction :** {class_name} (Classe {prediction})")
        else:
            st.error(f"Erreur lors de la prédiction : {response.text}")
    else:
        st.warning("Veuillez télécharger une image avant de lancer la prédiction.")
```
## Guide étape par étape

### Étape 1 : Entraînement des modèles
0. Télécharger des images pour le test
    ### `download_test.py`
```python=
import numpy as np
import matplotlib.pyplot as plt
import openml as oml
import os

# Charger le dataset Fashion MNIST
fmnist = oml.datasets.get_dataset(40996)
X, y, _, _ = fmnist.get_data(target=fmnist.default_target_attribute)

# Convertir X en tableau NumPy
X = X.to_numpy()
print(y)
# Créer un dossier pour sauvegarder les images
output_dir = "test_images"
os.makedirs(output_dir, exist_ok=True)

# Sauvegarder 10 images de test
for i in range(10,20):  # Vous pouvez changer le nombre d'images à sauvegarder
    image = X[i].reshape(28, 28)  # Redimensionner en 28x28 pixels
    image_path = os.path.join(output_dir, f"test_image_{i}.png")
    plt.imsave(image_path, image, cmap='gray')
    print(f"Image {i} sauvegardée sous {image_path}")
```
2. Exécutez le script `train_models.py` pour entraîner les modèles et les sauvegarder.
   ```bash
   python train_models.py
   ```
2. Les modèles entraînés seront sauvegardés sous les noms `logistic_regression.pkl`, `linear_svc.pkl`, et `knn.pkl`.

### Étape 2 : Démarrage de l'API Flask
1. Exécutez le script api.py pour démarrer l'API Flask.

```bash
python api.py
```
2. L'API sera disponible à l'adresse **`http://127.0.0.1:5000.`**

### Étape 3 : Utilisation de l'interface Streamlit
1. Exécutez le script app.py pour démarrer l'interface Streamlit.

```bash
streamlit run app.py
```
<div style="background-color: #1e3a5f; border-left: 5px solid #3498db; padding: 10px; margin: 10px 0; color: #ffffff;">
    <strong>⚠️ Avertissement :</strong> Si vous rencontrez un problème avec sklearn et joblib et threadpool. Veuillez éxécuter cette commande ( dans l'environement <b>ml</b> ).
</div>

```bash=
pip install --upgrade scikit-learn threadpoolctl joblib
```
2. L'interface sera disponible dans votre navigateur à l'adresse indiquée dans le terminal.
![app](https://hackmd.io/_uploads/SJ24nbriyx.png)


3. Téléchargez une image de 28x28 pixels et sélectionnez un modèle pour obtenir une prédiction.

![appML0](https://hackmd.io/_uploads/SJzeyGrskx.gif)
