{"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":"nvidiaTeslaT4","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":"%%writefile /kaggle/working/train.py\nimport os\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport torch\nimport torch.nn as nn\nimport torch.distributed as dist\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.utils.data.distributed import DistributedSampler\nfrom torchvision import transforms\nfrom tqdm.auto import tqdm\nimport timm\n\n# ======================\n# Dataset\n# ======================\nclass ChestXRayDataset(Dataset):\n    def __init__(self, df, img_size=(384, 384), transforms=None):\n        self.df = df\n        self.img_size = img_size\n        self.label_columns = [\n            'Atelectasis', 'Cardiomegaly', 'Consolidation', 'Edema', 'Enlarged Cardiomediastinum',\n            'Fracture', 'Lung Lesion', 'Lung Opacity', 'No Finding', 'Pleural Effusion',\n            'Pleural Other', 'Pneumonia', 'Pneumothorax', 'Support Devices'\n        ]\n        self.image_dir = '/kaggle/input/grand-xray-slam-division-a/train1'\n        self.transforms = transforms\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_path = os.path.join(self.image_dir, row['Image_name'])\n\n        img = cv2.imread(img_path, cv2.IMREAD_COLOR)\n        if img is None:\n            img = np.zeros((self.img_size[0], self.img_size[1], 3), dtype=np.uint8)\n\n        img = cv2.resize(img, self.img_size)\n\n        if self.transforms:\n            img = self.transforms(img)\n\n        labels = row[self.label_columns].values.astype(np.float32)\n        return img, torch.tensor(labels)\n\n# ======================\n# Model\n# ======================\ndef create_model(num_classes=14):\n    model = timm.create_model(\"seresnextaa201d_32x8d.sw_in12k_ft_in1k_384\", pretrained=True, num_classes=num_classes)\n    return model\n\n# ======================\n# Main DDP Training\n# ======================\ndef main():\n    dist.init_process_group(backend=\"nccl\")\n    local_rank = int(os.environ[\"LOCAL_RANK\"])\n    torch.cuda.set_device(local_rank)\n    device = torch.device(\"cuda\", local_rank)\n\n    train_df = pd.read_csv('/kaggle/input/grand-xray-slam-division-a/train1.csv')\n\n    img_transforms = transforms.Compose([\n        transforms.ToPILImage(),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    ])\n\n    train_dataset = ChestXRayDataset(train_df, img_size=(384, 384), transforms=img_transforms)\n    train_sampler = DistributedSampler(train_dataset)\n    train_loader = DataLoader(train_dataset, batch_size=12, sampler=train_sampler,\n                              num_workers=4, pin_memory=True)\n\n    model = create_model().to(device)\n    model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[local_rank], output_device=local_rank)\n\n    criterion = nn.BCEWithLogitsLoss()\n    optimizer = torch.optim.AdamW(model.parameters(), lr=5e-4, weight_decay=1e-4)\n\n    # Scheduler: Warmup + Cosine\n    num_epochs = 3\n    total_steps = len(train_loader) * num_epochs\n    warmup_steps = int(0.1 * total_steps)\n\n    def lr_lambda(step):\n        if step < warmup_steps:\n            return float(step) / float(max(1, warmup_steps))\n        progress = (step - warmup_steps) / float(max(1, total_steps - warmup_steps))\n        return 0.5 * (1.0 + np.cos(np.pi * progress))\n\n    scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)\n    scaler = torch.cuda.amp.GradScaler()\n\n    for epoch in range(num_epochs):\n        train_sampler.set_epoch(epoch)\n        model.train()\n        running_loss = 0.0\n\n        for images, labels in tqdm(train_loader, disable=(dist.get_rank() != 0)):\n            images, labels = images.to(device, non_blocking=True), labels.to(device, non_blocking=True)\n\n            optimizer.zero_grad()\n            with torch.cuda.amp.autocast():\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n\n            scaler.scale(loss).backward()\n            scaler.step(optimizer)\n            scaler.update()\n            scheduler.step()\n            running_loss += loss.item() * images.size(0)\n\n        if dist.get_rank() == 0:\n            print(f\"Epoch {epoch+1}/{num_epochs} | Train Loss {running_loss/len(train_dataset):.4f}\")\n\n    if dist.get_rank() == 0:\n        torch.save(model.module.state_dict(), \"best_model.pth\")\n        print(\"✅ Model saved as best_model.pth\")\n\n    dist.destroy_process_group()\n\nif __name__ == \"__main__\":\n    main()\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-28T12:59:07.102178Z","iopub.execute_input":"2025-09-28T12:59:07.10241Z","iopub.status.idle":"2025-09-28T12:59:07.113035Z","shell.execute_reply.started":"2025-09-28T12:59:07.102386Z","shell.execute_reply":"2025-09-28T12:59:07.112337Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%writefile /kaggle/working/inference.py\nimport os\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom tqdm.auto import tqdm\nimport timm\n\n# ======================\n# Dataset\n# ======================\nclass ChestXRayTestDataset(Dataset):\n    def __init__(self, df, img_size=(384, 384), transforms=None):\n        self.df = df\n        self.img_size = img_size\n        self.image_dir = '/kaggle/input/grand-xray-slam-division-a/test1'\n        self.transforms = transforms\n        self.label_columns = [\n            'Atelectasis', 'Cardiomegaly', 'Consolidation', 'Edema', 'Enlarged Cardiomediastinum',\n            'Fracture', 'Lung Lesion', 'Lung Opacity', 'No Finding', 'Pleural Effusion',\n            'Pleural Other', 'Pneumonia', 'Pneumothorax', 'Support Devices'\n        ]\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_path = os.path.join(self.image_dir, row['Image_name'])\n\n        img = cv2.imread(img_path, cv2.IMREAD_COLOR)\n        if img is None:\n            img = np.zeros((self.img_size[0], self.img_size[1], 3), dtype=np.uint8)\n\n        img = cv2.resize(img, self.img_size)\n\n        if self.transforms:\n            img = self.transforms(img)\n\n        return img, row['Image_name']\n\n# ======================\n# Model\n# ======================\ndef create_model(num_classes=14):\n    model = timm.create_model(\"seresnextaa201d_32x8d.sw_in12k_ft_in1k_384\", pretrained=False, num_classes=num_classes)\n    return model\n\n# ======================\n# Inference\n# ======================\ndef main():\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n    test_files = os.listdir('/kaggle/input/grand-xray-slam-division-a/test1')\n    test_df = pd.DataFrame({\"Image_name\": test_files})\n\n    img_transforms = transforms.Compose([\n        transforms.ToPILImage(),\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n    ])\n\n    test_dataset = ChestXRayTestDataset(test_df, img_size=(384, 384), transforms=img_transforms)\n    test_loader = DataLoader(test_dataset, batch_size=18, shuffle=False, num_workers=4, pin_memory=True)\n\n    model = create_model()\n    model.load_state_dict(torch.load(\"/kaggle/working/best_model.pth\", map_location=device))\n    model.to(device)\n    model.eval()\n\n    all_probs, all_names = [], []\n\n    with torch.no_grad():\n        for images, names in tqdm(test_loader, desc=\"Inference\", leave=True):\n            images = images.to(device, non_blocking=True)\n            outputs = model(images)\n            probs = torch.sigmoid(outputs).cpu().numpy()\n            all_probs.append(probs)\n            all_names.extend(names)\n\n    all_probs = np.vstack(all_probs)\n    submission = pd.DataFrame(all_probs, columns=test_dataset.label_columns)\n    submission.insert(0, \"Image_name\", all_names)\n\n    submission.to_csv(\"/kaggle/working/submission.csv\", index=False)\n    print(\"✅ Saved submission.csv\")\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T12:59:07.114458Z","iopub.execute_input":"2025-09-28T12:59:07.114672Z","iopub.status.idle":"2025-09-28T12:59:07.142156Z","shell.execute_reply.started":"2025-09-28T12:59:07.114645Z","shell.execute_reply":"2025-09-28T12:59:07.141416Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!torchrun --nproc_per_node=2 /kaggle/working/train.py","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-28T12:59:07.142993Z","iopub.execute_input":"2025-09-28T12:59:07.143245Z","execution_failed":"2025-09-28T13:00:10.718Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!python /kaggle/working/inference.py","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-28T13:00:10.718Z"}},"outputs":[],"execution_count":null}]}