{"cells":[{"metadata":{},"cell_type":"markdown","source":"# About this Notebook\n\n","execution_count":null},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\nimport numpy as np \nimport pandas as pd \nfrom datetime import datetime\nimport time\nimport random\nfrom tqdm.autonotebook import tqdm\nimport numba\n\n\nimport sys\nsys.path.append('../input/mean-average-precision/')\nimport mAP\n\n#Torch\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset,DataLoader\nfrom torch.utils.data.sampler import SequentialSampler, RandomSampler\n\n#torchvision\nimport torchvision\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom torchvision.models.detection import FasterRCNN\nfrom torchvision.models.detection.rpn import AnchorGenerator\n\n\n#sklearn\nfrom sklearn.model_selection import StratifiedKFold\n\n#CV\nimport cv2\n\n#Albumenatations\nimport albumentations as A\n\nfrom albumentations.pytorch.transforms import ToTensorV2\n\n#Glob\nfrom glob import glob","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Utils\n\n* AverageMeter - class for averaging loss,metric,etc over epochs","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"class AverageMeter(object):\n    \"\"\"Computes and stores the average and current value\"\"\"\n    def __init__(self):\n        self.reset()\n\n    def reset(self):\n        self.val = 0\n        self.avg = 0\n        self.sum = 0\n        self.count = 0\n\n    def update(self, val, n=1):\n        self.val = val\n        self.sum += val * n\n        self.count += n\n        self.avg = self.sum / self.count","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Configuration\n\nBasic configuration for this model","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"n_folds = 5\nseed = 2020\nnum_classes = 2\nBATCH_SIZE = 8\nLR = 0.002\nEPOCHS = 1","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# AS PER COMPETITION METRIC\niou_thresholds = numba.typed.List()\n\nfor x in [0.5, 0.55, 0.6, 0.65, 0.7, 0.75]:\n    iou_thresholds.append(x)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Seed Everything\n\nSeeding everything for reproducible results","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def seed_everything(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = True\nseed_everything(seed)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Preparing the Data\n\n* For preparation of data I use code from Alex's awesome kernel [here] (https://www.kaggle.com/shonenkov/training-efficientdet)\n* The data can be split into any number of folds as you want , split is stratified based on number of boxes and source","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"marking = pd.read_csv('../input/global-wheat-detection/train.csv')\n\nbboxs = np.stack(marking['bbox'].apply(lambda x: np.fromstring(x[1:-1], sep=',')))\nfor i, column in enumerate(['x', 'y', 'w', 'h']):\n    marking[column] = bboxs[:,i]\nmarking.drop(columns=['bbox'], inplace=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Creating Folds\nskf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=seed)\n\ndf_folds = marking[['image_id']].copy()\ndf_folds.loc[:, 'bbox_count'] = 1\ndf_folds = df_folds.groupby('image_id').count()\ndf_folds.loc[:, 'source'] = marking[['image_id', 'source']].groupby('image_id').min()['source']\ndf_folds.loc[:, 'stratify_group'] = np.char.add(\n    df_folds['source'].values.astype(str),\n    df_folds['bbox_count'].apply(lambda x: f'_{x // 15}').values.astype(str)\n)\ndf_folds.loc[:, 'fold'] = 0\n\nfor fold_number, (train_index, val_index) in enumerate(skf.split(X=df_folds.index, y=df_folds['stratify_group'])):\n    df_folds.loc[df_folds.iloc[val_index].index, 'fold'] = fold_number","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Augmentations","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_train_transforms():\n    return A.Compose(\n        [   A.OneOf([\n                A.HueSaturationValue(hue_shift_limit=0.2, sat_shift_limit=0.2,\n                                     val_shift_limit=0.2, p=0.3), \n                A.RandomBrightnessContrast(brightness_limit=0.2,  \n                                           contrast_limit=0.2, p=0.3),\n                A.RGBShift(r_shift_limit=20/255, g_shift_limit=20/255, b_shift_limit=10/255,p=0.3),\n            ], p=0.2),\n            A.OneOf([\n                A.RandomGamma(gamma_limit=(80, 120), p=0.3),\n                A.Blur(p=0.6),\n                A.GaussNoise(var_limit=(0.01, 0.05), mean=0, p=0.05),\n                A.ToGray(p=0.05)], p=0.2),\n\n            A.OneOf([\n                A.HorizontalFlip(p=1), \n                A.VerticalFlip(p=1),  \n                A.Transpose(p=1)                \n                ], p=1),         \n             A.RandomFog(fog_coef_lower=0.1, fog_coef_upper=0.2, p=0.05), \n             A.Resize(height=512, width=512, p=1),\n             A.Cutout(num_holes=random.randint(1, 6), max_h_size=64, max_w_size=64, fill_value=0, p=0.15),\n             ToTensorV2(p=5.0),\n             ],\n             \n        p=1.0, bbox_params=A.BboxParams(format='pascal_voc',min_area=0, min_visibility=0,label_fields=['labels'])\n    )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_valid_transforms():\n    return A.Compose([A.Resize(height=512, width=512, p=1.0),\n                      ToTensorV2(p=1.0)], \n                      p=1.0, \n                      bbox_params=A.BboxParams(format='pascal_voc',min_area=0, min_visibility=0,label_fields=['labels'])\n                      )","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Creating Dataset\n\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"DIR_TRAIN = '../input/global-wheat-detection/train'\n\nclass WheatDataset(Dataset):\n    def __init__(self,image_ids,dataframe,transforms=None):\n        self.image_ids = image_ids\n        self.df = dataframe\n        self.transforms = transforms\n        \n        \n    def __len__(self) -> int:\n        return self.image_ids.shape[0]\n    \n    def __getitem__(self,index):\n        image_id = self.image_ids[index]\n        records = self.df[self.df['image_id'] == image_id]\n        \n        image = cv2.imread(f'{DIR_TRAIN}/{image_id}.jpg', cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n        image /= 255.0\n        \n        # Faster RCN N takes in data in pascal format, so we change\n        boxes = records[['x', 'y', 'w', 'h']].values\n        boxes[:, 2] = boxes[:, 0] + boxes[:, 2]\n        boxes[:, 3] = boxes[:, 1] + boxes[:, 3]\n        \n        #Area of bb\n        area = (boxes[:, 3] - boxes[:, 1]) * (boxes[:, 2] - boxes[:, 0])\n        area = torch.as_tensor(area, dtype=torch.float32)\n        \n        # there is only one class: wheat \n        labels = torch.ones((boxes.shape[0],), 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['image_id'] = torch.tensor([index])\n        target['area'] = area\n        target['iscrowd'] = iscrowd\n        \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            #if this creates issues, use target['boxes'] = torch.as_tensor(sample['bboxes']) instead of below line\n            target['boxes'] = torch.stack(tuple(map(torch.tensor, zip(*sample['bboxes'])))).permute(1, 0)\n            target['boxes'] = target['boxes'].float()\n        \n        return image, target, image_id","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Metric","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def calculate_final_score(all_predictions, score_threshold,form):\n    final_scores = []\n    for i in range(len(all_predictions)):\n        gt_boxes = all_predictions[i]['gt_boxes'].copy()\n        pred_boxes = all_predictions[i]['pred_boxes'].copy()\n        scores = all_predictions[i]['scores'].copy()\n        image_id = all_predictions[i]['image_id']\n\n        indexes = np.where(scores>score_threshold)\n        pred_boxes = pred_boxes[indexes]\n        scores = scores[indexes]\n\n        image_precision = mAP.calculate_image_precision(gt_boxes, pred_boxes,thresholds=iou_thresholds,form=form)\n        final_scores.append(image_precision)\n\n    return np.mean(final_scores)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model\n\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_model():\n    model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True)\n    \n    in_features = model.roi_heads.box_predictor.cls_score.in_features\n\n    # replace the pre-trained head with a new one\n    model.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)\n    \n    return model","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Training Function","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def train_fn(data_loader,model,optimizer,device,scheduler,epoch):\n    model.train()\n    \n    summary_loss = AverageMeter()\n    \n    tk0 = tqdm(data_loader, total=len(data_loader))\n    \n    for step, (images, targets, image_ids) in enumerate(tk0):\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        losses = sum(loss for loss in loss_dict.values())\n        \n        optimizer.zero_grad()\n\n        losses.backward()\n        optimizer.step()\n        if scheduler is not None:\n            scheduler.step()\n        \n        summary_loss.update(losses.item(),BATCH_SIZE)\n        tk0.set_postfix(loss=summary_loss.avg)\n        \n    return summary_loss","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Eval Function","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def eval_fn(data_loader, model, device):\n    model.eval()\n    all_predictions = []\n    \n    with torch.no_grad():\n        \n        tk0 = tqdm(data_loader, total=len(data_loader))\n        for step, (images, targets, image_ids) in enumerate(tk0):\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            output = model(images)\n            \n            for i in range(len(images)):\n                boxes = output[i]['boxes'].detach().cpu().numpy()\n                scores = output[i]['scores'].detach().cpu().numpy()\n                \n                all_predictions.append({\n                    'pred_boxes': (boxes).astype(int),\n                    'scores': scores,\n                    'gt_boxes': (targets[i]['boxes'].cpu().numpy()).astype(int),\n                    'image_id': image_ids[i],\n                })\n                \n                \n    return all_predictions","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Engine","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def collate_fn(batch):\n    return tuple(zip(*batch))\n\ncv_score = []","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def run(fold):\n    \n    df_train = df_folds[df_folds['fold'] != fold]\n    df_valid = df_folds[df_folds['fold'] == fold]\n    \n    train_dataset = WheatDataset(\n    image_ids=df_train.index.values,\n    dataframe=marking,\n    transforms=get_train_transforms()\n    )\n\n    valid_dataset = WheatDataset(\n    image_ids=df_valid.index.values,\n    dataframe=marking,\n    transforms=get_valid_transforms()\n    )\n    \n    train_data_loader = DataLoader(\n    train_dataset,\n    batch_size=BATCH_SIZE,\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=BATCH_SIZE,\n    shuffle=False,\n    num_workers=4,\n    collate_fn=collate_fn\n    )\n    \n    device = torch.device('cuda')\n    model = get_model()\n    model = model.to(device)\n    \n    params = [p for p in model.parameters() if p.requires_grad]\n    optimizer = torch.optim.SGD(params, lr=LR, momentum=0.9, weight_decay=0.0007)\n    scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', \n                                                           factor=0.5, patience=5, verbose=True,\n                                                           threshold=0.0001, threshold_mode='rel',\n                                                           cooldown=0, min_lr=0, eps=1e-08)\n    \n    \n    best_map_score = 0\n\n    for epoch in range(EPOCHS):\n        train_loss = train_fn(train_data_loader, model, optimizer,device,scheduler=None,epoch=epoch)\n        prediction = eval_fn(valid_data_loader, model, device)\n        valid_map_score = calculate_final_score(prediction,0.4,form='pascal_voc')\n        \n        scheduler.step(valid_map_score)\n        \n        print('|EPOCH {}| TRAIN_LOSS {}| VALID_MAP_SCORE {}|'.format(epoch+1,train_loss.avg,valid_map_score))\n        if valid_map_score > best_map_score:\n            best_map_score = valid_map_score\n            print('Best model for fold {} found in Epoch {}'.format(fold,epoch+1))\n            torch.save(model.state_dict(), f'frcnn_best_{fold}.pth')\n        \n    cv_score.append(best_map_score)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"run(fold=0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#run(fold=1)89","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#run(fold=2)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#run(fold=3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#run(fold=4)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#run(fold=5)","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}