{"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":99552,"databundleVersionId":13762876,"sourceType":"competition"},{"sourceId":262351767,"sourceType":"kernelVersion"}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# <div style=\"color:#18ad4c; font-family: 'Segoe UI'; text-align: center; border-top:5px solid #3e3ec7; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">Train 3D CNN for 🧠 Intracranial Aneurysm</div>\n\n\n## Preprocessing was done in this notebook\n[https://www.kaggle.com/code/taimour/preprocess-files-for-aneurysm-3](https://www.kaggle.com/code/taimour/preprocess-files-for-aneurysm-3)","metadata":{}},{"cell_type":"markdown","source":"# <div style=\"color:#18ad4c; font-family: 'Segoe UI'; text-align: center; border-top:5px solid #815ecc; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">📚 Step 1: Setup - Import Basic Tools</div>","metadata":{}},{"cell_type":"code","source":"# Let's import the tools we need (like bringing tools to a workshop)\nimport os\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import roc_auc_score\nimport json\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nimport random\nfrom scipy.ndimage import rotate, zoom, gaussian_filter\n\n# Check if we can use GPU (makes training faster)\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\n\n# Reproducibility (so results are consistent)\ntorch.manual_seed(42)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"execution_failed":"2025-09-21T04:39:47.414Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#18ad4c; font-family: 'Segoe UI'; text-align: center; border-top:5px solid #3e3ec7; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">📖 Step 2: Load the Preprocessed Data</div>","metadata":{}},{"cell_type":"code","source":"# Path to our preprocessed data (from the previous notebook)\nPREPROCESSED_DIR = \"/kaggle/input/preprocess-files-for-aneurysm-3/preprocessed_data\"\nTRAIN_CSV_PATH = \"/kaggle/input/rsna-intracranial-aneurysm-detection/train.csv\"\n\n# Load metadata to see what we have\nwith open(os.path.join(PREPROCESSED_DIR, 'metadata.json'), 'r') as f:\n    metadata = json.load(f)\n\nprint(f\"Found {metadata['series_count']} successfully processed brain scans\")\nprint(f\"Failed to process: {metadata['failed_series_count']} scans\")\n\n# Load the training labels (which scans have aneurysms)\ntrain_df = pd.read_csv(TRAIN_CSV_PATH)\n\n# Keep only the series we successfully processed\nprocessed_ids = metadata['series_ids']\ntrain_df = train_df[train_df['SeriesInstanceUID'].isin(processed_ids)].copy()\n\n# Filter out only CT\nprocessed_ids = [sid for sid, mod in metadata['modalities'].items() if mod == \"CT\"]\ntrain_df = train_df[train_df['SeriesInstanceUID'].isin(processed_ids)].copy()\nprint(f\"Using {len(train_df)} scans for training\")\n\n# Define our target columns (the aneurysm locations)\nLABEL_COLS = [\n    'Left Infraclinoid Internal Carotid Artery',\n    'Right Infraclinoid Internal Carotid Artery',\n    'Left Supraclinoid Internal Carotid Artery',\n    'Right Supraclinoid Internal Carotid Artery',\n    'Left Middle Cerebral Artery',\n    'Right Middle Cerebral Artery',\n    'Anterior Communicating Artery',\n    'Left Anterior Cerebral Artery',\n    'Right Anterior Cerebral Artery',\n    'Left Posterior Communicating Artery',\n    'Right Posterior Communicating Artery',\n    'Basilar Tip',\n    'Other Posterior Circulation',\n    'Aneurysm Present',  # This is the main label we care about most\n]","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-21T04:39:47.416Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#18ad4c; font-family: 'Segoe UI'; text-align: center; border-top:5px solid #3e3ec7; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">💎 Step 3: Prepare Data for Training</div>","metadata":{}},{"cell_type":"code","source":"# Create a simple dataset class (like a photo album for our brain scans)\nclass BrainScanDataset(Dataset):\n    def __init__(self, series_ids, labels_df, data_dir):\n        self.series_ids = series_ids\n        self.labels_df = labels_df.set_index('SeriesInstanceUID')\n        self.data_dir = data_dir\n    \n    def __len__(self):\n        return len(self.series_ids)\n\n    def __getitem__(self, idx):\n        series_id = self.series_ids[idx]\n        \n        # Load the preprocessed brain scan\n        volume = np.load(os.path.join(self.data_dir, f\"{series_id}.npy\"))\n        \n        # Get the labels\n        labels = self.labels_df.loc[series_id][LABEL_COLS].values.astype(np.float32)\n\n        # Convert to PyTorch tensors\n        volume_tensor = torch.FloatTensor(volume).unsqueeze(0)\n        labels_tensor = torch.FloatTensor(labels)\n        \n        return volume_tensor, labels_tensor\n\n# Split data into training and validation sets (80% for learning, 20% for testing)\ntrain_ids, val_ids = train_test_split(\n    processed_ids, \n    test_size=0.2, \n    random_state=42,\n    stratify=train_df['Aneurysm Present'].values  # Keep same proportion of positive cases\n)\n\n# Create datasets\ntrain_dataset = BrainScanDataset(train_ids, train_df, PREPROCESSED_DIR)\nval_dataset = BrainScanDataset(val_ids, train_df, PREPROCESSED_DIR)\n\n# Create data loaders (like a conveyor belt bringing data to our model)\ntrain_loader = DataLoader(train_dataset, batch_size=4, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=4, shuffle=False, num_workers=2)\n\nprint(f\"Training set: {len(train_dataset)} scans\")\nprint(f\"Validation set: {len(val_dataset)} scans\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-21T04:39:47.416Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#18ad4c; font-family: 'Segoe UI'; text-align: center; border-top:5px solid #3e3ec7; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">3️⃣🇩 Step 4: Create a Simple Model</div>","metadata":{}},{"cell_type":"code","source":"class ResidualBlock3D(nn.Module):\n    \"\"\"Properly handles channel dimension changes in residual connections\"\"\"\n    def __init__(self, in_channels, out_channels, stride=1):\n        super(ResidualBlock3D, self).__init__()\n        \n        self.conv1 = nn.Conv3d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm3d(out_channels)\n        self.relu = nn.ReLU(inplace=True)\n        self.conv2 = nn.Conv3d(out_channels, out_channels, kernel_size=3, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm3d(out_channels)\n        \n        # Projection shortcut if dimensions change\n        self.projection = None\n        if stride != 1 or in_channels != out_channels:\n            self.projection = nn.Sequential(\n                nn.Conv3d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),\n                nn.BatchNorm3d(out_channels)\n            )\n    \n    def forward(self, x):\n        identity = x\n        \n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu(out)\n        \n        out = self.conv2(out)\n        out = self.bn2(out)\n        \n        if self.projection is not None:\n            identity = self.projection(x)\n            \n        out += identity\n        out = self.relu(out)\n        \n        return out\n\nclass Improved3DModel(nn.Module):\n    \"\"\"Fixed architecture with proper residual connections\"\"\"\n    def __init__(self, num_labels=14):\n        super(Improved3DModel, self).__init__()\n        \n        # Initial feature extraction\n        self.initial = nn.Sequential(\n            nn.Conv3d(1, 32, kernel_size=3, stride=1, padding=1, bias=False),\n            nn.BatchNorm3d(32),\n            nn.ReLU(inplace=True),\n            nn.Conv3d(32, 32, kernel_size=3, stride=1, padding=1, bias=False),\n            nn.BatchNorm3d(32),\n            nn.ReLU(inplace=True)\n        )\n        \n        # Residual blocks with proper downsampling\n        self.res1 = ResidualBlock3D(32, 32, stride=1)\n        self.pool1 = nn.MaxPool3d(2)\n        \n        self.res2 = ResidualBlock3D(32, 64, stride=1)\n        self.pool2 = nn.MaxPool3d(2)\n        \n        self.res3 = ResidualBlock3D(64, 128, stride=1)\n        self.pool3 = nn.MaxPool3d(2)\n        \n        self.res4 = ResidualBlock3D(128, 256, stride=1)\n        \n        # Global pooling and classification\n        self.global_pool = nn.AdaptiveAvgPool3d(1)\n        self.dropout = nn.Dropout(0.5)\n        self.fc1 = nn.Linear(256, 128)\n        self.relu = nn.ReLU()\n        self.dropout2 = nn.Dropout(0.3)\n        \n        # Separate heads for main label and locations\n        self.main_head = nn.Linear(128, 1)\n        self.location_head = nn.Linear(128, 13)\n    \n    def forward(self, x):\n        x = self.initial(x)\n        x = self.res1(x)\n        x = self.pool1(x)\n        x = self.res2(x)\n        x = self.pool2(x)\n        x = self.res3(x)\n        x = self.pool3(x)\n        x = self.res4(x)\n        \n        x = self.global_pool(x)\n        x = x.view(x.size(0), -1)\n        x = self.dropout(x)\n        \n        x = self.fc1(x)\n        x = self.relu(x)\n        x = self.dropout2(x)\n        \n        main_pred = self.main_head(x)\n        location_preds = self.location_head(x)\n        \n        return torch.cat([location_preds, main_pred], dim=1)\n\n# Create and move improved model to GPU\nmodel = Improved3DModel(num_labels=len(LABEL_COLS)).to(device)\n\n# Show how many parameters our model has (like counting brain cells)\ntotal_params = sum(p.numel() for p in model.parameters())\nprint(f\"Model created with {total_params:,} parameters\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-21T04:39:47.416Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#18ad4c; font-family: 'Segoe UI'; text-align: center; border-top:5px solid #3e3ec7; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🩻 Step 5: Train the Model</div>","metadata":{}},{"cell_type":"code","source":"# # Set up training parameters\nlearning_rate = 0.001\nnum_epochs = 50  # We'll keep it short for this beginner example\n\n# Competition-weighted loss function\n# Aneurysm Present gets 13x more weight than other locations\nweights = torch.ones(len(LABEL_COLS)).to(device)\nweights[-1] = 13.0  # Last label is \"Aneurysm Present\"\ncriterion = nn.BCEWithLogitsLoss(pos_weight=weights)\n\n# Optimizer with weight decay for regularization\noptimizer = optim.AdamW(model.parameters(), lr=learning_rate, weight_decay=1e-4)\n\n# Learning rate scheduler\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer, mode='max', factor=0.5, patience=3, verbose=True\n)\n\n# Training with early stopping\nbest_score = 0.0\npatience = 5\nno_improve = 0\ntrain_losses = []\nval_scores = []\n\nprint(\"\\nStarting training with improved model...\")\nfor epoch in range(num_epochs):\n    # Training phase\n    model.train()\n    running_loss = 0.0\n    \n    train_bar = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{num_epochs} [Train]\")\n    for volumes, labels in train_bar:\n        volumes = volumes.to(device)\n        labels = labels.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(volumes)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        running_loss += loss.item()\n        train_bar.set_postfix(loss=f\"{running_loss/len(train_bar):.4f}\")\n    \n    # Validation phase\n    model.eval()\n    all_preds = []\n    all_labels = []\n    \n    with torch.no_grad():\n        for volumes, labels in val_loader:\n            volumes = volumes.to(device)\n            labels = labels.to(device)\n            \n            outputs = model(volumes)\n            probs = torch.sigmoid(outputs)\n            \n            all_preds.append(probs.cpu())\n            all_labels.append(labels.cpu())\n    \n    # Calculate competition metric\n    preds = torch.cat(all_preds).numpy()\n    labels = torch.cat(all_labels).numpy()\n    \n    # Calculate weighted AUC\n    ap_auc = roc_auc_score(labels[:, -1], preds[:, -1])  # Aneurysm Present\n    other_auc = np.mean([roc_auc_score(labels[:, i], preds[:, i]) for i in range(13)])\n    competition_score = 0.5 * (ap_auc + other_auc)\n    \n    # Track metrics\n    avg_train_loss = running_loss / len(train_loader)\n    train_losses.append(avg_train_loss)\n    val_scores.append(competition_score)\n    \n    print(f\"Epoch {epoch+1}/{num_epochs} | Train Loss: {avg_train_loss:.4f} | \"\n          f\"Competition Score: {competition_score:.4f} | \"\n          f\"Main AUC: {ap_auc:.4f}, Other AUC: {other_auc:.4f}\")\n    \n    # Learning rate adjustment\n    scheduler.step(competition_score)\n    \n    # Early stopping\n    if competition_score > best_score:\n        best_score = competition_score\n        torch.save(model.state_dict(), \"/kaggle/working/best_model.pth\")\n        print(f\"\\n Epoch for best model = {epoch+1}\")\n        no_improve = 0\n    else:\n        no_improve += 1\n        if no_improve >= patience:\n            print(f\"\\nEarly stopping triggered after epoch {epoch+1}\")\n            break\n\nprint(f\"\\nTraining complete! Best competition score: {best_score:.4f}\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-21T04:39:47.417Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#18ad4c; font-family: 'Segoe UI'; text-align: center; border-top:5px solid #3e3ec7; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🗂️ Step 6: Evaluate the Model</div>","metadata":{}},{"cell_type":"code","source":"# Let's see how well our model did\nmodel.eval()\nall_preds = []\nall_labels = []\n\nwith torch.no_grad():\n    for volumes, labels in val_loader:\n        volumes = volumes.to(device)\n        \n        # Get predictions\n        outputs = model(volumes)\n        probs = torch.sigmoid(outputs)  # Convert to probabilities (0 to 1)\n        \n        all_preds.append(probs.cpu().numpy())\n        all_labels.append(labels.numpy())\n\n# Combine all predictions and labels\npreds = np.vstack(all_preds)\nlabels = np.vstack(all_labels)\n\n# Calculate AUC for each label (how well we can tell positive from negative)\nauc_scores = []\nfor i, col in enumerate(LABEL_COLS):\n    try:\n        auc = roc_auc_score(labels[:, i], preds[:, i])\n        auc_scores.append(auc)\n        print(f\"{col[:30]:<30} AUC: {auc:.4f}\")\n    except:\n        print(f\"{col[:30]:<30} AUC: Not enough samples\")\n\n# Calculate the competition metric (special weighted average)\nap_auc = auc_scores[-1]  # AUC for \"Aneurysm Present\" (last column)\nother_auc = np.mean(auc_scores[:-1])  # Average of other 13 locations\ncompetition_score = 0.5 * (ap_auc + other_auc)\n\nprint(f\"\\nCompetition Metric Score: {competition_score:.4f}\")\nprint(f\"(Main label AUC: {ap_auc:.4f}, Average other locations: {other_auc:.4f})\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-21T04:39:47.417Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#18ad4c; font-family: 'Segoe UI'; text-align: center; border-top:5px solid #3e3ec7; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🏁 Step 7: Visualize Results</div>","metadata":{}},{"cell_type":"code","source":"# Plot training progress\nplt.figure(figsize=(10, 6))\nplt.plot(train_losses, label='Training Loss')\nplt.plot(val_scores, label='Validation Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Training Progress')\nplt.legend()\nplt.grid(True)\nplt.savefig('/kaggle/working/training_curve.png')\nplt.show()\n\n# Show some example predictions\nsample_idx = 0\nprint(\"\\nExample Prediction:\")\nfor i, col in enumerate(LABEL_COLS):\n    print(f\"{col}: Actual={labels[sample_idx, i]:.0f}, Predicted={preds[sample_idx, i]:.4f}\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-21T04:39:47.417Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#18ad4c; font-family: 'Segoe UI'; text-align: center; border-top:5px solid #3e3ec7; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🚆 Step 8: Save Everything for Later</div>","metadata":{}},{"cell_type":"code","source":"# Save the final model\ntorch.save(model.state_dict(), \"/kaggle/working/final_model.pth\")\nprint(\"Final model saved to /kaggle/working/final_model.pth\")\n\n# Save evaluation results\nresults = {\n    \"competition_score\": float(competition_score),\n    \"main_label_auc\": float(ap_auc),\n    \"average_other_auc\": float(other_auc),\n    \"label_aucs\": {col: float(auc) for col, auc in zip(LABEL_COLS, auc_scores)}\n}\n\nwith open('/kaggle/working/results.json', 'w') as f:\n    json.dump(results, f, indent=2)\n\nprint(\"Training results saved to /kaggle/working/results.json\")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-09-21T04:39:47.418Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#18ad4c; font-family: 'Segoe UI'; text-align: center; border-top:5px solid #3e3ec7; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">✅ Step 9: FineTune Model on Full Data</div>","metadata":{}},{"cell_type":"code","source":"# ===========================================================\n# FINAL TRAINING ON FULL DATASET (for submission model)\n# ===========================================================\nprint(\"\\n\" + \"=\"*50)\nprint(\"RETRAINING ON FULL DATASET FOR FINAL SUBMISSION\")\nprint(\"=\"*50)\n\n# Create dataset with ALL available data\nfull_dataset = BrainScanDataset(processed_ids, train_df, PREPROCESSED_DIR)\nfull_loader = DataLoader(full_dataset, batch_size=4, shuffle=True, num_workers=2)\n\nprint(f\"Training final model on {len(full_dataset)} total samples (no validation split)\")\n\n# Recreate the model\nfinal_model = Improved3DModel(num_labels=len(LABEL_COLS)).to(device)\n\n# WARM-START FROM OUR BEST MODEL (critical improvement)\ntry:\n    # Load the best model we found during development\n    final_model.load_state_dict(torch.load(\"/kaggle/working/best_model.pth\", map_location=device))\n    print(\"✅ Warm-starting from best development model\")\nexcept Exception as e:\n    print(f\"⚠️  Could not load best model: {str(e)}\")\n    print(\"Starting from random initialization instead\")\n\n# Use a smaller learning rate for fine-tuning\noptimizer = optim.AdamW(final_model.parameters(), lr=learning_rate/10, weight_decay=1e-4)\nfinal_model.train()\n\n# Train for fewer epochs (just fine-tuning)\nfull_epochs = 5\nfor epoch in range(full_epochs):\n    running_loss = 0.0\n    \n    train_bar = tqdm(full_loader, desc=f\"Full Training Epoch {epoch+1}/{full_epochs}\")\n    for volumes, labels in train_bar:\n        volumes = volumes.to(device)\n        labels = labels.to(device)\n        \n        optimizer.zero_grad()\n        outputs = final_model(volumes)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        running_loss += loss.item()\n        train_bar.set_postfix(loss=f\"{running_loss/len(train_bar):.4f}\")\n    \n    avg_loss = running_loss / len(full_loader)\n    print(f\"Full Training Epoch {epoch+1}/{full_epochs} | Loss: {avg_loss:.4f}\")\n\n# Save the FINAL model trained on all data\ntorch.save(final_model.state_dict(), \"/kaggle/working/final_full_model.pth\")\nprint(\"\\n🎉 FINAL MODEL TRAINED ON FULL DATASET SAVED!\")\nprint(\"Use this model for submissions: /kaggle/working/final_full_model.pth\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}