{"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":"# About the notebook\n- segmentation_models_pytorch starter code for the competition\n- GroupKFold 5 folds\n- EfficientnetB3 as encoder Feature Pyramid Network (FPN) as decoder\n- Wandb.ai\n    - Pytorch W&B Usage Examples from https://docs.wandb.ai/guides/integrations/pytorch\n- Inference notebook is coming soon\n\n\nIf this notebook is helpful, feel free to upvote!","metadata":{}},{"cell_type":"code","source":"# Install SMP\n!pip install segmentation_models_pytorch -q\n!pip install timm -q","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-10-21T15:59:04.513550Z","iopub.execute_input":"2021-10-21T15:59:04.513808Z","iopub.status.idle":"2021-10-21T15:59:25.807963Z","shell.execute_reply.started":"2021-10-21T15:59:04.513730Z","shell.execute_reply":"2021-10-21T15:59:25.807032Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Import necessary libraries","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# Libraries\n# ====================================================\nimport os\nimport sys\nimport cv2\nimport pdb\nimport time\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\nimport random\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm_notebook as tqdm\nfrom sklearn.model_selection import GroupKFold\n\nimport torch\nimport torch.nn as nn\nfrom torch.nn import functional as F\nfrom torch.utils.data import DataLoader, Dataset, sampler\nfrom torch.utils.data.sampler import SequentialSampler, RandomSampler\n\nimport timm\nimport segmentation_models_pytorch as smp\nfrom matplotlib import pyplot as plt\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\ndef seed(seed):\n    np.random.seed(seed)\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    print('Done seeding.')\n    \nseed(42)","metadata":{"execution":{"iopub.status.busy":"2021-10-21T16:01:00.081344Z","iopub.execute_input":"2021-10-21T16:01:00.082097Z","iopub.status.idle":"2021-10-21T16:01:10.205700Z","shell.execute_reply.started":"2021-10-21T16:01:00.082056Z","shell.execute_reply":"2021-10-21T16:01:10.204102Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Config","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# CFG\n# ====================================================\nclass CFG:\n    apex=True\n    debug=False\n    num_workers=4\n    model_name='effb3+FPN'\n    encoder_name='timm-efficientnet-b3'\n    decoder_name='FPN'\n    scheduler='CosineAnnealingLR' # ['ReduceLROnPlateau', 'CosineAnnealingLR', 'CosineAnnealingWarmRestarts']\n    epochs=5\n    T_max=3 \n    lr=1e-4\n    min_lr=1e-6\n    batch_size=32\n    image_size = [384, 384]\n    mean=timm.data.IMAGENET_DEFAULT_MEAN\n    std=timm.data.IMAGENET_DEFAULT_STD\n    seed=42\n    n_fold=5\n    trn_fold=[0] # You go with [0, 1, 2, 3, 4] if you want to train for 5 folds\n    train=True,\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    sample_submission='../input/sartorius-cell-instance-segmentation/sample_submission.csv'\n    train_df_path=\"../input/sartorius-cell-instance-segmentation/train.csv\"\n    train_base=\"../input/sartorius-cell-instance-segmentation/train\"\n    test_base=\"../input/sartorius-cell-instance-segmentation/test\"\n    ","metadata":{"execution":{"iopub.status.busy":"2021-10-21T16:01:52.742552Z","iopub.execute_input":"2021-10-21T16:01:52.743348Z","iopub.status.idle":"2021-10-21T16:01:52.793230Z","shell.execute_reply.started":"2021-10-21T16:01:52.743293Z","shell.execute_reply":"2021-10-21T16:01:52.792467Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Logging to Wandb.ai","metadata":{}},{"cell_type":"code","source":"# 1-) Go to wandb.ai/authorize copy the api key\n# 2-) From the top menu click Add-ons > secrets > add a new secret > name 'Label' as wandb and paste your api key to value\n#from kaggle_secrets import UserSecretsClient\n#user_secrets = UserSecretsClient()\n#wandb_api = user_secrets.get_secret(\"wandb\")","metadata":{"execution":{"iopub.status.busy":"2021-10-21T16:02:39.476068Z","iopub.execute_input":"2021-10-21T16:02:39.476339Z","iopub.status.idle":"2021-10-21T16:02:39.479738Z","shell.execute_reply.started":"2021-10-21T16:02:39.476308Z","shell.execute_reply":"2021-10-21T16:02:39.479020Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#import wandb\n#wandb.login(key=wandb_api)\n\n#def class2dict(f):\n#    return dict((name, getattr(f, name)) for name in dir(f) if not name.startswith('__'))\n\n#run = wandb.init(project=\"sartarious-segmentation-exps\", \n#                 name=\"exp1\",\n#                 config=class2dict(CFG),\n#                 group=CFG.model_name,\n#                 job_type=\"train\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2021-10-21T16:02:39.724371Z","iopub.execute_input":"2021-10-21T16:02:39.724617Z","iopub.status.idle":"2021-10-21T16:02:39.728684Z","shell.execute_reply.started":"2021-10-21T16:02:39.724589Z","shell.execute_reply":"2021-10-21T16:02:39.727852Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Utils","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# Image Utils\n# ====================================================\ndef rle_decode(mask_rle, shape, color=1):\n    '''\n    mask_rle: run-length as string formated (start length)\n    shape: (height,width) of array to return \n    Returns numpy array, 1 - mask, 0 - background\n\n    '''\n    s = mask_rle.split()\n    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]\n    starts -= 1\n    ends = starts + lengths\n    img = np.zeros(shape[0] * shape[1], dtype=np.float32)\n    for lo, hi in zip(starts, ends):\n        img[lo : hi] = color\n    return img.reshape(shape)\n\n\ndef build_masks(df_train, image_id, input_shape):\n    height, width = input_shape\n    labels = df_train[df_train[\"id\"] == image_id][\"annotation\"].tolist()\n    mask = np.zeros((height, width))\n    for label in labels:\n        mask += rle_decode(label, shape=(height, width))\n    mask = mask.clip(0, 1)\n    return mask","metadata":{"execution":{"iopub.status.busy":"2021-10-21T16:02:40.638950Z","iopub.execute_input":"2021-10-21T16:02:40.639679Z","iopub.status.idle":"2021-10-21T16:02:40.648423Z","shell.execute_reply.started":"2021-10-21T16:02:40.639637Z","shell.execute_reply":"2021-10-21T16:02:40.647674Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Dataset Generator","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# Dataset generator\n# ====================================================\nclass DatasetRetriever(Dataset):\n    def __init__(self, df, \n                 base_path:str, \n                 image_size:list, \n                 mean:int, std:int\n                ):\n        \n        self.df = df\n        self.base_path = base_path\n        self.image_size = image_size\n        self.mean = mean \n        self.std = std\n        self.gb = self.df.groupby('id')\n        self.image_ids = df.id.unique().tolist()\n        \n        # Image augmentations\n        self.transforms = A.Compose([\n                    A.Resize(self.image_size[0], self.image_size[1]),\n                    A.Normalize(mean=self.mean, std=self.std, p=1), \n                    A.HorizontalFlip(p=0.5), A.VerticalFlip(p=0.5),\n                    ToTensorV2()\n                ])\n    \n    def __len__(self):\n        return len(self.image_ids)\n        \n    def __getitem__(self, idx):\n        image_id = self.image_ids[idx]\n        df = self.gb.get_group(image_id)\n        \n        annotations = df['annotation'].tolist()\n        image_path = os.path.join(self.base_path, image_id + \".png\")\n        \n        image = cv2.imread(image_path)\n        \n        mask = build_masks(self.df, image_id, input_shape=(520, 704))\n        mask = (mask >= 1).astype('float32')\n        augmented = self.transforms(image=image, mask=mask)\n        \n        image = augmented['image']\n        mask = augmented['mask']\n        \n        return image, mask.reshape((1, self.image_size[0], self.image_size[1]))\n","metadata":{"execution":{"iopub.status.busy":"2021-10-21T16:02:41.499365Z","iopub.execute_input":"2021-10-21T16:02:41.499970Z","iopub.status.idle":"2021-10-21T16:02:41.511430Z","shell.execute_reply.started":"2021-10-21T16:02:41.499925Z","shell.execute_reply":"2021-10-21T16:02:41.510590Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ====================================================\n# Custom Loss for competition metric | from:https://www.kaggle.com/julian3833/sartorius-starter-baseline-torch-u-net\n# ====================================================\n\ndef dice_loss(input, target):\n    input = torch.sigmoid(input)\n    smooth = 1.0\n    iflat = input.view(-1)\n    tflat = target.view(-1)\n    intersection = (iflat * tflat).sum()\n    return ((2.0 * intersection + smooth) / (iflat.sum() + tflat.sum() + smooth))\n\nclass FocalLoss(nn.Module):\n    def __init__(self, gamma):\n        super().__init__()\n        self.gamma = gamma\n\n    def forward(self, input, target):\n        if not (target.size() == input.size()):\n            raise ValueError(\"Target size ({}) must be the same as input size ({})\"\n                             .format(target.size(), input.size()))\n        max_val = (-input).clamp(min=0)\n        loss = input - input * target + max_val + \\\n            ((-max_val).exp() + (-input - max_val).exp()).log()\n        invprobs = F.logsigmoid(-input * (target * 2.0 - 1.0))\n        loss = (invprobs * self.gamma).exp() * loss\n        return loss.mean()\n\n    \nclass MixedLoss(nn.Module):\n    def __init__(self, alpha, gamma):\n        super().__init__()\n        self.alpha = alpha\n        self.focal = FocalLoss(gamma)\n\n    def forward(self, input, target):\n        loss = self.alpha*self.focal(input, target) - torch.log(dice_loss(input, target))\n        return loss.mean()    \n    ","metadata":{"execution":{"iopub.status.busy":"2021-10-21T16:02:42.457252Z","iopub.execute_input":"2021-10-21T16:02:42.457884Z","iopub.status.idle":"2021-10-21T16:02:42.469488Z","shell.execute_reply.started":"2021-10-21T16:02:42.457847Z","shell.execute_reply":"2021-10-21T16:02:42.468407Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## GroupKFold","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(CFG.train_df_path)\ngkf  = GroupKFold(n_splits = CFG.n_fold)\ndf['fold'] = -1\nfor fold, (train_idx, val_idx) in enumerate(gkf.split(df, groups = df.id.tolist())):\n    df.loc[val_idx, 'fold'] = fold","metadata":{"execution":{"iopub.status.busy":"2021-10-21T16:02:44.868067Z","iopub.execute_input":"2021-10-21T16:02:44.868860Z","iopub.status.idle":"2021-10-21T16:02:45.454660Z","shell.execute_reply.started":"2021-10-21T16:02:44.868808Z","shell.execute_reply":"2021-10-21T16:02:45.453895Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training loop","metadata":{}},{"cell_type":"code","source":"def train_fn(df):\n    print(f'CONFGI:\\n{CFG}')\n    for fold in range(CFG.n_fold):\n        if fold in CFG.trn_fold:\n            print(f'='*25,'Fold: ',fold,'='*25)\n\n            valid_df = df.loc[df['fold'] == fold]\n            train_df = df.loc[df['fold'] != fold]\n\n            train_dataset = DatasetRetriever(\n                                    df=train_df,\n                                    base_path=CFG.train_base,\n                                    image_size=CFG.image_size,\n                                    mean=timm.data.IMAGENET_DEFAULT_MEAN,\n                                    std=timm.data.IMAGENET_DEFAULT_STD)\n\n            valid_dataset = DatasetRetriever(\n                                    df=valid_df,\n                                    base_path=CFG.train_base,\n                                    image_size=CFG.image_size,\n                                    mean=timm.data.IMAGENET_DEFAULT_MEAN,\n                                    std=timm.data.IMAGENET_DEFAULT_STD)        \n\n            train_loader = DataLoader(train_dataset, \n                                      batch_size=CFG.batch_size,\n                                      sampler=RandomSampler(train_dataset), \n                                      num_workers=CFG.num_workers, drop_last=True)\n\n            valid_loader = DataLoader(valid_dataset, \n                                      batch_size=CFG.batch_size, \n                                      sampler=SequentialSampler(valid_dataset), \n                                      num_workers=CFG.num_workers, drop_last=False)\n\n            print('TRAIN: {} | VALID: {}'.format(len(train_loader.dataset), len(valid_loader.dataset)))\n\n            # Define the model:\n            model = smp.FPN(f'{CFG.encoder_name}', \n                            encoder_weights='noisy-student', \n                            activation=None)\n            model.to(CFG.device)\n\n            criterion = MixedLoss(10.0, 2.0)\n\n            optimizer = torch.optim.Adam(model.parameters(), lr=CFG.lr)\n            scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, CFG.epochs-1)\n            scaler = torch.cuda.amp.GradScaler()\n            checkpoint = f'effb3_fpn-{fold}-fold_best.pth'\n\n            count = 0\n            best_epoch = 0\n\n            for epoch in range(CFG.epochs):\n                print('Epoch: {}'.format(epoch))\n                model.train()\n\n                loop = tqdm(train_loader)\n                for images, masks in loop:\n                    images=images.to(CFG.device)\n                    masks =masks.to(CFG.device)\n\n                    optimizer.zero_grad()\n\n                    with torch.cuda.amp.autocast():\n                        outputs = model(images)\n                        loss = criterion(outputs, masks)\n\n                    scaler.scale(loss).backward()\n                    scaler.step(optimizer)\n                    scaler.update()\n\n                    loop.set_description(f'Epoch : {epoch}/{CFG.epochs} | LOSS:{loss} | LR:{optimizer.param_groups[0][\"lr\"]}')\n\n\n                # Validation loop \n                model.eval()\n                for images, masks, in tqdm(valid_loader):\n                    images = images.to(CFG.device)\n                    masks = masks.to(CFG.device)\n                    with torch.cuda.amp.autocast(), torch.no_grad():\n                        outputs = model(images)\n                        val_loss = criterion(outputs, masks)\n                print('End of epoch. Val loss: {}'.format(val_loss))\n\n\n                if epoch == 0:\n                    best_val_loss = val_loss\n                    print('Saving the model...')\n                    torch.save(model.state_dict(), checkpoint)\n                if epoch != 0:\n                    if val_loss < best_val_loss:\n                        print('Saving the model!')\n                        torch.save(model.state_dict(), checkpoint)\n                        best_val_loss = val_loss\n                    else:\n                        best_val_loss = best_val_loss\n                scheduler.step()\n","metadata":{"execution":{"iopub.status.busy":"2021-10-21T16:02:45.515142Z","iopub.execute_input":"2021-10-21T16:02:45.515615Z","iopub.status.idle":"2021-10-21T16:02:45.534407Z","shell.execute_reply.started":"2021-10-21T16:02:45.515573Z","shell.execute_reply":"2021-10-21T16:02:45.533684Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"if __name__ == '__main__':\n    train_fn(df=df)","metadata":{"execution":{"iopub.status.busy":"2021-10-21T16:02:46.185941Z","iopub.execute_input":"2021-10-21T16:02:46.186201Z","iopub.status.idle":"2021-10-21T16:06:43.652598Z","shell.execute_reply.started":"2021-10-21T16:02:46.186172Z","shell.execute_reply":"2021-10-21T16:06:43.651719Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Thank you!\nThat's pretty much it for now. I'll be working on inference notebook and Wandb will be usable soon. Pleas `upvote` if you found this notebook helpful!","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}