{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<p style='text-align: center;'><span style=\"color: #000508; font-family: Segoe UI; font-size: 2.5em; font-weight: 350;\"> [TRAIN] EfficientDet + augmentation + Stratified folding + mixed precision</span></p>\n\n<br><br>\n<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">Highlights</span><br>\n\n<p style='text-align: left;'><span style=\"color: #000508; font-family: Segoe UI; font-size: 1.3em;font-weight: 350;\"> 👋 Big thanks to: </span></p>\n\n\n>  [Alex Shonenkov for his great notebook](https://www.kaggle.com/shonenkov/training-efficientdet)<br>\n>  [Aleksandr for his help](https://www.kaggle.com/vodan37)<br>\n\n\n<br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.3em; font-weight: 400;\">I adopted his notebook to SIIM-FISABIO-RSNA COVID-19 competition and what is waiting for you in this notebook:</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">1. Step by step <b>EfficientDet</b> training pipeline (you can use this pipeline for another detection task).</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">2. Apex installation for mixed precision. I will tell about this great tool later.</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">3. Dividing data into stratified folds.</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">4. Good augmentation strategy.</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">5. EfficientDet models family for different detection tasks and GPU possibilities.</span><br>\n<br>\n\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">OK, Let's go! 🚀</span><br>","metadata":{}},{"cell_type":"markdown","source":"<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">Data preparation</span>\n\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">Before start we should prepare our data.</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">I prepared images by myself because It takes a long time - about 2 hours and no need to do it every time. I uploaded images to public dataset <b>simcov19eqhistorig</b>. Just check input part at the right corner.</span><br>\n\n\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.3em; font-weight: 350;\">Main features:</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">1. Convert from DCM to PNG</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">2. Non resized images (original size)</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">3. Added equalizing histogram in images (it gave better results)</span><br>\n<br>\n\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em; font-weight: 350;\">OK. It means there is nothing to do and go further.</span><br>","metadata":{}},{"cell_type":"markdown","source":"<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">Mixed precision</span>\n\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">You can read more about mixed precision here: </span>[Introducing native PyTorch automatic mixed precision for faster training on NVIDIA GPUs](https://pytorch.org/blog/accelerating-training-on-nvidia-gpus-with-pytorch-automatic-mixed-precision)\n\n<p style='text-align: left;'><span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">From official site:</span></p>\n\n>  Most deep learning frameworks, including PyTorch, train with 32-bit floating point (FP32) arithmetic by default. However this is not essential to achieve full accuracy for many deep learning models. In 2017, NVIDIA researchers developed a methodology for mixed-precision training, which combined single-precision (FP32) with half-precision (e.g. FP16) format when training a network, and achieved the same accuracy as FP32 training using the same hyperparameters, with additional performance benefits on NVIDIA GPUs: <br>1. Shorter training time;<br>2. Lower memory requirements, enabling larger batch sizes, larger models, or larger inputs.\n\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">And with mixed precision you can decrease GPU's usage and increase training speed without decreasing accuracy.<br>The GPU is very significant resourse for you if you have only Google Colab with 16Gb GPU as me.</span><br><br>\n\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">Using mixed precision with PyTorch is very simple. Let's install <b>Apex</b> library and later I will show how to integrate it into training process.</span><br><br>","metadata":{}},{"cell_type":"code","source":"# installing NVIDIA apex\n!git clone https://github.com/NVIDIA/apex\n!pip install -v --no-cache-dir --global-option=\"--cpp_ext\" --global-option=\"--cuda_ext\" ./apex","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import apex","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">Install dependencies</span>","metadata":{}},{"cell_type":"code","source":"!pip install albumentations==0.4.6\n!pip install effdet\n!pip install timm\n!pip install pycocotools","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# check what GPU we have\n!nvidia-smi","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import sys\nimport torch\nimport os\nfrom datetime import datetime\nimport time\nimport random\nimport cv2\nimport pandas as pd\nimport numpy as np\nimport albumentations as A\nimport matplotlib.pyplot as plt\nfrom albumentations.pytorch.transforms import ToTensorV2\nfrom sklearn.model_selection import StratifiedKFold\nfrom torch.utils.data import Dataset,DataLoader\nfrom torch.utils.data.sampler import SequentialSampler, RandomSampler\nfrom glob import glob\nfrom effdet import get_efficientdet_config, EfficientDet, DetBenchTrain\nfrom effdet.efficientdet import HeadNet\nfrom effdet import create_model\nfrom pathlib import Path\nfrom tqdm import tqdm\n\nSEED = 42\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)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">Main constants</span>","metadata":{}},{"cell_type":"code","source":"# image size that we will use during training. Image size should be divided by 128. \n# You can increase image size and it gives you better results but It requires more GPU resource. \n# This image size is good for this notebook with 16Gb GPU. \nimg_size = 768 \n\n# number of fold that we are going to choose for validation during the training\nfold_number = 0\n\n# format of our images\nimage_ext = 'png'\n\n# path to images directory\nTRAIN_ROOT_PATH = '/kaggle/input/siimcov19eqhistorig/train_eq_hist_orig_png'","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">Prepare labels</span>\n<br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">Prepare information about bboxes to appropriate format.</span><br><br>","metadata":{}},{"cell_type":"code","source":"def get_all_files_in_folder(folder, types):\n    files_grabbed = []\n    for t in types:\n        files_grabbed.extend(folder.rglob(t))\n    files_grabbed = sorted(files_grabbed, key=lambda x: x)\n    return files_grabbed\n\ndef prepare_csv_for_efdet(input_filepath, output_filepath):\n    train_image_df = pd.read_csv(input_filepath)\n    train_image_df['id'] = train_image_df['id'].str.split('_', expand=True)[0]\n\n    image_ids = train_image_df['id'].tolist()\n    labels_raw = train_image_df['label'].tolist()\n    boxes_raw = train_image_df['boxes'].tolist()\n\n    images = get_all_files_in_folder(Path('/kaggle/input/siimcov19eqhistorig/train_eq_hist_orig_png'), ['*.png'])\n\n    result = []\n    for image_path in tqdm(images, colour = '#00ff00'):\n        s = image_path.stem + ','\n\n        boxes = []\n        for image_id, label in zip(image_ids, labels_raw):\n            if image_id == image_path.stem:\n\n                label_split = label.split('opacity')\n                if len(label_split) > 1:\n\n                    for l in label_split:\n                        if l != '':\n                            box = l.split(' ')\n                            x1 = (float(box[2]))\n                            if x1 < 0: x1 = 0.0\n                            y1 = (float(box[3]))\n                            if y1 < 0: y1 = 0.0\n                            x2 = (float(box[4]))\n                            y2 = (float(box[5]))\n\n                            boxes.append([x1, y1, x2, y2])\n\n        boxes_str = ''\n        if len(boxes):\n            for box in boxes:\n                boxes_str += str(box[0]) + ' ' + str(box[1]) + ' ' + str(box[2]) + ' ' + str(box[3]) + ';'\n\n            s += boxes_str[:-1]\n        else:\n            boxes_str = 'no_box'\n            s += boxes_str\n\n        s += ',0'\n        result.append(s)\n\n    with open(output_filepath, 'w') as f:\n        f.write('image_name,BoxesString,domain\\n')\n        for item in result:\n            f.write(\"%s\\n\" % item)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# extract all labels and save them to CSV file\ninput_filepath = '/kaggle/input/siim-covid19-detection/train_image_level.csv'\noutput_filepath = '/kaggle/working/images_train.csv'\nprepare_csv_for_efdet(input_filepath, output_filepath)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# read our new CSV\ndf = pd.read_csv('/kaggle/working/images_train.csv')\n\n# remove samples without bboxes\ndf.drop(df[df.BoxesString == 'no_box'].index, inplace=True)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# divide on folds\nskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)\ndf_folds = df[['image_name']].copy()\nfor fold_number, (train_index, val_index) in enumerate(skf.split(X=df.image_name, y=df['domain'])):\n    df_folds.loc[df_folds.iloc[val_index].index, 'fold'] = fold_number","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">Augmentations</span>\n<br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">I used original augmentation of Alex Shonenkov with several changes:</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">1. Delete random crops because we have images with different resolutions and it can lead to bboxes on the edge of image or cutted bboxes.</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">2. Delete Cutout augmentation because this augmentation is good for a lot of bboxes in one sample and for finding bboxes on the edge of image. Our dataset has only bboxes in the center of image and only 2 bboxes on image.</span><br><br>","metadata":{}},{"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.9),\n                            A.RandomBrightnessContrast(brightness_limit=0.2, \n                                                    contrast_limit=0.2, p=0.9),\n                        ],p=0.9),\n                        A.ToGray(p=0.01),\n                        A.HorizontalFlip(p=0.5),\n                        A.VerticalFlip(p=0.5),\n                        A.RandomRotate90(p=0.5),\n                        A.Transpose(p=0.5),\n                        A.ImageCompression(quality_lower=85, quality_upper=95, p=0.5),\n                        A.OneOf([\n                            A.Blur(blur_limit=3, p=1.0), \n                            A.MedianBlur(blur_limit=3, p=1.0),\n                            A.MotionBlur(p=1)], p=0.5),\n                        A.Resize(height=img_size, width=img_size, p=1),\n                        # A.Cutout(num_holes=8, max_h_size=64, max_w_size=64, fill_value=0, p=0.5), \n                        ToTensorV2(p=1.0), \n                        ],\n                        p=1.0,\n                        bbox_params=A.BboxParams(\n                                                format=\"pascal_voc\",\n                                                min_area=0, \n                                                min_visibility=0,\n                                                label_fields=['labels']\n                    )\n    )\n\n\ndef get_valid_transforms():\n    return A.Compose(\n        [\n            A.Resize(height=img_size, width=img_size, p=1.0),\n            ToTensorV2(p=1.0),\n        ], \n        p=1.0, \n        bbox_params=A.BboxParams(\n            format='pascal_voc',\n            min_area=0, \n            min_visibility=0,\n            label_fields=['labels']\n        )\n    )","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">Dataset</span>\n<br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">Alex added great custom cutmix augmentation but I trained without it. I think this augmentation gives worse results with this dataset. You can check it 🙂</span><br>","metadata":{}},{"cell_type":"code","source":"class DatasetRetriever(Dataset):\n\n    def __init__(self, marking, image_ids, transforms=None, test=False):\n        super().__init__()\n\n        self.image_ids = image_ids\n        self.marking = marking\n        self.transforms = transforms\n        self.test = test\n\n    def __getitem__(self, index: int):\n        index = self.image_ids[index]\n        image_name = self.marking.loc[index]['image_name']\n\n        # if self.test or random.random() > 0.3:\n        image, boxes = self.load_image_and_boxes(index)\n        # else:\n            # image, boxes = self.load_cutmix_image_and_boxes(index)\n\n\n        # there is only one class\n        labels = torch.ones((boxes.shape[0],), dtype=torch.int64)\n        target = {'boxes': boxes, 'labels': labels, 'image_id': torch.tensor([index])}\n        \n        if self.transforms:\n            for i in range(10):\n                sample = self.transforms(**{\n                    'image': image,\n                    'bboxes': target['boxes'],\n                    'labels': labels\n                })\n                if len(sample['bboxes']) > 0:\n                    image = sample['image']\n                    target['boxes'] = torch.stack(tuple(map(torch.tensor, zip(*sample['bboxes'])))).permute(1, 0)\n                    target['boxes'][:, [0, 1, 2, 3]] = target['boxes'][:, [1, 0, 3, 2]]  # yxyx: be warning\n                    target['labels'] = target['labels'][:len(target['boxes'])]\n                    break\n\n        return image, target, image_name\n\n    def __len__(self) -> int:\n        return self.image_ids.shape[0]\n\n    def load_image_and_boxes(self, index):\n        image_name = self.marking['image_name'][index]\n        image = cv2.imread(TRAIN_ROOT_PATH+'/'+image_name + '.' + image_ext, cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n        image /= 255.0\n        row = self.marking.loc[index]\n\n        bboxes = []\n        if row['BoxesString'] != 'no_box':\n            for bbox in row['BoxesString'].split(';'):\n                bboxes.append(list(map(float, bbox.split(' '))))\n        return image, np.array(bboxes)\n\n#     def load_cutmix_image_and_boxes(self, index, imsize=1024):\n#         \"\"\"\n#         This implementation of cutmix author:  https://www.kaggle.com/nvnnghia\n#         Refactoring and adaptation: https://www.kaggle.com/shonenkov\n#         \"\"\"\n#         w, h = imsize, imsize\n#         s = imsize // 2\n\n#         xc, yc = [int(random.uniform(imsize * 0.25, imsize * 0.75)) for _ in range(2)]  # center x, y\n#         indexes = [index] + [random.randint(0, self.image_ids.shape[0] - 1) for _ in range(3)]\n\n#         result_image = np.full((imsize, imsize, 3), 1, dtype=np.float32)\n#         result_boxes = []\n\n#         for i, index in enumerate(indexes):\n#             image, boxes = self.load_image_and_boxes(index)\n#             if i == 0:\n#                 x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc  # xmin, ymin, xmax, ymax (large image)\n#                 x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h  # xmin, ymin, xmax, ymax (small image)\n#             elif i == 1:  # top right\n#                 x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc\n#                 x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h\n#             elif i == 2:  # bottom left\n#                 x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)\n#                 x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, max(xc, w), min(y2a - y1a, h)\n#             elif i == 3:  # bottom right\n#                 x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)\n#                 x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)\n#             result_image[y1a:y2a, x1a:x2a] = image[y1b:y2b, x1b:x2b]\n#             padw = x1a - x1b\n#             padh = y1a - y1b\n\n#             boxes[:, 0] += padw\n#             boxes[:, 1] += padh\n#             boxes[:, 2] += padw\n#             boxes[:, 3] += padh\n\n#             result_boxes.append(boxes)\n\n#         result_boxes = np.concatenate(result_boxes, 0)\n#         np.clip(result_boxes[:, 0:], 0, 2 * s, out=result_boxes[:, 0:])\n#         result_boxes = result_boxes.astype(np.int32)\n#         result_boxes = result_boxes[\n#             np.where((result_boxes[:, 2] - result_boxes[:, 0]) * (result_boxes[:, 3] - result_boxes[:, 1]) > 0)]\n#         return result_image, result_boxes","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# create datasets using fold_number\ntrain_dataset = DatasetRetriever(\n    image_ids=df_folds[df_folds['fold'] != fold_number].index.values,\n    marking=df,\n    transforms=get_train_transforms(),\n    test=False,\n)\n\nvalidation_dataset = DatasetRetriever(\n    image_ids=df_folds[df_folds['fold'] == fold_number].index.values,\n    marking=df,\n    transforms=get_valid_transforms(),\n    test=True,\n)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Let's look at one sample\n\nimage, target, image_id = train_dataset[1]\n\nboxes = target['boxes'].cpu().numpy().astype(np.int32)\nlabels = target['labels'].cpu().numpy().astype(np.int32)\n\nnumpy_image = image.permute(1,2,0).cpu().numpy()\nnumpy_image_box = numpy_image.copy()\nfig, ax = plt.subplots(1, 1, figsize=(16, 8))\n\nfor box in boxes:\n    cv2.rectangle(numpy_image_box, (int(box[1]), int(box[0])), (int(box[3]),  int(box[2])), (0, 1, 0), 2)\n\nax.set_axis_off()\nax.imshow(numpy_image_box)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">Fitter</span>\n<br>","metadata":{}},{"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","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">Training</span>\n<br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">Below there is the whole training pipeline. Notice that I added #apex comment near parts where we use mixed precision</span><br>","metadata":{}},{"cell_type":"code","source":"import warnings\n\nwarnings.filterwarnings(\"ignore\")\n\nclass Fitter:\n    \n    def __init__(self, model, device, config):\n        self.config = config\n        self.epoch = 0\n\n        self.base_dir = f'./{config.folder}'\n        if not os.path.exists(self.base_dir):\n            os.makedirs(self.base_dir)\n        \n        self.log_path = f'{self.base_dir}/log.txt'\n        self.best_summary_loss = 10**5\n\n        self.model = model\n        self.device = device\n\n        param_optimizer = list(self.model.named_parameters())\n        no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight']\n        optimizer_grouped_parameters = [\n            {'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)], 'weight_decay': 0.001},\n            {'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)], 'weight_decay': 0.0}\n        ] \n\n        self.optimizer = torch.optim.AdamW(self.model.parameters(), lr=config.lr)\n        self.scheduler = config.SchedulerClass(self.optimizer, **config.scheduler_params)\n        self.log(f'Fitter prepared. Device is {self.device}')\n\n    def fit(self, train_loader, validation_loader):\n        for e in range(self.config.n_epochs):\n            if self.config.verbose:\n                lr = self.optimizer.param_groups[0]['lr']\n                timestamp = datetime.utcnow().isoformat()\n                self.log(f'\\n{timestamp}\\nLR: {lr}')\n\n            t = time.time()\n            summary_loss = self.train_one_epoch(train_loader)\n            self.log(f'[RESULT]: Train. Epoch: {self.epoch}, summary_loss: {summary_loss.avg:.5f}, time: {(time.time() - t):.5f}')\n            self.save(f'{self.base_dir}/last-checkpoint.bin')\n\n            t = time.time()\n            summary_loss = self.validation(validation_loader)\n            self.log(f'[RESULT]: Val. Epoch: {self.epoch}, summary_loss: {summary_loss.avg:.5f}, time: {(time.time() - t):.5f}')\n            if summary_loss.avg < self.best_summary_loss:\n                self.best_summary_loss = summary_loss.avg\n                self.model.eval()\n                self.save(f'{self.base_dir}/best-checkpoint-{str(self.epoch).zfill(3)}epoch.bin')\n                for path in sorted(glob(f'{self.base_dir}/best-checkpoint-*epoch.bin'))[:-3]:\n                    os.remove(path)\n\n            if self.config.validation_scheduler:\n                self.scheduler.step(metrics=summary_loss.avg)\n\n            self.epoch += 1\n\n    def validation(self, val_loader):\n        self.model.eval()\n        summary_loss = AverageMeter()\n        t = time.time()\n        for step, (images, targets, image_ids) in enumerate(val_loader):\n            # if self.config.verbose:\n            #     if step % self.config.verbose_step == 0:\n            #         print(\n            #             f'Val Step {step}/{len(val_loader)}, ' + \\\n            #             f'summary_loss: {summary_loss.avg:.5f}, ' + \\\n            #             f'time: {(time.time() - t):.5f}', end='\\r'\n            #         )\n\n            print(\n                f'Val Step {step}/{len(val_loader)}, ' + \\\n                f'summary_loss: {summary_loss.avg:.5f}, ' + \\\n                f'time: {(time.time() - t):.5f}', end='\\r'\n            )\n            with torch.no_grad():\n                images = torch.stack(images)\n                batch_size = images.shape[0]\n                images = images.to(self.device).float()\n                boxes = [target['boxes'].to(self.device).float() for target in targets]\n                labels = [target['labels'].to(self.device).float() for target in targets]\n\n                target_res = {}\n                target_res['bbox'] = boxes\n                target_res['cls'] = labels \n                target_res[\"img_scale\"] = torch.tensor([1.0] * batch_size, dtype=torch.float).to(self.device)\n                target_res[\"img_size\"] = torch.tensor([images[0].shape[-2:]] * batch_size, dtype=torch.float).to(self.device)\n\n                outputs = self.model(images, target_res)\n                loss = outputs['loss']\n                summary_loss.update(loss.detach().item(), batch_size)\n\n        return summary_loss\n\n    def train_one_epoch(self, train_loader):\n        self.model.train()\n        summary_loss = AverageMeter()\n        t = time.time()\n        \n        #apex\n        scaler = torch.cuda.amp.GradScaler()\n\n\n        for step, (images, targets, image_ids) in enumerate(train_loader):\n            # if self.config.verbose:\n            #     if step % self.config.verbose_step == 0:\n            #         print(\n            #             f'Train Step {step}/{len(train_loader)}, ' + \\\n            #             f'summary_loss: {summary_loss.avg:.5f}, ' + \\\n            #             f'time: {(time.time() - t):.5f}', end='\\r'\n            #         )\n\n            print(\n                f'Train Step {step}/{len(train_loader)}, ' + \\\n                f'summary_loss: {summary_loss.avg:.5f}, ' + \\\n                f'time: {(time.time() - t):.5f}', end='\\r'\n            )\n            \n            images = torch.stack(images)\n            images = images.to(self.device).float()\n            batch_size = images.shape[0]\n            \n            target_res = {}\n\n            boxes = [target['boxes'].to(self.device).float() for target in targets]\n            labels = [target['labels'].to(self.device).float() for target in targets]\n\n            target_res['bbox'] = boxes\n            target_res['cls'] = labels \n\n            \n            self.optimizer.zero_grad()\n\n            #apex\n            with torch.cuda.amp.autocast():\n                outputs = self.model(images, target_res)\n            \n            loss = outputs['loss']\n\n            #apex\n            scaler.scale(loss).backward()\n\n            summary_loss.update(loss.detach().item(), batch_size)\n\n            #apex\n            scaler.step(self.optimizer)\n\n            if self.config.step_scheduler:\n                self.scheduler.step()\n\n            #apex\n            scaler.update()\n\n        return summary_loss\n    \n    def save(self, path):\n        self.model.eval()\n        torch.save({\n            'model_state_dict': self.model.model.state_dict(),\n            'optimizer_state_dict': self.optimizer.state_dict(),\n            'scheduler_state_dict': self.scheduler.state_dict(),\n            'best_summary_loss': self.best_summary_loss,\n            'epoch': self.epoch,\n        }, path)\n\n    def load(self, path):\n        checkpoint = torch.load(path)\n        self.model.model.load_state_dict(checkpoint['model_state_dict'])\n        self.optimizer.load_state_dict(checkpoint['optimizer_state_dict'])\n        self.scheduler.load_state_dict(checkpoint['scheduler_state_dict'])\n        self.best_summary_loss = checkpoint['best_summary_loss']\n        self.epoch = checkpoint['epoch'] + 1\n        \n    def log(self, message):\n        if self.config.verbose:\n            print(message)\n        with open(self.log_path, 'a+') as logger:\n            logger.write(f'{message}\\n')","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class TrainGlobalConfig:\n    num_workers = 2\n    batch_size = 2\n    n_epochs = 1\n    lr = 0.0002\n\n    # folder where we are going to save weights\n    folder = '/output/fold0'\n\n    verbose = True\n    verbose_step = 1\n    \n    step_scheduler = False  # do scheduler.step after optimizer.step\n    validation_scheduler = True  # do scheduler.step after validation stage loss\n\n#     SchedulerClass = torch.optim.lr_scheduler.OneCycleLR\n#     scheduler_params = dict(\n#         max_lr=0.001,\n#         epochs=n_epochs,\n#         steps_per_epoch=int(len(train_dataset) / batch_size),\n#         pct_start=0.1,\n#         anneal_strategy='cos', \n#         final_div_factor=10**5\n#     )\n    \n    SchedulerClass = torch.optim.lr_scheduler.ReduceLROnPlateau\n    scheduler_params = dict(\n        mode='min',\n        factor=0.5,\n        patience=5,\n        verbose=True, \n        threshold=0.0001,\n        threshold_mode='abs',\n        cooldown=0, \n        min_lr=1e-8,\n        eps=1e-08\n    )","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def collate_fn(batch):\n    return tuple(zip(*batch))\n\ndef run_training():\n    device = torch.device('cuda:0')\n    net.to(device)\n\n    train_loader = torch.utils.data.DataLoader(\n        train_dataset,\n        batch_size=TrainGlobalConfig.batch_size,\n        sampler=RandomSampler(train_dataset),\n        pin_memory=False,\n        drop_last=False,\n        num_workers=TrainGlobalConfig.num_workers,\n        collate_fn=collate_fn,\n    )\n    val_loader = torch.utils.data.DataLoader(\n        validation_dataset, \n        batch_size=TrainGlobalConfig.batch_size,\n        num_workers=TrainGlobalConfig.num_workers,\n        shuffle=False,\n        sampler=SequentialSampler(validation_dataset),\n        pin_memory=False,\n        collate_fn=collate_fn,\n    )\n\n    fitter = Fitter(model=net, device=device, config=TrainGlobalConfig)\n    \n    # Attention! Add this line if you want to continue training with your weights\n    # fitter.load('/content/gdrive/MyDrive/datasets/sber_food/efficientdet/fold0/last-checkpoint.bin')\n    fitter.fit(train_loader, val_loader)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">Model</span>\n<br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">I choose tf_efficientdet_d6 because It's max model that can fit in 16Gb GPU. If you have more GPU you can use tf_efficientdet_d7. I uploaded pretrained weights for all models.</span><br>","metadata":{}},{"cell_type":"code","source":"from effdet import create_model_from_config, get_efficientdet_config\ndevice = 'cuda'\ndef get_net():\n    config = get_efficientdet_config('tf_efficientdet_d6')\n\n    config.image_size = [img_size,img_size]\n    config.norm_kwargs=dict(eps=.001, momentum=.01)\n\n    net = EfficientDet(config, pretrained_backbone=False)\n    checkpoint = torch.load('/kaggle/input/efficientdet-init-weights/efficientdet_d6-51cb0132.pth')\n    net.load_state_dict(checkpoint)\n\n    # we have only one class - opacity\n    net.reset_head(num_classes=1)\n    net.class_net = HeadNet(config, num_outputs=config.num_classes)\n\n    return DetBenchTrain(net, config)\n\nnet = get_net()\nnet.to(device)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# start training\nrun_training()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<span style=\"color: #027fc1; font-family: Segoe UI; font-size: 1.9em; font-weight: 350;\">How to improve:</span>\n<br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">1. Increase image size</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">2. Play with augmentation and try to add cutmix </span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">3. Choose stronger model, for example tf_efficientdet_d7</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">4. Increase batch size</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">5. Experiment with hyperparameters</span><br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">6. Train 5 folds and make ensemble</span><br>\n<br>\n<span style=\"color: #000508; font-family: Segoe UI; font-size: 1.2em;\">Thank you and Good luck! 😃</span><br>","metadata":{}},{"cell_type":"markdown","source":"<center><img border=\"0\" alt=\"Ask Me Something\" src=\"https://img.shields.io/badge/Please-Upvote%20If%20you%20like%20this-07b3c8?style=for-the-badge&logo=kaggle\" width=\"260\" height=\"20\"></center>","metadata":{}}]}