{"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":44890,"databundleVersionId":4852232,"sourceType":"competition"}],"dockerImageVersionId":31260,"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# 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","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-15T07:39:17.873740Z","iopub.execute_input":"2026-02-15T07:39:17.874007Z","iopub.status.idle":"2026-02-15T07:39:22.131339Z","shell.execute_reply.started":"2026-02-15T07:39:17.873980Z","shell.execute_reply":"2026-02-15T07:39:22.130605Z"},"_kg_hide-output":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pip install torch torchvision pytorch-lightning pandas opencv-python albumentations timm\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-15T07:39:13.204049Z","iopub.execute_input":"2026-02-15T07:39:13.204247Z","iopub.status.idle":"2026-02-15T07:39:17.871905Z","shell.execute_reply.started":"2026-02-15T07:39:13.204226Z","shell.execute_reply":"2026-02-15T07:39:17.871043Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport cv2\nimport torch\nimport torch.nn as nn\nimport pytorch_lightning as pl\nimport timm \nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom pytorch_lightning.callbacks import ModelCheckpoint\n\nCONFIG = {\n    \"seed\": 42,\n    \"img_size\": 224,\n    \"batch_size\": 32,\n    \"epochs\": 15,\n    \"lr\": 1e-4,\n    \"model_name\": \"resnet50\",\n    \"num_classes\": 3,\n    \"data_dir\": \"/kaggle/input/diabetic-retinopathy-classification-3/kaggle_retina\", \n    \"num_workers\": 2\n}\n\npl.seed_everything(CONFIG['seed'])\n\nclass RetinaDataset(Dataset):\n    def __init__(self, df, img_dir, transform=None, is_test=False):\n        self.df = df\n        self.img_dir = img_dir\n        self.transform = transform\n        self.is_test = is_test\n        \n        # Robust file mapping\n        self.file_map = {}\n        for root, dirs, files in os.walk(img_dir):\n            for file in files:\n                if file.lower().endswith(('.png', '.jpg', '.jpeg')):\n                    base_id = os.path.splitext(file)[0]\n                    self.file_map[base_id] = os.path.join(root, file)\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        # Dynamic ID column\n        id_col = 'id_code' if 'id_code' in self.df.columns else 'Id'\n        img_id = str(row[id_col])\n        \n        img_path = self.file_map.get(img_id)\n\n        if img_path is None:\n            # Return black image if missing\n            image = np.zeros((CONFIG['img_size'], CONFIG['img_size'], 3), dtype=np.uint8)\n        else:\n            image = cv2.imread(img_path)\n            if image is None: \n                image = np.zeros((CONFIG['img_size'], CONFIG['img_size'], 3), dtype=np.uint8)\n            else:\n                image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\n        if self.transform:\n            augmented = self.transform(image=image)\n            image = augmented['image']\n\n        if self.is_test:\n            return image, img_id\n        else:\n            # Dynamic Label Column\n            cat_col = 'diagnosis' if 'diagnosis' in self.df.columns else 'Category'\n            \n            # Clamp labels > 2 to 2\n            label_val = int(row[cat_col])\n            if label_val > 2: \n                label_val = 2\n            \n            label = torch.tensor(label_val, dtype=torch.long)\n            return image, label\n\nclass RetinaDataModule(pl.LightningDataModule):\n    def __init__(self, config):\n        super().__init__()\n        self.config = config\n        \n    def setup(self, stage=None):\n        # Augmentations\n        self.train_transform = A.Compose([\n            A.Resize(self.config['img_size'], self.config['img_size']),\n            A.HorizontalFlip(p=0.5),\n            A.VerticalFlip(p=0.5),\n            A.Rotate(limit=30, p=0.7),\n            A.RandomBrightnessContrast(p=0.2),\n            A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n            ToTensorV2()\n        ])\n        \n        self.val_transform = A.Compose([\n            A.Resize(self.config['img_size'], self.config['img_size']),\n            A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n            ToTensorV2()\n        ])\n\n        # Load Train CSV\n        train_csv = os.path.join(self.config['data_dir'], 'train.csv')\n        full_df = pd.read_csv(train_csv)\n        full_df.columns = full_df.columns.str.strip()\n        \n        # Identify label column\n        cat_col = 'diagnosis' if 'diagnosis' in full_df.columns else 'Category'\n        \n        # Create stratified column (clamping 4->2)\n        full_df['stratify_col'] = full_df[cat_col].apply(lambda x: 2 if x > 2 else x)\n\n        train_df, val_df = train_test_split(\n            full_df, test_size=0.2, stratify=full_df['stratify_col'], random_state=self.config['seed']\n        )\n        \n        self.train_ds = RetinaDataset(train_df, os.path.join(self.config['data_dir'], 'train'), transform=self.train_transform)\n        self.val_ds = RetinaDataset(val_df, os.path.join(self.config['data_dir'], 'train'), transform=self.val_transform)\n        \n        # Load Submission Template\n        sub_csv = os.path.join(self.config['data_dir'], 'submission.csv')\n        self.sample_sub = pd.read_csv(sub_csv)\n        self.sample_sub.columns = self.sample_sub.columns.str.strip()\n        \n        self.test_ds = RetinaDataset(self.sample_sub, os.path.join(self.config['data_dir'], 'test'), transform=self.val_transform, is_test=True)\n\n    def train_dataloader(self):\n        return DataLoader(self.train_ds, batch_size=self.config['batch_size'], shuffle=True, num_workers=self.config['num_workers'])\n    def val_dataloader(self):\n        return DataLoader(self.val_ds, batch_size=self.config['batch_size'], shuffle=False, num_workers=self.config['num_workers'])\n    def test_dataloader(self):\n        return DataLoader(self.test_ds, batch_size=self.config['batch_size'], shuffle=False, num_workers=self.config['num_workers'])\n\nclass RetinaClassifier(pl.LightningModule):\n    def __init__(self, config):\n        super().__init__()\n        self.save_hyperparameters()\n        self.config = config\n        self.model = timm.create_model(config['model_name'], pretrained=True, num_classes=config['num_classes'])\n        self.criterion = nn.CrossEntropyLoss()\n        \n    def forward(self, x):\n        return self.model(x)\n\n    def training_step(self, batch, batch_idx):\n        x, y = batch\n        loss = self.criterion(self(x), y)\n        self.log('train_loss', loss, prog_bar=True)\n        return loss\n\n    def validation_step(self, batch, batch_idx):\n        x, y = batch\n        logits = self(x)\n        loss = self.criterion(logits, y)\n        acc = (logits.argmax(dim=1) == y).float().mean()\n        self.log('val_loss', loss, prog_bar=True)\n        self.log('val_acc', acc, prog_bar=True)\n        return loss\n\n    def configure_optimizers(self):\n        opt = torch.optim.AdamW(self.parameters(), lr=self.config['lr'])\n        sch = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=self.config['epochs'])\n        return [opt], [sch]\n\nif __name__ == \"__main__\":\n    dm = RetinaDataModule(CONFIG)\n    model = RetinaClassifier(CONFIG)\n    \n    # Checkpoint callback\n    checkpoint_callback = ModelCheckpoint(\n        monitor='val_acc',\n        dirpath='checkpoints',\n        filename='best-checkpoint',\n        save_top_k=1,\n        mode='max'\n    )\n    \n    trainer = pl.Trainer(\n        max_epochs=CONFIG['epochs'],\n        accelerator='auto',\n        callbacks=[checkpoint_callback],\n        log_every_n_steps=10\n    )\n\n    trainer.fit(model, dm)\n\n    print(\"Generating submission...\")\n    \n    # Load Best Model\n    best_model_path = checkpoint_callback.best_model_path\n    print(f\"Loading best model from: {best_model_path}\")\n    model = RetinaClassifier.load_from_checkpoint(best_model_path)\n    model.eval()\n    if torch.cuda.is_available():\n        model.cuda()\n\n    # Inference Loop\n    predictions_map = {}\n    test_loader = dm.test_dataloader()\n    \n    with torch.no_grad():\n        for images, ids in test_loader:\n            if torch.cuda.is_available():\n                images = images.cuda()\n            logits = model(images)\n            preds = torch.argmax(logits, dim=1).cpu().numpy()\n            \n            for id_val, pred_val in zip(ids, preds):\n                predictions_map[id_val] = pred_val\n\n    # Create final dataframe\n    submission_df = dm.sample_sub.copy()\n    id_col = 'id_code' if 'id_code' in submission_df.columns else 'Id'\n    \n    # Map predictions\n    submission_df['diagnosis'] = submission_df[id_col].astype(str).map(predictions_map).fillna(0).astype(int)\n    \n    # Rename for final output\n    submission_df = submission_df.rename(columns={id_col: 'id_code'})\n    submission_df = submission_df[['id_code', 'diagnosis']]\n    \n    submission_df.to_csv('submission.csv', index=False)\n    print(\"Saved submission.csv successfully!\")\n    print(submission_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-15T07:40:18.172828Z","iopub.execute_input":"2026-02-15T07:40:18.173122Z","iopub.status.idle":"2026-02-15T07:44:28.066796Z","shell.execute_reply.started":"2026-02-15T07:40:18.173097Z","shell.execute_reply":"2026-02-15T07:44:28.065653Z"},"_kg_hide-input":true},"outputs":[],"execution_count":null}]}