{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":10338,"databundleVersionId":862042}],"dockerImageVersionId":31328,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"f2206cc6","cell_type":"code","source":"import os\nimport csv\nimport copy\nimport time\nimport math\nimport random\nfrom pathlib import Path\nfrom functools import lru_cache\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom PIL import Image\n\nimport pydicom\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.optim import lr_scheduler\nfrom torch.utils.data import Dataset, DataLoader\n\nimport torchvision\nfrom torchvision import models, transforms\n\nfrom sklearn.metrics import (\n    classification_report,\n    confusion_matrix,\n    precision_score,\n    recall_score,\n    f1_score,\n    roc_auc_score,\n)\nfrom tqdm.auto import tqdm\n\nprint(\"Torch version:\", torch.__version__)\nprint(\"Torchvision version:\", torchvision.__version__)\nprint(\"CUDA available:\", torch.cuda.is_available())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T14:43:05.658212Z","iopub.execute_input":"2026-03-28T14:43:05.658486Z","iopub.status.idle":"2026-03-28T14:43:10.224534Z","shell.execute_reply.started":"2026-03-28T14:43:05.658461Z","shell.execute_reply":"2026-03-28T14:43:10.223744Z"}},"outputs":[],"execution_count":null},{"id":"4299d9e6-f02f-4eeb-9980-27ca022eda8a","cell_type":"markdown","source":"**configuration**","metadata":{}},{"id":"bf5deda8","cell_type":"code","source":"# =========================\n# 2. Configuration\n# =========================\nDATA_DIR = Path(\"/kaggle/input/competitions/rsna-pneumonia-detection-challenge\")\nOUTPUT_DIR = Path(\"outputs\")\nOUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n\nBATCH_SIZE = 8\nNUM_WORKERS = 0\nNUM_EPOCHS = 5\nLR = 1e-4\nSTEP_SIZE = 2\nGAMMA = 0.1\nINPUT_SIZE = 224\nNUM_CLASSES = 2\n\nDEVICE = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", DEVICE)\nprint(\"DATA_DIR exists:\", DATA_DIR.exists())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T14:43:17.450929Z","iopub.execute_input":"2026-03-28T14:43:17.451445Z","iopub.status.idle":"2026-03-28T14:43:17.457877Z","shell.execute_reply.started":"2026-03-28T14:43:17.451414Z","shell.execute_reply":"2026-03-28T14:43:17.457200Z"}},"outputs":[],"execution_count":null},{"id":"bbb8adbd-ed18-4281-b9c1-cd7ecb0b8787","cell_type":"markdown","source":"**Build RSNA train/val tables**","metadata":{}},{"id":"0320bae8","cell_type":"code","source":"\nfrom sklearn.model_selection import train_test_split\n\ntrain_images_dir = DATA_DIR / \"stage_2_train_images\"\nlabels_path = DATA_DIR / \"stage_2_train_labels.csv\"\n\nlabels_df = pd.read_csv(labels_path)\n\n# RSNA: plusieurs lignes possibles par patientId\n# Target = 1 si au moins une box de pneumonie existe\npatient_df = labels_df.groupby(\"patientId\", as_index=False)[\"Target\"].max()\npatient_df[\"label\"] = patient_df[\"Target\"].astype(int)\n\npatient_df[\"image_path\"] = patient_df[\"patientId\"].apply(\n    lambda x: str(train_images_dir / f\"{x}.dcm\")\n)\npatient_df[\"filename\"] = patient_df[\"patientId\"].apply(lambda x: f\"{x}.dcm\")\n\ntrain_df, val_df = train_test_split(\n    patient_df[[\"patientId\", \"image_path\", \"filename\", \"label\"]],\n    test_size=0.2,\n    random_state=42,\n    stratify=patient_df[\"label\"]\n)\n\ntrain_df = train_df.reset_index(drop=True)\nval_df = val_df.reset_index(drop=True)\n\nprint(\"Train size:\", len(train_df))\nprint(\"Val size:\", len(val_df))\nprint(train_df[\"label\"].value_counts())\nprint(val_df[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T14:48:23.212642Z","iopub.execute_input":"2026-03-28T14:48:23.213323Z","iopub.status.idle":"2026-03-28T14:48:23.514721Z","shell.execute_reply.started":"2026-03-28T14:48:23.213289Z","shell.execute_reply":"2026-03-28T14:48:23.513811Z"}},"outputs":[],"execution_count":null},{"id":"62c5546f-a076-49be-b78c-377476e87af7","cell_type":"markdown","source":"** Train / val split**","metadata":{}},{"id":"dd5c8603","cell_type":"code","source":"\ntrain_df, val_df = train_test_split(\n    rsna_df,\n    test_size=VAL_SIZE,\n    random_state=SEED,\n    stratify=rsna_df[\"label\"],\n)\n\ntrain_df = train_df.reset_index(drop=True)\nval_df = val_df.reset_index(drop=True)\n\nprint(\"Train size:\", len(train_df))\nprint(\"Val size:\", len(val_df))\nprint(\"\\nTrain label counts:\")\nprint(train_df[\"label\"].value_counts())\nprint(\"\\nVal label counts:\")\nprint(val_df[\"label\"].value_counts())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T14:48:37.503523Z","iopub.execute_input":"2026-03-28T14:48:37.503850Z","iopub.status.idle":"2026-03-28T14:48:37.511141Z","shell.execute_reply.started":"2026-03-28T14:48:37.503823Z","shell.execute_reply":"2026-03-28T14:48:37.510153Z"}},"outputs":[],"execution_count":null},{"id":"a5375c02-9d3b-41c3-bb3a-883ad4d43088","cell_type":"markdown","source":"**Dataset & transforms**","metadata":{}},{"id":"ac34b765","cell_type":"code","source":"\nmean = [0.485, 0.456, 0.406]\nstd = [0.229, 0.224, 0.225]\n\ndata_transforms = {\n    \"train\": transforms.Compose([\n        transforms.Resize((INPUT_SIZE, INPUT_SIZE)),\n        transforms.RandomHorizontalFlip(p=0.5),\n        transforms.ToTensor(),\n        transforms.Normalize(mean, std),\n    ]),\n    \"val\": transforms.Compose([\n        transforms.Resize((INPUT_SIZE, INPUT_SIZE)),\n        transforms.ToTensor(),\n        transforms.Normalize(mean, std),\n    ]),\n}\n\n@lru_cache(maxsize=128)\ndef load_dicom_as_rgb(path_str):\n    ds = pydicom.dcmread(path_str)\n\n    img = ds.pixel_array.astype(np.float32)\n\n    if getattr(ds, \"PhotometricInterpretation\", \"\") == \"MONOCHROME1\":\n        img = img.max() - img\n\n    img_min = img.min()\n    img_max = img.max()\n\n    if img_max > img_min:\n        img = (img - img_min) / (img_max - img_min)\n    else:\n        img = np.zeros_like(img, dtype=np.float32)\n\n    img = (img * 255.0).clip(0, 255).astype(np.uint8)\n\n    img = np.stack([img, img, img], axis=-1)\n    return Image.fromarray(img)\n\nclass RSNADataset(Dataset):\n    def __init__(self, dataframe, transform=None, return_labels=True):\n        df = dataframe.reset_index(drop=True).copy()\n\n        self.image_paths = df[\"image_path\"].tolist()\n        self.filenames = df[\"filename\"].tolist()\n        self.transform = transform\n        self.return_labels = return_labels\n        self.labels = df[\"label\"].astype(int).tolist() if return_labels else None\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        image = load_dicom_as_rgb(self.image_paths[idx])\n\n        if self.transform is not None:\n            image = self.transform(image)\n\n        if self.return_labels:\n            return image, self.labels[idx]\n        return image\n\n    def get_filename(self, idx):\n        return self.filenames[idx]\n\ntrain_dataset = RSNADataset(train_df, transform=data_transforms[\"train\"], return_labels=True)\nval_dataset = RSNADataset(val_df, transform=data_transforms[\"val\"], return_labels=True)\n\nimage_datasets = {\n    \"train\": train_dataset,\n    \"val\": val_dataset,\n}\n\ndataloaders = {\n    split: DataLoader(\n        image_datasets[split],\n        batch_size=BATCH_SIZE,\n        shuffle=(split == \"train\"),\n        num_workers=NUM_WORKERS,\n        pin_memory=torch.cuda.is_available(),\n    )\n    for split in [\"train\", \"val\"]\n}\n\ndataset_sizes = {split: len(image_datasets[split]) for split in [\"train\", \"val\"]}\nclass_names = [\"normal\", \"pneumonia\"]\n\nprint(\"Dataset sizes:\", dataset_sizes)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T14:48:43.555405Z","iopub.execute_input":"2026-03-28T14:48:43.555727Z","iopub.status.idle":"2026-03-28T14:48:43.575525Z","shell.execute_reply.started":"2026-03-28T14:48:43.555698Z","shell.execute_reply":"2026-03-28T14:48:43.574784Z"}},"outputs":[],"execution_count":null},{"id":"f0088092-db75-42c5-aed7-efe70e8dac6f","cell_type":"markdown","source":"**Visualisation rapide**","metadata":{}},{"id":"348a3aac","cell_type":"code","source":"def imshow(inp, title=None):\n    inp = inp.numpy().transpose((1, 2, 0))\n    inp = np.array(std) * inp + np.array(mean)\n    inp = np.clip(inp, 0, 1)\n    plt.figure(figsize=(10, 6))\n    plt.imshow(inp)\n    if title is not None:\n        plt.title(title)\n    plt.axis(\"off\")\n    plt.show()\n\ninputs, classes = next(iter(dataloaders[\"train\"]))\ngrid = torchvision.utils.make_grid(inputs[:8])\nimshow(grid, title=[class_names[int(x)] for x in classes[:8]])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T14:48:52.174893Z","iopub.execute_input":"2026-03-28T14:48:52.175676Z","iopub.status.idle":"2026-03-28T14:48:53.067996Z","shell.execute_reply.started":"2026-03-28T14:48:52.175646Z","shell.execute_reply":"2026-03-28T14:48:53.067211Z"}},"outputs":[],"execution_count":null},{"id":"10499808-2c8e-4ae2-9d12-40911799ed20","cell_type":"markdown","source":"**Utilities**","metadata":{}},{"id":"7b5bc625","cell_type":"code","source":"\ndef plot_history(val_values, train_values, metric_name, output_dir=OUTPUT_DIR):\n    plt.figure(figsize=(8, 5))\n    plt.title(f\"{metric_name} after epoch: {len(train_values)}\")\n    plt.xlabel(\"Epoch\")\n    plt.ylabel(metric_name)\n    plt.plot(range(1, len(train_values) + 1), train_values, label=f\"Train {metric_name}\")\n    plt.plot(range(1, len(val_values) + 1), val_values, label=f\"Validation {metric_name}\")\n    plt.legend()\n    plt.tight_layout()\n    plt.savefig(output_dir / f\"{metric_name.lower()}_resnet18.png\")\n    plt.close()\n\ndef build_model(num_classes):\n    model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\n    num_ftrs = model.fc.in_features\n    model.fc = nn.Linear(num_ftrs, num_classes)\n    return model.to(DEVICE)\n\ndef forward_with_loss(model, inputs, labels, criterion):\n    logits = model(inputs)\n    loss = criterion(logits, labels)\n    return logits, loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T14:49:00.925619Z","iopub.execute_input":"2026-03-28T14:49:00.926286Z","iopub.status.idle":"2026-03-28T14:49:00.932851Z","shell.execute_reply.started":"2026-03-28T14:49:00.926252Z","shell.execute_reply":"2026-03-28T14:49:00.931867Z"}},"outputs":[],"execution_count":null},{"id":"eaa5dbe0-92b8-4f92-8894-1d01ed0e899e","cell_type":"markdown","source":"**Training function**","metadata":{}},{"id":"09a56cd0","cell_type":"code","source":"def train_model(model, criterion, optimizer, scheduler, num_epochs=25, model_name=\"resnet18\"):\n    since = time.time()\n    best_model_wts = copy.deepcopy(model.state_dict())\n    best_acc = 0.0\n\n    train_loss_hist, val_loss_hist = [], []\n    train_acc_hist, val_acc_hist = [], []\n\n    for epoch in range(num_epochs):\n        print(f\"\\nEpoch {epoch+1}/{num_epochs}\")\n        print(\"-\" * 30)\n\n        for phase in [\"train\", \"val\"]:\n            if phase == \"train\":\n                model.train()\n            else:\n                model.eval()\n\n            running_loss = 0.0\n            running_corrects = 0\n\n            progress_bar = tqdm(dataloaders[phase], desc=f\"{phase}\", leave=False)\n\n            for inputs, labels in progress_bar:\n                inputs = inputs.to(DEVICE, non_blocking=True)\n                labels = labels.to(DEVICE, non_blocking=True)\n\n                optimizer.zero_grad()\n\n                with torch.set_grad_enabled(phase == \"train\"):\n                    logits, loss = forward_with_loss(model, inputs, labels, criterion)\n                    _, preds = torch.max(logits, 1)\n\n                    if phase == \"train\":\n                        loss.backward()\n                        optimizer.step()\n\n                running_loss += loss.detach().item() * inputs.size(0)\n                running_corrects += torch.sum(preds == labels).item()\n\n            epoch_loss = running_loss / dataset_sizes[phase]\n            epoch_acc = running_corrects / dataset_sizes[phase]\n\n            print(f\"{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}\")\n\n            if phase == \"train\":\n                train_loss_hist.append(epoch_loss)\n                train_acc_hist.append(epoch_acc)\n                scheduler.step()\n            else:\n                val_loss_hist.append(epoch_loss)\n                val_acc_hist.append(epoch_acc)\n\n                if epoch_acc > best_acc:\n                    best_acc = epoch_acc\n                    best_model_wts = copy.deepcopy(model.state_dict())\n\n        torch.save(model.state_dict(), OUTPUT_DIR / f\"{model_name}_last.pth\")\n\n    time_elapsed = time.time() - since\n    print(f\"\\nTraining complete in {time_elapsed/60:.1f} min\")\n    print(f\"Best val Acc: {best_acc:.4f}\")\n\n    model.load_state_dict(best_model_wts)\n    torch.save(model.state_dict(), OUTPUT_DIR / f\"{model_name}_best.pth\")\n\n    plot_history(val_loss_hist, train_loss_hist, \"Loss\")\n    plot_history(val_acc_hist, train_acc_hist, \"Accuracy\")\n\n    history_df = pd.DataFrame({\n        \"epoch\": list(range(1, len(train_loss_hist) + 1)),\n        \"train_loss\": train_loss_hist,\n        \"val_loss\": val_loss_hist,\n        \"train_acc\": train_acc_hist,\n        \"val_acc\": val_acc_hist,\n    })\n    history_df.to_csv(OUTPUT_DIR / f\"{model_name}_history.csv\", index=False)\n\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T14:49:07.054648Z","iopub.execute_input":"2026-03-28T14:49:07.055000Z","iopub.status.idle":"2026-03-28T14:49:07.066093Z","shell.execute_reply.started":"2026-03-28T14:49:07.054943Z","shell.execute_reply":"2026-03-28T14:49:07.065333Z"}},"outputs":[],"execution_count":null},{"id":"10e2fd37-4462-4195-97d1-9053f9e6e5d5","cell_type":"markdown","source":"**Export probabilities to CSV**","metadata":{}},{"id":"5ab2b917","cell_type":"code","source":"\ndef export_probabilities(model, dataset, csv_path, labels_csv_path):\n    loader = DataLoader(\n        dataset,\n        batch_size=1,\n        shuffle=False,\n        num_workers=0,\n        pin_memory=torch.cuda.is_available(),\n    )\n\n    model.eval()\n    total = 0\n    correct = 0\n    rows = []\n    label_rows = []\n\n    with torch.no_grad():\n        for i, (images, labels) in enumerate(tqdm(loader, desc=f\"Export -> {Path(csv_path).name}\")):\n            images = images.to(DEVICE, non_blocking=True)\n            labels = labels.to(DEVICE, non_blocking=True)\n\n            outputs = model(images)\n            probs = torch.softmax(outputs, dim=1).cpu().numpy()[0].tolist()\n            _, predicted = torch.max(outputs, 1)\n\n            sample_name = dataset.get_filename(i)\n\n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n\n            rows.append(probs + [sample_name])\n            label_rows.append([sample_name, int(labels.item())])\n\n    acc = correct / total if total > 0 else 0.0\n    print(f\"Export accuracy ({Path(csv_path).name}): {acc:.4f}\")\n\n    with open(csv_path, \"w\", newline=\"\") as f:\n        writer = csv.writer(f)\n        writer.writerows(rows)\n\n    with open(labels_csv_path, \"w\", newline=\"\") as f:\n        writer = csv.writer(f)\n        writer.writerows(label_rows)\n\n    print(\"Saved:\", csv_path)\n    print(\"Saved:\", labels_csv_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T14:49:15.752587Z","iopub.execute_input":"2026-03-28T14:49:15.752908Z","iopub.status.idle":"2026-03-28T14:49:15.761319Z","shell.execute_reply.started":"2026-03-28T14:49:15.752878Z","shell.execute_reply":"2026-03-28T14:49:15.760484Z"}},"outputs":[],"execution_count":null},{"id":"ab371a53","cell_type":"markdown","source":"## 11. Entraîner les modèles de base\n\n","metadata":{}},{"id":"cf061313","cell_type":"code","source":"\ndef train_and_export_single_model():\n    model_name = \"resnet18\"\n    print(f\"\\n========== {model_name.upper()} ==========\")\n\n    model = build_model(NUM_CLASSES)\n\n    criterion = nn.CrossEntropyLoss()\n    optimizer = optim.Adam(model.parameters(), lr=LR)\n    scheduler = lr_scheduler.StepLR(optimizer, step_size=STEP_SIZE, gamma=GAMMA)\n\n    model = train_model(\n        model=model,\n        criterion=criterion,\n        optimizer=optimizer,\n        scheduler=scheduler,\n        num_epochs=NUM_EPOCHS,\n        model_name=model_name,\n    )\n\n    export_probabilities(\n        model=model,\n        dataset=train_dataset,\n        csv_path=OUTPUT_DIR / f\"{model_name}_train.csv\",\n        labels_csv_path=OUTPUT_DIR / \"train_labels.csv\",\n    )\n\n    export_probabilities(\n        model=model,\n        dataset=val_dataset,\n        csv_path=OUTPUT_DIR / f\"{model_name}_test.csv\",\n        labels_csv_path=OUTPUT_DIR / \"test_labels.csv\",\n    )\n\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T14:49:26.748173Z","iopub.execute_input":"2026-03-28T14:49:26.748800Z","iopub.status.idle":"2026-03-28T14:49:26.754299Z","shell.execute_reply.started":"2026-03-28T14:49:26.748770Z","shell.execute_reply":"2026-03-28T14:49:26.753478Z"}},"outputs":[],"execution_count":null},{"id":"47505a98-66a2-42af-9698-1636dbdd8720","cell_type":"markdown","source":"**Run RESNET18 only**","metadata":{}},{"id":"4d521030-d089-4917-a4f2-5230963710a8","cell_type":"code","source":"\ntrained_model = train_and_export_single_model()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T14:49:34.617697Z","iopub.execute_input":"2026-03-28T14:49:34.618603Z","iopub.status.idle":"2026-03-28T16:02:01.388839Z","shell.execute_reply.started":"2026-03-28T14:49:34.618569Z","shell.execute_reply":"2026-03-28T16:02:01.388049Z"}},"outputs":[],"execution_count":null},{"id":"5c37cf87","cell_type":"code","source":"# =========================\n# 12. Ensemble helpers\n# =========================\ndef getfile(filename):\n    df = pd.read_csv(filename, header=None)\n    df = np.asarray(df)[:, :-1].astype(np.float64)  # dernière colonne = nom fichier\n    return df\n\ndef getlabels(filename):\n    df = pd.read_csv(filename, header=None)\n    df = np.asarray(df)[:, 1]\n    return df.astype(int)\n\ndef predicting(prob_matrix):\n    return np.argmax(prob_matrix, axis=1).astype(int)\n\ndef show_metrics(labels, predictions, classes):\n    print(\"Classification Report:\")\n    print(classification_report(labels, predictions, target_names=classes, digits=4))\n\n    matrix = confusion_matrix(labels, predictions)\n    print(\"Confusion matrix:\")\n    print(matrix)\n\n    classwise_acc = matrix.diagonal() / matrix.sum(axis=1)\n    print(\"\\nClasswise Accuracy:\", classwise_acc)\n\n    plt.figure(figsize=(5, 4))\n    plt.imshow(matrix)\n    plt.title(\"Confusion Matrix\")\n    plt.xlabel(\"Predicted\")\n    plt.ylabel(\"True\")\n    plt.xticks(range(len(classes)), classes)\n    plt.yticks(range(len(classes)), classes)\n    for i in range(matrix.shape[0]):\n        for j in range(matrix.shape[1]):\n            plt.text(j, i, matrix[i, j], ha=\"center\", va=\"center\")\n    plt.tight_layout()\n    plt.show()\n\ndef get_weights(matrix):\n    weights = []\n    for i in range(matrix.shape[0]):\n        row = matrix[i]\n        w = 0.0\n        for j in range(row.shape[0]):\n            w += np.tanh(row[j])\n        weights.append(w)\n    return weights\n\ndef get_scores(labels, *argv):\n    count = len(argv)\n    metrics_matrix = np.zeros(shape=(4, count))\n    num_classes_local = np.unique(labels).shape[0]\n\n    for i, prob in enumerate(argv):\n        preds = predicting(prob)\n\n        if num_classes_local == 2:\n            pre = precision_score(labels, preds, zero_division=0)\n            rec = recall_score(labels, preds, zero_division=0)\n            f1 = f1_score(labels, preds, zero_division=0)\n\n            # AUC binaire : on utilise la proba de la classe positive\n            auc = roc_auc_score(labels, prob[:, 1])\n        else:\n            pre = precision_score(labels, preds, average=\"macro\", zero_division=0)\n            rec = recall_score(labels, preds, average=\"macro\", zero_division=0)\n            f1 = f1_score(labels, preds, average=\"macro\", zero_division=0)\n            auc = roc_auc_score(labels, prob, average=\"macro\", multi_class=\"ovo\")\n\n        metrics_matrix[:, i] = np.array([pre, rec, f1, auc])\n\n    weights = get_weights(metrics_matrix.T)\n    return weights\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T16:02:46.266067Z","iopub.execute_input":"2026-03-28T16:02:46.266417Z","iopub.status.idle":"2026-03-28T16:02:46.278307Z","shell.execute_reply.started":"2026-03-28T16:02:46.266389Z","shell.execute_reply":"2026-03-28T16:02:46.277535Z"}},"outputs":[],"execution_count":null},{"id":"4a2d9984-a022-4616-8bd8-70d619b86568","cell_type":"markdown","source":"**Métrique et matrice de confusion**","metadata":{}},{"id":"53867c7f-6a3f-49ba-84b9-4a7298fc3457","cell_type":"code","source":"\nprobs = getfile(\"outputs/resnet18_test.csv\")\n\n# Charger les labels correctement (colonne 1 seulement)\nlabels_df = pd.read_csv(\"outputs/test_labels.csv\", header=None)\nlabels = labels_df.iloc[:, 1].astype(int).values  # ⚠️ colonne 1 = label\n\n# Prédictions\npreds = predicting(probs)\n\n# Noms des classes\nclass_names = [\"Normal\", \"Pneumonia\"]\n\n# Affichage des métriques\nshow_metrics(labels, preds, class_names)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-28T16:16:33.017418Z","iopub.execute_input":"2026-03-28T16:16:33.018193Z","iopub.status.idle":"2026-03-28T16:16:33.137333Z","shell.execute_reply.started":"2026-03-28T16:16:33.018159Z","shell.execute_reply":"2026-03-28T16:16:33.136562Z"}},"outputs":[],"execution_count":null}]}