{"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":"markdown","source":"### Pre-trained Fine-tuning for X-rays\n\nThis notebook provides a robust baseline for the **Grand X-ray Slam: Division A** competition by applying **transfer learning** with a pre-trained **ResNet18** model. It demonstrates a complete pipeline for multi-label chest X-ray classification on 14 different conditions.","metadata":{}},{"cell_type":"code","source":"# Import Libraries\nimport os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport tensorflow as tf\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nimport torch.nn as nn\nfrom sklearn.model_selection import train_test_split\nfrom tqdm import tqdm\n\n# Print PyTorch version and set device\nprint(f\"PyTorch version: {torch.__version__}\")\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-07T17:21:08.033295Z","iopub.execute_input":"2025-09-07T17:21:08.033495Z","iopub.status.idle":"2025-09-07T17:21:27.216298Z","shell.execute_reply.started":"2025-09-07T17:21:08.033479Z","shell.execute_reply":"2025-09-07T17:21:27.215567Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def _bytes_feature(value):\n    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))\n\ndef _float_list_feature(value):\n    return tf.train.Feature(float_list=tf.train.FloatList(value=value))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T17:32:36.822432Z","iopub.execute_input":"2025-09-07T17:32:36.823299Z","iopub.status.idle":"2025-09-07T17:32:36.82757Z","shell.execute_reply.started":"2025-09-07T17:32:36.823266Z","shell.execute_reply":"2025-09-07T17:32:36.82693Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def convert_to_tfrecord(df, label_columns, image_dir, output_path):\n    with tf.io.TFRecordWriter(output_path) as writer:\n        for _, row in tqdm(df.iterrows(), total=len(df), desc=\"Converting to TFRecord\"):\n            img_path = os.path.join(image_dir, row['Image_name'])\n            img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)\n            if img is None:\n                img = np.zeros((224, 224), dtype=np.uint8)\n            else:\n                img = cv2.resize(img, (224, 224))\n                img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)\n            img_bytes = cv2.imencode('.jpg', img)[1].tobytes()\n            labels = row[label_columns].values.astype(np.float32)\n\n            feature = {\n                'image': _bytes_feature(img_bytes),\n                'label': _float_list_feature(labels),\n            }\n            example = tf.train.Example(features=tf.train.Features(feature=feature))\n            writer.write(example.SerializeToString())\n\nconvert_to_tfrecord(train_data, label_columns, '/kaggle/input/grand-xray-slam-division-a/train1/', 'train.tfrecord')\nconvert_to_tfrecord(val_data, label_columns, '/kaggle/input/grand-xray-slam-division-a/train1/', 'val.tfrecord')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T11:33:18.054517Z","iopub.execute_input":"2025-09-07T11:33:18.055126Z","iopub.status.idle":"2025-09-07T13:03:37.682526Z","shell.execute_reply.started":"2025-09-07T11:33:18.055099Z","shell.execute_reply":"2025-09-07T13:03:37.681692Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import Dataset\n\nclass TFRecordDataset(Dataset):\n    def __init__(self, tfrecord_path, transforms=None):\n        self.tfrecord_path = tfrecord_path\n        self.transforms = transforms\n        # Load the entire dataset into memory as a list of (image, label) pairs\n        self.data = []\n        dataset = tf.data.TFRecordDataset(tfrecord_path)\n        for example_proto in dataset:\n            example = self._parse_tfrecord(example_proto)\n            self.data.append(example)\n\n    def _parse_tfrecord(self, example_proto):\n        feature_description = {\n            'image': tf.io.FixedLenFeature([], tf.string),\n            'label': tf.io.FixedLenFeature([14], tf.float32),\n        }\n        example = tf.io.parse_single_example(example_proto, feature_description)\n        image = tf.io.decode_jpeg(example['image'], channels=3).numpy()\n        label = example['label'].numpy()\n        return image, label\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        image, label = self.data[idx]\n        if self.transforms:\n            image = self.transforms(image)\n        return image, torch.tensor(label)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-07T13:09:59.273209Z","iopub.execute_input":"2025-09-07T13:09:59.273921Z","iopub.status.idle":"2025-09-07T13:09:59.280668Z","shell.execute_reply.started":"2025-09-07T13:09:59.273893Z","shell.execute_reply":"2025-09-07T13:09:59.279926Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_transforms = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(10),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n])\n\nval_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\ntrain_dataset = TFRecordDataset('train.tfrecord', transforms=train_transforms)\nval_dataset = TFRecordDataset('val.tfrecord', transforms=val_transforms)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T13:10:10.185985Z","iopub.execute_input":"2025-09-07T13:10:10.186286Z","iopub.status.idle":"2025-09-07T13:13:09.415297Z","shell.execute_reply.started":"2025-09-07T13:10:10.186264Z","shell.execute_reply":"2025-09-07T13:13:09.414595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch_size = 32\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=batch_size,\n    shuffle=True,\n    num_workers=0,  # Disable multiprocessing\n    pin_memory=True,\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=batch_size,\n    shuffle=False,\n    num_workers=0,  # Disable multiprocessing\n    pin_memory=True,\n)\n\nprint(\"TFRecord DataLoaders created successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T13:13:09.416783Z","iopub.execute_input":"2025-09-07T13:13:09.417373Z","iopub.status.idle":"2025-09-07T13:13:09.422183Z","shell.execute_reply.started":"2025-09-07T13:13:09.417351Z","shell.execute_reply":"2025-09-07T13:13:09.42145Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\ndef create_model(num_classes=14):\n    model = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)\n    for param in model.parameters():\n        param.requires_grad = False\n    num_ftrs = model.fc.in_features\n    model.fc = nn.Linear(num_ftrs, num_classes)\n    for param in model.layer4.parameters():\n        param.requires_grad = True\n    return model\n\nmodel = create_model().to(device)\ncriterion = nn.BCEWithLogitsLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=0.001)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T17:21:47.322048Z","iopub.execute_input":"2025-09-07T17:21:47.322326Z","iopub.status.idle":"2025-09-07T17:21:48.571018Z","shell.execute_reply.started":"2025-09-07T17:21:47.322306Z","shell.execute_reply":"2025-09-07T17:21:48.57022Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_model(model, train_loader, val_loader, criterion, optimizer, num_epochs=10):\n    best_val_loss = float('inf')\n    for epoch in range(num_epochs):\n        model.train()\n        running_loss = 0.0\n        for inputs, labels in tqdm(train_loader, desc=f\"Epoch {epoch + 1}/{num_epochs}\"):\n            inputs, labels = inputs.to(device), labels.to(device)\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n            optimizer.zero_grad()\n            loss.backward()\n            optimizer.step()\n            running_loss += loss.item() * inputs.size(0)\n        train_loss = running_loss / len(train_loader.dataset)\n\n        model.eval()\n        val_loss = 0.0\n        with torch.no_grad():\n            for inputs, labels in val_loader:\n                inputs, labels = inputs.to(device), labels.to(device)\n                outputs = model(inputs)\n                loss = criterion(outputs, labels)\n                val_loss += loss.item() * inputs.size(0)\n        val_loss = val_loss / len(val_loader.dataset)\n        print(f\"Epoch {epoch + 1}/{num_epochs}, Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}\")\n        if val_loss < best_val_loss:\n            best_val_loss = val_loss\n            torch.save(model.state_dict(), 'best_model.pth')\n    return model\n\nmodel = train_model(model, train_loader, val_loader, criterion, optimizer, num_epochs=10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T13:13:10.018231Z","iopub.execute_input":"2025-09-07T13:13:10.018454Z","iopub.status.idle":"2025-09-07T14:10:54.264672Z","shell.execute_reply.started":"2025-09-07T13:13:10.018436Z","shell.execute_reply":"2025-09-07T14:10:54.263864Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls /kaggle/input/grand-xray-slam-division-a/","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T14:16:59.904428Z","iopub.execute_input":"2025-09-07T14:16:59.905218Z","iopub.status.idle":"2025-09-07T14:17:00.76524Z","shell.execute_reply.started":"2025-09-07T14:16:59.905188Z","shell.execute_reply":"2025-09-07T14:17:00.764417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!ls /kaggle/working/","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T17:31:06.175461Z","iopub.execute_input":"2025-09-07T17:31:06.176282Z","iopub.status.idle":"2025-09-07T17:31:06.323206Z","shell.execute_reply.started":"2025-09-07T17:31:06.176253Z","shell.execute_reply":"2025-09-07T17:31:06.322518Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def convert_test_to_tfrecord(df, image_dir, output_path):\n    with tf.io.TFRecordWriter(output_path) as writer:\n        for _, row in tqdm(df.iterrows(), total=len(df), desc=\"Converting TEST to TFRecord\"):\n            img_path = os.path.join(image_dir, row['Image_name'])\n            img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)\n            if img is None:\n                img = np.zeros((224, 224), dtype=np.uint8)\n            else:\n                img = cv2.resize(img, (224, 224))\n                img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)\n            img_bytes = cv2.imencode('.jpg', img)[1].tobytes()\n\n            feature = {\n                'image': _bytes_feature(img_bytes),\n            }\n            example = tf.train.Example(features=tf.train.Features(feature=feature))\n            writer.write(example.SerializeToString())\n\n# Load sample_submission (for image names)\nsample_submission = pd.read_csv(\"/kaggle/input/grand-xray-slam-division-a/sample_submission_1.csv\")\nconvert_test_to_tfrecord(sample_submission, \"/kaggle/input/grand-xray-slam-division-a/test1\", \"test.tfrecord\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T17:33:35.05447Z","iopub.execute_input":"2025-09-07T17:33:35.054778Z","iopub.status.idle":"2025-09-07T18:02:03.824444Z","shell.execute_reply.started":"2025-09-07T17:33:35.054754Z","shell.execute_reply":"2025-09-07T18:02:03.823582Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create the same model structure\nmodel = create_model(num_classes=14).to(device)\nmodel.load_state_dict(torch.load(\"best_model.pth\", map_location=device))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T18:02:03.825901Z","iopub.execute_input":"2025-09-07T18:02:03.826191Z","iopub.status.idle":"2025-09-07T18:02:04.419714Z","shell.execute_reply.started":"2025-09-07T18:02:03.826168Z","shell.execute_reply":"2025-09-07T18:02:04.419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TFRecordTestDataset(Dataset):\n    def __init__(self, tfrecord_path, transforms=None):\n        self.transforms = transforms\n        self.data = []\n        dataset = tf.data.TFRecordDataset(tfrecord_path)\n        for example_proto in dataset:\n            example = self._parse_tfrecord(example_proto)\n            self.data.append(example)\n\n    def _parse_tfrecord(self, example_proto):\n        feature_description = {\n            \"image\": tf.io.FixedLenFeature([], tf.string),\n        }\n        example = tf.io.parse_single_example(example_proto, feature_description)\n        image = tf.io.decode_jpeg(example[\"image\"], channels=3).numpy()\n        return image\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        image = self.data[idx]\n        if self.transforms:\n            image = self.transforms(image)\n        return image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T18:02:04.420533Z","iopub.execute_input":"2025-09-07T18:02:04.421057Z","iopub.status.idle":"2025-09-07T18:02:04.426289Z","shell.execute_reply.started":"2025-09-07T18:02:04.421035Z","shell.execute_reply":"2025-09-07T18:02:04.425586Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Transforms (same as val)\ntest_transforms = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],\n                         std=[0.229, 0.224, 0.225]),\n])\n\n# DataLoader\ntest_dataset = TFRecordTestDataset(\"test.tfrecord\", transforms=test_transforms)\ntest_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, num_workers=2)\n\n# Load model\nmodel = create_model(num_classes=14).to(device)\nmodel.load_state_dict(torch.load(\"best_model.pth\", map_location=device))\nmodel.eval()\n\n# Predictions\npredictions = []\nwith torch.no_grad():\n    for images in test_loader:\n        images = images.to(device)\n        outputs = model(images)\n        batch_preds = torch.sigmoid(outputs).cpu().numpy()\n        predictions.append(batch_preds)\n\npredictions = np.vstack(predictions)\npredictions = predictions[:len(sample_submission)]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T18:02:04.427356Z","iopub.execute_input":"2025-09-07T18:02:04.427593Z","iopub.status.idle":"2025-09-07T18:04:36.774648Z","shell.execute_reply.started":"2025-09-07T18:02:04.427567Z","shell.execute_reply":"2025-09-07T18:04:36.773737Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"label_columns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T18:09:48.012829Z","iopub.execute_input":"2025-09-07T18:09:48.013495Z","iopub.status.idle":"2025-09-07T18:09:48.019154Z","shell.execute_reply.started":"2025-09-07T18:09:48.013469Z","shell.execute_reply":"2025-09-07T18:09:48.018536Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"label_columns = sample_submission.columns[1:]\nprint(\"Final label columns:\", label_columns)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T18:11:30.436687Z","iopub.execute_input":"2025-09-07T18:11:30.436987Z","iopub.status.idle":"2025-09-07T18:11:30.442809Z","shell.execute_reply.started":"2025-09-07T18:11:30.436964Z","shell.execute_reply":"2025-09-07T18:11:30.442025Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T18:09:57.043767Z","iopub.execute_input":"2025-09-07T18:09:57.044024Z","iopub.status.idle":"2025-09-07T18:09:57.069807Z","shell.execute_reply.started":"2025-09-07T18:09:57.044005Z","shell.execute_reply":"2025-09-07T18:09:57.069064Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df = sample_submission.copy()\nsubmission_df[label_columns] = predictions\nsubmission_df.to_csv(\"submission.csv\", index=False)\nprint(\"✅ Submission file created: submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T18:11:43.777343Z","iopub.execute_input":"2025-09-07T18:11:43.777726Z","iopub.status.idle":"2025-09-07T18:11:44.437263Z","shell.execute_reply.started":"2025-09-07T18:11:43.777705Z","shell.execute_reply":"2025-09-07T18:11:44.436602Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-07T18:11:55.587425Z","iopub.execute_input":"2025-09-07T18:11:55.588041Z","iopub.status.idle":"2025-09-07T18:11:55.604123Z","shell.execute_reply.started":"2025-09-07T18:11:55.588017Z","shell.execute_reply":"2025-09-07T18:11:55.603513Z"}},"outputs":[],"execution_count":null}]}