{"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":112899,"databundleVersionId":13449579,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nimport timm # PyTorch Image Models library, the easiest way to use EfficientNet\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_auc_score\nfrom tqdm.notebook import tqdm\nimport warnings\n\nwarnings.filterwarnings(\"ignore\")\n\n# --- 1. Configuration ---\nclass Config:\n    DEBUG = False # Set to False for the full performance run\n\n    # Data paths\n    BASE_PATH = \"/kaggle/input/grand-xray-slam-division-a/\"\n    TRAIN_IMG_PATH = os.path.join(BASE_PATH, \"train1/\")\n    TEST_IMG_PATH = os.path.join(BASE_PATH, \"test1/\")\n    TRAIN_CSV = os.path.join(BASE_PATH, \"train1.csv\")\n    SAMPLE_SUB_CSV = os.path.join(BASE_PATH, \"sample_submission_1.csv\")\n    \n    IMAGE_COLUMN_NAME = 'Image_name'\n\n    # --- NEW: Upgraded Model Architecture ---\n    MODEL_NAME = 'efficientnet_b4'\n    # EfficientNet models have specific input sizes they were trained on\n    IMG_SIZE = 380 \n    \n    # Training parameters\n    BATCH_SIZE = 16 # Reduced batch size to fit the larger model in memory\n    EPOCHS_INITIAL = 6\n    EPOCHS_PSEUDO = 3\n    LEARNING_RATE = 1e-4\n    NUM_WORKERS = 2\n    DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\n    # Stricter Pseudo-labeling thresholds\n    PSEUDO_CONFIDENCE_HIGH = 0.97\n    PSEUDO_CONFIDENCE_LOW = 0.03\n    \n    DEBUG_PSEUDO_SAMPLE_SIZE = 500\n    DEBUG_FINAL_INFERENCE_SIZE = 50\n    \n    TARGET_LABELS = [\n        'Atelectasis', 'Cardiomegaly', 'Consolidation', 'Edema', \n        'Enlarged Cardiomediastinum', 'Fracture', 'Lung Lesion', \n        'Lung Opacity', 'No Finding', 'Pleural Effusion', 'Pleural Other', \n        'Pneumonia', 'Pneumothorax', 'Support Devices'\n    ]\n    NUM_CLASSES = len(TARGET_LABELS)\n\nprint(f\"Using device: {Config.DEVICE}\")\nprint(f\"Using model: {Config.MODEL_NAME} with image size {Config.IMG_SIZE}\")\n\n# --- 2. Focal Loss ---\nclass FocalLoss(nn.Module):\n    def __init__(self, alpha=0.25, gamma=2.0, reduction='mean'):\n        super(FocalLoss, self).__init__()\n        self.alpha = alpha\n        self.gamma = gamma\n        self.reduction = reduction\n\n    def forward(self, inputs, targets):\n        bce_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduction='none')\n        pt = torch.exp(-bce_loss)\n        focal_loss = self.alpha * (1 - pt)**self.gamma * bce_loss\n        return focal_loss.mean()\n\n# --- 3. Data Loading ---\nprint(\"Loading data...\")\ntrain_df = pd.read_csv(Config.TRAIN_CSV)\nsample_submission_df = pd.read_csv(Config.SAMPLE_SUB_CSV)\ntrain_df['ImagePath'] = train_df[Config.IMAGE_COLUMN_NAME].apply(lambda x: os.path.join(Config.TRAIN_IMG_PATH, x))\nsample_submission_df['ImagePath'] = sample_submission_df[Config.IMAGE_COLUMN_NAME].apply(lambda x: os.path.join(Config.TEST_IMG_PATH, x))\n\nif Config.DEBUG:\n    print(\"Debug mode ON: Using a small subset of data.\")\n    train_df = train_df.sample(frac=0.01, random_state=42).reset_index(drop=True)\n\ntrain_split_df, val_split_df = train_test_split(train_df, test_size=0.1, random_state=42, stratify=train_df['No Finding'])\n\n# --- 4. Dataset and Augmentations ---\nclass ChestXRayDataset(Dataset):\n    def __init__(self, df, transform=None, is_test=False):\n        self.df = df\n        self.transform = transform\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_path = self.df.iloc[idx]['ImagePath']\n        image = Image.open(img_path).convert('RGB')\n        if self.transform:\n            image = self.transform(image)\n        if self.is_test:\n            return image\n        else:\n            labels = self.df.iloc[idx][Config.TARGET_LABELS].values.astype(np.float32)\n            return image, torch.tensor(labels, dtype=torch.float32)\n\ndata_transforms = {\n    'train': transforms.Compose([\n        transforms.Resize((Config.IMG_SIZE, Config.IMG_SIZE)),\n        transforms.RandomHorizontalFlip(),\n        transforms.RandomRotation(10),\n        transforms.ColorJitter(brightness=0.1, contrast=0.1),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n    ]),\n    'val': transforms.Compose([\n        transforms.Resize((Config.IMG_SIZE, Config.IMG_SIZE)),\n        transforms.ToTensor(),\n        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n    ]),\n}\n\n# --- 5. Model and Training Functions ---\ndef get_model():\n    # Using timm to create the EfficientNet model\n    model = timm.create_model(Config.MODEL_NAME, pretrained=True, num_classes=Config.NUM_CLASSES)\n    return model.to(Config.DEVICE)\n\ndef run_training(model, train_loader, val_loader, criterion, optimizer, scheduler, epochs, save_path):\n    best_auc = 0\n    for epoch in range(epochs):\n        model.train()\n        train_loss = 0\n        for images, labels in tqdm(train_loader, desc=f\"Training Epoch {epoch+1}/{epochs}\"):\n            images, labels = images.to(Config.DEVICE), labels.to(Config.DEVICE)\n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            loss.backward()\n            optimizer.step()\n            train_loss += loss.item() * images.size(0)\n        \n        model.eval()\n        val_loss = 0\n        all_labels, all_preds = [], []\n        with torch.no_grad():\n            for images, labels in tqdm(val_loader, desc=f\"Validating Epoch {epoch+1}/{epochs}\"):\n                images, labels = images.to(Config.DEVICE), labels.to(Config.DEVICE)\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n                val_loss += loss.item() * images.size(0)\n                all_labels.append(labels.cpu().numpy())\n                all_preds.append(torch.sigmoid(outputs).cpu().numpy())\n        \n        train_loss /= len(train_loader.dataset)\n        val_loss /= len(val_loader.dataset)\n        val_auc = roc_auc_score(np.concatenate(all_labels), np.concatenate(all_preds), average='macro')\n        \n        print(f\"Epoch {epoch+1} | Train Loss: {train_loss:.4f} | Val Loss: {val_loss:.4f} | Val AUC: {val_auc:.4f}\")\n        scheduler.step(val_auc)\n        \n        if val_auc > best_auc:\n            best_auc = val_auc\n            torch.save(model.state_dict(), save_path)\n            print(f\"New best model saved with AUC: {best_auc:.4f}\")\n\n# --- STAGE 1: Initial Training ---\nprint(\"\\n\" + \"=\"*20 + \" STAGE 1: Initial Training on EfficientNet-B4 \" + \"=\"*20)\ninitial_model = get_model()\ncriterion = FocalLoss()\noptimizer = optim.Adam(initial_model.parameters(), lr=Config.LEARNING_RATE)\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.2, patience=1, verbose=True)\ntrain_loader = DataLoader(ChestXRayDataset(train_split_df, data_transforms['train']), batch_size=Config.BATCH_SIZE, shuffle=True, num_workers=Config.NUM_WORKERS)\nval_loader = DataLoader(ChestXRayDataset(val_split_df, data_transforms['val']), batch_size=Config.BATCH_SIZE, shuffle=False, num_workers=Config.NUM_WORKERS)\nrun_training(initial_model, train_loader, val_loader, criterion, optimizer, scheduler, Config.EPOCHS_INITIAL, \"initial_best_model.pth\")\n\n# --- STAGE 2: Generate High-Confidence Pseudo-Labels ---\nprint(\"\\n\" + \"=\"*20 + \" STAGE 2: Generating Pseudo-Labels with EfficientNet-B4 \" + \"=\"*20)\nteacher_model = get_model()\nteacher_model.load_state_dict(torch.load(\"initial_best_model.pth\"))\nteacher_model.eval()\n\ntest_df_for_pseudo = sample_submission_df.sample(n=Config.DEBUG_PSEUDO_SAMPLE_SIZE, random_state=42) if Config.DEBUG else sample_submission_df\nif Config.DEBUG: print(f\"Debug mode ON: Using {len(test_df_for_pseudo)} samples for pseudo-labeling.\")\n\ntest_loader_for_pseudo = DataLoader(ChestXRayDataset(test_df_for_pseudo, data_transforms['val'], is_test=True), batch_size=Config.BATCH_SIZE, shuffle=False, num_workers=Config.NUM_WORKERS)\npreds_list = []\nwith torch.no_grad():\n    for images in tqdm(test_loader_for_pseudo, desc=\"Generating Predictions for Pseudo-Labels\"):\n        outputs = teacher_model(images.to(Config.DEVICE))\n        preds_list.append(torch.sigmoid(outputs).cpu().numpy())\npredictions = np.concatenate(preds_list)\n\nis_confident = np.all((predictions < Config.PSEUDO_CONFIDENCE_LOW) | (predictions > Config.PSEUDO_CONFIDENCE_HIGH), axis=1)\npseudo_labels_df = test_df_for_pseudo[is_confident].copy()\nconfident_preds = predictions[is_confident]\npseudo_labels_df[Config.TARGET_LABELS] = (confident_preds > 0.5).astype(int)\nprint(f\"Created {len(pseudo_labels_df)} high-confidence pseudo-labeled examples.\")\n\n# --- STAGE 3: Re-training ---\nfinal_model_path = \"initial_best_model.pth\"\nif len(pseudo_labels_df) > 0:\n    print(\"\\n\" + \"=\"*20 + \" STAGE 3: Re-training with Pseudo-Labels \" + \"=\"*20)\n    combined_df = pd.concat([train_df, pseudo_labels_df], ignore_index=True)\n    combined_loader = DataLoader(ChestXRayDataset(combined_df, data_transforms['train']), batch_size=Config.BATCH_SIZE, shuffle=True, num_workers=Config.NUM_WORKERS)\n    \n    final_model = get_model()\n    final_model.load_state_dict(torch.load(\"initial_best_model.pth\"))\n    optimizer = optim.Adam(final_model.parameters(), lr=Config.LEARNING_RATE / 10)\n    scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.2, patience=2, verbose=True)\n    \n    run_training(final_model, combined_loader, val_loader, criterion, optimizer, scheduler, Config.EPOCHS_PSEUDO, \"final_best_model.pth\")\n    final_model_path = \"final_best_model.pth\"\nelse:\n    print(\"No high-confidence pseudo-labels found. Using initial model for prediction.\")\n\n# --- FINAL INFERENCE ---\nprint(\"\\n\" + \"=\"*20 + \" FINAL INFERENCE \" + \"=\"*20)\nfinal_model = get_model()\nfinal_model.load_state_dict(torch.load(final_model_path))\nfinal_model.eval()\n\nfinal_test_df = sample_submission_df.sample(n=Config.DEBUG_FINAL_INFERENCE_SIZE, random_state=42) if Config.DEBUG else sample_submission_df\nif Config.DEBUG: print(f\"Debug mode ON: Running final inference on {len(final_test_df)} samples.\")\n\nfinal_test_loader = DataLoader(ChestXRayDataset(final_test_df, data_transforms['val'], is_test=True), batch_size=Config.BATCH_SIZE, shuffle=False, num_workers=Config.NUM_WORKERS)\nfinal_preds = []\nwith torch.no_grad():\n    for images in tqdm(final_test_loader, desc=\"Final Predicting\"):\n        outputs = final_model(images.to(Config.DEVICE))\n        final_preds.append(torch.sigmoid(outputs).cpu().numpy())\n\npredictions = np.concatenate(final_preds)\nsubmission_df = pd.DataFrame(predictions, columns=Config.TARGET_LABELS)\nsubmission_df[Config.IMAGE_COLUMN_NAME] = final_test_df[Config.IMAGE_COLUMN_NAME].values\nsubmission_df = submission_df[[Config.IMAGE_COLUMN_NAME] + Config.TARGET_LABELS]\nsubmission_df.to_csv(\"submission.csv\", index=False)\n\nprint(\"Submission file created successfully!\")\nprint(submission_df.head())\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-25T13:53:50.105914Z","iopub.execute_input":"2025-08-25T13:53:50.106152Z","execution_failed":"2025-08-25T13:55:39.767Z"}},"outputs":[],"execution_count":null}]}