{"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":"# Grand X-ray Slam: Division A - Simple CNN Baseline\n\n**A Beginner-Friendly Model for Multi-Label Chest X-ray Classification**\n\nThis notebook provides a simple CNN baseline for *Grand X-ray Slam: Division A*[](https://www.kaggle.com/competitions/grand-xray-slam-division-a). It uses minimal preprocessing, a small image size (128x128), and trains for 1 epoch to guide beginners in building a multi-label model for 14 chest conditions. The model generates predictions for `sample_submission_1.csv`. Join [*Division B*](https://www.kaggle.com/competitions/grand-xray-slam-division-b) to boost your leaderboard rank!\n\n**Goals**:\n- Load and preprocess data from `train1.csv` and `train1/`.\n- Train a simple CNN with AUC-ROC metric.\n- Generate submission for `test1/` images.","metadata":{}},{"cell_type":"markdown","source":"# Import Libraries","metadata":{}},{"cell_type":"code","source":"# Import libraries\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport os\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_auc_score\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.utils import Sequence\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Print TensorFlow version\nimport tensorflow as tf\nprint(f\"TensorFlow version: {tf.__version__}\")\nprint(f\"GPU Available: {tf.config.list_physical_devices('GPU')}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:07:26.996478Z","iopub.execute_input":"2025-08-22T13:07:26.997247Z","iopub.status.idle":"2025-08-22T13:07:27.002707Z","shell.execute_reply.started":"2025-08-22T13:07:26.997219Z","shell.execute_reply":"2025-08-22T13:07:27.001941Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load and Split Data","metadata":{}},{"cell_type":"code","source":"# Load train.csv\ntry:\n    train_df = pd.read_csv('/kaggle/input/grand-xray-slam-division-a/train1.csv')\n    print(f\"Loaded train1.csv with {len(train_df)} rows\")\nexcept FileNotFoundError:\n    print(\"Error: train1.csv not found. Ensure dataset is attached.\")\n    raise\n\n# Define 14 condition labels\nlabel_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# Verify label columns exist\nmissing_cols = [col for col in label_columns if col not in train_df.columns]\nif missing_cols:\n    print(f\"Error: Missing columns in train1.csv: {missing_cols}\")\n    raise KeyError(f\"Missing columns: {missing_cols}\")\n\n# Split into train and validation (80/20)\ntrain_data, val_data = train_test_split(\n    train_df, test_size=0.2, random_state=42, stratify=train_df['No Finding']\n)\nprint(f\"Train samples: {len(train_data)}, Validation samples: {len(val_data)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:07:27.012071Z","iopub.execute_input":"2025-08-22T13:07:27.012803Z","iopub.status.idle":"2025-08-22T13:07:27.297769Z","shell.execute_reply.started":"2025-08-22T13:07:27.012776Z","shell.execute_reply":"2025-08-22T13:07:27.296883Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data Generator","metadata":{}},{"cell_type":"code","source":"# Custom data generator for images\nclass ChestXRayGenerator(Sequence):\n    def __init__(self, df, batch_size=32, img_size=(128, 128), is_test=False):\n        self.df = df\n        self.batch_size = batch_size\n        self.img_size = img_size\n        self.is_test = is_test\n        self.label_columns = label_columns\n        self.image_dir = '/kaggle/input/grand-xray-slam-division-a/train1/' if not is_test else '/kaggle/input/grand-xray-slam-division-a/test1/'\n\n        # Verify image directory\n        if not os.path.exists(self.image_dir):\n            print(f\"Error: Image directory {self.image_dir} not found.\")\n            raise FileNotFoundError(f\"Directory {self.image_dir} missing.\")\n\n    def __len__(self):\n        return (len(self.df) + self.batch_size - 1) // self.batch_size\n\n    def __getitem__(self, idx):\n        start_idx = idx * self.batch_size\n        end_idx = min(start_idx + self.batch_size, len(self.df))\n        batch = self.df.iloc[start_idx:end_idx]\n        images = []\n        labels = []\n\n        for _, row in batch.iterrows():\n            img_path = os.path.join(self.image_dir, row['Image_name'])\n            img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)\n            if img is None:\n                print(f\"Warning: Image {img_path} not found, using zero array.\")\n                img = np.zeros(self.img_size)\n            img = cv2.resize(img, self.img_size)\n            img = img / 255.0\n            img = np.expand_dims(img, axis=-1)\n            images.append(img)\n            if not self.is_test:\n                labels.append(row[self.label_columns].values.astype(np.float32))\n\n        images = np.array(images, dtype=np.float32)\n        if not self.is_test:\n            labels = np.array(labels, dtype=np.float32)\n            return images, labels\n        return images\n\n# Create generators\nbatch_size = 128\ntry:\n    train_generator = ChestXRayGenerator(train_data, batch_size=batch_size)\n    val_generator = ChestXRayGenerator(val_data, batch_size=batch_size)\n    print(\"Train and validation generators created successfully.\")\nexcept Exception as e:\n    print(f\"Error creating generators: {e}\")\n    raise","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:07:27.300203Z","iopub.execute_input":"2025-08-22T13:07:27.300434Z","iopub.status.idle":"2025-08-22T13:07:27.311253Z","shell.execute_reply.started":"2025-08-22T13:07:27.300413Z","shell.execute_reply":"2025-08-22T13:07:27.31052Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Build CNN Model","metadata":{}},{"cell_type":"code","source":"# Build simple CNN model\ndef create_cnn_model(input_shape=(128, 128, 1), num_classes=14):\n    model = Sequential([\n        Conv2D(32, (3, 3), activation='relu', input_shape=input_shape),\n        MaxPooling2D((2, 2)),\n        Conv2D(64, (3, 3), activation='relu'),\n        MaxPooling2D((2, 2)),\n        Conv2D(128, (3, 3), activation='relu'),\n        MaxPooling2D((2, 2)),\n        Flatten(),\n        Dense(128, activation='relu'),\n        Dropout(0.5),\n        Dense(num_classes, activation='sigmoid')  # Sigmoid for multi-label\n    ])\n    return model\n\n# Create and compile model\nmodel = create_cnn_model()\nmodel.compile(\n    optimizer=Adam(learning_rate=0.001),\n    loss='binary_crossentropy',\n    metrics=['AUC']\n)\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:07:27.312034Z","iopub.execute_input":"2025-08-22T13:07:27.312237Z","iopub.status.idle":"2025-08-22T13:07:27.395181Z","shell.execute_reply.started":"2025-08-22T13:07:27.312221Z","shell.execute_reply":"2025-08-22T13:07:27.394551Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train Model","metadata":{}},{"cell_type":"code","source":"# Train for 1 epoch\nhistory = model.fit(\n    train_generator,\n    validation_data=val_generator,\n    epochs=1,\n    verbose=1\n)\n\n# Print validation AUC-ROC\nval_auc = history.history['val_AUC'][-1] if 'val_AUC' in history.history else 0.0\nprint(f\"Validation AUC-ROC: {val_auc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T13:07:27.396519Z","iopub.execute_input":"2025-08-22T13:07:27.396766Z","iopub.status.idle":"2025-08-22T14:10:45.572318Z","shell.execute_reply.started":"2025-08-22T13:07:27.396746Z","shell.execute_reply":"2025-08-22T14:10:45.571688Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Generate Sample Submission","metadata":{}},{"cell_type":"code","source":"# Load sample submission and test data\ntry:\n    sample_submission = pd.read_csv('/kaggle/input/grand-xray-slam-division-a/sample_submission_1.csv')\n    print(f\"Loaded sample_submission_1.csv with {len(sample_submission)} rows\")\nexcept FileNotFoundError:\n    print(\"Error: sample_submission_1.csv not found.\")\n    raise\n\ntest_generator = ChestXRayGenerator(sample_submission, batch_size=batch_size, is_test=True)\n\n# Predict on test set\npredictions = []\nfor i in range(len(test_generator)):\n    batch_images = test_generator[i]\n    batch_preds = model.predict(batch_images, verbose=0)\n    predictions.append(batch_preds)\n\n# Combine predictions\npredictions = np.vstack(predictions)\npredictions = predictions[:len(sample_submission)]\n\n# Create submission file\nsubmission_df = sample_submission.copy()\nsubmission_df[label_columns] = predictions\nsubmission_df.to_csv('sample_submission.csv', index=False)\nprint(\"Submission file created: submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T15:05:52.036312Z","iopub.execute_input":"2025-08-22T15:05:52.036527Z","iopub.status.idle":"2025-08-22T15:33:13.325295Z","shell.execute_reply.started":"2025-08-22T15:05:52.036509Z","shell.execute_reply":"2025-08-22T15:33:13.324419Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Summary & Next Steps\n\n**Model Performance**:\n- Validation AUC-ROC: See Cell 6 output\n- Trained for 1 epoch, ~128,000 parameters\n- Simple CNN with 3 convolutional blocks\n\n**Training Setup**:\n- Image size: 128x128 pixels\n- Batch size: 128\n- Optimizer: Adam (lr=0.001)\n- Loss: Binary crossentropy\n\n**Next Steps for Beginners**:\n1. Add basic data augmentation (e.g., rotation, flip).\n2. Try deeper models like ResNet18.\n3. Handle class imbalance with weighted loss.\n4. Explore metadata (Age, Sex, View) in model.\n\n**Tips**:\n- Check [EDA notebook](https://www.kaggle.com/code/guntasdhanjal/grand-x-ray-slam-division-a-eda) for label prevalence (~45% Lung Opacity, ~31% No Finding).\n- Use Kaggle GPU for faster training.\n- Experiment with hyperparameters for better AUC-ROC.\n\nGood luck in *Grand X-ray Slam: Division A*! Try [*Division B*](https://www.kaggle.com/competitions/grand-xray-slam-division-b) for more!","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}