{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport cv2\nimport os, re\n\nimport torch\n\nimport torchvision\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor, FasterRCNN\nfrom torchvision.models.detection.backbone_utils import resnet_fpn_backbone\n\nfrom torch.utils.data import DataLoader, Dataset\n\nfrom matplotlib import pyplot as plt \nplt.rcParams['figure.figsize'] = (10.0, 10.0)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"DATA_DIR = \"/kaggle/input/global-wheat-detection\"\nDEVICE = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')\nON_CPU = DEVICE == torch.device('cpu')\nTRAIN_BATCH_SIZE = 4 if ON_CPU else 6\nVALID_BATCH_SIZE = 2 if ON_CPU else 2\nNUM_EPOCHS = 5 if ON_CPU else 17\nNEW_COLUMNS = ['x', 'y', 'w', 'h']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(os.path.join(DATA_DIR, \"train.csv\"))\ntest_df = pd.read_csv(os.path.join(DATA_DIR, \"sample_submission.csv\"))\n\nUNIQ_TRAIN_IMAGE_IDS = train_df[\"image_id\"].unique()\n\ntrain_df.shape, test_df.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# expand the bbox coordinates into x, y, w, h\ndef expand_bbox(x):\n    # also convert everything to np.float\n    r = np.array(re.findall(\"([0-9]+[.]?[0-9]*)\", x), dtype=np.float)\n    if len(r) == 0:\n        r = [-1, -1, -1, -1]\n    return r\n\n# initialize new columns with -1\nfor new_column in NEW_COLUMNS:\n    train_df[new_column] = -1\n\ntrain_df[NEW_COLUMNS] = np.stack(train_df['bbox'].apply(lambda x: expand_bbox(x)))\ntrain_df.drop(columns=['bbox'], inplace=True)\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_image(image_path):\n    image = cv2.imread(image_path, cv2.IMREAD_COLOR)\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    assert image is not None, f\"IMAGE NOT FOUND AT {image_path}\"\n    return image","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def draw_bboxes(boxes, image, color=(255,0,0)):\n    for box in boxes:\n        cv2.rectangle(\n            image,\n            (int(box[0]), int(box[1])),\n            (int(box[2]), int(box[3])),\n            color, 3\n        )\n    return image","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def plot_random_train_sample():\n    image_id = np.random.choice(UNIQ_TRAIN_IMAGE_IDS)\n    plt.title(image_id)\n    image = load_image(os.path.join(DATA_DIR, \"train\", f\"{image_id}.jpg\"))\n    bboxes = (train_df[train_df[\"image_id\"] == image_id][NEW_COLUMNS]).to_numpy()\n    bboxes[:, 2] = bboxes[:, 0] + bboxes[:, 2]\n    bboxes[:, 3] = bboxes[:, 1] + bboxes[:, 3]\n    plt.imshow(draw_bboxes(bboxes, image))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_random_train_sample()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class WheatDataset(Dataset):\n    def __init__(self, df, image_dir, transforms=None):\n        super().__init__()\n\n        self.df = df\n        self.image_dir = image_dir\n        self.transforms = transforms\n        self.image_ids = df['image_id'].unique()\n\n    def __len__(self) -> int:\n        return len(self.image_ids)\n\n    def __getitem__(self, idx: int):\n        image_id = self.image_ids[idx]\n        image = load_image(os.path.join(self.image_dir, f\"{image_id}.jpg\")).astype(np.float32)\n        image /= 255.0\n        # change the shape from [h,w,c] to [c,h,w]  \n        image = torch.from_numpy(image).permute(2,0,1)\n\n        records = self.df[self.df['image_id'] == image_id]\n\n        boxes = records[NEW_COLUMNS].values\n        area = boxes[:, 2] * boxes[:, 3]\n        area = torch.as_tensor(area, dtype=torch.float32)\n        # change the co-ordinates into expected [x, y, x+w, y+h] format\n        boxes[:, 2] = boxes[:, 0] + boxes[:, 2]\n        boxes[:, 3] = boxes[:, 1] + boxes[:, 3]\n        \n        boxes = torch.as_tensor(boxes, dtype=torch.float32)\n\n        # since all the boxes are wheat, it's all 1s\n        labels = torch.ones((boxes.shape[0],), dtype=torch.int64)\n        \n        # consider iscrowd false for all the boxes\n        iscrowd = torch.zeros((boxes.shape[0],), dtype=torch.int64)\n\n        target = {}\n        target[\"boxes\"] = boxes\n        target[\"labels\"] = labels\n        target[\"image_id\"] = torch.tensor([idx])\n        target[\"area\"] = area\n        target[\"iscrowd\"] = iscrowd\n\n        if self.transforms:\n            pass\n\n        return image, target","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_model():\n    \"\"\"\n    https://stackoverflow.com/questions/58362892/resnet-18-as-backbone-in-faster-r-cnn\n    \"\"\"\n    backbone = resnet_fpn_backbone('resnet152', pretrained=True)\n    model = FasterRCNN(backbone, num_classes=2)\n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# 12% into validation\nn_validation = int(0.13* len(UNIQ_TRAIN_IMAGE_IDS))\nvalid_ids = UNIQ_TRAIN_IMAGE_IDS[-n_validation:]\ntrain_ids = UNIQ_TRAIN_IMAGE_IDS[:-n_validation]\n\ndf_in_valid = train_df[train_df['image_id'].isin(valid_ids)]\ndf_in_train = train_df[train_df['image_id'].isin(train_ids)]\n\nprint(\"%i training samples\\n%i validation samples\" % (len(df_in_train[\"image_id\"].unique()), len(df_in_valid[\"image_id\"].unique())))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_dataset = WheatDataset(df_in_train, os.path.join(DATA_DIR, \"train\"))\nvalid_dataset = WheatDataset(df_in_valid, os.path.join(DATA_DIR, \"train\"))\n\n# since our single getitem returns image, targets. [shape of targets is different depending on the number of bounding boxes in the image] ?\ndef collate_fn(batch):\n    return tuple(zip(*batch))\n\ntrain_data_loader = DataLoader(\n    train_dataset,\n    batch_size=TRAIN_BATCH_SIZE,\n    shuffle=True,\n    num_workers=1,\n    collate_fn=collate_fn\n)\n\nvalid_data_loader = DataLoader(\n    valid_dataset,\n    batch_size=VALID_BATCH_SIZE,\n    shuffle=False,\n    num_workers=1,\n    collate_fn=collate_fn\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class 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","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Taken from https://github.com/Bjarten/early-stopping-pytorch\nclass EarlyStopping:\n    \"\"\"Early stops the training if validation loss doesn't improve after a given patience.\"\"\"\n    def __init__(self, patience=7, verbose=False, delta=0, path='checkpoint.pth', trace_func=print):\n        \"\"\"\n        Args:\n            patience (int): How long to wait after last time validation loss improved.\n                            Default: 7\n            verbose (bool): If True, prints a message for each validation loss improvement. \n                            Default: False\n            delta (float): Minimum change in the monitored quantity to qualify as an improvement.\n                            Default: 0\n            path (str): Path for the checkpoint to be saved to.\n                            Default: 'checkpoint.pt'\n            trace_func (function): trace print function.\n                            Default: print            \n        \"\"\"\n        self.patience = patience\n        self.verbose = verbose\n        self.counter = 0\n        self.best_score = None\n        self.early_stop = False\n        self.val_loss_min = np.Inf\n        self.delta = delta\n        self.path = path\n        self.epoch = 0\n        self.trace_func = trace_func\n    def __call__(self, val_loss, epoch, model):\n\n        score = -val_loss\n\n        if self.best_score is None:\n            self.best_score = score\n            self.epoch = epoch\n            self.save_checkpoint(val_loss, model)\n        elif score < self.best_score + self.delta:\n            self.counter += 1\n            self.trace_func(f'EarlyStopping counter: {self.counter} out of {self.patience}')\n            if self.counter >= self.patience:\n                self.early_stop = True\n        else:\n            self.best_score = score\n            self.epoch = epoch\n            self.save_checkpoint(val_loss, model)\n            self.counter = 0\n\n    def save_checkpoint(self, val_loss, model):\n        '''Saves model when validation loss decrease.'''\n        if self.verbose:\n            self.trace_func(f'Validation loss decreased ({self.val_loss_min:.6f} --> {val_loss:.6f}).  Saving model ...')\n        torch.save(model.state_dict(), self.path)\n        self.val_loss_min = val_loss","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = get_model()\nmodel.to(DEVICE)\noptimizer = torch.optim.SGD(model.parameters(), lr=0.005, momentum=0.9, weight_decay=0.0005)\nlr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\ntrain_losses = Averager()\ntotalTrainLosses = []\nval_losses = Averager()\ntotalValLosses = []\nlowest_val_loss = float('inf')\nearly_stopping = EarlyStopping(patience = 7, path = \"checkpoint.pth\", verbose=True)\n# has to be in train mode for both train and valid coz the outputs are different in two cases\nmodel.train()\nfor epoch in range(NUM_EPOCHS):\n    train_losses.reset()\n    val_losses.reset()\n    for batch_index, (images, targets) in enumerate(train_data_loader):\n        # move the images and targets to device\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        loss_dict = model(images, targets)\n        loss = sum(loss for loss in loss_dict.values())\n        # track the loss\n        train_losses.send(loss.item())\n\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        if batch_index % 50 == 0:\n            print(f\"Epoch: {epoch} Batch Index: {batch_index} Loss: {loss.item()}\")\n\n    # evaluate\n    with torch.no_grad():\n        for _, (images, targets) in enumerate(valid_data_loader):\n            # move the images and targets to device\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            val_loss_dict = model(images, targets)\n            val_loss = sum(loss for loss in val_loss_dict.values())\n\n            # track the loss\n            val_losses.send(val_loss.item())\n\n    if val_losses.value >= lowest_val_loss and lr_scheduler is not None:\n        lr_scheduler.step()\n\n    # print stats\n    print(f\"Epoch #{epoch} TRAIN LOSS: {train_losses.value} VALIDATION LOSS: {val_losses.value}\\n\")\n    totalTrainLosses.append(train_losses.value)\n    totalValLosses.append(val_losses.value)\n    early_stopping(val_losses.value, epoch, model)\n    if early_stopping.early_stop:\n        print(\"Early stopping!\")\n        break\n\nplt.plot(totalTrainLosses, label = \"Training Loss\", color = \"deepskyblue\")\nplt.plot(totalValLosses, label = \"Validation Loss\", color = \"darkorange\")\nplt.axvline(early_stopping.epoch, label = \"Early Stop\", color = \"red\")\nplt.legend()\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.title(\"Training Loss vs Validation Loss\")\nplt.show()\nos.rename(\"checkpoint.pth\", \"resnet152.pth\")\nprint(\"Training finished!\")","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}