{"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":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Title: \"Grand X-Ray Slam: Division A\"\n\n# Author: \"Ramandip Singh\"\n\n# Date: \"09-19-2025\"","metadata":{}},{"cell_type":"markdown","source":"## 1. Setup and Data Loading\nFirst, let's set up the environment and load metadata file. We'll also define our target labels.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nimport os\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:36.607548Z","iopub.execute_input":"2025-10-03T19:59:36.607802Z","iopub.status.idle":"2025-10-03T19:59:45.331974Z","shell.execute_reply.started":"2025-10-03T19:59:36.607776Z","shell.execute_reply":"2025-10-03T19:59:45.331194Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Configuration ---\nDATA_DIR = \"/kaggle/input/grand-xray-slam-division-a/train1\"\nCSV_PATH = \"/kaggle/input/grand-xray-slam-division-a/train1.csv\" \nMODEL_NAME = \"efficientnet_b0\"\nIMAGE_SIZE = 256\nBATCH_SIZE = 32\nLEARNING_RATE = 1e-3\nEPOCHS = 10","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:45.332774Z","iopub.execute_input":"2025-10-03T19:59:45.333091Z","iopub.status.idle":"2025-10-03T19:59:45.33718Z","shell.execute_reply.started":"2025-10-03T19:59:45.333073Z","shell.execute_reply":"2025-10-03T19:59:45.336299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Load Data ---\n\ndf = pd.read_csv(CSV_PATH)\nprint(\"Actual columns in your CSV file:\")\nprint(df.columns)\nprint(\"-\" * 25)\n\n# --- Define Labels ---\nLABELS = [ ... ]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:45.33928Z","iopub.execute_input":"2025-10-03T19:59:45.339619Z","iopub.status.idle":"2025-10-03T19:59:45.666467Z","shell.execute_reply.started":"2025-10-03T19:59:45.339593Z","shell.execute_reply":"2025-10-03T19:59:45.665812Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Define Labels ---\n# These are the 14 conditions we need to predict\nLABELS = [\n    'Atelectasis', 'Cardiomegaly', 'Consolidation', 'Edema',\n    'Enlarged Cardiomediastinum', 'Fracture', 'Lung Lesion',\n    'Lung Opacity', 'Pleural Effusion', 'Pleural Other',\n    'Pneumonia', 'Pneumothorax', 'Support Devices', 'No Finding'\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:45.66712Z","iopub.execute_input":"2025-10-03T19:59:45.667357Z","iopub.status.idle":"2025-10-03T19:59:45.671286Z","shell.execute_reply.started":"2025-10-03T19:59:45.667334Z","shell.execute_reply":"2025-10-03T19:59:45.670355Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Data Cleaning and Preprocessing\n\nAs you noted, Age and Sex have missing values (NaN). We need to handle these before training. A simple strategy is to fill them with the median (for age) and mode (for sex).","metadata":{}},{"cell_type":"code","source":"# --- Handle Missing Values ---\n\n# Fill missing Age with the median age\nmedian_age = df['Age'].median()\ndf.loc[:, 'Age'] = df['Age'].fillna(median_age)\n\n# Fill missing Sex with the most frequent value (mode)\nmode_sex = df['Sex'].mode()[0]\ndf.loc[:, 'Sex'] = df['Sex'].fillna(mode_sex)\n\n# We might also want to normalize Age\ndf['Age'] = (df['Age'] - df['Age'].min()) / (df['Age'].max() - df['Age'].min())\n\n# Let's verify\nprint(\"NaNs after cleaning:\")\nprint(df[['Age', 'Sex']].isnull().sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:45.672081Z","iopub.execute_input":"2025-10-03T19:59:45.672888Z","iopub.status.idle":"2025-10-03T19:59:45.729891Z","shell.execute_reply.started":"2025-10-03T19:59:45.672869Z","shell.execute_reply":"2025-10-03T19:59:45.729039Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Creating a Custom PyTorch Dataset\nThis is the most critical part. We need a custom Dataset class to load images and their corresponding labels on the fly.","metadata":{}},{"cell_type":"code","source":"class ChestXRayDataset(Dataset):\n    def __init__(self, dataframe, image_dir, labels, transform=None):\n        self.df = dataframe\n        self.image_dir = image_dir\n        self.transform = transform\n        self.labels = labels\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        if torch.is_tensor(idx):\n            idx = idx.tolist()\n\n        # --- FIX: Changed 'Image_Name' to 'Image_name' to match CSV file ---\n        img_path = os.path.join(self.image_dir, self.df.iloc[idx]['Image_name'])\n        \n        image = Image.open(img_path).convert(\"RGB\")\n        \n        labels_vector = self.df.iloc[idx][self.labels].values.astype(np.float32)\n        labels_tensor = torch.tensor(labels_vector, dtype=torch.float32)\n\n        if self.transform:\n            image = self.transform(image)\n\n        return image, labels_tensor","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:45.730779Z","iopub.execute_input":"2025-10-03T19:59:45.731083Z","iopub.status.idle":"2025-10-03T19:59:45.737371Z","shell.execute_reply.started":"2025-10-03T19:59:45.731055Z","shell.execute_reply":"2025-10-03T19:59:45.736534Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Augmentations, Model, and Training Setup\nNow, we set up our data augmentations, create the model, and define the loss function and optimizer.\n\n### Data Augmentation\nWe'll create two sets of transformations: one for training (with random augmentations) and one for validation (just resizing and normalizing).","metadata":{}},{"cell_type":"code","source":"# ImageNet stats for normalization\nmean = [0.485, 0.456, 0.406]\nstd = [0.229, 0.224, 0.225]\n\ntrain_transform = transforms.Compose([\n    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(10),\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std)\n])\n\nval_transform = transforms.Compose([\n    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std)\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:45.738311Z","iopub.execute_input":"2025-10-03T19:59:45.738564Z","iopub.status.idle":"2025-10-03T19:59:45.760574Z","shell.execute_reply.started":"2025-10-03T19:59:45.738541Z","shell.execute_reply":"2025-10-03T19:59:45.759398Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Handling Class Imbalance\nRare conditions are tough. We can help the model by using a weighted loss function. The weight for each class will be higher for rarer classes.","metadata":{}},{"cell_type":"code","source":"# Calculate positive weights for the loss function\n# weight = number of negative samples / number of positive samples\npos_weights = []\nfor label in LABELS:\n    num_pos = df[label].sum()\n    num_neg = len(df) - num_pos\n    pos_weights.append(num_neg / num_pos)\n\npos_weights = torch.tensor(pos_weights, dtype=torch.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:45.761398Z","iopub.execute_input":"2025-10-03T19:59:45.761676Z","iopub.status.idle":"2025-10-03T19:59:45.777458Z","shell.execute_reply.started":"2025-10-03T19:59:45.761645Z","shell.execute_reply":"2025-10-03T19:59:45.77689Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Model Setup\nWe'll use timm (PyTorch Image Models), a fantastic library for computer vision models.","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nimport timm\n\n# --- Create the Model ---\ndef create_model(num_classes):\n    model = timm.create_model(MODEL_NAME, pretrained=True)\n    in_features = model.classifier.in_features\n    model.classifier = nn.Linear(in_features, num_classes)\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:45.779276Z","iopub.execute_input":"2025-10-03T19:59:45.779472Z","iopub.status.idle":"2025-10-03T19:59:50.157363Z","shell.execute_reply.started":"2025-10-03T19:59:45.779457Z","shell.execute_reply":"2025-10-03T19:59:50.156792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Split Data into Training and Validation Sets ---\ntrain_df, val_df = train_test_split(df, test_size=0.2, random_state=42)\n\n# --- Create Datasets and DataLoaders ---\ntrain_dataset = ChestXRayDataset(train_df, DATA_DIR, LABELS, transform=train_transform)\nval_dataset = ChestXRayDataset(val_df, DATA_DIR, LABELS, transform=val_transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:50.158128Z","iopub.execute_input":"2025-10-03T19:59:50.158348Z","iopub.status.idle":"2025-10-03T19:59:50.196722Z","shell.execute_reply.started":"2025-10-03T19:59:50.15833Z","shell.execute_reply":"2025-10-03T19:59:50.195957Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Instantiate Model, Loss, and Optimizer ---\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = create_model(num_classes=len(LABELS)).to(device)\ncriterion = nn.BCEWithLogitsLoss(pos_weight=pos_weights.to(device))\noptimizer = torch.optim.AdamW(model.parameters(), lr=LEARNING_RATE)\n\n# --- Training and Validation Functions ---\ndef train_one_epoch(model, loader, optimizer, criterion, device):\n    model.train()\n    running_loss = 0.0\n    for i, (images, labels) in enumerate(loader):\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        if (i+1) % 100 == 0:\n            print(f\"Batch {i+1}/{len(loader)}, Loss: {loss.item():.4f}\")\n    return running_loss / len(loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:50.197618Z","iopub.execute_input":"2025-10-03T19:59:50.197926Z","iopub.status.idle":"2025-10-03T19:59:51.557269Z","shell.execute_reply.started":"2025-10-03T19:59:50.197906Z","shell.execute_reply":"2025-10-03T19:59:51.556446Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- THIS IS THE MISSING FUNCTION ---\ndef validate_one_epoch(model, loader, criterion, device):\n    model.eval() # Set model to evaluation mode\n    running_loss = 0.0\n    with torch.no_grad(): # Disable gradient calculation\n        for images, labels in loader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            running_loss += loss.item()\n    return running_loss / len(loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:51.558169Z","iopub.execute_input":"2025-10-03T19:59:51.558451Z","iopub.status.idle":"2025-10-03T19:59:51.563201Z","shell.execute_reply.started":"2025-10-03T19:59:51.558427Z","shell.execute_reply":"2025-10-03T19:59:51.562618Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Main Training Loop ---\nbest_val_loss = float('inf')\n\nfor epoch in range(EPOCHS):\n    print(f\"--- Epoch {epoch+1}/{EPOCHS} ---\")\n    \n    # Training phase\n    train_loss = train_one_epoch(model, train_loader, optimizer, criterion, device)\n    print(f\"Epoch {epoch+1} Training Loss: {train_loss:.4f}\")\n\n    # Validation phase\n    val_loss = validate_one_epoch(model, val_loader, criterion, device)\n    print(f\"Epoch {epoch+1} Validation Loss: {val_loss:.4f}\")\n\n    # Save the best model checkpoint\n    if val_loss < best_val_loss:\n        best_val_loss = val_loss\n        torch.save(model.state_dict(), \"best_model.pth\")\n        print(f\"New best model saved with validation loss: {best_val_loss:.4f}\")\n\nprint(\"Finished Training\")\nprint(f\"Best validation loss achieved: {best_val_loss:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-03T19:59:51.563884Z","iopub.execute_input":"2025-10-03T19:59:51.56413Z","iopub.status.idle":"2025-10-04T03:24:20.364231Z","shell.execute_reply.started":"2025-10-03T19:59:51.564109Z","shell.execute_reply":"2025-10-04T03:24:20.363255Z"}},"outputs":[],"execution_count":null}]}