{"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":"code","source":"!pip install --upgrade pip\n!pip install '../input/timm-package/timm-0.1.26-py3-none-any.whl'\n!pip install pycocotools\n!mkdir ../working/efficientdet\n!cp ../input/efficientdet/efficientdet_d7-f05bf714.pth ../working/efficientdet/efficientdet_d7-f05bf714.pth","metadata":{"execution":{"iopub.status.busy":"2023-05-11T06:18:04.846254Z","iopub.execute_input":"2023-05-11T06:18:04.846609Z","iopub.status.idle":"2023-05-11T06:18:48.304351Z","shell.execute_reply.started":"2023-05-11T06:18:04.846575Z","shell.execute_reply":"2023-05-11T06:18:48.303361Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"execution":{"iopub.status.busy":"2023-05-11T06:21:26.194646Z","iopub.execute_input":"2023-05-11T06:21:26.195020Z","iopub.status.idle":"2023-05-11T06:21:29.214072Z","shell.execute_reply.started":"2023-05-11T06:21:26.194986Z","shell.execute_reply":"2023-05-11T06:21:29.212819Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"DATA_DIR = '../input/global-wheat-detection'\nTRAIN_ROOT_PATH = DATA_DIR + \"/train\"\nSEED = 42\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","metadata":{"execution":{"iopub.status.busy":"2023-05-11T06:18:48.306748Z","iopub.execute_input":"2023-05-11T06:18:48.307115Z","iopub.status.idle":"2023-05-11T06:18:48.317327Z","shell.execute_reply.started":"2023-05-11T06:18:48.307076Z","shell.execute_reply":"2023-05-11T06:18:48.315071Z"},"trusted":true},"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\nfrom albumentations.pytorch.transforms import ToTensorV2\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.utils.data.sampler import SequentialSampler, RandomSampler\nfrom glob import glob\nfrom sklearn.model_selection import StratifiedKFold\n\nsys.path.insert(0, \"../input/timm-efficientdet-pytorch\")\nsys.path.insert(0, \"../input/omegaconf\")\n\n# Albumentations\ndef get_train_transforms():\n    return A.Compose(\n        [\n            A.Resize(height=512, width=512, p=1),\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# transform for validation set\ndef get_valid_transforms():\n    return A.Compose(\n        [\n            A.Resize(height=512, width=512, 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    )\n\nclass DatasetRetriever(Dataset):\n    def __init__(self, marking, image_ids, transforms=None, test=False):\n        super().__init__()\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        image_id = self.image_ids[index]\n        # for half of the image, we perform a cutmix\n        image, boxes = self.load_image_and_boxes(index)\n\n\n        # there is only one class\n        labels = torch.ones((boxes.shape[0],), dtype=torch.int64)\n\n        target = {}\n        target['boxes'] = boxes\n        target['labels'] = labels\n        target['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                    break\n\n        return image, target, image_id\n\n    def __len__(self) -> int:\n        return self.image_ids.shape[0]\n\n    def load_image_and_boxes(self, index):\n        image_id = self.image_ids[index]\n        image = cv2.imread(f'{TRAIN_ROOT_PATH}/{image_id}.jpg', cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n        image /= 255.0\n        records = self.marking[self.marking['image_id'] == image_id]\n        boxes = records[['x', 'y', 'w', 'h']].values\n        boxes[:, 2] = boxes[:, 0] + boxes[:, 2]\n        boxes[:, 3] = boxes[:, 1] + boxes[:, 3]\n        return image, boxes\n\n    # cutmix augmentation, see description\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\n\n# Statistics\nclass 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\n\n# Fitter\nclass Fitter:\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 ** 9\n\n        self.train_his = pd.DataFrame(columns=['train_loss', 'val_loss'])\n\n        self.model = model\n        self.device = device\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            train_loss_avg = summary_loss.avg\n\n            self.log(\n                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            val_loss_avg = summary_loss.avg\n            self.log(\n                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.train_his = self.train_his.append(\n                {'epoch': self.epoch, 'train_loss': train_loss_avg, 'val_loss': val_loss_avg}, ignore_index=True)\n            self.train_his.to_csv('train_his.csv', mode='a')\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            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                loss, _, _ = self.model(images, boxes, labels)\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        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            images = torch.stack(images)\n            images = images.to(self.device).float()\n            batch_size = images.shape[0]\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            self.optimizer.zero_grad()\n\n            loss, _, _ = self.model(images, boxes, labels)\n\n            loss.backward()\n\n            summary_loss.update(loss.detach().item(), batch_size)\n\n            self.optimizer.step()\n\n            if self.config.step_scheduler:\n                self.scheduler.step()\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')\n\n\nclass TrainGlobalConfig:\n    folder = '../working/efficientdet'\n\n    num_workers = 2\n    batch_size = 4\n    n_epochs = 50\n    lr = 0.01\n\n    \n    verbose = True\n    verbose_step = 1\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.ReduceLROnPlateau\n    scheduler_params = dict(\n        mode='min',\n        factor=0.5,\n        patience=1,\n        verbose=False,\n        threshold=0.0001,\n        threshold_mode='abs',\n        cooldown=0,\n        min_lr=1e-8,\n        eps=1e-08\n    )\n\ndef collate_fn(batch):\n    return tuple(zip(*batch))\n\nfrom effdet import get_efficientdet_config, EfficientDet, DetBenchTrain\nfrom effdet.efficientdet import HeadNet\n\n\ndef get_net():\n    config = get_efficientdet_config('tf_efficientdet_d7')\n    net = EfficientDet(config, pretrained_backbone=False)\n    if not os.path.isdir(TrainGlobalConfig.folder):\n        os.mkdir(TrainGlobalConfig.folder)\n    checkpoint_path = TrainGlobalConfig.folder + \"/efficientdet_d7-f05bf714.pth\"\n    assert os.path.isfile(checkpoint_path)\n    checkpoint = torch.load(checkpoint_path)\n    net.load_state_dict(checkpoint)\n    config.num_classes = 1\n    config.image_size = 512\n    net.class_net = HeadNet(config, num_outputs=config.num_classes, norm_kwargs=dict(eps=.001, momentum=.01))\n    return DetBenchTrain(net, config)\n\ndef train_efficientdet(marking, df_folds, fold):\n    # prepare dataset and dataloader\n    train_dataset = DatasetRetriever(\n        image_ids=df_folds[df_folds['fold'] != fold].index.values,\n        marking=marking,\n        transforms=get_train_transforms(),\n        test=False,\n    )\n    validation_dataset = DatasetRetriever(\n        image_ids=df_folds[df_folds['fold'] == fold].index.values,\n        marking=marking,\n        transforms=get_valid_transforms(),\n        test=True,\n    )\n    train_loader = DataLoader(\n        train_dataset,\n        batch_size=TrainGlobalConfig.batch_size,\n        sampler=RandomSampler(train_dataset),\n        pin_memory=False,\n        drop_last=True,\n        num_workers=TrainGlobalConfig.num_workers,\n        collate_fn=collate_fn,\n    )\n    val_loader = 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# prepare for models and devices\n    # find available devices\n    device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n    # get a fresh model\n    net = get_net()\n    net.to(device)\n# start training\n    fitter = Fitter(model=net, device=device, config=TrainGlobalConfig)\n    fitter.fit(train_loader, val_loader)\n","metadata":{"execution":{"iopub.status.busy":"2023-05-11T06:18:48.319966Z","iopub.execute_input":"2023-05-11T06:18:48.320552Z","iopub.status.idle":"2023-05-11T06:18:51.758926Z","shell.execute_reply.started":"2023-05-11T06:18:48.320484Z","shell.execute_reply":"2023-05-11T06:18:51.758105Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"seed_everything(SEED)\n# set fold for validation\nFOLD = 0\n# read dataset metadata\nmarking = pd.read_csv(DATA_DIR + '/train.csv')\n# convert string to int list\nbboxs = np.stack(marking['bbox'].apply(lambda x: np.fromstring(x[1:-1], sep=',')))\n# reformat the target bounding boxes for yolo format, F-RCNN format...\nfor i, column in enumerate(['x', 'y', 'w', 'h']):\n    marking[column] = bboxs[:, i]\nmarking.drop(columns=['bbox'], inplace=True)\nmarking['x_center'] = marking['x'] + marking['w']/2\nmarking['y_center'] = marking['y'] + marking['h']/2\n# single class in this dataset\nmarking['classes'] = 0\n\n# group data uniformly\nskf = StratifiedKFold(n_splits=5, shuffle=True, random_state=SEED)\ndf_folds = marking[['image_id']].copy()\ndf_folds.loc[:, 'bbox_count'] = 1\ndf_folds = df_folds.groupby('image_id').count() # calculate the target frequency for each image\ndf_folds.loc[:, 'source'] = marking[['image_id', 'source']].groupby('image_id').min()['source'] # group by 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)\n# assign fold number\ndf_folds.loc[:, 'fold'] = 0\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\n","metadata":{"execution":{"iopub.status.busy":"2023-05-11T06:18:51.762595Z","iopub.execute_input":"2023-05-11T06:18:51.762908Z","iopub.status.idle":"2023-05-11T06:18:53.236847Z","shell.execute_reply.started":"2023-05-11T06:18:51.762878Z","shell.execute_reply":"2023-05-11T06:18:53.235857Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_efficientdet(marking, df_folds, FOLD)","metadata":{"execution":{"iopub.status.busy":"2023-05-11T06:18:53.239164Z","iopub.execute_input":"2023-05-11T06:18:53.239772Z","iopub.status.idle":"2023-05-11T06:18:54.611186Z","shell.execute_reply.started":"2023-05-11T06:18:53.239728Z","shell.execute_reply":"2023-05-11T06:18:54.609658Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}