{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":24800,"databundleVersionId":1831594,"sourceType":"competition"},{"sourceId":1800067,"sourceType":"datasetVersion","datasetId":1069810}],"dockerImageVersionId":30043,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport cv2\nimport os\nimport re\nimport time\n\nfrom sklearn.model_selection import GroupKFold\n\nimport albumentations as A\nfrom albumentations.pytorch.transforms import ToTensorV2\n\nimport torch\nimport torchvision\n\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom torchvision.models.detection import FasterRCNN\n\nfrom torch.utils.data import DataLoader, Dataset\n\nfrom matplotlib import pyplot as plt","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:07.700419Z","iopub.execute_input":"2025-04-14T08:01:07.700778Z","iopub.status.idle":"2025-04-14T08:01:10.989005Z","shell.execute_reply.started":"2025-04-14T08:01:07.700739Z","shell.execute_reply":"2025-04-14T08:01:10.988195Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dir = '../input/vinbigdata-512-image-dataset/vinbigdata/train'\ntest_dir = '../input/vinbigdata-512-image-dataset/vinbigdata/test'\ntrain_df = pd.read_csv('../input/vinbigdata-512-image-dataset/vinbigdata/train.csv')","metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:10.990788Z","iopub.execute_input":"2025-04-14T08:01:10.991028Z","iopub.status.idle":"2025-04-14T08:01:11.172959Z","shell.execute_reply.started":"2025-04-14T08:01:10.991005Z","shell.execute_reply":"2025-04-14T08:01:11.172081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.175022Z","iopub.execute_input":"2025-04-14T08:01:11.175358Z","iopub.status.idle":"2025-04-14T08:01:11.199146Z","shell.execute_reply.started":"2025-04-14T08:01:11.175323Z","shell.execute_reply":"2025-04-14T08:01:11.198277Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = train_df[train_df['class_id'] != 14].reset_index(drop=True)\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.201013Z","iopub.execute_input":"2025-04-14T08:01:11.201351Z","iopub.status.idle":"2025-04-14T08:01:11.235493Z","shell.execute_reply.started":"2025-04-14T08:01:11.201315Z","shell.execute_reply":"2025-04-14T08:01:11.234772Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df['image_path'] = '../input/vinbigdata-512-image-dataset/vinbigdata/train/'+train_df.image_id+'.png'\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.236948Z","iopub.execute_input":"2025-04-14T08:01:11.237282Z","iopub.status.idle":"2025-04-14T08:01:11.266217Z","shell.execute_reply.started":"2025-04-14T08:01:11.237247Z","shell.execute_reply":"2025-04-14T08:01:11.265417Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## GROUP KFOLD","metadata":{}},{"cell_type":"code","source":"gkf  = GroupKFold(n_splits = 5)\ntrain_df['fold'] = -1\nfor fold, (train_idx, val_idx) in enumerate(gkf.split(train_df, groups = train_df.image_id.tolist())):\n    train_df.loc[val_idx, 'fold'] = fold\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.267347Z","iopub.execute_input":"2025-04-14T08:01:11.267616Z","iopub.status.idle":"2025-04-14T08:01:11.335019Z","shell.execute_reply.started":"2025-04-14T08:01:11.267555Z","shell.execute_reply":"2025-04-14T08:01:11.334222Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.groupby('fold')['image_id'].agg(lambda x: x.nunique()).reset_index()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.336151Z","iopub.execute_input":"2025-04-14T08:01:11.336472Z","iopub.status.idle":"2025-04-14T08:01:11.353229Z","shell.execute_reply.started":"2025-04-14T08:01:11.336434Z","shell.execute_reply":"2025-04-14T08:01:11.35254Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_SIZE = 512\ntrain_df['xmin'] = (train_df['x_min']/train_df['width'])*IMG_SIZE\ntrain_df['ymin'] = (train_df['y_min']/train_df['height'])*IMG_SIZE\ntrain_df['xmax'] = (train_df['x_max']/train_df['width'])*IMG_SIZE\ntrain_df['ymax'] = (train_df['y_max']/train_df['height'])*IMG_SIZE","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.354204Z","iopub.execute_input":"2025-04-14T08:01:11.354404Z","iopub.status.idle":"2025-04-14T08:01:11.36491Z","shell.execute_reply.started":"2025-04-14T08:01:11.354384Z","shell.execute_reply":"2025-04-14T08:01:11.364327Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"assert train_df['xmin'].all() <= IMG_SIZE\nassert train_df['ymin'].all() <= IMG_SIZE\nassert train_df['xmax'].all() <= IMG_SIZE\nassert train_df['ymax'].all() <= IMG_SIZE","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.366154Z","iopub.execute_input":"2025-04-14T08:01:11.366475Z","iopub.status.idle":"2025-04-14T08:01:11.374956Z","shell.execute_reply.started":"2025-04-14T08:01:11.36644Z","shell.execute_reply":"2025-04-14T08:01:11.374357Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df[train_df['image_id'] == '9a5094b2563a1ef3ff50dc5c7ff71345']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.376252Z","iopub.execute_input":"2025-04-14T08:01:11.376591Z","iopub.status.idle":"2025-04-14T08:01:11.407455Z","shell.execute_reply.started":"2025-04-14T08:01:11.376533Z","shell.execute_reply":"2025-04-14T08:01:11.406755Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_dict = dict(set(zip(train_df.class_id, train_df.class_name)))\nclasses = []\nfor key in sorted(class_dict.keys()): \n    classes.append(class_dict[key])\n\nclasses = ['_'] + classes   # adding background\nclasses","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.408304Z","iopub.execute_input":"2025-04-14T08:01:11.408497Z","iopub.status.idle":"2025-04-14T08:01:11.422422Z","shell.execute_reply.started":"2025-04-14T08:01:11.408478Z","shell.execute_reply":"2025-04-14T08:01:11.421769Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class VBDDataset(Dataset):\n    def __init__(self, dataframe, image_dir, transforms=None):\n        super().__init__()\n\n        self.image_ids = dataframe['image_id'].unique()\n        self.df = dataframe\n        self.image_dir = image_dir\n        self.transforms = transforms\n\n    def __getitem__(self, idx):\n\n        image_id = self.image_ids[idx]\n        records = self.df[self.df['image_id'] == image_id]\n\n        image = cv2.imread(f'{self.image_dir}/{image_id}.png', cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n        image /= 255.0\n\n        boxes = records[['xmin', 'ymin', 'xmax', 'ymax']].values\n        \n        area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])\n        area = torch.as_tensor(area, dtype=torch.float32)\n        # all the labels are shifted by 1 to accomodate background\n        labels = torch.squeeze(torch.as_tensor((records.class_id.values+1,), dtype=torch.int64))\n        \n        # suppose all instances are not crowd\n        iscrowd = torch.zeros((records.shape[0],), dtype=torch.int64)\n        \n        target = {}\n        target['boxes'] = boxes\n        target['labels'] = labels\n        # target['masks'] = None\n        target['image_id'] = torch.tensor([idx])\n        target['area'] = area\n        target['iscrowd'] = iscrowd\n        if self.transforms:\n            sample = {\n                'image': image,\n                'bboxes': target['boxes'],\n                'labels': labels\n            }\n            sample = self.transforms(**sample)\n            image = sample['image']\n            \n            target['boxes'] = torch.as_tensor(sample['bboxes'])\n\n        return image, target, image_id\n\n    def __len__(self):\n        return self.image_ids.shape[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.423452Z","iopub.execute_input":"2025-04-14T08:01:11.423708Z","iopub.status.idle":"2025-04-14T08:01:11.436207Z","shell.execute_reply.started":"2025-04-14T08:01:11.423685Z","shell.execute_reply":"2025-04-14T08:01:11.435533Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dt = VBDDataset(train_df, train_dir)\ndt[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.43728Z","iopub.execute_input":"2025-04-14T08:01:11.437621Z","iopub.status.idle":"2025-04-14T08:01:11.588281Z","shell.execute_reply.started":"2025-04-14T08:01:11.437561Z","shell.execute_reply":"2025-04-14T08:01:11.587501Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Albumentations\ndef get_train_transform():\n    return A.Compose([\n        A.Flip(0.5),\n        ToTensorV2(p=1.0)\n    ], bbox_params={'format': 'pascal_voc', 'label_fields': ['labels']})\n\ndef get_valid_transform():\n    return A.Compose([\n        ToTensorV2(p=1.0)\n    ], bbox_params={'format': 'pascal_voc', 'label_fields': ['labels']})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.591482Z","iopub.execute_input":"2025-04-14T08:01:11.59172Z","iopub.status.idle":"2025-04-14T08:01:11.596416Z","shell.execute_reply.started":"2025-04-14T08:01:11.591696Z","shell.execute_reply":"2025-04-14T08:01:11.59579Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:11.598148Z","iopub.execute_input":"2025-04-14T08:01:11.598477Z","iopub.status.idle":"2025-04-14T08:01:13.158597Z","shell.execute_reply.started":"2025-04-14T08:01:11.598443Z","shell.execute_reply":"2025-04-14T08:01:13.1577Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:13.159977Z","iopub.execute_input":"2025-04-14T08:01:13.160259Z","iopub.status.idle":"2025-04-14T08:01:13.16852Z","shell.execute_reply.started":"2025-04-14T08:01:13.160232Z","shell.execute_reply":"2025-04-14T08:01:13.16747Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_classes = 15  # 14 classes + background\n\n# get number of input features for the classifier\nin_features = model.roi_heads.box_predictor.cls_score.in_features\n\n# replace the pre-trained head with a new one\nmodel.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:13.169836Z","iopub.execute_input":"2025-04-14T08:01:13.170154Z","iopub.status.idle":"2025-04-14T08:01:13.182184Z","shell.execute_reply.started":"2025-04-14T08:01:13.170123Z","shell.execute_reply":"2025-04-14T08:01:13.181371Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# A Class for keeping track of average\nclass Averager:\n    def __init__(self):\n        self.current_total = 0.0\n        self.iterations = 0.0\n\n    def send(self, value):\n        self.current_total += value\n        self.iterations += 1\n\n    @property\n    def value(self):\n        if self.iterations == 0:\n            return 0\n        else:\n            return 1.0 * self.current_total / self.iterations\n\n    def reset(self):\n        self.current_total = 0.0\n        self.iterations = 0.0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:13.183434Z","iopub.execute_input":"2025-04-14T08:01:13.183717Z","iopub.status.idle":"2025-04-14T08:01:13.194262Z","shell.execute_reply.started":"2025-04-14T08:01:13.18369Z","shell.execute_reply":"2025-04-14T08:01:13.193369Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Testing Sample","metadata":{}},{"cell_type":"code","source":"def collate_fn(batch):\n    return tuple(zip(*batch))\n\ntrain_dataset = VBDDataset(train_df, train_dir, get_train_transform())\nvalid_dataset = VBDDataset(train_df, train_dir, get_valid_transform())\n\n\ntrain_data_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=4,\n    collate_fn=collate_fn\n)\n\nvalid_data_loader = DataLoader(\n    valid_dataset,\n    batch_size=8,\n    shuffle=False,\n    num_workers=4,\n    collate_fn=collate_fn\n)","metadata":{"_kg_hide-input":true,"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:13.195339Z","iopub.execute_input":"2025-04-14T08:01:13.195615Z","iopub.status.idle":"2025-04-14T08:01:13.215095Z","shell.execute_reply.started":"2025-04-14T08:01:13.195572Z","shell.execute_reply":"2025-04-14T08:01:13.21429Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\n\nimages, targets, image_ids = next(iter(train_data_loader))\nimages = list(image.to(device) for image in images)\ntargets = [{k: v.to(device) for k, v in t.items()} for t in targets]\n\nboxes = targets[2]['boxes'].cpu().numpy().astype(np.int32)\nsample = images[2].permute(1,2,0).cpu().numpy()\n\nfig, ax = plt.subplots(1, 1, figsize=(16, 8))\n\nfor box in boxes:\n    cv2.rectangle(sample,\n                  (box[0], box[1]),\n                  (box[2], box[3]),\n                  (220, 0, 0), 3)\n    \nax.set_axis_off()\nax.imshow(sample)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:13.216105Z","iopub.execute_input":"2025-04-14T08:01:13.216359Z","iopub.status.idle":"2025-04-14T08:01:18.770314Z","shell.execute_reply.started":"2025-04-14T08:01:13.216333Z","shell.execute_reply":"2025-04-14T08:01:18.769538Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training","metadata":{}},{"cell_type":"code","source":"def get_dataloaders(df, trn_idx, val_idx):\n    \n    train_ = df.loc[trn_idx,:].reset_index(drop=True)\n    valid_ = df.loc[val_idx,:].reset_index(drop=True)\n        \n    def collate_fn(batch):\n        return tuple(zip(*batch))\n\n    train_dataset = VBDDataset(train_, train_dir, get_train_transform())\n    valid_dataset = VBDDataset(valid_, train_dir, get_valid_transform())\n\n\n    train_data_loader = DataLoader(\n        train_dataset,\n        batch_size=16,\n        shuffle=False,\n        num_workers=4,\n        collate_fn=collate_fn\n    )\n\n    valid_data_loader = DataLoader(\n        valid_dataset,\n        batch_size=8,\n        shuffle=False,\n        num_workers=4,\n        collate_fn=collate_fn\n    )\n    \n    return train_data_loader, valid_data_loader\n\n\n\ndef train_model(model, dataloader, device, epochs, optimizer, lr_scheduler, fold):\n    \n    best_loss = 1e10\n    loss_hist = Averager()\n    itr = 1\n    all_losses = []\n    \n    model.train()\n    \n    for epoch in range(epochs):\n        loss_hist.reset() \n    \n        for images, targets, image_ids in dataloader:\n\n            images = list(image.to(device) for image in images)\n            targets = [{k: v.to(device) for k, v in t.items()} for t in targets]\n\n            loss_dict = model(images, targets)\n\n            print(loss_dict)\n            losses = sum(loss for loss in loss_dict.values())\n            loss_value = losses.item()\n\n            loss_hist.send(loss_value)\n            all_losses.append(loss_value)\n            \n            optimizer.zero_grad()\n            losses.backward()\n            optimizer.step()\n\n            if itr % 50 == 0:\n                print(f\"Iteration #{itr} loss: {loss_value}\")\n\n            itr += 1\n        \n        # saving the model based on training loss for now. - later can be moved to validation\n        if loss_hist.value < best_loss:\n            best_loss = loss_hist.value\n            torch.save(model.state_dict(), f'fasterrcnn_model_{fold}.pt')\n\n        # update the learning rate\n        if lr_scheduler is not None:\n            lr_scheduler.step()\n\n        print(f\"Epoch #{epoch} loss: {loss_hist.value}\\n\")\n        \n    return all_losses\n        \n        \ndef validate_model(model, dataloader, device):\n    print(\"\\n Starting Validation ... \")\n    loss_hist = Averager()\n    itr = 1\n\n    loss_hist.reset() \n\n    for images, targets, image_ids in dataloader:\n\n        images = list(image.to(device) for image in images)\n        targets = [{k: v.to(device) for k, v in t.items()} for t in targets]\n\n        loss_dict = model(images, targets)\n        # print(loss_dict)\n        losses = sum(loss for loss in loss_dict.values())\n        loss_value = losses.item()\n\n        loss_hist.send(loss_value)\n\n        if itr % 50 == 0:\n            print(f\"Iteration #{itr} loss: {loss_value}\")\n\n        itr += 1\n\n    print(f\"\\nFinal loss: {loss_hist.value}\")\n\n\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:18.771644Z","iopub.execute_input":"2025-04-14T08:01:18.771894Z","iopub.status.idle":"2025-04-14T08:01:18.787828Z","shell.execute_reply.started":"2025-04-14T08:01:18.771866Z","shell.execute_reply":"2025-04-14T08:01:18.786754Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_fold(fold):\n    print(f\"Starting fold {fold}\")\n    start = time.time()\n    trn_idx = train_df[train_df['fold'] != fold].index\n    val_idx = train_df[train_df['fold'] == fold].index\n    \n    \n    trainloader, valloader = get_dataloaders(train_df, trn_idx, val_idx)\n    \n    loss_hist = train_model(model, trainloader, device, epochs, optimizer, lr_scheduler, fold)\n    \n    # plot training loss\n    plt.figure(figsize=(8,5))\n    plt.plot(loss_hist)\n    plt.title(\"Training Loss Statistic\", size=17)\n    plt.xlabel(\"Iteration\", size=15)\n    plt.ylabel(\"Loss Value\", size=15)\n    plt.show()\n    \n    validate_model(model, valloader, device)\n    \n    print(f\"Completed Fold {fold} in {round(time.time()-start, 2)} seconds\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:18.789183Z","iopub.execute_input":"2025-04-14T08:01:18.789618Z","iopub.status.idle":"2025-04-14T08:01:18.802734Z","shell.execute_reply.started":"2025-04-14T08:01:18.78956Z","shell.execute_reply":"2025-04-14T08:01:18.802053Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.to(device)\n\n# set params for model\nparams = [p for p in model.parameters() if p.requires_grad]\n\n# set optimizer\noptimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)\n\n# set lr scheduler\nlr_scheduler = None\n\n# set epochs\nepochs = 2\n\n# set folds\nnum_folds = 1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:01:18.803871Z","iopub.execute_input":"2025-04-14T08:01:18.804189Z","iopub.status.idle":"2025-04-14T08:01:18.880737Z","shell.execute_reply.started":"2025-04-14T08:01:18.804156Z","shell.execute_reply":"2025-04-14T08:01:18.88015Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for fold in range(num_folds):\n    run_fold(fold)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:39:28.836824Z","iopub.execute_input":"2025-04-14T08:39:28.837184Z","iopub.status.idle":"2025-04-14T08:53:36.103128Z","shell.execute_reply.started":"2025-04-14T08:39:28.837156Z","shell.execute_reply":"2025-04-14T08:53:36.101973Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualize Model","metadata":{}},{"cell_type":"code","source":"images, targets, image_ids = next(iter(valid_data_loader))\n\nimages = list(img.to(device) for img in images)\ntargets = [{k: v.to(device) for k, v in t.items()} for t in targets]\n\nboxes = targets[1]['boxes'].cpu().numpy().astype(np.int32)\nsample = images[1].permute(1,2,0).cpu().numpy()\nclss = targets[1]['labels'].cpu().numpy().astype(np.int32)\n\nmodel.eval()\ncpu_device = torch.device(\"cpu\")\n\noutputs = model(images)\noutputs = [{k: v.to(cpu_device) for k, v in t.items()} for t in outputs]\n\nfig, ax = plt.subplots(1, 1, figsize=(16, 8))\n\nfor box, clas in zip(boxes, clss):\n    cv2.putText(sample, f\"{classes[clas]}\", (box[0], box[1]), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,0,255), 1)\n    cv2.rectangle(sample,\n                  (box[0], box[1]),\n                  (box[2], box[3]),\n                  (220, 0, 0), 1)\n    \nax.set_axis_off()\nax.imshow(sample)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:53:36.10532Z","iopub.execute_input":"2025-04-14T08:53:36.105643Z","iopub.status.idle":"2025-04-14T08:53:37.288035Z","shell.execute_reply.started":"2025-04-14T08:53:36.105599Z","shell.execute_reply":"2025-04-14T08:53:37.287067Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Acknowledgements\nNotebook Heavily inspired by this Notebook - https://www.kaggle.com/pestipeti/pytorch-starter-fasterrcnn-train/notebook.\n\n**If the kernel helps you in any way, kindly Upvote**","metadata":{}},{"cell_type":"markdown","source":"## Evaluate","metadata":{}},{"cell_type":"code","source":"import torch\nimport numpy as np\nfrom tqdm import tqdm\n\n# ==== 1. Tính IoU ====\ndef compute_iou(box1, box2):\n    x1, y1, x2, y2 = box1\n    x1g, y1g, x2g, y2g = box2\n\n    xi1 = max(x1, x1g)\n    yi1 = max(y1, y1g)\n    xi2 = min(x2, x2g)\n    yi2 = min(y2, y2g)\n\n    inter_area = max(0, xi2 - xi1) * max(0, yi2 - yi1)\n    box_area = (x2 - x1) * (y2 - y1)\n    gt_area = (x2g - x1g) * (y2g - y1g)\n    union_area = box_area + gt_area - inter_area\n\n    return inter_area / union_area if union_area > 0 else 0\n\n\n# ==== 2. Tính Precision, Recall, AP ====\ndef calculate_precision_recall_ap(pred_boxes, gt_boxes, iou_threshold=0.5):\n    pred_boxes.sort(key=lambda x: x[2], reverse=True)\n    tp, fp, matched = [], [], set()\n\n    for pred in pred_boxes:\n        image_id, cls, score, *pred_box = pred\n        match_found = False\n        for i, gt in enumerate(gt_boxes):\n            if gt[0] == image_id and gt[1] == cls:\n                iou = compute_iou(pred_box, gt[2:])\n                if iou >= iou_threshold and i not in matched:\n                    match_found = True\n                    matched.add(i)\n                    break\n        tp.append(1 if match_found else 0)\n        fp.append(0 if match_found else 1)\n\n    tp_cum = np.cumsum(tp)\n    fp_cum = np.cumsum(fp)\n    precisions = tp_cum / (tp_cum + fp_cum + 1e-6)\n    recalls = tp_cum / len(gt_boxes) if gt_boxes else np.zeros_like(tp_cum)\n\n    ap = 0.0\n    for t in np.linspace(0, 1, 11):\n        p = precisions[recalls >= t]\n        ap += np.max(p) if p.size else 0\n    ap /= 11.0\n    return precisions, recalls, ap\n\n\n# ==== 3. Tính mAP ====\ndef compute_map(pred_boxes, gt_boxes, iou_thresh=0.5):\n    aps = []\n    classes = set([b[1] for b in gt_boxes])\n    for cls in classes:\n        pred_cls = [b for b in pred_boxes if b[1] == cls]\n        gt_cls = [b for b in gt_boxes if b[1] == cls]\n        _, _, ap = calculate_precision_recall_ap(pred_cls, gt_cls, iou_thresh)\n        aps.append(ap)\n    return np.mean(aps), aps\n\n\n# ==== 4. Inference + gom pred/gt từ valid_data_loader ====\ndef evaluate_model(model, valid_data_loader, device):\n    model.eval()\n    cpu_device = torch.device(\"cpu\")\n\n    pred_boxes = []\n    gt_boxes = []\n\n    with torch.no_grad():\n        for images, targets, image_ids in tqdm(valid_data_loader):\n            images = list(img.to(device) for img in images)\n            targets = [{k: v.to(device) for k, v in t.items()} for t in targets]\n\n            outputs = model(images)\n            outputs = [{k: v.to(cpu_device).detach() for k, v in t.items()} for t in outputs]\n\n            for i, (target, output) in enumerate(zip(targets, outputs)):\n                image_id = image_ids[i]\n                gt_cls = target['labels'].cpu().numpy()\n                gt_bx = target['boxes'].cpu().numpy()\n                for cls, box in zip(gt_cls, gt_bx):\n                    gt_boxes.append([image_id, cls, *box])\n\n                pred_cls = output['labels'].cpu().numpy()\n                pred_bx = output['boxes'].cpu().numpy()\n                scores = output['scores'].cpu().numpy()\n\n                for cls, score, box in zip(pred_cls, scores, pred_bx):\n                    pred_boxes.append([image_id, cls, score, *box])\n\n    mAP, aps = compute_map(pred_boxes, gt_boxes, iou_thresh=0.5)\n    return mAP, aps\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:53:37.289693Z","iopub.execute_input":"2025-04-14T08:53:37.289986Z","iopub.status.idle":"2025-04-14T08:53:37.311959Z","shell.execute_reply.started":"2025-04-14T08:53:37.289956Z","shell.execute_reply":"2025-04-14T08:53:37.311159Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mAP, aps = evaluate_model(model, valid_data_loader, device)\nprint(f\"mAP@0.5: {mAP:.4f}\")\nfor idx, ap in enumerate(aps):\n    print(f\"AP for class {idx}: {ap:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:53:37.31318Z","iopub.execute_input":"2025-04-14T08:53:37.313408Z","iopub.status.idle":"2025-04-14T08:58:20.320993Z","shell.execute_reply.started":"2025-04-14T08:53:37.313385Z","shell.execute_reply":"2025-04-14T08:58:20.320112Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### New","metadata":{}},{"cell_type":"code","source":"import torch\nimport numpy as np\nfrom tqdm import tqdm\nfrom collections import defaultdict\n\n\ndef compute_iou(box1, box2):\n    \"\"\"Compute IoU between two boxes.\"\"\"\n    x1, y1, x2, y2 = box1\n    x1g, y1g, x2g, y2g = box2\n    xi1 = max(x1, x1g)\n    yi1 = max(y1, y1g)\n    xi2 = min(x2, x2g)\n    yi2 = min(y2, y2g)\n    inter_area = max(0, xi2 - xi1) * max(0, yi2 - yi1)\n    union_area = (x2 - x1) * (y2 - y1) + (x2g - x1g) * (y2g - y1g) - inter_area\n    return inter_area / union_area if union_area > 0 else 0\n\n\ndef calculate_metrics(pred_boxes, gt_boxes, iou_threshold=0.5):\n    \"\"\"Calculate full evaluation metrics for one class.\"\"\"\n    pred_boxes.sort(key=lambda x: x[2], reverse=True)\n    tp, fp, matched_gt_idx, iou_list = [], [], set(), []\n\n    for pred in pred_boxes:\n        image_id, cls, score, *pred_box = pred\n        match_found = False\n        for i, gt in enumerate(gt_boxes):\n            if gt[0] == image_id and gt[1] == cls and i not in matched_gt_idx:\n                iou = compute_iou(pred_box, gt[2:])\n                if iou >= iou_threshold:\n                    match_found = True\n                    matched_gt_idx.add(i)\n                    iou_list.append(iou)\n                    break\n        tp.append(1 if match_found else 0)\n        fp.append(0 if match_found else 1)\n\n    tp_cum = np.cumsum(tp)\n    fp_cum = np.cumsum(fp)\n    eps = 1e-6\n\n    precision = tp_cum / (tp_cum + fp_cum + eps)\n    recall = tp_cum / (len(gt_boxes) + eps)\n\n    # 11-point Interpolated Average Precision\n    ap = 0.0\n    for t in np.linspace(0, 1, 11):\n        p = precision[recall >= t]\n        ap += np.max(p) if p.size else 0\n    ap /= 11.0\n\n    final_precision = precision[-1] if len(precision) else 0\n    final_recall = recall[-1] if len(recall) else 0\n    f1_score = 2 * final_precision * final_recall / (final_precision + final_recall + eps)\n    mean_iou = np.mean(iou_list) if iou_list else 0\n\n    return {\n        \"AP\": ap,\n        \"precision\": final_precision,\n        \"recall\": final_recall,\n        \"f1_score\": f1_score,\n        \"mean_iou\": mean_iou,\n    }\n\n\ndef evaluate_model(model, valid_data_loader, device, iou_thresh=0.5):\n    model.eval()\n    cpu_device = torch.device(\"cpu\")\n    pred_boxes, gt_boxes = [], []\n\n    with torch.no_grad():\n        for images, targets, image_ids in tqdm(valid_data_loader, desc=\"Evaluating\"):\n            images = list(img.to(device) for img in images)\n            targets = [{k: v.to(device) for k, v in t.items()} for t in targets]\n\n            outputs = model(images)\n            outputs = [{k: v.to(cpu_device).detach() for k, v in t.items()} for t in outputs]\n\n            for i, (target, output) in enumerate(zip(targets, outputs)):\n                image_id = image_ids[i]\n                gt_cls = target['labels'].cpu().numpy()\n                gt_bx = target['boxes'].cpu().numpy()\n                for cls, box in zip(gt_cls, gt_bx):\n                    gt_boxes.append([image_id, cls, *box])\n\n                pred_cls = output['labels'].cpu().numpy()\n                pred_bx = output['boxes'].cpu().numpy()\n                scores = output['scores'].cpu().numpy()\n                for cls, score, box in zip(pred_cls, scores, pred_bx):\n                    pred_boxes.append([image_id, cls, score, *box])\n\n    # === Group predictions and GT by class\n    classes = sorted(set([b[1] for b in gt_boxes + pred_boxes]))\n    results = {}\n    for cls in classes:\n        preds = [b for b in pred_boxes if b[1] == cls]\n        gts = [b for b in gt_boxes if b[1] == cls]\n        metrics = calculate_metrics(preds, gts, iou_threshold=iou_thresh)\n        results[cls] = metrics\n\n    # === Compute mean of each metric\n    mean_metrics = {}\n    for key in [\"AP\", \"precision\", \"recall\", \"f1_score\", \"mean_iou\"]:\n        mean_metrics[f\"mean_{key}\"] = np.mean([res[key] for res in results.values()])\n\n    return mean_metrics, results\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:58:20.325902Z","iopub.execute_input":"2025-04-14T08:58:20.326162Z","iopub.status.idle":"2025-04-14T08:58:20.350352Z","shell.execute_reply.started":"2025-04-14T08:58:20.326134Z","shell.execute_reply":"2025-04-14T08:58:20.349425Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mean_metrics, per_class_metrics = evaluate_model(model, valid_data_loader, device)\n\nprint(\"=== Mean Metrics (mAP) ===\")\nfor k, v in mean_metrics.items():\n    print(f\"{k}: {v:.4f}\")\n\nprint(\"\\n=== Per-Class Metrics ===\")\nfor cls_id, metrics in per_class_metrics.items():\n    print(f\"Class {cls_id}: \", end=\"\")\n    print(\", \".join(f\"{k}={v:.3f}\" for k, v in metrics.items()))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:58:20.351757Z","iopub.execute_input":"2025-04-14T08:58:20.352156Z","iopub.status.idle":"2025-04-14T09:03:05.647152Z","shell.execute_reply.started":"2025-04-14T08:58:20.352119Z","shell.execute_reply":"2025-04-14T09:03:05.646168Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Tính IoU","metadata":{}},{"cell_type":"code","source":"def compute_iou(box1, box2):\n    \"\"\"Tính IoU giữa 2 box\"\"\"\n    x1, y1, x2, y2 = box1\n    x1g, y1g, x2g, y2g = box2\n\n    xi1 = max(x1, x1g)\n    yi1 = max(y1, y1g)\n    xi2 = min(x2, x2g)\n    yi2 = min(y2, y2g)\n\n    inter_area = max(0, xi2 - xi1) * max(0, yi2 - yi1)\n    box_area = (x2 - x1) * (y2 - y1)\n    gt_area = (x2g - x1g) * (y2g - y1g)\n    union_area = box_area + gt_area - inter_area\n\n    return inter_area / union_area if union_area > 0 else 0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:15:40.971891Z","iopub.execute_input":"2025-04-14T08:15:40.972306Z","iopub.status.idle":"2025-04-14T08:15:40.980411Z","shell.execute_reply.started":"2025-04-14T08:15:40.972268Z","shell.execute_reply":"2025-04-14T08:15:40.97941Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Tính Precision, Recall, AP cho 1 class","metadata":{}},{"cell_type":"code","source":"def calculate_precision_recall_ap(pred_boxes, gt_boxes, iou_threshold=0.5):\n    \"\"\"\n    pred_boxes: list of [image_id, class_id, confidence, x1, y1, x2, y2]\n    gt_boxes:   list of [image_id, class_id, x1, y1, x2, y2]\n    \"\"\"\n    pred_boxes.sort(key=lambda x: x[2], reverse=True)\n    tp = []\n    fp = []\n    matched = set()\n\n    for pred in pred_boxes:\n        image_id, cls, score, *pred_box = pred\n        match_found = False\n        for i, gt in enumerate(gt_boxes):\n            if gt[0] == image_id and gt[1] == cls:\n                iou = compute_iou(pred_box, gt[2:])\n                if iou >= iou_threshold and i not in matched:\n                    match_found = True\n                    matched.add(i)\n                    break\n        if match_found:\n            tp.append(1)\n            fp.append(0)\n        else:\n            tp.append(0)\n            fp.append(1)\n\n    tp_cum = np.cumsum(tp)\n    fp_cum = np.cumsum(fp)\n    precisions = tp_cum / (tp_cum + fp_cum + 1e-6)\n    recalls = tp_cum / len(gt_boxes)\n\n    # Tính Average Precision theo cách PASCAL VOC\n    ap = 0.0\n    for t in np.linspace(0, 1, 11):\n        p = precisions[recalls >= t]\n        ap += np.max(p) if p.size else 0\n    ap /= 11.0\n    return precisions, recalls, ap","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:16:44.034702Z","iopub.execute_input":"2025-04-14T08:16:44.035027Z","iopub.status.idle":"2025-04-14T08:16:44.046003Z","shell.execute_reply.started":"2025-04-14T08:16:44.035Z","shell.execute_reply":"2025-04-14T08:16:44.04473Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_boxes = []\ngt_boxes = []\n\nfor i, (image, target) in enumerate(zip(images, targets)):\n    image_id = image_ids[i]\n    gt_cls = target['labels'].cpu().numpy()\n    gt_bx = target['boxes'].cpu().numpy()\n\n    for cls, box in zip(gt_cls, gt_bx):\n        gt_boxes.append([image_id, cls, *box])\n\n    out = outputs[i]\n    pred_bx = out['boxes'].detach().cpu().numpy()\n    pred_cls = out['labels'].detach().cpu().numpy()\n    scores = out['scores'].detach().cpu().numpy()\n    # pred_cls = out['labels'].cpu().numpy()\n    # pred_bx = out['boxes'].cpu().numpy()\n    # scores = out['scores'].cpu().numpy()\n\n    for cls, score, box in zip(pred_cls, scores, pred_bx):\n        pred_boxes.append([image_id, cls, score, *box])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:18:56.633121Z","iopub.execute_input":"2025-04-14T08:18:56.633522Z","iopub.status.idle":"2025-04-14T08:18:56.645855Z","shell.execute_reply.started":"2025-04-14T08:18:56.633486Z","shell.execute_reply":"2025-04-14T08:18:56.644968Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Tính mAP","metadata":{}},{"cell_type":"code","source":"def compute_map(pred_boxes, gt_boxes, iou_thresh=0.5):\n    aps = []\n    classes = set([b[1] for b in gt_boxes])\n    for cls in classes:\n        pred_cls = [b for b in pred_boxes if b[1] == cls]\n        gt_cls = [b for b in gt_boxes if b[1] == cls]\n        _, _, ap = calculate_precision_recall_ap(pred_cls, gt_cls, iou_thresh)\n        aps.append(ap)\n    return np.mean(aps)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-14T08:16:57.461111Z","iopub.execute_input":"2025-04-14T08:16:57.461431Z","iopub.status.idle":"2025-04-14T08:16:57.467362Z","shell.execute_reply.started":"2025-04-14T08:16:57.461401Z","shell.execute_reply":"2025-04-14T08:16:57.46652Z"}},"outputs":[],"execution_count":null}]}