{"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":[{"sourceId":124046,"databundleVersionId":14573278,"sourceType":"competition"}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"#je commence avec tout nos diffentre imports donc on aura besoin\nimport os\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\nimport torchvision.models as models\n\nfrom sklearn.model_selection import train_test_split\n\n#je choisis une seed pour la repoducibilité\ndef seed_everything(seed):\n    import random\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\nseed_everything(123)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", device)\n\ncheminData = \"/kaggle/input/2025-competition-apprentissage-i-3-d\"\ncheminTrain = os.path.join(cheminData, \"train\")\ncheminTest  = os.path.join(cheminData, \"test\")\ncheminCSV = os.path.join(cheminData, \"train.csv\")\ncheminSample = os.path.join(cheminData, \"sampleSubmission.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T19:02:15.848650Z","iopub.execute_input":"2025-12-30T19:02:15.849229Z","iopub.status.idle":"2025-12-30T19:02:15.857411Z","shell.execute_reply.started":"2025-12-30T19:02:15.849202Z","shell.execute_reply":"2025-12-30T19:02:15.856802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(cheminCSV)\nprint(train_df.head())\nprint(train_df[\"Category\"].value_counts())\ntrain_df[\"path\"] = train_df[\"ID\"].apply(lambda x: os.path.join(cheminTrain, f\"{x}.png\"))\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T19:02:15.858663Z","iopub.execute_input":"2025-12-30T19:02:15.859003Z","iopub.status.idle":"2025-12-30T19:02:15.898267Z","shell.execute_reply.started":"2025-12-30T19:02:15.858963Z","shell.execute_reply":"2025-12-30T19:02:15.897674Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tr_df, val_df = train_test_split(\n    train_df,\n    test_size=0.2,\n    stratify=train_df[\"Category\"],\n    random_state=42\n)\n\nprint(\"Train:\", len(tr_df), \"Val:\", len(val_df))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T19:02:15.899064Z","iopub.execute_input":"2025-12-30T19:02:15.899344Z","iopub.status.idle":"2025-12-30T19:02:15.911760Z","shell.execute_reply.started":"2025-12-30T19:02:15.899313Z","shell.execute_reply":"2025-12-30T19:02:15.911122Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#je convertis nos image en grayscale avec 3 canaux\ntrain_tfms = T.Compose([\n    T.Resize((224, 224)),\n    T.RandomHorizontalFlip(0.5),\n    T.RandomVerticalFlip(0.5),\n    T.RandomRotation(20),\n    \n    #ajout de contraste sur nos image pour meilleure detection\n    T.RandomAutocontrast(p=0.5),\n    T.RandomEqualize(p=0.2),\n    \n    T.ToTensor(),\n    T.Lambda(lambda x: x.repeat(3,1,1) if x.shape[0] == 1 else x),\n    T.Normalize(mean=[0.485, 0.456, 0.406],\n                std=[0.229, 0.224, 0.225])\n])\n\nval_tfms = T.Compose([\n    T.Resize((224, 224)),\n    T.ToTensor(),\n    T.Lambda(lambda x: x.repeat(3,1,1) if x.shape[0] == 1 else x),\n    T.Normalize(mean=[0.485, 0.456, 0.406],\n                std=[0.229, 0.224, 0.225])\n])\n\nclass FIBSEMDataset(Dataset):\n    def __init__(self, df, transform=None, is_test=False, cheminTest=None):\n        self.df = df.reset_index(drop=True)\n        self.transform = transform\n        self.is_test = is_test\n        self.cheminTest = cheminTest\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        if self.is_test:\n            img_id = self.df.loc[idx, \"ID\"]\n            path = os.path.join(self.cheminTest, f\"{img_id}.png\")\n            img = Image.open(path).convert(\"L\")\n            if self.transform:\n                img = self.transform(img)\n            return img_id, img\n\n        path = self.df.loc[idx, \"path\"]\n        y = int(self.df.loc[idx, \"Category\"])\n        img = Image.open(path).convert(\"L\")\n        if self.transform:\n            img = self.transform(img)\n        return img, y\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T19:02:15.913337Z","iopub.execute_input":"2025-12-30T19:02:15.913574Z","iopub.status.idle":"2025-12-30T19:02:15.922542Z","shell.execute_reply.started":"2025-12-30T19:02:15.913554Z","shell.execute_reply":"2025-12-30T19:02:15.921817Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BATCH_SIZE = 64\nNUM_WORKERS = 2\n\ntrain_loader = DataLoader(\n    FIBSEMDataset(tr_df, transform=train_tfms),\n    batch_size=BATCH_SIZE, shuffle=True,\n    num_workers=NUM_WORKERS, pin_memory=True\n)\n\nval_loader = DataLoader(\n    FIBSEMDataset(val_df, transform=val_tfms),\n    batch_size=BATCH_SIZE, shuffle=False,\n    num_workers=NUM_WORKERS, pin_memory=True\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T19:02:15.923388Z","iopub.execute_input":"2025-12-30T19:02:15.923622Z","iopub.status.idle":"2025-12-30T19:02:15.941839Z","shell.execute_reply.started":"2025-12-30T19:02:15.923604Z","shell.execute_reply":"2025-12-30T19:02:15.940977Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_classes = 5\n\n#je charge le modèle\nmodel = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.DEFAULT)\n\n#et je change la t^te de classification \nin_features = model.classifier[1].in_features\nmodel.classifier[1] = nn.Linear(in_features, num_classes)\nmodel = model.to(device)\n\n# Les classes sont déséquilibrées (0 beaucoup plus fréquent) je pondere CrossEntropy\n# pour que les erreur sur les classes rares soit pas trop pénalisés \ncounts = train_df[\"Category\"].value_counts().sort_index().values\nclass_weights = 1.0 / torch.tensor(counts, dtype=torch.float32)\nclass_weights = class_weights / class_weights.sum()\nclass_weights = class_weights.to(device)\ncriterion = nn.CrossEntropyLoss(weight=class_weights, label_smoothing=0.05)\n\n\noptimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-4)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=8)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T19:02:15.942833Z","iopub.execute_input":"2025-12-30T19:02:15.943186Z","iopub.status.idle":"2025-12-30T19:02:16.093233Z","shell.execute_reply.started":"2025-12-30T19:02:15.943157Z","shell.execute_reply":"2025-12-30T19:02:16.092382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def accuracy(logits, y):\n    preds = logits.argmax(dim=1)\n    return (preds == y).float().mean().item()\n\n\n#Entraîne un EfficientNet-B0 avec une seed donné et sauvegarde le meilleure modèle\ndef train_with_seed(seed, save_path, epochs=6):\n    seed_everything(seed)\n    num_classes = 5\n    model = models.efficientnet_b0(weights=models.EfficientNet_B0_Weights.DEFAULT)\n    in_features = model.classifier[1].in_features\n    model.classifier[1] = nn.Linear(in_features, num_classes)\n    model = model.to(device)\n    criterion = nn.CrossEntropyLoss(weight=class_weights, label_smoothing=0.05)\n    optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-4)\n    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)\n\n    bestVal = 0.0\n\n    def train_one_epoch_local():\n        model.train()\n        loss_sum, acc_sum, n = 0.0, 0.0, 0\n        for x, y in tqdm(train_loader, leave=False):\n            x, y = x.to(device), y.to(device)\n            optimizer.zero_grad(set_to_none=True)\n            logits = model(x)\n            loss = criterion(logits, y)\n            loss.backward()\n            optimizer.step()\n\n            bs = x.size(0)\n            loss_sum += loss.item() * bs\n            acc_sum += accuracy(logits, y) * bs\n            n += bs\n        return loss_sum / n, acc_sum / n\n\n    @torch.no_grad()\n    def evaluate_local():\n        model.eval()\n        loss_sum, acc_sum, n = 0.0, 0.0, 0\n        for x, y in val_loader:\n            x, y = x.to(device), y.to(device)\n            logits = model(x)\n            loss = criterion(logits, y)\n\n            bs = x.size(0)\n            loss_sum += loss.item() * bs\n            acc_sum += accuracy(logits, y) * bs\n            n += bs\n        return loss_sum / n, acc_sum / n\n\n    for ep in range(1, epochs + 1):\n        tr_loss, tr_acc = train_one_epoch_local()\n        val_loss, val_acc = evaluate_local()\n        scheduler.step()\n\n        print(f\"seed {seed} Epoch {ep}/{epochs} , \"\n              f\"train acc {tr_acc:.4f} , val acc {val_acc:.4f}\")\n\n        if val_acc > bestVal:\n            bestVal = val_acc\n            torch.save(model.state_dict(), save_path)\n\n    print(f\"[seed {seed}] Best val acc: {bestVal:.4f} saved to {save_path}\")\n    return save_path\n    \n#Entraîne un EfficientNet-B2 avec une seed donné et sauvegarde le meilleure modèle\ndef train_with_seed_b2(seed, save_path, epochs=6):\n    seed_everything(seed)\n    num_classes = 5\n    model = models.efficientnet_b2(weights=models.EfficientNet_B2_Weights.DEFAULT)\n    in_features = model.classifier[1].in_features\n    model.classifier[1] = nn.Linear(in_features, 5)\n    model = model.to(device)\n    criterion = nn.CrossEntropyLoss(weight=class_weights, label_smoothing=0.05)\n    optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-4)\n    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)\n\n    bestVal = 0.0\n\n    def train_one_epoch_local():\n        model.train()\n        loss_sum, acc_sum, n = 0.0, 0.0, 0\n        for x, y in tqdm(train_loader, leave=False):\n            x, y = x.to(device), y.to(device)\n            optimizer.zero_grad(set_to_none=True)\n            logits = model(x)\n            loss = criterion(logits, y)\n            loss.backward()\n            optimizer.step()\n\n            bs = x.size(0)\n            loss_sum += loss.item() * bs\n            acc_sum += accuracy(logits, y) * bs\n            n += bs\n        return loss_sum / n, acc_sum / n\n\n    @torch.no_grad()\n    def evaluate_local():\n        model.eval()\n        loss_sum, acc_sum, n = 0.0, 0.0, 0\n        for x, y in val_loader:\n            x, y = x.to(device), y.to(device)\n            logits = model(x)\n            loss = criterion(logits, y)\n\n            bs = x.size(0)\n            loss_sum += loss.item() * bs\n            acc_sum += accuracy(logits, y) * bs\n            n += bs\n        return loss_sum / n, acc_sum / n\n\n    for ep in range(1, epochs + 1):\n        tr_loss, tr_acc = train_one_epoch_local()\n        val_loss, val_acc = evaluate_local()\n        scheduler.step()\n\n        print(f\"B2 seed {seed} Epoch {ep}/{epochs} , \"\n              f\"train acc {tr_acc:.4f} , val acc {val_acc:.4f}\")\n\n        if val_acc > bestVal:\n            bestVal = val_acc\n            torch.save(model.state_dict(), save_path)\n\n    print(f\"[b2 seed {seed}] Best val acc: {bestVal:.4f} saved to {save_path}\")\n    return save_path\n\n#je construit 4 ensemble avec des seeds différentes\np1 = train_with_seed(23,  \"best_seed23.pt\",  epochs=6)\ncheminBestmodel=\"best_seed23.pt\"\n\np2 = train_with_seed(14, \"best_seed14.pt\", epochs=6)\n\np3 = train_with_seed(26,\"best_seed26.pt\",epochs=6)\n\nPB2= train_with_seed(191,\"best_b2_seed191.pt\",epochs=6)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T19:02:16.094854Z","iopub.execute_input":"2025-12-30T19:02:16.095188Z","iopub.status.idle":"2025-12-30T19:10:37.449226Z","shell.execute_reply.started":"2025-12-30T19:02:16.095167Z","shell.execute_reply":"2025-12-30T19:10:37.448389Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_model():\n    #Construit un EfficientNet-B0 avec une tête de classification adaptée au problème\n    m = models.efficientnet_b0(weights=None) \n    in_features = m.classifier[1].in_features\n    m.classifier[1] = nn.Linear(in_features, 5)\n    return m.to(device)\n\ndef build_model_b2():\n    #Construit un EfficientNet-B2 avec une tête de classification adaptée au problème\n    m = models.efficientnet_b2(weights=None) \n    in_features = m.classifier[1].in_features\n    m.classifier[1] = nn.Linear(in_features, 5)\n    return m.to(device)\n\n# loading de 3 EfficientNet-B0 entraînés avec des seeds différentes\nb0_1 = build_model()\nb0_1.load_state_dict(torch.load(\"best_seed23.pt\", map_location=device))\nb0_1.eval()\n\nb0_2 = build_model()\nb0_2.load_state_dict(torch.load(\"best_seed14.pt\", map_location=device))\nb0_2.eval()\n\nb0_3 = build_model()\nb0_3.load_state_dict(torch.load(\"best_seed26.pt\", map_location=device))\nb0_3.eval()\n\n# loading d'un EfficientNet-B2\nb2_1 = build_model()\nb2_1.load_state_dict(torch.load(\"best_b2_seed191.pt\", map_location=device))\nb2_1.eval()\n\nsample_df = pd.read_csv(cheminSample)\ntest_df = sample_df.copy()\n\ntest_loader = DataLoader(\n    FIBSEMDataset(test_df, transform=val_tfms, is_test=True, cheminTest=cheminTest),\n    batch_size=128, shuffle=False,\n    num_workers=NUM_WORKERS, pin_memory=True\n)\n\nids_all, preds_all = [], []\n\nwith torch.no_grad():\n    for ids, x in tqdm(test_loader):\n        x = x.to(device)\n        views = [\n            x,                                 # image originale\n            torch.flip(x, dims=[3]),           # symétrie horizontale\n            torch.flip(x, dims=[2]),           # symétrie verticale\n            torch.rot90(x, 1, [2,3]),           # rotation 90 deg\n            torch.rot90(x, 2, [2,3]),           # rotation 180 deg\n            torch.rot90(x, 3, [2,3]),           # rotation 270 deg\n        ]\n\n        logits = 0\n        for v in views:\n            #mean des logits des 4 modèles\n            logits += (b0_1(v) + b0_2(v) + b0_3(v) + b2_1(v)) / 4\n        logits /= len(views)\n\n        preds = logits.argmax(dim=1).cpu().numpy()\n        ids = [int(i) for i in ids]\n\n        ids_all.extend(ids)\n        preds_all.extend(preds.tolist())\n\nsub = pd.DataFrame({\"ID\": ids_all, \"Category\": preds_all})\nsub[\"ID\"] = sub[\"ID\"].astype(np.int32)\nsub[\"Category\"] = sub[\"Category\"].astype(np.int32)\n\nsub.to_csv(\"resultat_b0b2.csv\", index=False)\nprint(sub.shape)\nsub.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T19:10:37.450619Z","iopub.execute_input":"2025-12-30T19:10:37.450907Z","iopub.status.idle":"2025-12-30T19:11:25.528738Z","shell.execute_reply.started":"2025-12-30T19:10:37.450878Z","shell.execute_reply":"2025-12-30T19:11:25.527948Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#je charge le meilleur modèle\nmodel.load_state_dict(torch.load(cheminBestmodel, map_location=device))\nmodel.eval()\n\ncheminSample = pd.read_csv(cheminSample)\ntest_df = cheminSample.copy()\n\ntest_loader = DataLoader(\n    FIBSEMDataset(test_df, transform=val_tfms, is_test=True, cheminTest=cheminTest),\n    batch_size=128, shuffle=False,\n    num_workers=NUM_WORKERS, pin_memory=True\n)\n\nids_all, preds_all = [], []\n\nwith torch.no_grad():\n    for ids, x in tqdm(test_loader):\n        x = x.to(device)\n\n        #je fait la moyenne des prediction par rapport a leurs symétries\n        #car la detection d'une mitochondrie par exemple reste la meme peux importe le sens de l'image\n        #donc on rend notre prédiction plus robuste avecs nos symétries\n        logits0 = model(x)\n        logits1 = model(torch.flip(x, dims=[3]))  # symétrie horizontal\n        logits2 = model(torch.flip(x, dims=[2]))  # symétrie vertical\n        rotate90  = torch.rot90(x, 1, [2,3])\n        rotate180 = torch.rot90(x, 2, [2,3])\n        rotate270 = torch.rot90(x, 3, [2,3])\n        logits3 = model(rotate90)\n        logits4 = model(rotate180)\n        logits5 = model(rotate270)\n\n        logits = (logits0+logits1+logits2+logits3+logits4+logits5) / 6\n\n        \n        preds = logits.argmax(dim=1).cpu().numpy()\n        ids=[int(i) for i in ids]\n        ids_all.extend(ids)\n        preds_all.extend(preds.tolist())\n\nsub = pd.DataFrame({\"ID\": ids_all, \"Category\": preds_all})\nsub[\"ID\"] = sub[\"ID\"].astype(np.int32)\nsub[\"Category\"] = sub[\"Category\"].astype(np.int32)\nsub.to_csv(\"resultat.csv\", index=False)\nsub.head(),sub.dtypes\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T19:11:25.530094Z","iopub.execute_input":"2025-12-30T19:11:25.530362Z","iopub.status.idle":"2025-12-30T19:11:49.992741Z","shell.execute_reply.started":"2025-12-30T19:11:25.530338Z","shell.execute_reply":"2025-12-30T19:11:49.991947Z"}},"outputs":[],"execution_count":null}]}