{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Change Log\n**Version 1:** added EDA, augmentations, Faster-RCNN, icevision training mode + pytorch lightning, only positive examples<br>\n**Version 3:** added folds training, fixed class map object\n\n=================================================================\n\nThis kernel works in GPU mode"},{"metadata":{"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"!pip install icevision[all]\n!pip install matplotlib==3.1.3\n!pip install tqdm==4.45.0","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\nimport random\nimport cv2\nimport warnings\nimport torch\nimport numpy as np\nimport pandas as pd\n\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport matplotlib.ticker as ticker\n\nimport pytorch_lightning as pl\nfrom pytorch_lightning.loggers import CSVLogger\n\nfrom sklearn.model_selection import GroupKFold\nfrom icevision.all import *\n\nwarnings.filterwarnings('ignore')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"SEED = 2021\nDEBUG = False\nIMG_DIM = 512\nRESIZE_DIM = 384\nPRESIZE = 512\nBATCH_SIZE = 48\nNUM_WORKERS = 4\nN_FOLDS = 5\nFOLDS_IDS = [0]\nLR = 1e-5\nWDECAY = 1e-4\nNUM_EPOCHS = 60\n\ndef 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\n\nseed_everything(SEED)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Brief EDA"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"source = Path(\"../input/vinbigdata-512-png\")\n\ntrain_df = pd.read_csv(source / 'train.csv')\ntest_df = pd.read_csv(source / 'test.csv')\n\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f\"Number of samples in train df: {train_df.shape[0]}\")\nprint(f\"Number of samples in test df: {test_df.shape[0]}\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = train_df.fillna(0)\ntrain_df = train_df.astype({'x_min': 'int32', 'x_max': 'int32', 'y_min': 'int32', 'y_max': 'int32'})","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"ncount = len(train_df)\n\nplt.figure(figsize=(12,8))\nax = sns.countplot(x=\"class_name\", data=train_df, order = train_df[\"class_name\"].value_counts().index)\nplt.title('Distribution of class names')\n\nax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right')\n\nax2=ax.twinx()\n\nax2.yaxis.tick_left()\nax.yaxis.tick_right()\n\nax.yaxis.set_label_position('right')\nax2.yaxis.set_label_position('left')\n\nax2.set_ylabel('Frequency [%]')\n\nfor p in ax.patches:\n    x=p.get_bbox().get_points()[:,0]\n    y=p.get_bbox().get_points()[1,1]\n    ax.annotate('{:.1f}%'.format(100.*y/ncount), (x.mean(), y), \n            ha='center', va='bottom')\n\nax.yaxis.set_major_locator(ticker.LinearLocator(11))\n\nax2.set_ylim(0,100)\nax.set_ylim(0,ncount)\n\nax2.yaxis.set_major_locator(ticker.MultipleLocator(10))\n\nax2.grid(None);","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df['image_id'].value_counts()[:10]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df['image_id'].value_counts().mean()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def recalculate_coordinate(original_size, new_size, original_coordinate):\n  return int(original_coordinate / (original_size / new_size))\n\ntrain_df['x_min_new'] = train_df.apply(lambda x: recalculate_coordinate(x['width'], IMG_DIM, x['x_min']), axis=1)\ntrain_df['y_min_new'] = train_df.apply(lambda x: recalculate_coordinate(x['height'], IMG_DIM, x['y_min']), axis=1)\ntrain_df['x_max_new'] = train_df.apply(lambda x: recalculate_coordinate(x['width'], IMG_DIM, x['x_max']), axis=1)\ntrain_df['y_max_new'] = train_df.apply(lambda x: recalculate_coordinate(x['height'], IMG_DIM, x['y_max']), axis=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.loc[train_df['image_id'] == '051132a778e61a86eb147c7c6f564dfe']","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Good tutorial on images, bounding boxes and their augmentations visualisations:\n\n[Using Albumentations to augment bounding boxes for object detection tasks](https://albumentations.ai/docs/examples/example_bboxes/)"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def plot_original_images(img_id: str = '',\n                         df: pd.DataFrame = None):\n    image = cv2.imread(f'{\"../input/vinbigdata-512-png/train\"}/{img_id}.png')\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n    bboxes = df.loc[df['image_id'] == img_id, ['x_min_new',\t'y_min_new', 'x_max_new', 'y_max_new']].values\n    class_names = df.loc[df['image_id'] == img_id, ['class_name']].values\n    \n    fig, ax = plt.subplots(1, 1, figsize=(16, 10))\n    for box, class_name in zip(bboxes, class_names):\n        cv2.rectangle(image, (box[0], box[1]), (box[2], box[3]), (0, 0, 255), 1)\n        \n        ((text_width, text_height), _) = cv2.getTextSize(class_name[0], cv2.FONT_HERSHEY_SIMPLEX, 0.35, 1)    \n        cv2.rectangle(image, (box[0], box[1] - int(1.3 * text_height)), (box[0] + text_width, box[1]), (0, 0, 255), -1)\n        cv2.putText(\n            image,\n            text=class_name[0],\n            org=(box[0], box[1] - int(0.3 * text_height)),\n            fontFace=cv2.FONT_HERSHEY_SIMPLEX,\n            fontScale=0.35, \n            color=(255, 255, 255), \n            lineType=cv2.LINE_AA,\n        )\n    \n    ax.set_axis_off()\n    ax.imshow(image / 255)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for idx in train_df['image_id'].value_counts()[:5].index:\n    plot_original_images(idx, train_df)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Group k-Fold"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = train_df[train_df.class_id!=14].reset_index(drop = True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"if DEBUG:\n    train_df = train_df[:3000]\n    test_df = test_df[:3000]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"gkf  = GroupKFold(n_splits = N_FOLDS)\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()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Dataset class, augmentations visualization"},{"metadata":{"trusted":true},"cell_type":"code","source":"class VinBigData(parsers.Parser, parsers.FilepathMixin, parsers.LabelsMixin, parsers.BBoxesMixin):\n    pass\n\nVinBigData.generate_template()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class VinBigData(parsers.FasterRCNN, parsers.FilepathMixin, parsers.SizeMixin):\n    def __init__(self, df, source):\n        self.df = df\n        self.source = source\n\n    def __iter__(self):\n        yield from self.df.itertuples()\n\n    def __len__(self):\n        return len(self.df)\n\n    def imageid(self, o) -> Hashable:\n        return o.image_id\n\n    def filepath(self, o) -> Union[str, Path]:\n        return self.source / f\"{o.image_id}.png\"\n\n    def image_width_height(self, o) -> Tuple[int, int]:\n        return get_image_size(self.filepath(o))\n\n    def labels(self, o) -> List[int]:\n        return [o.class_id]\n\n    def bboxes(self, o) -> List[BBox]:\n        return [BBox.from_xyxy(*[o.x_min_new, o.y_min_new, o.x_max_new, o.y_max_new])]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"IDX_TO_CLASS = dict(sorted(list(zip(list(train_df['class_id'].unique()), list(train_df['class_name'].unique())))))\nIDX_TO_CLASS","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"ClassMap(list(IDX_TO_CLASS.values()), background=None)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class_map = ClassMap(list(IDX_TO_CLASS.values()), background=None)\n\nparser = VinBigData(train_df[:20], source / \"train\")\ntrain_rs, valid_rs = parser.parse()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_rs[:1]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"valid_rs[:1]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# no augmentations\nshow_records(train_rs[:3], ncols=3, class_map=class_map, figsize=(16, 8))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# imagenet normalization params are used\ntrain_tfms = tfms.A.Adapter([*tfms.A.aug_tfms(size=RESIZE_DIM, presize=PRESIZE), tfms.A.Normalize()])\nvalid_tfms = tfms.A.Adapter([*tfms.A.resize_and_pad(RESIZE_DIM), tfms.A.Normalize()])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_ds = Dataset(train_rs, train_tfms)\nvalid_ds = Dataset(valid_rs, valid_tfms)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"samples = [train_ds[2] for _ in range(6)]\nshow_samples(samples, ncols=3, class_map=class_map, display_label=True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Faster-RCNN"},{"metadata":{"trusted":true},"cell_type":"code","source":"class PL_Model(faster_rcnn.lightning.ModelAdapter):       \n    def configure_optimizers(self):\n        optimizer = torch.optim.AdamW(self.parameters(), lr=LR, weight_decay=WDECAY)\n        scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, \n                                                               factor=0.1, \n                                                               mode='min', \n                                                               patience=10)\n        \n#         scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, \n#                                                                12,\n#                                                                eta_min=0.01, \n#                                                                last_epoch=-1)\n        return [optimizer], [{\"scheduler\": scheduler,\n                              \"interval\": 'epoch',\n                              'monitor': 'val_loss'}]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"parser = VinBigData(train_df, source / \"train\")\n\nfor n_fold in FOLDS_IDS:\n    # preparing folds\n    train_fold = tuple(np.unique(train_df.loc[lambda train_df: train_df[\"fold\"] != n_fold]['image_id'].values))\n    val_fold = tuple(np.unique(train_df.loc[lambda train_df: train_df[\"fold\"] == n_fold]['image_id'].values))\n\n    presplits = [train_fold, val_fold]\n    data_splitter = FixedSplitter(presplits)\n    train_rs, valid_rs = parser.parse(data_splitter)\n\n    # dataset classes based on defined group split\n    train_ds = Dataset(train_rs, train_tfms)\n    valid_ds = Dataset(valid_rs, valid_tfms)\n    \n    # corresponding dataloaders\n    train_dl = faster_rcnn.train_dl(train_ds, batch_size=BATCH_SIZE, num_workers=NUM_WORKERS, shuffle=True)\n    valid_dl = faster_rcnn.valid_dl(valid_ds, batch_size=BATCH_SIZE, num_workers=NUM_WORKERS, shuffle=False)\n\n    metrics = [COCOMetric(metric_type=COCOMetricType.bbox)]\n    backbone = backbones.resnet_fpn.resnext50_32x4d(pretrained=True)\n    model = faster_rcnn.model(backbone=backbone, num_classes=len(class_map))\n    \n    # fast ai\n#     learn = faster_rcnn.fastai.learner(dls=[train_dl, valid_dl], model=model, metrics=metrics)\n#     learn.fine_tune(NUM_EPOCHS, lr=LR)\n\n    # pytorch lighting\n    pl_model = PL_Model(model, metrics=metrics)\n    \n    early_stopping = pl.callbacks.EarlyStopping(monitor='val_loss', patience=10)\n    model_checkpoint = pl.callbacks.ModelCheckpoint(filepath = './',\n                                                    verbose = True, \n                                                    save_top_k=1,\n                                                    monitor = 'val_loss',\n                                                    mode = 'min',\n                                                    prefix = f'frcnn-best-model-fold{n_fold}'\n                                                   )\n    \n    csv_logger = CSVLogger(\"./\", name=f'frcnn-best-model-fold{n_fold}')\n    \n    trainer = pl.Trainer(gpus = 1,\n                         logger=[csv_logger],\n                         log_every_n_steps=50,\n                         callbacks=[early_stopping, model_checkpoint],\n                         check_val_every_n_epoch=1,\n                         accumulate_grad_batches=2,\n                         distributed_backend='dp',\n                         gradient_clip_val=0.5,\n                         max_epochs=NUM_EPOCHS,\n                         num_sanity_val_steps=0,\n                         profiler=False,\n                         weights_summary=None)\n\n    trainer.fit(pl_model, train_dl, valid_dl)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"faster_rcnn.show_results(pl_model, valid_ds, class_map=class_map)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"logs = pd.read_csv(\"./frcnn-best-model-fold0/version_0/metrics.csv\")\nlogs.tail()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","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}