{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":124046,"databundleVersionId":14573278,"sourceType":"competition"}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"449acc05","cell_type":"markdown","source":"# Projet de Classification d'Images Biologiques FIB/SEM\n\n## Challenge de Deep Learning - Identification d'Organites Cellulaires\n\n---\n\n### Auteurs\n\n- **SCHELLENBAUM Mathieu**\n- **SELAO Steven**\n\n### Encadrant\n\n- **NAEGEL Benoît**\n\n---\n\n## Objectif du Projet\n\nCe notebook présente une solution complète pour classifier des images de microscopie électronique (FIB/SEM) en **5 classes d'organites cellulaires** :\n\n| Classe | Organite |\n|:------:|----------|\n| **0** | Autre (fond ou zone non annotée) |\n| **1** | Mitochondrie |\n| **2** | Réticulum endoplasmique |\n| **3** | Membrane nucléaire |\n| **4** | Membrane cellulaire |","metadata":{}},{"id":"8f942484","cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers, models, optimizers\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport cv2\nimport os\nimport numpy as np\n\n# =============================================================\n# 1. CONFIGURATION DES CHEMINS ET CHARGEMENT DES DONNÉES\n# =============================================================\n\n# Définition des dossiers sources sur l'environnement Kaggle\nbase_path = '/kaggle/input/2025-competition-apprentissage-i-3-d/'\ntrain_dir = os.path.join(base_path, 'train/')\ntest_dir = os.path.join(base_path, 'test/')\n\n# Lecture du fichier CSV d'entraînement : lie l'ID de l'image à sa classe\ntrain_df = pd.read_csv(os.path.join(base_path, 'train.csv'))\n# On ajoute l'extension .png car les noms dans le CSV sont des nombres bruts\ntrain_df['ID'] = train_df['ID'].astype(str) + \".png\"\n# La catégorie doit être en string pour que le générateur Keras la traite comme une classe\ntrain_df['Category'] = train_df['Category'].astype(str)\n\n# Même opération pour le fichier de test (soumission)\ntest_df = pd.read_csv(os.path.join(base_path, 'sampleSubmission.csv'))\ntest_df['ID'] = test_df['ID'].astype(str) + \".png\"\n\n# =============================================================\n# VISUALISATION DES DONNÉES (Exploration biologique)\n# =============================================================\nCLASS_NAMES = {\n    '0': 'Autre', \n    '1': 'Mitochondrie', \n    '2': 'Réticulum E.', \n    '3': 'Memb. Nucléaire', \n    '4': 'Memb. Cellulaire'\n}\n\nprint(\"Chargement d'aperçus des images biologiques...\")\nplt.figure(figsize=(15, 10))\nfor class_id in range(5):\n    # Sélection de 5 échantillons aléatoires par classe pour vérification visuelle\n    samples = train_df[train_df['Category'] == str(class_id)].sample(5, random_state=42)\n    for idx, (_, row) in enumerate(samples.iterrows()):\n        img_path = os.path.join(train_dir, row['ID'])\n        img = cv2.imread(img_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # Conversion BGR (OpenCV) vers RGB (Matplotlib)\n        \n        plt.subplot(5, 5, class_id * 5 + idx + 1)\n        plt.imshow(img)\n        plt.axis('off') # On cache les axes pour plus de clarté\n        if idx == 0:\n            plt.title(CLASS_NAMES[str(class_id)], loc='left', fontweight='bold')\nplt.tight_layout()\nplt.show()\n\n# =============================================================\n# 2. GÉNÉRATEURS DE DONNÉES ET AUGMENTATION (DATA AUGMENTATION)\n# =============================================================\n\n# ImageDataGenerator permet de modifier les images à la volée pour enrichir le dataset\ntrain_datagen = ImageDataGenerator(\n    rescale=1./255,          # Normalisation : les pixels passent de [0,255] à [0,1]\n    validation_split=0.2,    # Séparation automatique de 20% des données pour la validation\n    rotation_range=45,       # Rotation aléatoire (les organites n'ont pas de sens haut/bas)\n    horizontal_flip=True,    # Miroir horizontal\n    vertical_flip=True,      # Miroir vertical\n    fill_mode='reflect'      # Remplit les bords après rotation par symétrie (mieux pour les textures)\n)\n\n# Pour le test, on se contente de normaliser sans transformer les images\ntest_datagen = ImageDataGenerator(rescale=1./255)\n\nIMG_SIZE = (257, 257) # Taille standard des images du challenge\n\n# Création des flux (Iterateurs) de données\ntrain_generator = train_datagen.flow_from_dataframe(\n    dataframe=train_df, directory=train_dir, x_col=\"ID\", y_col=\"Category\",\n    target_size=IMG_SIZE, batch_size=32, class_mode=\"categorical\", subset=\"training\"\n)\n\nval_generator = train_datagen.flow_from_dataframe(\n    dataframe=train_df, directory=train_dir, x_col=\"ID\", y_col=\"Category\",\n    target_size=IMG_SIZE, batch_size=32, class_mode=\"categorical\", subset=\"validation\"\n)\n\n# =============================================================\n# 3. ARCHITECTURE DU MODÈLE (Réseau de Neurones Convolutionnel)\n# =============================================================\n\nmodel = models.Sequential([\n    layers.Input(shape=(257, 257, 3)), # Entrée : Image couleur 257x257\n    \n    # Bloc Convolutionnel 1 : Détection de textures simples\n    layers.Conv2D(32, (3, 3), activation='relu', padding='same'),\n    layers.BatchNormalization(), # Stabilise l'entraînement\n    layers.MaxPooling2D((2, 2)), # Divise la résolution par 2\n    \n    # Bloc Convolutionnel 2 : Formes intermédiaires\n    layers.Conv2D(64, (3, 3), activation='relu', padding='same'),\n    layers.BatchNormalization(),\n    layers.MaxPooling2D((2, 2)),\n    \n    # Bloc Convolutionnel 3 : Motifs complexes\n    layers.Conv2D(128, (3, 3), activation='relu', padding='same'),\n    layers.BatchNormalization(),\n    layers.MaxPooling2D((2, 2)),\n\n    # Bloc Convolutionnel 4 : Haute abstraction\n    layers.Conv2D(256, (3, 3), activation='relu', padding='same'),\n    layers.BatchNormalization(),\n    layers.MaxPooling2D((2, 2)),\n    \n    # Tête de classification\n    layers.GlobalAveragePooling2D(), # Réduit chaque filtre à une seule valeur (robuste)\n    layers.Dense(256, activation='relu'),\n    layers.Dropout(0.5),             # Désactive 50% des neurones pour éviter le surapprentissage\n    layers.Dense(5, activation='softmax') # Sortie : Probabilités pour les 5 classes\n])\n\n# Compilation avec l'optimiseur Adam (standard efficace)\nmodel.compile(\n    optimizer=optimizers.Adam(learning_rate=1e-3),\n    loss='categorical_crossentropy', # Perte adaptée à la classification multi-classe\n    metrics=['accuracy']\n)\n\n# =============================================================\n# 4. ENTRAÎNEMENT ET OPTIMISATION DYNAMIQUE\n# =============================================================\n\ncallbacks = [\n    # Arrête si la perte de validation ne baisse plus pendant 12 époques\n    EarlyStopping(monitor='val_loss', patience=12, restore_best_weights=True, verbose=1),\n    # Réduit le taux d'apprentissage si le modèle stagne pour affiner les poids\n    ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=1e-6, verbose=1)\n]\n\n# Lancement de l'apprentissage\nhistory = model.fit(\n    train_generator,\n    epochs=20, \n    validation_data=val_generator,\n    callbacks=callbacks,\n    verbose=1\n)\n\n# =============================================================\n# VISUALISATION DES COURBES D'ENTRAÎNEMENT\n# =============================================================\nplt.figure(figsize=(12, 5))\n# Graphique de la précision (doit monter)\nplt.subplot(1, 2, 1)\nplt.plot(history.history['accuracy'], label='Entraînement')\nplt.plot(history.history['val_accuracy'], label='Validation')\nplt.title(\"Précision du modèle\")\nplt.xlabel('Époque')\nplt.legend()\nplt.grid(True, alpha=0.3)\n\n# Graphique de la perte (doit descendre)\nplt.subplot(1, 2, 2)\nplt.plot(history.history['loss'], label='Entraînement')\nplt.plot(history.history['val_loss'], label='Validation')\nplt.title(\"Perte (Loss) du modèle\")\nplt.xlabel('Époque')\nplt.legend()\nplt.grid(True, alpha=0.3)\nplt.show()\n\n# =============================================================\n# 5. GÉNÉRATION DES PRÉDICTIONS POUR LA COMPÉTITION\n# =============================================================\n\n# Configuration du générateur pour le test (shuffle=False pour garder l'ordre des IDs)\ntest_generator = test_datagen.flow_from_dataframe(\n    dataframe=test_df, directory=test_dir, x_col=\"ID\", y_col=None,\n    target_size=IMG_SIZE, batch_size=1, class_mode=None, shuffle=False\n)\n\n# Calcul des probabilités pour chaque image\npredictions = model.predict(test_generator)\n# On récupère l'indice de la classe ayant la plus haute probabilité\npredicted_classes = np.argmax(predictions, axis=1)\n\n# Préparation du DataFrame final pour la soumission Kaggle\nsubmission = pd.DataFrame({\n    # On retire le \".png\" pour correspondre au format d'ID attendu\n    \"ID\": test_df[\"ID\"].str.replace(\".png\", \"\", regex=False),\n    \"Category\": predicted_classes\n})\n\n# Exportation au format CSV\nsubmission.to_csv(\"resultat.csv\", index=False)\nprint(\"Soumission terminée : resultat.csv généré.\")","metadata":{},"outputs":[],"execution_count":null}]}