{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":11871891,"sourceType":"datasetVersion","datasetId":7460756}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Importations","metadata":{}},{"cell_type":"code","source":"!pip install umap-learn\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom PIL import Image\nimport numpy as np\nfrom sklearn.decomposition import PCA\nimport matplotlib.pyplot as plt\nfrom mpl_toolkits.mplot3d import Axes3D","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T12:55:44.148984Z","iopub.execute_input":"2025-05-21T12:55:44.149254Z","iopub.status.idle":"2025-05-21T12:55:44.815672Z","shell.execute_reply.started":"2025-05-21T12:55:44.149225Z","shell.execute_reply":"2025-05-21T12:55:44.814868Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Appliquer ACP","metadata":{}},{"cell_type":"code","source":"def load_images_from_folder(folder):\n    images = []\n    for filename in os.listdir(folder):\n        img = Image.open(os.path.join(folder, filename)).convert('RGB')\n        if img is not None:\n            img = img.resize((512, 512))  # Resize to make them uniform\n            img_array = np.array(img)\n            img_array = img_array.flatten()  # Flatten the 2D image to 1D array\n            images.append(img_array)\n    return images\n\ndef apply_pca(images):\n    pca = PCA(n_components=3)\n    transformed_data = pca.fit_transform(images)\n    return transformed_data\n\ndef plot_3d(data_M, data_NM):\n    fig = plt.figure()\n    ax = fig.add_subplot(111, projection='3d')\n\n    ax.scatter(data_M[:, 0], data_M[:, 1], data_M[:, 2], c='red', label='M')\n    ax.scatter(data_NM[:, 0], data_NM[:, 1], data_NM[:, 2], c='blue', label='NM')\n\n    ax.set_xlabel('Component 1')\n    ax.set_ylabel('Component 2')\n    ax.set_zlabel('Component 3')\n    ax.legend()\n\n    plt.show()\n\n# Load images\npath_M = '/kaggle/input/dataset-mini/Dataset_mini/M'\npath_NM = '/kaggle/input/dataset-mini/Dataset_mini/NM'\n\nM_images = load_images_from_folder(path_M)\nNM_images = load_images_from_folder(path_NM)\n\n# Apply PCA\nM_data = apply_pca(M_images)\nNM_data = apply_pca(NM_images)\n\n# Plot\nplot_3d(M_data, NM_data)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-21T12:56:06.165273Z","iopub.execute_input":"2025-05-21T12:56:06.165946Z","iopub.status.idle":"2025-05-21T12:56:47.142754Z","shell.execute_reply.started":"2025-05-21T12:56:06.165921Z","shell.execute_reply":"2025-05-21T12:56:47.142040Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Affichage du plot","metadata":{}},{"cell_type":"code","source":"\ndef plot_2d(data_M, data_NM):\n    plt.figure(figsize=(10, 7))\n    plt.scatter(data_M[:, 0], data_M[:, 1], c='red', label='M', edgecolor='k')\n    plt.scatter(data_NM[:, 0], data_NM[:, 1], c='blue', label='NM', edgecolor='k')\n    plt.xlabel('Composent 1')\n    plt.ylabel('Composent 2')\n    plt.title('ACP des images du Dataset')\n    plt.legend()\n    plt.grid(True)\n    \n    plt.savefig('acp_2d.png')\n    plt.savefig('acp_2d.svg')\n\n    plt.show()\n\n# Use this function instead of plot_3d in your main code\nplot_2d(M_data, NM_data)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T12:56:57.680512Z","iopub.execute_input":"2025-05-21T12:56:57.681320Z","iopub.status.idle":"2025-05-21T12:56:58.206729Z","shell.execute_reply.started":"2025-05-21T12:56:57.681293Z","shell.execute_reply":"2025-05-21T12:56:58.205935Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Distribution des images par classe","metadata":{}},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\n# === 1. Chemins des dossiers ===\ndataset_path = \"/kaggle/input/dataset-mini/Dataset_mini\"\n\n# === 2. Compter les fichiers par classe ===\ndata = []\n\nfor label in ['M', 'NM']:\n    class_dir = os.path.join(dataset_path, label)\n    count = len([f for f in os.listdir(class_dir) if f.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp'))])\n    data.append({'classe': label, 'nb_images': count})\n\ndf = pd.DataFrame(data).sort_values('classe')\n\n# === 3. Afficher la distribution ===\ndf.set_index('classe')['nb_images'].plot(kind='bar', color=['red', 'blue'])\n\nplt.title('Distribution des classes (M / NM)')\nplt.xlabel('Classe')\nplt.ylabel('Nombre d\\'images')\nplt.xticks(rotation=0)\nplt.tight_layout()\n\n# === 4. Sauvegarde ===\nplt.savefig('/kaggle/working/distribution_classes.png')\nplt.savefig('/kaggle/working/distribution_classes.svg')\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-20T10:31:58.439940Z","iopub.execute_input":"2025-05-20T10:31:58.440448Z","iopub.status.idle":"2025-05-20T10:31:59.222680Z","shell.execute_reply.started":"2025-05-20T10:31:58.440422Z","shell.execute_reply":"2025-05-20T10:31:59.221783Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Indice de Ficher et Silhouette score","metadata":{}},{"cell_type":"code","source":"import numpy as np\nfrom sklearn.metrics import silhouette_score\nfrom sklearn.preprocessing import LabelEncoder\n\n# Supposons que :\n# - pca_2d est un array numpy (N, 2) contenant les deux composantes\n# - labels est une liste ou array des classes correspondantes ('M' ou 'NM')\n\n# Exemple : \n# pca_2d = np.array([[...], [...], ...])\n# labels = ['M', 'NM', 'M', ...]\n\n# Encodage numérique des labels\nle = LabelEncoder()\nencoded_labels = le.fit_transform(labels)  # 'M' → 1, 'NM' → 0 (par ex.)\n\n# 1. Silhouette score\nsil_score = silhouette_score(pca_2d, encoded_labels)\n\n# 2. Indice de Fisher (sur la 1ère composante)\ngroup0 = pca_2d[encoded_labels == 0][:, 0]  # Component 1 for class 0\ngroup1 = pca_2d[encoded_labels == 1][:, 0]  # Component 1 for class 1\n\nmean_diff = np.abs(np.mean(group0) - np.mean(group1))\nvar_sum = np.var(group0) + np.var(group1)\nfisher_score = (mean_diff ** 2) / var_sum if var_sum > 0 else 0\n\nprint(f\"✅ Silhouette score : {sil_score:.4f}\")\nprint(f\"✅ Indice de Fisher (composante 1) : {fisher_score:.4f}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### **Score**\t**Interprétation**\n\n* Silhouette > 0.5\t**Bonne séparation**\n* Silhouette ~ 0.2 - 0.4\t**Séparation moyenne**\n* Silhouette < 0.1\t**Mélange fort**\n## _____________________________________________\n\n* Fisher > 1\t**Bonne séparation sur l’axe choisi**\n* Fisher ~ 0.2 - 0.5\t**Séparation faible**","metadata":{}},{"cell_type":"code","source":"import os\nimport numpy as np\nfrom tqdm import tqdm\nfrom PIL import Image\n\nfrom sklearn.decomposition import PCA\nfrom sklearn.metrics import silhouette_score\nfrom sklearn.preprocessing import LabelEncoder\n\nimport matplotlib.pyplot as plt\nimport torch\nimport torchvision.transforms as transforms\nimport torchvision.models as models\n\n# === 1. Chargement des images et étiquettes ===\n\ndataset_path = \"/kaggle/input/dataset-mini/Dataset_mini\"\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor()\n])\n\nimage_paths = []\nlabels = []\n\nfor label_name in ['M', 'NM']:\n    label_path = os.path.join(dataset_path, label_name)\n    for fname in os.listdir(label_path):\n        if fname.lower().endswith(('.jpg', '.png', '.jpeg')):\n            image_paths.append(os.path.join(label_path, fname))\n            labels.append(label_name)\n\n# === 2. Feature extraction via ResNet18 ===\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nmodel = models.resnet18(pretrained=True)\nmodel = torch.nn.Sequential(*list(model.children())[:-1])  # remove final layer\nmodel.eval().to(device)\n\nfeatures = []\n\nfor img_path in tqdm(image_paths, desc=\"Extraction de features\"):\n    try:\n        img = Image.open(img_path).convert('RGB')\n        tensor = transform(img).unsqueeze(0).to(device)\n        with torch.no_grad():\n            feat = model(tensor).squeeze().cpu().numpy()\n        features.append(feat)\n    except Exception as e:\n        print(f\"Erreur avec {img_path}: {e}\")\n\nfeatures = np.array(features)\nlabels = np.array(labels)\n\n# === 3. PCA à 2 composantes ===\n\npca = PCA(n_components=2)\npca_2d = pca.fit_transform(features)\n\n# === 4. Silhouette score + indice de Fisher ===\n\nencoded_labels = LabelEncoder().fit_transform(labels)\n\nsil_score = silhouette_score(pca_2d, encoded_labels)\n\ngroup0 = pca_2d[encoded_labels == 0][:, 0]  # Component 1 for NM\ngroup1 = pca_2d[encoded_labels == 1][:, 0]  # Component 1 for M\n\nmean_diff = np.abs(np.mean(group0) - np.mean(group1))\nvar_sum = np.var(group0) + np.var(group1)\nfisher_score = (mean_diff ** 2) / var_sum if var_sum > 0 else 0\n\nprint(\"\\n--- Résultats ---\")\nprint(f\"Silhouette Score     : {sil_score:.4f}\")\nprint(f\"Indice de Fisher (C1): {fisher_score:.4f}\")\n\n# === 5. Scatter plot ===\n\nplt.figure(figsize=(10, 6))\nfor i, label in enumerate(['M', 'NM']):\n    idx = encoded_labels == i\n    plt.scatter(pca_2d[idx, 0], pca_2d[idx, 1],\n                label=label, alpha=0.6,\n                c='red' if label == 'M' else 'blue', s=15)\nplt.title(\"ACP des images du Dataset\")\nplt.xlabel(\"Composante 1\")\nplt.ylabel(\"Composante 2\")\nplt.legend()\nplt.grid(True)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T12:57:21.246052Z","iopub.execute_input":"2025-05-21T12:57:21.246756Z","iopub.status.idle":"2025-05-21T12:57:36.788872Z","shell.execute_reply.started":"2025-05-21T12:57:21.246720Z","shell.execute_reply":"2025-05-21T12:57:36.788117Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## t-SNE","metadata":{}},{"cell_type":"code","source":"import os\nimport torch\nimport torchvision.transforms as transforms\nimport torchvision.models as models\nfrom torchvision.datasets import ImageFolder\nfrom torch.utils.data import DataLoader\nfrom sklearn.manifold import TSNE\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm\n\n# === 1. Préparer le dataset ===\ndata_dir = '/kaggle/input/dataset-mini/Dataset_mini'  # <-- change ça avec le chemin réel\n\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\ndataset = ImageFolder(data_dir, transform=transform)\ndataloader = DataLoader(dataset, batch_size=32, shuffle=False)\n\n# === 2. Charger ResNet50 sans la couche finale ===\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = models.resnet50(pretrained=True)\nmodel = torch.nn.Sequential(*list(model.children())[:-1])  # Supprimer la couche fc\nmodel.to(device)\nmodel.eval()\n\n# === 3. Extraire les features ===\nfeatures = []\nlabels = []\n\nwith torch.no_grad():\n    for images, lbls in tqdm(dataloader):\n        images = images.to(device)\n        output = model(images)\n        output = output.view(output.size(0), -1)  # Flatten\n        features.append(output.cpu())\n        labels.extend(lbls.numpy())\n\nfeatures = torch.cat(features).numpy()\n\n# === 4. Appliquer t-SNE ===\nprint(\"Applying t-SNE...\")\ntsne = TSNE(n_components=2, perplexity=30, random_state=42)\nfeatures_2d = tsne.fit_transform(features)\n\n# === 5. Visualiser ===\nimport numpy as np\nfeatures_2d = np.array(features_2d)\nlabels = np.array(labels)\nclass_names = dataset.classes\ncolors = ['red', 'blue']\n\nplt.figure(figsize=(10, 7))\nfor i, class_name in enumerate(class_names):\n    idx = labels == i\n    plt.scatter(features_2d[idx, 0], features_2d[idx, 1], c=colors[i], label=class_name, alpha=0.6, s=10)\n\nplt.legend()\nplt.title('t-SNE des embeddings extraits par ResNet50')\nplt.xlabel('Dimension 1')\nplt.ylabel('Dimension 2')\nplt.grid(True)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T12:58:13.443907Z","iopub.execute_input":"2025-05-21T12:58:13.444497Z","iopub.status.idle":"2025-05-21T12:58:26.815712Z","shell.execute_reply.started":"2025-05-21T12:58:13.444472Z","shell.execute_reply":"2025-05-21T12:58:26.815007Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## UMAP","metadata":{}},{"cell_type":"code","source":"import umap\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport warnings\n\n# Ignore le warning de UMAP sur n_jobs\nwarnings.filterwarnings(\"ignore\", category=UserWarning)\n\n# === UMAP : réduction à 2D ===\nreducer = umap.UMAP(n_neighbors=15, min_dist=0.1, metric='euclidean', random_state=42)\nfeatures_2d = reducer.fit_transform(features)  # 'features' = numpy array de shape (N, D)\n\n# === Visualisation ===\nfeatures_2d = np.array(features_2d)\nlabels = np.array(labels)\nclass_names = ['M', 'NM']\ncolors = ['red', 'blue']\n\nplt.figure(figsize=(10, 7))\nfor i, class_name in enumerate(class_names):\n    idx = labels == i\n    plt.scatter(features_2d[idx, 0], features_2d[idx, 1], c=colors[i], label=class_name, alpha=0.6, s=10)\n\nplt.legend()\nplt.title('UMAP des embeddings extraits par ResNet50')\nplt.xlabel('Dimension 1')\nplt.ylabel('Dimension 2')\nplt.grid(True)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T13:29:55.454740Z","iopub.execute_input":"2025-05-21T13:29:55.455069Z","iopub.status.idle":"2025-05-21T13:29:57.915842Z","shell.execute_reply.started":"2025-05-21T13:29:55.455047Z","shell.execute_reply":"2025-05-21T13:29:57.915053Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Evaluation quantitave du dataset\n\n1. **Silhouette Score**\nMesure la compacité intra-cluster vs. la séparation inter-cluster.\nValeur entre -1 (mauvais) et 1 (très bon).\n\n2. **Davies-Bouldin Index**\nPlus c’est bas, mieux c’est.\nMesure la ressemblance entre clusters.\n\n3. **Calinski-Harabasz Index**\nPlus c’est haut, mieux c’est.\nMesure le rapport entre séparation inter-classes et cohésion intra-classe","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import silhouette_score, davies_bouldin_score, calinski_harabasz_score\n\n# features = embeddings (extraits via ResNet50)\n# labels = 0 pour NM, 1 pour M\n\n# Silhouette score\nsil_score = silhouette_score(features, labels)\nprint(f\"Silhouette Score : {sil_score:.4f}\")\n\n# Davies-Bouldin Index\ndb_score = davies_bouldin_score(features, labels)\nprint(f\"Davies-Bouldin Index : {db_score:.4f}\")\n\n# Calinski-Harabasz Index\nch_score = calinski_harabasz_score(features, labels)\nprint(f\"Calinski-Harabasz Index : {ch_score:.2f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-21T14:27:09.461207Z","iopub.execute_input":"2025-05-21T14:27:09.461787Z","iopub.status.idle":"2025-05-21T14:27:09.547836Z","shell.execute_reply.started":"2025-05-21T14:27:09.461765Z","shell.execute_reply":"2025-05-21T14:27:09.547087Z"}},"outputs":[],"execution_count":null}]}