{"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":39272,"databundleVersionId":4629629,"sourceType":"competition"},{"sourceId":5089415,"sourceType":"datasetVersion","datasetId":2694061}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n\nfiling = '/kaggle/input/rsna-breast-cancer-detection-poi-images/bc_1280_train_lut'\n\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-24T16:47:41.669637Z","iopub.execute_input":"2025-05-24T16:47:41.670287Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train_3_convnext_on_roi_with_epoch_saving.py\n\nimport os\nimport glob\nimport pandas as pd\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, random_split\nfrom torchvision import transforms, models\n\nfrom tqdm import tqdm\nimport numpy as np\n\n# ─────────────────────────────────────────────────────────────────────────────\n# 0) FORCE GPU USAGE\n# ─────────────────────────────────────────────────────────────────────────────\nassert torch.cuda.is_available(), \"CUDA is not available – check your Kaggle Accelerator setting!\"\nDEVICE = torch.device('cuda')\nprint(f\"Using device: {DEVICE} → {torch.cuda.get_device_name(0)}\")\n\n# ─────────────────────────────────────────────────────────────────────────────\n# CONFIGURATION\n# ─────────────────────────────────────────────────────────────────────────────\nIMAGE_DIR   = \"/kaggle/input/rsna-breast-cancer-detection-poi-images/bc_1280_train_lut\"\nCSV_FILE    = \"/kaggle/input/rsna-breast-cancer-detection/train.csv\"\nSAVE_DIR    = \"/kaggle/working/model_weights\"\nBATCH_SIZE  = 32\nLR          = 1e-4\nEPOCHS      = 8       # decreased to 10\nNUM_MODELS  = 3        # decreased to 3\n# ─────────────────────────────────────────────────────────────────────────────\n\n# 1) Dataset for ROI images named \"<patient_id>_<image_id>.png\"\nclass RoiDataset(Dataset):\n    def __init__(self, image_dir, csv_path, transform=None):\n        self.transform = transform\n        df = pd.read_csv(csv_path, dtype=str)\n        df['key'] = df['patient_id'].str.strip() + \"_\" + df['image_id'].str.strip()\n        df['cancer'] = df['cancer'].astype(int)\n        mapping = df.set_index('key')['cancer'].to_dict()\n\n        all_paths = glob.glob(os.path.join(image_dir, \"*.png\"))\n        self.samples = [\n            (fp, mapping[os.path.splitext(os.path.basename(fp))[0]])\n            for fp in all_paths\n            if os.path.splitext(os.path.basename(fp))[0] in mapping\n        ]\n        if not self.samples:\n            raise RuntimeError(f\"No matching ROI PNGs in {image_dir}\")\n\n    def __len__(self):\n        return len(self.samples)\n\n    def __getitem__(self, idx):\n        fp, label = self.samples[idx]\n        img = Image.open(fp).convert(\"RGB\")\n        if self.transform:\n            img = self.transform(img)\n        return img, label\n\n# 2) ConvNeXt factory\nclass Creating_Convnet:\n    def creating_single_model(self):\n        return models.convnext_small(\n            weights=models.ConvNeXt_Small_Weights.IMAGENET1K_V1\n        )\n\n# 3) Full training harness with per-epoch best-model saving\nclass FullTrainingOfModel(Creating_Convnet):\n    def __init__(self, train_ds, val_ds, test_ds,\n                 batch_size, lr, num_epochs,\n                 device, class_names):\n        self.device       = device\n        self.train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True,\n                                       pin_memory=True, num_workers=4)\n        self.val_loader   = DataLoader(val_ds,   batch_size=batch_size, shuffle=False,\n                                       pin_memory=True, num_workers=2)\n        self.test_loader  = DataLoader(test_ds,  batch_size=batch_size, shuffle=False,\n                                       pin_memory=True, num_workers=2)\n        self.lr           = lr\n        self.num_epochs   = num_epochs\n        self.class_names  = class_names\n\n        self.models       = {}\n        self.histories    = {}\n        self.best_val_loss= {}\n\n    def initialize_models(self, num_models):\n        for i in range(num_models):\n            m = self.creating_single_model().to(self.device)\n            opt = optim.Adam(m.parameters(), lr=self.lr)\n            crit= nn.CrossEntropyLoss()\n            name= f\"model_{i}\"\n            self.models[name]        = m\n            self.histories[name]     = {'opt':opt, 'crit':crit, 'train_loss':[], 'val_loss':[]}\n            self.best_val_loss[name]= float('inf')\n\n    def train(self):\n        print(\"▶️ Starting training…\", flush=True)\n        for name, m in self.models.items():\n            h = self.histories[name]\n            for epoch in range(1, self.num_epochs+1):\n                print(f\"\\n--- [{name}] Epoch {epoch}/{self.num_epochs} ---\", flush=True)\n\n                # TRAIN LOOP\n                m.train()\n                running = 0.0\n                for x, y in tqdm(self.train_loader,\n                                 desc=f\"{name}|train\", unit=\"img\", leave=False):\n                    x, y = x.to(self.device, non_blocking=True), y.to(self.device, non_blocking=True)\n                    h['opt'].zero_grad()\n                    out  = m(x)\n                    loss = h['crit'](out, y)\n                    loss.backward()\n                    h['opt'].step()\n                    running += loss.item() * x.size(0)\n                tr_loss = running / len(self.train_loader.dataset)\n                h['train_loss'].append(tr_loss)\n\n                # VALIDATION LOOP\n                m.eval()\n                val_running = 0.0\n                with torch.no_grad():\n                    for x, y in tqdm(self.val_loader,\n                                     desc=f\"{name}|val\", unit=\"img\", leave=False):\n                        x, y = x.to(self.device, non_blocking=True), y.to(self.device, non_blocking=True)\n                        val_running += h['crit'](m(x), y).item() * x.size(0)\n                val_loss = val_running / len(self.val_loader.dataset)\n                h['val_loss'].append(val_loss)\n\n                print(f\"[{name}] Epoch {epoch} → train={tr_loss:.4f}, val={val_loss:.4f}\", flush=True)\n\n                # SAVE BEST MODEL FOR THIS EPOCH\n                if val_loss < self.best_val_loss[name]:\n                    self.best_val_loss[name] = val_loss\n                    best_path = os.path.join(self.save_dir, f\"{name}_best.pth\")\n                    torch.save(m.state_dict(), best_path)\n                    print(f\"💾 New best for {name} (val_loss={val_loss:.4f}), saved to {best_path}\", flush=True)\n\n    def save_all_models(self, out_dir):\n        # final save of last epoch\n        for name, m in self.models.items():\n            final_path = os.path.join(out_dir, f\"{name}_final.pth\")\n            torch.save(m.state_dict(), final_path)\n            print(f\"✅ Saved final {name} → {final_path}\", flush=True)\n\n    def train_and_save(self, out_dir):\n        # ensure save directory exists and store for use in train()\n        os.makedirs(out_dir, exist_ok=True)\n        self.save_dir = out_dir\n        self.train()\n        self.save_all_models(out_dir)\n\n# ─────────────────────────────────────────────────────────────────────────────\nif __name__ == \"__main__\":\n    # transforms\n    tfm = transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n    ])\n\n    # load & split dataset\n    full_ds = RoiDataset(IMAGE_DIR, CSV_FILE, transform=tfm)\n    print(f\"Total ROI samples: {len(full_ds)}\", flush=True)\n\n    n = len(full_ds)\n    n_tr  = int(0.8 * n)\n    n_val = int(0.1 * n)\n    n_te  = n - n_tr - n_val\n    train_ds, val_ds, test_ds = random_split(\n        full_ds, [n_tr, n_val, n_te],\n        generator=torch.Generator().manual_seed(42)\n    )\n    print(f\"Splits → train: {len(train_ds)}, val: {len(val_ds)}, test: {len(test_ds)}\", flush=True)\n\n    # build & run trainer\n    trainer = FullTrainingOfModel(\n        train_ds, val_ds, test_ds,\n        batch_size=BATCH_SIZE,\n        lr=LR,\n        num_epochs=EPOCHS,\n        device=DEVICE,\n        class_names=['no_tumor','tumor']\n    )\n    trainer.initialize_models(NUM_MODELS)\n    trainer.train_and_save(SAVE_DIR)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-25T07:15:11.937891Z","iopub.execute_input":"2025-05-25T07:15:11.938553Z","iopub.status.idle":"2025-05-25T17:26:33.06554Z","shell.execute_reply.started":"2025-05-25T07:15:11.938524Z","shell.execute_reply":"2025-05-25T17:26:33.064358Z"}},"outputs":[],"execution_count":null}]}