{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.13"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":19989,"databundleVersionId":1160143,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nimport os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.status.busy":"2025-11-17T06:43:22.806455Z","iopub.execute_input":"2025-11-17T06:43:22.806789Z","iopub.status.idle":"2025-11-17T06:43:22.810873Z","shell.execute_reply.started":"2025-11-17T06:43:22.80676Z","shell.execute_reply":"2025-11-17T06:43:22.809956Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# runtime check (GPU + CUDA)\n!nvidia-smi -L || true\n!nvidia-smi || true\n!nvcc --version || true\n\nimport sys, os, subprocess, json\ncuda_available = False\ntry:\n    import torch\n    cuda_available = torch.cuda.is_available()\nexcept Exception:\n    pass\n\nprint(\"Python:\", sys.version.split()[0])\nprint(\"Torch available:\", 'yes' if 'torch' in sys.modules else 'no', \"cuda_available:\", cuda_available)\n","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:22.812169Z","iopub.execute_input":"2025-11-17T06:43:22.812412Z","iopub.status.idle":"2025-11-17T06:43:23.389298Z","shell.execute_reply.started":"2025-11-17T06:43:22.812397Z","shell.execute_reply":"2025-11-17T06:43:23.388351Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import random\nfrom PIL import Image\nfrom collections import Counter\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nimport torch\nimport torchvision\nfrom torchvision import transforms as T\nfrom torchvision.models.detection.mask_rcnn import MaskRCNNPredictor","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:23.390567Z","iopub.execute_input":"2025-11-17T06:43:23.390875Z","iopub.status.idle":"2025-11-17T06:43:23.396322Z","shell.execute_reply.started":"2025-11-17T06:43:23.390843Z","shell.execute_reply":"2025-11-17T06:43:23.395475Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train=pd.read_csv(\"/kaggle/input/global-wheat-detection/train.csv\")","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:23.398171Z","iopub.execute_input":"2025-11-17T06:43:23.398483Z","iopub.status.idle":"2025-11-17T06:43:23.581566Z","shell.execute_reply.started":"2025-11-17T06:43:23.398463Z","shell.execute_reply":"2025-11-17T06:43:23.58072Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head()","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:23.582719Z","iopub.execute_input":"2025-11-17T06:43:23.582969Z","iopub.status.idle":"2025-11-17T06:43:23.592642Z","shell.execute_reply.started":"2025-11-17T06:43:23.582945Z","shell.execute_reply":"2025-11-17T06:43:23.591862Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"coords=pd.DataFrame(list(train.bbox.apply(lambda x:x[1:-1].split(\",\")).values),columns=[\"x1\",\"y1\",\"w\",\"h\"])","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:23.593328Z","iopub.execute_input":"2025-11-17T06:43:23.593529Z","iopub.status.idle":"2025-11-17T06:43:24.064091Z","shell.execute_reply.started":"2025-11-17T06:43:23.593513Z","shell.execute_reply":"2025-11-17T06:43:24.063301Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df=pd.concat([train,coords],axis=1)","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.064866Z","iopub.execute_input":"2025-11-17T06:43:24.065903Z","iopub.status.idle":"2025-11-17T06:43:24.084448Z","shell.execute_reply.started":"2025-11-17T06:43:24.065874Z","shell.execute_reply":"2025-11-17T06:43:24.083681Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df[\"x1\"]=pd.to_numeric(df[\"x1\"])\ndf[\"y1\"]=pd.to_numeric(df[\"y1\"])\ndf[\"w\"]=pd.to_numeric(df[\"w\"])\ndf[\"h\"]=pd.to_numeric(df[\"h\"])","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.085289Z","iopub.execute_input":"2025-11-17T06:43:24.085502Z","iopub.status.idle":"2025-11-17T06:43:24.293352Z","shell.execute_reply.started":"2025-11-17T06:43:24.085485Z","shell.execute_reply":"2025-11-17T06:43:24.292549Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df[\"x2\"]=df[\"x1\"]+df[\"w\"]\ndf[\"y2\"]=df[\"y1\"]+df[\"h\"]","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.294176Z","iopub.execute_input":"2025-11-17T06:43:24.294423Z","iopub.status.idle":"2025-11-17T06:43:24.301404Z","shell.execute_reply.started":"2025-11-17T06:43:24.294406Z","shell.execute_reply":"2025-11-17T06:43:24.30069Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.drop([\"bbox\",\"width\",\"height\",\"w\",\"h\",\"source\"],axis=1,inplace=True)","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.304331Z","iopub.execute_input":"2025-11-17T06:43:24.304589Z","iopub.status.idle":"2025-11-17T06:43:24.342408Z","shell.execute_reply.started":"2025-11-17T06:43:24.304572Z","shell.execute_reply":"2025-11-17T06:43:24.341654Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.head()","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.343302Z","iopub.execute_input":"2025-11-17T06:43:24.343544Z","iopub.status.idle":"2025-11-17T06:43:24.353434Z","shell.execute_reply.started":"2025-11-17T06:43:24.343518Z","shell.execute_reply":"2025-11-17T06:43:24.352628Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"unique_imgs=df.image_id.unique()","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.354391Z","iopub.execute_input":"2025-11-17T06:43:24.354933Z","iopub.status.idle":"2025-11-17T06:43:24.375242Z","shell.execute_reply.started":"2025-11-17T06:43:24.354906Z","shell.execute_reply":"2025-11-17T06:43:24.374493Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"unique_imgs","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.37596Z","iopub.execute_input":"2025-11-17T06:43:24.376219Z","iopub.status.idle":"2025-11-17T06:43:24.38286Z","shell.execute_reply.started":"2025-11-17T06:43:24.376181Z","shell.execute_reply":"2025-11-17T06:43:24.382051Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CustDat(torch.utils.data.Dataset):\n    def __init__(self, df, unique_imgs, indices):\n        self.df = df\n        self.unique_imgs = unique_imgs\n        self.indices = indices\n\n    def __len__(self):\n        return len(self.indices)\n\n    def __getitem__(self, idx):\n        image_name = self.unique_imgs[self.indices[idx]]\n\n        # get xywh\n        # Note: This line needs to be updated to correctly use x1, y1, x2, y2 from the dataframe\n        b = self.df[self.df.image_id == image_name][[\"x1\",\"y1\",\"x2\",\"y2\"]].values.astype(\"float\")\n\n        # convert to xyxy (assuming b now contains [x1, y1, x2, y2])\n        boxes = b # already in xyxy format after data prep\n        \n        # filter invalid boxes\n        valid = (boxes[:,2] > boxes[:,0]) & (boxes[:,3] > boxes[:,1])\n        boxes = boxes[valid]\n\n        labels = torch.ones((boxes.shape[0],), dtype=torch.int64)\n\n        # load image\n        img = Image.open(f\"/kaggle/input/global-wheat-detection/train/{image_name}.jpg\").convert(\"RGB\")\n        \n        # ==========================================================\n        # 🎭 MASK R-CNN FIX: ADD DUMMY MASKS\n        # The Mask R-CNN requires a mask for every box.\n        # We create a placeholder mask tensor of shape [N, H, W] \n        # where N is the number of boxes, and H, W are image dimensions (1024x1024)\n        # We fill them with ones (fully segmented region).\n        # ==========================================================\n        \n        if boxes.shape[0] == 0:\n            # Handle empty case (important for data loading stability)\n            masks = torch.empty((0, img.height, img.width), dtype=torch.uint8)\n        else:\n            # Create N masks (one for each box), size HxW, filled with ones.\n            masks = torch.ones((boxes.shape[0], img.height, img.width), dtype=torch.uint8)\n\n        target = {\n            \"boxes\": torch.tensor(boxes, dtype=torch.float32),\n            \"labels\": labels,\n            \"masks\": masks  # <--- NEW REQUIRED KEY\n        }\n\n        img = T.ToTensor()(img)\n        return img, target","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.383784Z","iopub.execute_input":"2025-11-17T06:43:24.384083Z","iopub.status.idle":"2025-11-17T06:43:24.401268Z","shell.execute_reply.started":"2025-11-17T06:43:24.384061Z","shell.execute_reply":"2025-11-17T06:43:24.400542Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_inds , val_inds = train_test_split(range(unique_imgs.shape[0]) , test_size = 0.1)","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.402002Z","iopub.execute_input":"2025-11-17T06:43:24.402233Z","iopub.status.idle":"2025-11-17T06:43:24.418971Z","shell.execute_reply.started":"2025-11-17T06:43:24.402207Z","shell.execute_reply":"2025-11-17T06:43:24.418327Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def custom_collate(data):\n    return data","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.419767Z","iopub.execute_input":"2025-11-17T06:43:24.420056Z","iopub.status.idle":"2025-11-17T06:43:24.434276Z","shell.execute_reply.started":"2025-11-17T06:43:24.419977Z","shell.execute_reply":"2025-11-17T06:43:24.433329Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dl = torch.utils.data.DataLoader(CustDat(df , unique_imgs , train_inds) ,\n                                        batch_size = 2,\n                                        shuffle = True ,\n                                        collate_fn = custom_collate ,\n                                        pin_memory = True if torch.cuda.is_available() else False)\n\nval_dl = torch.utils.data.DataLoader(CustDat(df , unique_imgs , val_inds) ,\n                                        batch_size = 2 ,\n                                        shuffle = True ,\n                                        collate_fn = custom_collate ,\n                                        pin_memory = True if torch.cuda.is_available() else False)","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.434973Z","iopub.execute_input":"2025-11-17T06:43:24.435148Z","iopub.status.idle":"2025-11-17T06:43:24.449395Z","shell.execute_reply.started":"2025-11-17T06:43:24.435134Z","shell.execute_reply":"2025-11-17T06:43:24.448652Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torchvision\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom torchvision.models.detection.mask_rcnn import MaskRCNNPredictor \nimport torch.nn as nn\n\ndef get_mask_rcnn_model(num_classes):\n    \"\"\"\n    Loads a pre-trained Mask R-CNN and replaces the final layers \n    to accommodate a custom number of classes.\n    \"\"\"\n    # Load an instance segmentation model pre-trained on COCO\n    # We use 'MaskRCNN_ResNet50_FPN_Weights.DEFAULT' for the latest best weights\n    model = torchvision.models.detection.maskrcnn_resnet50_fpn(weights=\"DEFAULT\")\n    \n    # --- 1. Replace the Box Predictor (for object detection) ---\n    \n    # Get the number of input features for the classifier\n    in_features = model.roi_heads.box_predictor.cls_score.in_features\n    \n    # Replace the pre-trained box predictor with a new one\n    model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)\n    \n    # --- 2. Replace the Mask Predictor (for instance segmentation) ---\n    \n    # Get the number of input features for the mask classifier\n    in_features_mask = model.roi_heads.mask_predictor.conv5_mask.in_channels\n    hidden_layer = model.roi_heads.mask_predictor.conv5_mask.out_channels\n    \n    # Replace the pre-trained mask predictor with a new one.\n    # The MaskRCNNPredictor expects 3 arguments: in_channels, hidden_channels, num_classes\n    model.roi_heads.mask_predictor = MaskRCNNPredictor(\n        in_features_mask,\n        hidden_layer,\n        num_classes\n    )\n\n    # Freeze the new layers if you only want to train the final heads\n    # for a quick fine-tuning run (optional, depends on your goal)\n    # for param in model.parameters():\n    #     param.requires_grad = True\n\n    return model\n\n# Example usage (assuming num_classes is defined based on your dataset)\nnum_classes = 2 # e.g., 1 object class + 1 background class (always 0)\nmodel = get_mask_rcnn_model(num_classes)","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:24.450127Z","iopub.execute_input":"2025-11-17T06:43:24.450413Z","iopub.status.idle":"2025-11-17T06:43:25.178597Z","shell.execute_reply.started":"2025-11-17T06:43:24.45039Z","shell.execute_reply":"2025-11-17T06:43:25.177782Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== HYPERPARAMETERS TO TUNE =====\n\nhyperparams = {\n    \"learning_rate\": 0.0005,\n    \"weight_decay\": 1e-4,\n    \"score_threshold\": 0.35,\n    \"nms_threshold\": 0.45,\n    \"batch_size\": 2,          # set in DataLoader\n    \"num_epochs\": 50,\n}\n\n# Apply thresholds to Faster-RCNN\nmodel.roi_heads.score_thresh = hyperparams[\"score_threshold\"]\nmodel.roi_heads.nms_thresh   = hyperparams[\"nms_threshold\"]\n\n# Use AdamW (better for fine-tuning)\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=hyperparams[\"learning_rate\"],\n    weight_decay=hyperparams[\"weight_decay\"]\n)\n\nprint(\"Using hyperparameters:\", hyperparams)\n","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:25.179528Z","iopub.execute_input":"2025-11-17T06:43:25.17978Z","iopub.status.idle":"2025-11-17T06:43:25.18634Z","shell.execute_reply.started":"2025-11-17T06:43:25.179757Z","shell.execute_reply":"2025-11-17T06:43:25.185518Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:25.187056Z","iopub.execute_input":"2025-11-17T06:43:25.187302Z","iopub.status.idle":"2025-11-17T06:43:25.205309Z","shell.execute_reply.started":"2025-11-17T06:43:25.187282Z","shell.execute_reply":"2025-11-17T06:43:25.204555Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:25.206063Z","iopub.execute_input":"2025-11-17T06:43:25.206374Z","iopub.status.idle":"2025-11-17T06:43:25.221167Z","shell.execute_reply.started":"2025-11-17T06:43:25.206352Z","shell.execute_reply":"2025-11-17T06:43:25.220483Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- EARLY STOPPING HYPERPARAMETERS ---\nES_PATIENCE = 8      # How many epochs to wait for improvement\nES_MIN_DELTA = 0.001 # Minimum change in loss to qualify as improvement\n# ------------------------------------\n\n# --- Setup Checkpoint Path and Variables ---\nsave_dir = \"saved_models\"\nos.makedirs(save_dir, exist_ok=True)\ncheckpoint_path = os.path.join(save_dir, \"mask_rcnn_checkpoint.pth\") \nbest_model_path = os.path.join(save_dir, \"best_mask_rcnn_weights.pth\")\n\nstart_epoch = 0\nloss_history = [] \n# Early Stopping variables\nbest_loss = float('inf') # Initialize with a very large number\npatience_counter = 0     # Counter for epochs without improvement\n\n# --- Resuming Logic ---\nif os.path.exists(checkpoint_path):\n    print(\"Checkpoint found. Resuming training...\")\n    checkpoint = torch.load(checkpoint_path, map_location=device)\n    \n    # Load the state dictionaries\n    model.load_state_dict(checkpoint['model_state_dict'])\n    optimizer.load_state_dict(checkpoint['optimizer_state_dict'])\n    \n    # Restore training state and Early Stopping variables\n    start_epoch = checkpoint['epoch']\n    loss_history = checkpoint.get('loss_history', []) \n    best_loss = checkpoint.get('best_loss', float('inf'))\n    patience_counter = checkpoint.get('patience_counter', 0)\n    \n    print(f\"Resumed from epoch {start_epoch}. Total epochs to run: {hyperparams['num_epochs']}\")\nelse:\n    print(\"No checkpoint found. Starting training from scratch.\")\n\nmodel.train() # CRITICAL: Set model to training mode\nmodel.to(device)\n\n# ----------------------------------------------------\n# --- 2. Training Loop with Checkpoint Saving and Early Stopping ---\n# ----------------------------------------------------\nfor epoch in range(start_epoch, hyperparams[\"num_epochs\"]):\n    epoch_loss = 0\n    torch.cuda.empty_cache()\n\n    for batch in train_dl:\n        imgs = [sample[0] for sample in batch]\n        targets = [sample[1] for sample in batch]\n\n        imgs = [img.to(device) for img in imgs]\n        targets = [{k: v.to(device) for k, v in t.items()} for t in targets]\n\n        loss_dict = model(imgs, targets)\n        loss = sum(loss_dict.values())\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        \n        epoch_loss += loss.item()\n\n    loss_history.append(epoch_loss)\n    print(f\"Epoch {epoch+1}/{hyperparams['num_epochs']}, Loss = {epoch_loss:.4f}\")\n\n    # --- Early Stopping and Best Model Check ---\n    if epoch_loss < best_loss - ES_MIN_DELTA:\n        print(f\"-> Loss improved from {best_loss:.4f} to {epoch_loss:.4f}. Patience reset.\")\n        best_loss = epoch_loss\n        patience_counter = 0\n        \n        # Save the BEST model weights (for inference later)\n        torch.save(model.state_dict(), best_model_path)\n        print(f\"-> Saved BEST model weights to {best_model_path}\")\n\n    else:\n        patience_counter += 1\n        print(f\"-> Loss did not improve. Patience: {patience_counter}/{ES_PATIENCE}\")\n\n    # --- Check for Stop Condition ---\n    if patience_counter >= ES_PATIENCE:\n        print(f\"################################################\")\n        print(f\"# Early stopping triggered after {patience_counter} epochs without improvement! #\")\n        print(f\"################################################\")\n        break\n\n    # --- Checkpoint Saving Logic (Saves current state for resuming) ---\n    checkpoint = {\n        'epoch': epoch + 1,\n        'model_state_dict': model.state_dict(),\n        'optimizer_state_dict': optimizer.state_dict(),\n        'loss_history': loss_history,\n        # Save ES variables\n        'best_loss': best_loss,\n        'patience_counter': patience_counter,\n    }\n\n    torch.save(checkpoint, checkpoint_path)\n    print(f\"-> Checkpoint saved to {checkpoint_path} at epoch {epoch + 1}\")","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:30:35.354921Z","iopub.execute_input":"2025-11-17T08:30:35.355591Z","iopub.status.idle":"2025-11-17T08:40:27.714389Z","shell.execute_reply.started":"2025-11-17T08:30:35.355562Z","shell.execute_reply":"2025-11-17T08:40:27.713647Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"loss_history","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T08:44:13.260743Z","iopub.execute_input":"2025-11-17T08:44:13.261055Z","iopub.status.idle":"2025-11-17T08:44:13.266423Z","shell.execute_reply.started":"2025-11-17T08:44:13.261034Z","shell.execute_reply":"2025-11-17T08:44:13.265679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8,5))\nplt.plot(loss_history, marker='o')\nplt.title(\"Training Loss per Epoch\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.grid()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:44:18.669421Z","iopub.execute_input":"2025-11-17T08:44:18.6697Z","iopub.status.idle":"2025-11-17T08:44:18.843665Z","shell.execute_reply.started":"2025-11-17T08:44:18.669679Z","shell.execute_reply":"2025-11-17T08:44:18.842896Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compute_iou(box1, box2):\n    xA = max(box1[0], box2[0])\n    yA = max(box1[1], box2[1])\n    xB = min(box1[2], box2[2])\n    yB = min(box1[3], box2[3])\n\n    inter = max(0, xB - xA) * max(0, yB - yA)\n\n    area1 = (box1[2]-box1[0]) * (box1[3]-box1[1])\n    area2 = (box2[2]-box2[0]) * (box2[3]-box2[1])\n\n    union = area1 + area2 - inter + 1e-6\n    return inter / union\n","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:47:18.45926Z","iopub.execute_input":"2025-11-17T08:47:18.459965Z","iopub.status.idle":"2025-11-17T08:47:18.464604Z","shell.execute_reply.started":"2025-11-17T08:47:18.459941Z","shell.execute_reply":"2025-11-17T08:47:18.463865Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# model.eval()\n# data = iter(val_dl).__next__()","metadata":{"execution":{"iopub.status.busy":"2025-11-17T06:43:26.61378Z","iopub.status.idle":"2025-11-17T06:43:26.614071Z","shell.execute_reply.started":"2025-11-17T06:43:26.613941Z","shell.execute_reply":"2025-11-17T06:43:26.613957Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def evaluate(model, dataloader, device, iou_threshold=0.5):\n    model.eval()\n    tp = fp = fn = 0\n    ious = []\n\n    with torch.no_grad():\n        for batch in dataloader:\n            img = batch[0][0].to(device)\n            tgt = batch[0][1]\n\n            output = model([img])[0]\n\n            pred_boxes = output[\"boxes\"].cpu()\n            pred_scores = output[\"scores\"].cpu()\n            gt_boxes = tgt[\"boxes\"].cpu()\n\n            keep = torchvision.ops.nms(pred_boxes, pred_scores, 0.45)\n            pred_boxes = pred_boxes[keep]\n            pred_scores = pred_scores[keep]\n\n            matched_gt = set()\n\n            for i, pb in enumerate(pred_boxes):\n                if pred_scores[i] < hyperparams[\"score_threshold\"]:\n                    continue\n                max_iou = 0\n                max_gt = -1\n\n                for j, gb in enumerate(gt_boxes):\n                    iou = compute_iou(pb, gb)\n                    if iou > max_iou:\n                        max_iou = iou\n                        max_gt = j\n\n                if max_iou >= iou_threshold:\n                    tp += 1\n                    matched_gt.add(max_gt)\n                    ious.append(max_iou)\n                else:\n                    fp += 1\n\n            fn += len(gt_boxes) - len(matched_gt)\n\n    precision = tp / (tp + fp + 1e-6)\n    recall    = tp / (tp + fn + 1e-6)\n    f1        = 2 * precision * recall / (precision + recall + 1e-6)\n    mean_iou  = sum(ious)/len(ious)\n\n    # simplified mAP@0.5\n    map50 = precision * recall  # proxy for mAP-like behavior\n\n    return {\n        \"Precision\": precision,\n        \"Recall\": recall,\n        \"F1\": f1,\n        \"IoU\": mean_iou,\n        \"mAP@0.5\": map50\n    }\n","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:47:23.500289Z","iopub.execute_input":"2025-11-17T08:47:23.500534Z","iopub.status.idle":"2025-11-17T08:47:23.508216Z","shell.execute_reply.started":"2025-11-17T08:47:23.500516Z","shell.execute_reply":"2025-11-17T08:47:23.507436Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"metrics = evaluate(model, val_dl, device)\nmetrics\n","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:47:28.066312Z","iopub.execute_input":"2025-11-17T08:47:28.066593Z","iopub.status.idle":"2025-11-17T08:48:46.088123Z","shell.execute_reply.started":"2025-11-17T08:47:28.066573Z","shell.execute_reply":"2025-11-17T08:48:46.087467Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(6,5))\nplt.bar(metrics.keys(), metrics.values(), color=\"skyblue\")\nplt.title(\"Validation Metrics\")\nplt.ylabel(\"Score\")\nplt.ylim(0,1)\nplt.grid(axis='y')\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:48:46.21089Z","iopub.execute_input":"2025-11-17T08:48:46.211226Z","iopub.status.idle":"2025-11-17T08:48:46.32786Z","shell.execute_reply.started":"2025-11-17T08:48:46.211168Z","shell.execute_reply":"2025-11-17T08:48:46.327244Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from PIL import ImageDraw","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:50:04.316093Z","iopub.execute_input":"2025-11-17T08:50:04.31641Z","iopub.status.idle":"2025-11-17T08:50:04.320309Z","shell.execute_reply.started":"2025-11-17T08:50:04.316386Z","shell.execute_reply":"2025-11-17T08:50:04.31943Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\nbatch = next(iter(val_dl))\n\nimg = batch[0][0].to(device)\ngt_boxes = batch[0][1]['boxes']\ngt_labels=batch[0][1]['labels']\n\noutput = model([img])[0]\n","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:49:11.347729Z","iopub.execute_input":"2025-11-17T08:49:11.348303Z","iopub.status.idle":"2025-11-17T08:49:11.55331Z","shell.execute_reply.started":"2025-11-17T08:49:11.348277Z","shell.execute_reply":"2025-11-17T08:49:11.552603Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"output","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:49:15.266596Z","iopub.execute_input":"2025-11-17T08:49:15.267148Z","iopub.status.idle":"2025-11-17T08:49:15.327737Z","shell.execute_reply.started":"2025-11-17T08:49:15.267125Z","shell.execute_reply":"2025-11-17T08:49:15.327072Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_boxes = output[\"boxes\"].cpu()\npred_scores = output[\"scores\"].cpu()\n\nkeep = torchvision.ops.nms(pred_boxes, pred_scores, hyperparams[\"nms_threshold\"])\npred_boxes = pred_boxes[keep]\npred_scores = pred_scores[keep]","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:49:25.080647Z","iopub.execute_input":"2025-11-17T08:49:25.081417Z","iopub.status.idle":"2025-11-17T08:49:25.086165Z","shell.execute_reply.started":"2025-11-17T08:49:25.08139Z","shell.execute_reply":"2025-11-17T08:49:25.085534Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_boxes.shape, keep.shape","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:49:30.860444Z","iopub.execute_input":"2025-11-17T08:49:30.861382Z","iopub.status.idle":"2025-11-17T08:49:30.866872Z","shell.execute_reply.started":"2025-11-17T08:49:30.861348Z","shell.execute_reply":"2025-11-17T08:49:30.866166Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# # Convert image","metadata":{}},{"cell_type":"code","source":"\nimg_np = (img.permute(1,2,0).cpu().numpy() * 255).astype(\"uint8\")","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:49:35.347151Z","iopub.execute_input":"2025-11-17T08:49:35.347986Z","iopub.status.idle":"2025-11-17T08:49:35.361973Z","shell.execute_reply.started":"2025-11-17T08:49:35.347962Z","shell.execute_reply":"2025-11-17T08:49:35.361244Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_np","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:49:38.876074Z","iopub.execute_input":"2025-11-17T08:49:38.876637Z","iopub.status.idle":"2025-11-17T08:49:38.881848Z","shell.execute_reply.started":"2025-11-17T08:49:38.876611Z","shell.execute_reply":"2025-11-17T08:49:38.881078Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"vis = Image.fromarray(img_np)\ndraw = ImageDraw.Draw(vis)\n\n# Green = Ground Truth\nfor g in gt_boxes:\n    draw.rectangle(list(g.detach().cpu().numpy()), outline=\"green\", width=2)\n\n# Red = Predictions\nfor p in pred_boxes:\n    draw.rectangle(list(p.detach().cpu().numpy()), outline=\"red\", width=2)\n\nvis","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:50:10.710165Z","iopub.execute_input":"2025-11-17T08:50:10.710751Z","iopub.status.idle":"2025-11-17T08:50:10.901292Z","shell.execute_reply.started":"2025-11-17T08:50:10.710729Z","shell.execute_reply":"2025-11-17T08:50:10.90021Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# ---- Random sample from validation set ----","metadata":{}},{"cell_type":"code","source":"import random\nfrom PIL import ImageDraw\n\n# ---- Random sample from validation set ----\nidx = random.choice(val_inds)                     # pick random index\ndataset = CustDat(df, unique_imgs, val_inds)      # reuse existing dataset\nimg, target = dataset[val_inds.index(idx)]        # load the sample\n\ngt_boxes = target[\"boxes\"]\nimg_tensor = img.to(device)\n\n# ---- Run model ----\nmodel.eval()\nwith torch.no_grad():\n    output = model([img_tensor])[0]\n\npred_boxes = output[\"boxes\"].cpu()\npred_scores = output[\"scores\"].cpu()\n\n# ---- Apply NMS ----\nkeep = torchvision.ops.nms(pred_boxes, pred_scores, hyperparams[\"nms_threshold\"])\npred_boxes = pred_boxes[keep]\npred_scores = pred_scores[keep]\n\n# ---- Convert to image ----\nimg_np = (img.permute(1, 2, 0).numpy() * 255).astype(\"uint8\")\nvis = Image.fromarray(img_np)\ndraw = ImageDraw.Draw(vis)\n\n# ---- Draw GT (green) ----\nfor g in gt_boxes:\n    draw.rectangle(list(g.numpy()), outline=\"green\", width=2)\n\n# ---- Draw Predictions (red) ----\nfor p in pred_boxes:\n    draw.rectangle(list(p.numpy()), outline=\"red\", width=2)\n\n# ---- Show image ----\nvis\n","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:50:18.23013Z","iopub.execute_input":"2025-11-17T08:50:18.230815Z","iopub.status.idle":"2025-11-17T08:50:18.570321Z","shell.execute_reply.started":"2025-11-17T08:50:18.230782Z","shell.execute_reply":"2025-11-17T08:50:18.569263Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# Create save folder\nsave_dir = \"saved_models\"\nos.makedirs(save_dir, exist_ok=True)\n\n# Determine save name based on epochs\nif hyperparams[\"num_epochs\"] == 10:\n    save_name = \"faster_rcnn_c1.pth\"\nelif hyperparams[\"num_epochs\"] == 15:\n    save_name = \"faster_rcnn_c2.pth\"\nelif hyperparams[\"num_epochs\"] == 20:\n    save_name = \"faster_rcnn_c3.pth\"\nelse:\n    # fallback for other epochs\n    save_name = f\"faster_rcnn_epochs_{hyperparams['num_epochs']}.pth\"\n\nsave_path = os.path.join(save_dir, save_name)\n\n# Save weights\ntorch.save(model.state_dict(), save_path)\n\nprint(f\"Model saved as: {save_path}\")\n","metadata":{"execution":{"iopub.status.busy":"2025-11-17T08:50:31.220643Z","iopub.execute_input":"2025-11-17T08:50:31.221338Z","iopub.status.idle":"2025-11-17T08:50:31.515826Z","shell.execute_reply.started":"2025-11-17T08:50:31.221312Z","shell.execute_reply":"2025-11-17T08:50:31.515079Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ls saved_models","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T08:50:55.667501Z","iopub.execute_input":"2025-11-17T08:50:55.668319Z","iopub.status.idle":"2025-11-17T08:50:55.845985Z","shell.execute_reply.started":"2025-11-17T08:50:55.668286Z","shell.execute_reply":"2025-11-17T08:50:55.845042Z"}},"outputs":[],"execution_count":null}]}