{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":3362,"databundleVersionId":31148}],"dockerImageVersionId":31286,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"#------------COMPRENDRE LES DONNEES------------\nimport os\nimport zipfile\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm # Pour voir la barre de progression\n\nzip_path = '/kaggle/input/competitions/dogs-vs-cats/train.zip'\nextract_path = '/kaggle/working/train_data'\nreal_images_path = os.path.join(extract_path, 'train')\n\n\nif not os.path.exists(real_images_path):\n    print(\"Extraction en cours...\")\n    with zipfile.ZipFile(zip_path, 'r') as zip_ref:\n        zip_ref.extractall(extract_path)\n    print(\"Extraction terminée !\")\n\n# Analyse des dimensions\nfilenames = os.listdir(real_images_path)\nwidths = []\nheights = []\n\nprint(f\"Analyse de {len(filenames)} images...\")\n\n# On analyse les 500 premières images pour aller vite (échantillon représentatif)\nfor i in tqdm(range(min(500, len(filenames)))):\n    img_path = os.path.join(real_images_path, filenames[i])\n    with Image.open(img_path) as img:\n        w, h = img.size\n        widths.append(w)\n        heights.append(h)\n\n#Affichage des statistiques\ndf_dims = pd.DataFrame({'Largeur': widths, 'Hauteur': heights})\n\nprint(\"\\n--- Statistiques des dimensions (Pixels) ---\")\nprint(df_dims.describe().loc[['min', 'max', 'mean']])\n\n#Affichage des 5 premières images avec leurs tailles respectives\nprint(\"\\n--- Détails des 5 premières images ---\")\nfor i in range(5):\n    img_name = filenames[i]\n    with Image.open(os.path.join(real_images_path, img_name)) as img:\n        print(f\"Fichier: {img_name} | Taille: {img.size}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-15T21:18:28.834560Z","iopub.execute_input":"2026-03-15T21:18:28.835107Z","iopub.status.idle":"2026-03-15T21:18:35.569511Z","shell.execute_reply.started":"2026-03-15T21:18:28.835078Z","shell.execute_reply":"2026-03-15T21:18:35.568740Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#------------VERIFIER LA BONNE QUALITE DES IMAGES ------------\nfrom PIL import Image\nimport os\n\ndef check_corrupted_images(path):\n    corrupted = []\n    for img_name in os.listdir(path):\n        img_path = os.path.join(path, img_name)\n        try:\n            with Image.open(img_path) as img:\n                img.verify() # Vérifie si le fichier est intact / noie et blanc sans le charger en RAM\n                if img.mode != 'RGB':\n                    print(f\"Image non-RGB détectée : {img_name} (Mode: {img.mode})\")\n        except (IOError, SyntaxError) as e:\n            print(f\"Fichier corrompu détecté : {img_name}\")\n            corrupted.append(img_name)\n    return corrupted\n\n# Utilisation\nbad_files = check_corrupted_images('/kaggle/working/train_data/train')\nprint(f\"Total d'images corrompues : {len(bad_files)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T20:07:08.327655Z","iopub.execute_input":"2026-03-15T20:07:08.328093Z","iopub.status.idle":"2026-03-15T20:07:10.130649Z","shell.execute_reply.started":"2026-03-15T20:07:08.328037Z","shell.execute_reply":"2026-03-15T20:07:10.129836Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#------------AFFICHER UNE IMAGE------------\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nimport os\n\ndef afficher_image_par_nom(nom_image):\n    # Chemin complet vers le dossier d'extraction\n    repertoire = '/kaggle/working/train_data/train'\n    chemin_complet = os.path.join(repertoire, nom_image)\n    \n    # Vérifier si le fichier existe avant d'essayer de l'ouvrir\n    if os.path.exists(chemin_complet):\n        img = mpimg.imread(chemin_complet)\n        plt.figure(figsize=(6, 6))\n        plt.imshow(img)\n        plt.title(f\"Affichage de : {nom_image}\\nDimensions : {img.shape}\")\n        plt.axis('off') # Cache les axes x et y\n        plt.show()\n    else:\n        print(f\"Erreur : L'image '{nom_image}' est introuvable dans {repertoire}\")\n\n# --- TESTEZ ICI ---\n# Remplacez par le nom que vous voulez vérifier\nafficher_image_par_nom('cat.5.jpg')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T20:07:19.461476Z","iopub.execute_input":"2026-03-15T20:07:19.461819Z","iopub.status.idle":"2026-03-15T20:07:19.673928Z","shell.execute_reply.started":"2026-03-15T20:07:19.461785Z","shell.execute_reply":"2026-03-15T20:07:19.672908Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"''' #-----------------STRUCTURER LES DOSSIERS \n+ repartitions des donnees dans les dossiers de train et test------------------'''\nimport os\nimport shutil\nfrom sklearn.model_selection import train_test_split\n\n# Définir le répertoire de base pour notre travail\nbase_dir = '/kaggle/working/data'\nos.makedirs(base_dir, exist_ok=True)\n\n# Créer les sous-dossiers Train et Test (Validation)\ntrain_dir = os.path.join(base_dir, 'train')\ntest_dir = os.path.join(base_dir, 'test')\n\nfor folder in [train_dir, test_dir]:\n    os.makedirs(os.path.join(folder, 'cats'), exist_ok=True)\n    os.makedirs(os.path.join(folder, 'dogs'), exist_ok=True)\n\n#Lister tous les fichiers images\nfilenames = '/kaggle/working/train_data/train'\nall_images = os.listdir(filenames)\n\n#Séparer les noms de fichiers (80% train, 20% test)\ntrain_files, test_files = train_test_split(all_images, test_size=0.2, random_state=42)\n\n# Fonction pour copier les fichiers dans la nouvelle structure\ndef remplir_dossiers(files, destination_parent):\n    for fname in files:\n        if fname.startswith('cat'):\n            label_dir = 'cats'\n        else:\n            label_dir = 'dogs'\n        \n        src = os.path.join(filenames, fname)\n        dst = os.path.join(destination_parent, label_dir, fname)\n        shutil.copyfile(src, dst)\n\n# 5. Lancement du remplissage\nremplir_dossiers(train_files, train_dir)\nremplir_dossiers(test_files, test_dir)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T21:18:43.628899Z","iopub.execute_input":"2026-03-15T21:18:43.629534Z","iopub.status.idle":"2026-03-15T21:18:46.217838Z","shell.execute_reply.started":"2026-03-15T21:18:43.629504Z","shell.execute_reply":"2026-03-15T21:18:46.216953Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#---------Verifier la repartition des images dans les dossiers crees--------\ndef verifier_repartition(repertoire_racine):\n    for split in ['train', 'test']:\n        print(f\"--- Dossier {split.upper()} ---\")\n        total_split = 0\n        for classe in ['cats', 'dogs']:\n            chemin = os.path.join(repertoire_racine, split, classe)\n            nb_images = len(os.listdir(chemin))\n            total_split += nb_images\n            print(f\"  {classe.capitalize()} : {nb_images} images\")\n        print(f\"TOTAL {split.upper()} : {total_split} images\\n\")\n\n# Utilisation\nverifier_repartition('/kaggle/working/data')\nshutil.rmtree('/kaggle/working/train_data')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T20:07:49.915740Z","iopub.execute_input":"2026-03-15T20:07:49.916939Z","iopub.status.idle":"2026-03-15T20:07:50.697118Z","shell.execute_reply.started":"2026-03-15T20:07:49.916902Z","shell.execute_reply":"2026-03-15T20:07:50.696255Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#------------(SIFT + SVM)-----------\nimport cv2\nimport numpy as np\nimport os\nfrom sklearn.svm import SVC\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.metrics import accuracy_score\nfrom sklearn.model_selection import GridSearchCV\nfrom sklearn.utils import shuffle\nfrom sklearn.metrics import confusion_matrix\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\ndef extract_sift_features(directory, img_size=128):\n    sift = cv2.SIFT_create()\n    features = []\n    labels = []\n    \n    for label_name in ['cats', 'dogs']:\n        folder_path = os.path.join(directory, label_name)\n        label = 0 if label_name == 'cats' else 1\n        \n        for img_name in os.listdir(folder_path):\n            img_path = os.path.join(folder_path, img_name)\n            img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) # SIFT travaille en gris\n            if img is None: continue\n            \n            # 1. Redimensionnement\n            img = cv2.resize(img, (img_size, img_size))\n            \n            # 2. Détection des points clés et calcul des descripteurs\n            # On utilise un \"BoW\" simplifié : on prend la moyenne des descripteurs \n            # pour avoir un vecteur de taille fixe pour le SVM\n            kp, desc = sift.detectAndCompute(img, None)\n            \n            if desc is not None:\n                # On fait la moyenne des descripteurs pour transformer \n                # l'image en un vecteur de 128 chiffres\n                features.append(np.mean(desc, axis=0))\n                labels.append(label)\n                \n    return np.array(features), np.array(labels)\n\nprint(\"Extraction des caractéristiques SIFT ...\")\nX_train, y_train = extract_sift_features('/kaggle/working/data/train')\nX_test, y_test = extract_sift_features('/kaggle/working/data/test')\n\n# Standardisation (Moyenne 0, Ecart-type 1)\nscaler = StandardScaler()\nX_train_scaled = scaler.fit_transform(X_train)\nX_test_scaled = scaler.transform(X_test)\n\n# Mélanger X et y de la même manière\nX_train_shuffled, y_train_shuffled = shuffle(X_train_scaled, y_train, random_state=42)\n\n# Définir les paramètres à tester\nparam_grid = {\n    'C': [0.1, 1, 10],\n    'gamma': ['scale', 0.01],\n    'kernel': ['rbf']\n}\n\n# Lancer la recherche sur un échantillon mélangé (1000 images)\ngrid = GridSearchCV(SVC(), param_grid, refit=True, verbose=2, cv=3)\ngrid.fit(X_train_shuffled[:1000], y_train_shuffled[:1000]) \nprint(f\"Meilleurs paramètres : {grid.best_params_}\")\n\n# Création et entraînement du SVM\nbest_svm = grid.best_estimator_\nbest_svm.fit(X_train_scaled, y_train)\nprint(\"Entraînement du SVM...\")\nbest_svm = grid.best_estimator_\nbest_svm.fit(X_train_scaled, y_train)\n\n# Évaluation\ny_pred = best_svm.predict(X_test_scaled)\nprint(f\"Précision finale (SIFT + SVM) : {accuracy_score(y_test, y_pred) * 100:.2f}%\")\n\n# Calculer la matrice de confusion pour le SVM\ncm_svm = confusion_matrix(y_test, y_pred)\n\n# Affichage graphique\nplt.figure(figsize=(8, 6))\nsns.heatmap(cm_svm, annot=True, fmt='d', cmap='Greens', \n            xticklabels=['Chat', 'Chien'], \n            yticklabels=['Chat', 'Chien'])\n\nplt.title('Matrice de Confusion - SVM (SIFT)')\nplt.ylabel('Vraie étiquette')\nplt.xlabel('Prédiction')\nplt.show()\n\n# le rapport détaillé pour le tableau du rapport\nfrom sklearn.metrics import classification_report\nprint(\"\\n--- Rapport de Classification SVM ---\")\nprint(classification_report(y_test, y_pred, target_names=['Chat', 'Chien']))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T22:49:20.227905Z","iopub.execute_input":"2026-03-15T22:49:20.228621Z","iopub.status.idle":"2026-03-15T22:53:40.389538Z","shell.execute_reply.started":"2026-03-15T22:49:20.228593Z","shell.execute_reply":"2026-03-15T22:53:40.388947Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tester l'impact de la taille des données\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# On définit les fractions à tester\nfractions = [0.1, 0.25, 0.5, 0.75, 1.0] \nresultats = []\n\nfor f in fractions:\n    # On calcule combien d'images cela représente\n    taille = int(len(X_train_scaled) * f)\n    \n    # On prend les 'taille' premières images (déjà mélangées si tu as fait le shuffle)\n    X_sub = X_train_shuffled[:taille]\n    y_sub = y_train_shuffled[:taille]\n    \n    # On entraîne un SVM rapide\n    test_svm = SVC(kernel='rbf', C=1.0)\n    test_svm.fit(X_sub, y_sub)\n    \n    # On évalue sur TOUT le jeu de test\n    score = test_svm.score(X_test_scaled, y_test)\n    resultats.append(score)\n    print(f\"Taille {int(f*100)}% ({taille} images) -> Précision: {score*100:.2f}%\")\n\n# Affichage de la courbe d'apprentissage (Learning Curve)\nplt.plot(fractions, resultats, marker='o')\nplt.title(\"Impact de la taille du dataset sur le SVM\")\nplt.xlabel(\"Fraction des données d'entraînement\")\nplt.ylabel(\"Accuracy\")\nplt.grid()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T21:34:52.737151Z","iopub.execute_input":"2026-03-15T21:34:52.737748Z","iopub.status.idle":"2026-03-15T21:36:07.705904Z","shell.execute_reply.started":"2026-03-15T21:34:52.737718Z","shell.execute_reply":"2026-03-15T21:36:07.705310Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# KNN et Arbre de decison avant CNN\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.tree import DecisionTreeClassifier\nimport pandas as pd\n\n# Dictionnaire pour centraliser nos résultats finaux\nfinal_comparison = {}\n\nprint(\"--- Optimisation du kNN ---\")\n# On teste plusieurs valeurs de K et différentes métriques de distance\nknn_params = {\n    'n_neighbors': [3, 5, 11, 21], \n    'weights': ['uniform', 'distance']\n}\nknn_grid = GridSearchCV(KNeighborsClassifier(), knn_params, cv=3, verbose=1)\nknn_grid.fit(X_train_shuffled[:1500], y_train_shuffled[:1500]) # Un peu plus d'images pour la stabilité\nfinal_comparison['kNN'] = knn_grid.score(X_test_scaled, y_test)\nprint(f\"Meilleur kNN : {knn_grid.best_params_} | Score: {final_comparison['kNN']*100:.2f}%\")\n\n\n\nprint(\"\\n--- Optimisation de l'Arbre de Décision ---\")\n# On cherche la profondeur optimale pour éviter le sur-apprentissage (overfitting)\ndt_params = {\n    'max_depth': [5, 10, 20, None],\n    'criterion': ['gini', 'entropy']\n}\ndt_grid = GridSearchCV(DecisionTreeClassifier(random_state=42), dt_params, cv=3, verbose=1)\ndt_grid.fit(X_train_shuffled[:1500], y_train_shuffled[:1500])\nfinal_comparison['Decision Tree'] = dt_grid.score(X_test_scaled, y_test)\nprint(f\"Meilleur Arbre : {dt_grid.best_params_} | Score: {final_comparison['Decision Tree']*100:.2f}%\")\n\n\n\n# Ajoutons tes résultats précédents pour avoir la vue d'ensemble\n# (Assure-toi d'avoir calculé ces variables avant)\nif 'accuracy_score' in globals() or 'y_pred' in locals():\n    final_comparison['SVM (Meilleur)'] = accuracy_score(y_test, y_pred)\n\n# --- SYNTHÈSE GRAPHIQUE ---\nplt.figure(figsize=(10, 6))\ncolors = ['skyblue', 'salmon', 'lightgreen']\nplt.bar(final_comparison.keys(), [v * 100 for v in final_comparison.values()], color=colors)\nplt.axhline(y=85, color='red', linestyle='--', label='Seuil visé CNN') # Ligne de référence\nplt.ylabel('Précision (Accuracy %)')\nplt.title('Comparaison de l\\'efficacité des algorithmes classiques (SIFT)')\nplt.ylim(0, 100)\nfor i, v in enumerate(final_comparison.values()):\n    plt.text(i, v*100 + 1, f\"{v*100:.2f}%\", ha='center', fontweight='bold')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T22:37:24.830007Z","iopub.execute_input":"2026-03-15T22:37:24.830767Z","iopub.status.idle":"2026-03-15T22:37:29.449071Z","shell.execute_reply.started":"2026-03-15T22:37:24.830738Z","shell.execute_reply":"2026-03-15T22:37:29.448192Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#-------------PREPARATION POUR LE CNN ------------------\n#-------------NORMALISATION /255 + REDIMENTIONNEMENT 128 * 128 + Data Augmentataion -----------\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom sklearn.metrics import classification_report, confusion_matrix\nimport seaborn as sns\n# Configuration\nIMG_SIZE = 128\nBATCH_SIZE = 32\n\n\n'''Générateurs de Keras:\nIls vont redimensionner et normaliser les images en temps réel pendant l'entraînement.'''\n\n# Data Normalisation  +  Data Augmentation\ntrain_datagen = ImageDataGenerator(\n    rescale=1./255,          # Effectue la normalisation automatique\n    rotation_range=40,       # Tourne l'image jusqu'à 40 degrés\n    width_shift_range=0.2,   # Déplace l'image horizontalement\n    height_shift_range=0.2,  # Déplace l'image verticalement\n    shear_range=0.2,         # Cisaillement\n    zoom_range=0.2,          # Zoom aléatoire\n    horizontal_flip=True,    # Retourne l'image (très utile pour les animaux)\n    fill_mode='nearest'      # Remplit les pixels vides créés par la rotation\n)\n\ntest_datagen = ImageDataGenerator(rescale=1./255)\n\n# Création des flux de données (Flux -> Modèle)\ntrain_generator = train_datagen.flow_from_directory(\n    '/kaggle/working/data/train',\n    target_size=(IMG_SIZE, IMG_SIZE),\n    batch_size=BATCH_SIZE,\n    class_mode='binary'\n)\n\ntest_generator = test_datagen.flow_from_directory(\n    '/kaggle/working/data/test',\n    target_size=(IMG_SIZE, IMG_SIZE),\n    batch_size=BATCH_SIZE,\n    class_mode='binary',\n    shuffle=False\n)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T21:36:22.391100Z","iopub.execute_input":"2026-03-15T21:36:22.391600Z","iopub.status.idle":"2026-03-15T21:36:23.148813Z","shell.execute_reply.started":"2026-03-15T21:36:22.391574Z","shell.execute_reply":"2026-03-15T21:36:23.148098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#----------MODELISATION DU MODELE CNN-----------\nfrom tensorflow.keras import layers, models\n\nmodel = models.Sequential([\n    # --- BLOC 1 : Détection des formes simples (lignes, bords) ---\n    layers.Conv2D(32, (3, 3), activation='relu', input_shape=(128, 128, 3)),\n    layers.MaxPooling2D(2, 2),#Couches de regroupement:Dans chaque carré de 2x2 pixels, on ne garde que la valeur la plus forte (le maximum).\n    \n    # --- BLOC 2 : Motifs plus complexes (yeux, oreilles) ---\n    layers.Conv2D(64, (3, 3), activation='relu'), #relu:Activation permettant d'ignorer les informations inutiles.\n    layers.MaxPooling2D(2, 2),\n    \n    # --- BLOC 3 : Consolidation ---\n    layers.Conv2D(128, (3, 3), activation='relu'),\n    layers.MaxPooling2D(2, 2),\n    \n    # --- Passage au Dense -----\n    layers.Flatten(), # On transforme les carrés de pixels en un vecteur\n    layers.Dense(512, activation='relu'),\n    layers.Dropout(0.5), # On éteint 50% des neurones aléatoirement pour forcer l'apprentissage\n    \n    #------Sortie binaire (Sigmoid)------\n    layers.Dense(1, activation='sigmoid') # 1 neurone final : 0 (chat) ou 1 (chien)\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T21:36:31.068452Z","iopub.execute_input":"2026-03-15T21:36:31.068993Z","iopub.status.idle":"2026-03-15T21:36:33.035912Z","shell.execute_reply.started":"2026-03-15T21:36:31.068963Z","shell.execute_reply":"2026-03-15T21:36:33.035119Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#--------COMPILATION DU MODELE-----------\nfrom tensorflow.keras import optimizers\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau\n\n# --- CONFIGURATION AVANCÉE (Le côté Premium) ---\n\n# 1. On définit un optimiseur avec un Learning Rate précis\nopt = optimizers.Adam(learning_rate=0.001)\nmodel.compile(\n    optimizer=opt,                # L'algorithme qui ajuste les poids\n    loss='binary_crossentropy',      # La mesure de l'erreur pour 2 classes\n    metrics=['accuracy']             # On veut voir le % de réussite\n)\n\n# Voir le résumé de ton IA\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T22:06:13.334925Z","iopub.execute_input":"2026-03-15T22:06:13.335258Z","iopub.status.idle":"2026-03-15T22:06:13.360691Z","shell.execute_reply.started":"2026-03-15T22:06:13.335233Z","shell.execute_reply":"2026-03-15T22:06:13.360148Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Lancement de l'entraînement\n# 2. Ajout d'un deuxième \"Callback\" : ReduceLROnPlateau\n# Si l'IA stagne, on réduit la vitesse d'apprentissage pour finir plus précisément\nreduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=2, min_lr=0.00001)\n\n# 3. Early Stopping (que tu avais déjà, très bien !)\nearly_stop = EarlyStopping(monitor='val_loss', patience=4, restore_best_weights=True)\n\n# --- LANCEMENT ---\nhistory = model.fit(\n    train_generator,\n    steps_per_epoch=train_generator.samples // BATCH_SIZE,\n    epochs=20, \n    validation_data=test_generator,\n    validation_steps=test_generator.samples // BATCH_SIZE,\n    callbacks=[early_stop, reduce_lr], # On met les deux ici\n    verbose=1\n)\nplt.plot(history.history['accuracy'], label='train_acc')\nplt.plot(history.history['val_accuracy'], label='val_acc')\nplt.title('Précision du modèle')\nplt.legend()\nplt.show()\n# Prédictions sur le test_generator\n# 2. Prédictions\nprint(\"Calcul des prédictions...\")\ntest_generator.reset()\ny_pred_cnn_prob = model.predict(test_generator)\ny_pred_cnn = (y_pred_cnn_prob > 0.5).astype(\"int32\")\n\n# 3. Récupération des vraies étiquettes\ny_true = test_generator.classes\n\n# 4. Affichage du rapport\nprint(\"\\n--- Rapport de Classification (CNN) ---\")\nprint(classification_report(y_true, y_pred_cnn, target_names=['Chat', 'Chien']))\n\n# 5. Affichage de la matrice de confusion\ncm = confusion_matrix(y_true, y_pred_cnn)\nplt.figure(figsize=(8, 6))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n            xticklabels=['Chat', 'Chien'], \n            yticklabels=['Chat', 'Chien'])\nplt.title('Matrice de Confusion - CNN')\nplt.ylabel('Vraie étiquette')\nplt.xlabel('Prédiction')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-15T22:06:58.855871Z","iopub.execute_input":"2026-03-15T22:06:58.856159Z","iopub.status.idle":"2026-03-15T22:32:41.348784Z","shell.execute_reply.started":"2026-03-15T22:06:58.856135Z","shell.execute_reply":"2026-03-15T22:32:41.348084Z"}},"outputs":[],"execution_count":null}]}