{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":2822650,"sourceType":"datasetVersion","datasetId":1715304},{"sourceId":12719470,"sourceType":"datasetVersion","datasetId":8039387}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import random\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport timm\nimport numpy as np\nimport cv2\nimport os\nimport pandas as pd\nfrom torch.utils.data import DataLoader, Dataset\nfrom tqdm import tqdm\nfrom albumentations import Compose, Normalize, HorizontalFlip, Rotate, ColorJitter\nfrom albumentations.pytorch import ToTensorV2\nfrom sklearn.model_selection import train_test_split\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nfrom sklearn.metrics import cohen_kappa_score\n\n\ndef set_seed(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    if torch.cuda.is_available():\n        torch.cuda.manual_seed_all(seed)\n        torch.backends.cudnn.deterministic = True\n        torch.backends.cudnn.benchmark = False\n\n\nset_seed(42)\n\n\nclass EfficientNetModel(nn.Module):\n    def __init__(self, num_classes=5, pretrained=True):\n        super().__init__()\n        self.model = timm.create_model('efficientnet_b0', pretrained=pretrained)\n        in_features = self.model.classifier.in_features\n        self.model.classifier = nn.Sequential(\n            nn.Dropout(p=0.5),\n            nn.Linear(in_features, 256),\n            nn.Mish(),\n            nn.Dropout(p=0.5),\n            nn.Linear(256, num_classes)\n        )\n\n    def forward(self, x):\n        return self.model(x)\n\n\nclass CustomImageDataset(Dataset):\n    def __init__(self, img_dir, labels_df, transform=None):\n        self.img_dir = img_dir\n        self.labels_df = labels_df\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.labels_df)\n\n    def __getitem__(self, idx):\n        img_id = self.labels_df.iloc[idx, 0]\n        label = int(self.labels_df.iloc[idx, 1])\n        img_path = os.path.join(self.img_dir, img_id + '.png')\n        try:\n            img = cv2.imread(img_path)\n            if img is None:\n                raise FileNotFoundError(f\"Image not found at {img_path}\")\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            if self.transform:\n                img = self.transform(image=img)[\"image\"]\n            return img, label\n        except Exception as e:\n            print(f\"Error loading image {img_path}: {e}\")\n            return self.__getitem__((idx + 1) % len(self))\n\n\ndef get_transforms(data_type='train', image_size=512):\n    if data_type == 'train':\n        return Compose([\n            Rotate(limit=40, p=0.5),\n            ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.1, p=0.3),\n            HorizontalFlip(p=0.5),\n            Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n            ToTensorV2(),\n        ])\n    else: # for 'valid' or 'test'\n        return Compose([\n            Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n            ToTensorV2(),\n        ])\n\n\ndef train(model, train_loader, valid_loader, device, num_epochs=30, lr=1e-4):\n    criterion = nn.CrossEntropyLoss()\n    optimizer = optim.Adam(model.parameters(), lr=lr, weight_decay=1e-5)\n    scheduler = ReduceLROnPlateau(optimizer, mode='max', factor=0.1, patience=3, verbose=True)\n\n    best_valid_kappa = 0.0\n    epochs_no_improve = 0\n    patience = 5\n    save_path = os.path.join(os.getcwd(), f'best_model_kappa_{IMAGE_SIZE}.pth')\n\n    for epoch in range(num_epochs):\n        model.train()\n        running_loss = 0.0\n        all_train_labels = []\n        all_train_preds = []\n\n        print(f\"\\n--- Epoch {epoch + 1}/{num_epochs} ---\")\n        for images, labels in tqdm(train_loader, desc=\"Training\"):\n            images, labels = images.to(device), labels.to(device)\n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            running_loss += loss.item()\n            _, predicted = torch.max(outputs, 1)\n\n            all_train_labels.extend(labels.cpu().numpy())\n            all_train_preds.extend(predicted.cpu().numpy())\n\n        train_loss = running_loss / len(train_loader)\n        train_acc = np.mean(np.array(all_train_preds) == np.array(all_train_labels))\n        train_kappa = cohen_kappa_score(all_train_labels, all_train_preds, weights='quadratic')\n\n        model.eval()\n        valid_running_loss = 0.0\n        all_valid_labels = []\n        all_valid_preds = []\n        with torch.no_grad():\n            for images, labels in tqdm(valid_loader, desc=\"Validating\"):\n                images, labels = images.to(device), labels.to(device)\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n                valid_running_loss += loss.item()\n                _, predicted = torch.max(outputs, 1)\n\n                all_valid_labels.extend(labels.cpu().numpy())\n                all_valid_preds.extend(predicted.cpu().numpy())\n\n        valid_loss = valid_running_loss / len(valid_loader)\n        valid_acc = np.mean(np.array(all_valid_preds) == np.array(all_valid_labels))\n        valid_kappa = cohen_kappa_score(all_valid_labels, all_valid_preds, weights='quadratic')\n\n        print(f\"Epoch {epoch + 1} Summary:\")\n        print(f\"  Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}, Train Kappa: {train_kappa:.4f}\")\n        print(f\"  Valid Loss: {valid_loss:.4f}, Valid Acc: {valid_acc:.4f}, Valid Kappa: {valid_kappa:.4f}\")\n\n        scheduler.step(valid_kappa)\n\n        if valid_kappa > best_valid_kappa:\n            best_valid_kappa = valid_kappa\n            epochs_no_improve = 0\n            torch.save(model.state_dict(), save_path)\n            print(f\"  -> New best model saved! Validation Kappa: {best_valid_kappa:.4f}\")\n        else:\n            epochs_no_improve += 1\n        if epochs_no_improve >= patience:\n            print(f\"\\nEarly stopping triggered after {patience} epochs with no improvement.\")\n            break\n\n\ndef load_and_predict_test_data(test_csv_path, img_dir, model, device, image_size=512):\n    print(f\"\\nReading test data from {test_csv_path}\")\n    test_df = pd.read_csv(test_csv_path)\n    predictions = []\n    test_transform = get_transforms(data_type='test', image_size=image_size)\n    model.eval()\n    for img_id in tqdm(test_df['id_code'], desc=\"Predicting on Test Set\"):\n        img_path = os.path.join(img_dir, img_id + '.png')\n        if not os.path.exists(img_path):\n            predictions.append(-1)\n            continue\n        img = cv2.imread(img_path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        image_tensor = test_transform(image=img)[\"image\"]\n        image_tensor = image_tensor.unsqueeze(0).to(device)\n        with torch.no_grad():\n            pred = model(image_tensor)\n            label = torch.argmax(pred, dim=1).item()\n        predictions.append(label)\n    test_df['diagnosis'] = predictions\n    submission_file = os.path.join(os.getcwd(), f'submission_kappa_{image_size}.csv')\n    test_df.to_csv(submission_file, index=False)\n    print(f\"\\nPredictions saved to {submission_file}\")\n\n\nif __name__ == \"__main__\":\n    # --- 1. Path Definitions (Key Modification Area) ---\n    IMAGE_SIZE = 512\n    \n    # <-- MODIFICATION 1: Define the names of your dataset folders as they appear in /kaggle/input\n    ORIGINAL_DATASET_NAME = 'aptos2019'\n    PREPROCESSED_DATASET_NAME = 'train-images-preprocessed-512'\n\n    # --- 2. Build the Correct Paths ---\n    # CSV label files come from the original competition dataset\n    base_input_dir = os.path.join('/kaggle/input', ORIGINAL_DATASET_NAME)\n    train_label_file = os.path.join(base_input_dir, 'train_1.csv') # <-- MODIFICATION 2: Changed to train_1.csv based on your image\n    test_label_file = os.path.join(base_input_dir, 'test.csv')\n\n    # Image paths now point to your uploaded pre-processed dataset\n    preprocessed_base_dir = os.path.join('/kaggle/input', PREPROCESSED_DATASET_NAME)\n    train_img_dir = os.path.join(preprocessed_base_dir, f'train_images_preprocessed_{IMAGE_SIZE}') # <-- MODIFICATION 3\n    test_img_dir = os.path.join(preprocessed_base_dir, f'test_images_preprocessed_{IMAGE_SIZE}')  # <-- MODIFICATION 4\n    \n    # Output paths for the model and submission file remain in the writable /kaggle/working/ directory\n    model_path = os.path.join(os.getcwd(), f'best_model_kappa_{IMAGE_SIZE}.pth')\n\n    # --- 3. Constants ---\n    BATCH_SIZE = 16\n    NUM_EPOCHS = 30\n    LEARNING_RATE = 1e-4\n    NUM_WORKERS = 2\n\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f\"Using device: {device}\")\n    print(f\"Reading training images from: {train_img_dir}\")\n    print(f\"Reading training labels from: {train_label_file}\")\n\n    # <-- MODIFICATION 5: The old check for folders in /kaggle/working/ is no longer needed and has been removed.\n\n    # --- 4. Main Training and Prediction Logic ---\n    run_training = True\n    if os.path.exists(model_path):\n        print(f\"Model file already exists at {model_path}. Skipping training.\")\n        run_training = False\n\n    if run_training:\n        print(f\"Starting training on {IMAGE_SIZE}x{IMAGE_SIZE} data...\")\n        all_labels_df = pd.read_csv(train_label_file)\n        # Rename columns to be generic if they are different in train_1.csv\n        all_labels_df.columns = ['id_code', 'diagnosis']\n\n        train_df, valid_df = train_test_split(\n            all_labels_df, test_size=0.2, random_state=42, stratify=all_labels_df['diagnosis']\n        )\n        \n        train_dataset = CustomImageDataset(train_img_dir, train_df, transform=get_transforms('train', IMAGE_SIZE))\n        valid_dataset = CustomImageDataset(train_img_dir, valid_df, transform=get_transforms('valid', IMAGE_SIZE))\n        \n        train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS, pin_memory=True)\n        valid_loader = DataLoader(valid_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS, pin_memory=True)\n\n        model = EfficientNetModel(num_classes=5, pretrained=True).to(device)\n        train(model, train_loader, valid_loader, device, num_epochs=NUM_EPOCHS, lr=LEARNING_RATE)\n\n    print(f\"\\nLoading best model from {model_path} for final prediction...\")\n    model = EfficientNetModel(num_classes=5, pretrained=False).to(device)\n    if not os.path.exists(model_path):\n        print(\"ERROR: Model file not found. Training may have failed or was skipped.\")\n    else:\n        model.load_state_dict(torch.load(model_path, map_location=device))\n        load_and_predict_test_data(test_label_file, test_img_dir, model, device, image_size=IMAGE_SIZE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-09T15:33:08.592091Z","iopub.execute_input":"2025-08-09T15:33:08.592867Z","iopub.status.idle":"2025-08-09T15:45:14.026963Z","shell.execute_reply.started":"2025-08-09T15:33:08.592831Z","shell.execute_reply":"2025-08-09T15:45:14.026050Z"}},"outputs":[],"execution_count":null}]}