{"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":"## Update info\n\n<u>Version1</u>: \n\n* First release. I tune epoch and so no.\n\n<u>Version2</u>: \n\n* Change following points:\n\n    * Used EDGE_ENHANCE filter to data.\n\n    * Used OneCycleLR Scheduler.\n    \n<u>Version3</u>: \n\n* Added support for new datasets; removed EDGE_ENHANCE filter once and for all.","metadata":{}},{"cell_type":"markdown","source":"------------------------","metadata":{}},{"cell_type":"markdown","source":"I create train and inference notebook for Unet++ of [segmentation_models.pytorch](https://github.com/qubvel/segmentation_models.pytorch).\n\nI use only pytorch for framework.\n\nThe accuracy of the model is going to be tuned and improved in the future.\n\nI published this notebook for our reference as an example implementation using only pytorch.","metadata":{}},{"cell_type":"markdown","source":"I refered following two great notebooks for training and inference.\n\n- https://www.kaggle.com/iafoss/hubmap-pytorch-fast-ai-starter\n\n- https://www.kaggle.com/curiosity806/hubmap-use-catalyst-smp-and-albumentations\n\nAnd refered following great notebook for dice loss.\n\n- https://www.kaggle.com/bigironsphere/loss-function-library-keras-pytorch\n\nKindly upvote and appreciate the original work.","metadata":{}},{"cell_type":"markdown","source":"## Load libraries","metadata":{}},{"cell_type":"code","source":"!pip install git+https://github.com/qubvel/segmentation_models.pytorch","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import gc\nimport os\nimport random\nimport time\nimport warnings\nwarnings.simplefilter(\"ignore\")\n\n#import pdb\n#import zipfile\n#import pydicom\nfrom albumentations import *\nfrom albumentations.pytorch import ToTensor\nimport cv2\nfrom matplotlib import pyplot as plt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image, ImageFilter\nimport segmentation_models_pytorch as smp\nfrom sklearn.model_selection import KFold\nimport tifffile as tiff\nimport torch\nimport torch.backends.cudnn as cudnn\nimport torch.nn as nn\nfrom torch.nn import functional as F\nimport torch.optim as optim\nfrom torch.optim.lr_scheduler import ReduceLROnPlateau\nfrom torch.utils.data import DataLoader, Dataset, sampler\nfrom tqdm import tqdm_notebook as tqdm\n\n%matplotlib inline","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Dataset","metadata":{}},{"cell_type":"code","source":"!mkdir ./masks\n!mkdir ./train\n\n!unzip ../input/256x256-images/masks.zip -d ./masks\n!unzip ../input/256x256-images/train.zip -d ./train","metadata":{"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"##  Set parameters","metadata":{}},{"cell_type":"code","source":"def set_seed(seed=2**3):\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    np.random.seed(seed)\n    random.seed(seed)\n    torch.backends.cudnn.deterministic = True\nset_seed(121)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"To save time, the number of \"folds\" is smaller. \nThese days, depending on the model, I think it's more common to use 5 ~ 10.","metadata":{}},{"cell_type":"code","source":"fold = 0\nnfolds = 5\nreduce = 4\nsz = 256\n\nBATCH_SIZE = 16\nDEVICE = ('cuda' if torch.cuda.is_available() else 'cpu')\nEPOCHS = 15\nNUM_WORKERS = 4\nSEED = 2020\nTH = 0.39  #threshold for positive predictions\n\nDATA = '../input/hubmap-kidney-segmentation/test/'\nLABELS = '../input/hubmap-kidney-segmentation/train.csv'\nMASKS = './masks/'\nTRAIN = './train/'\ndf_sample = pd.read_csv('../input/hubmap-kidney-segmentation/sample_submission.csv')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Util functions","metadata":{}},{"cell_type":"code","source":"#https://www.kaggle.com/bguberfain/memory-aware-rle-encoding\n#with bug fix\ndef rle_encode_less_memory(img):\n    #watch out for the bug\n    pixels = img.T.flatten()\n    \n    # This simplified method requires first and last pixel to be zero\n    pixels[0] = 0\n    pixels[-1] = 0\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 2\n    runs[1::2] -= runs[::2]\n    \n    return ' '.join(str(x) for x in runs)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Dataset","metadata":{}},{"cell_type":"code","source":"# https://www.kaggle.com/iafoss/256x256-images\nmean = np.array([0.65527532, 0.49901106, 0.69247992] )\nstd = np.array([0.25565283, 0.31975344, 0.21533712])\n\ndef img2tensor(img,dtype:np.dtype=np.float32):\n    if img.ndim==2 : img = np.expand_dims(img,2)\n    img = np.transpose(img,(2,0,1))\n    return torch.from_numpy(img.astype(dtype, copy=False))\n\nclass HuBMAPDataset(Dataset):\n    def __init__(self, fold=fold, train=True, tfms=None):\n        ids = pd.read_csv(LABELS).id.values\n        kf = KFold(n_splits=nfolds,random_state=SEED,shuffle=True)\n        ids = set(ids[list(kf.split(ids))[fold][0 if train else 1]])\n        self.fnames = [fname for fname in os.listdir(TRAIN) if fname.split('_')[0] in ids]\n        self.train = train\n        self.tfms = tfms\n        \n    def __len__(self):\n        return len(self.fnames)\n    \n    def __getitem__(self, idx):\n        fname = self.fnames[idx]\n        img = cv2.cvtColor(cv2.imread(os.path.join(TRAIN,fname)), cv2.COLOR_BGR2RGB)\n        mask = cv2.imread(os.path.join(MASKS,fname),cv2.IMREAD_GRAYSCALE)\n        if self.tfms is not None:\n            augmented = self.tfms(image=img,mask=mask)\n            img,mask = augmented['image'],augmented['mask']\n        return img2tensor((img/255.0 - mean)/std),img2tensor(mask)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_aug(p=1.0):\n    return Compose([\n        HorizontalFlip(),\n        VerticalFlip(),\n        RandomRotate90(),\n        ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.2, rotate_limit=15, p=0.9, \n                         border_mode=cv2.BORDER_REFLECT),\n        OneOf([\n            OpticalDistortion(p=0.3),\n            GridDistortion(p=.1),\n            IAAPiecewiseAffine(p=0.3),\n        ], p=0.3),\n        OneOf([\n            HueSaturationValue(10,15,10),\n            CLAHE(clip_limit=2),\n            RandomBrightnessContrast(),            \n        ], p=0.3),\n    ], p=p)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#example of train images with masks\nds = HuBMAPDataset(tfms=get_aug())\ndl = DataLoader(ds,batch_size=64,shuffle=False,num_workers=NUM_WORKERS)\nimgs,masks = next(iter(dl))\n\nplt.figure(figsize=(16,16))\nfor i,(img,mask) in enumerate(zip(imgs,masks)):\n    img = ((img.permute(1,2,0)*std + mean)*255.0).numpy().astype(np.uint8)\n    plt.subplot(8,8,i+1)\n    plt.imshow(img,vmin=0,vmax=255)\n    plt.imshow(mask.squeeze().numpy(), alpha=0.2)\n    plt.axis('off')\n    plt.subplots_adjust(wspace=None, hspace=None)\n    \ndel ds,dl,imgs,masks","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Model","metadata":{}},{"cell_type":"code","source":"def get_UnetPlusPlus():\n    model =  smp.UnetPlusPlus(\n                 encoder_name='efficientnet-b3',\n                 encoder_weights='imagenet',\n                 in_channels=3,\n                 classes=1)\n    return model","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## DiceLoss\n\nNote that this loss represents 1 - DiceLoss.","metadata":{}},{"cell_type":"code","source":"#https://www.kaggle.com/bigironsphere/loss-function-library-keras-pytorch\nclass DiceLoss(nn.Module):\n    def __init__(self, weight=None, size_average=True):\n        super(DiceLoss, self).__init__()\n\n    def forward(self, inputs, targets, smooth=1):\n        \n        #comment out if your model contains a sigmoid or equivalent activation layer\n        inputs = F.sigmoid(inputs)       \n        \n        #flatten label and prediction tensors\n        inputs = inputs.view(-1)\n        targets = targets.view(-1)\n        \n        intersection = (inputs * targets).sum()                            \n        dice = (2.*intersection + smooth)/(inputs.sum() + targets.sum() + smooth)  \n        \n        return 1 - dice","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Training","metadata":{}},{"cell_type":"code","source":"cv_score = 0\nfor fold in range(nfolds):\n    ds_t = HuBMAPDataset(fold=fold, train=True, tfms=get_aug())\n    ds_v = HuBMAPDataset(fold=fold, train=False)\n    dataloader_t = torch.utils.data.DataLoader(ds_t,batch_size=BATCH_SIZE, shuffle=False,num_workers=NUM_WORKERS)\n    dataloader_v = torch.utils.data.DataLoader(ds_t,batch_size=BATCH_SIZE, shuffle=False,num_workers=NUM_WORKERS)\n    model = get_UnetPlusPlus().to(DEVICE)\n    \n    optimizer = torch.optim.Adam([\n        {'params': model.decoder.parameters(), 'lr': 1e-3}, \n        {'params': model.encoder.parameters(), 'lr': 1e-3},  \n    ])\n    scheduler = optim.lr_scheduler.OneCycleLR(optimizer=optimizer, pct_start=0.1, div_factor=1e3, \n                                              max_lr=1e-2, epochs=EPOCHS, steps_per_epoch=len(dataloader_t))\n    \n    diceloss = DiceLoss()\n    \n    print(f\"########FOLD: {fold}##############\")\n    \n    for epoch in tqdm(range(EPOCHS)):\n        ###Train\n        model.train()\n        train_loss = 0\n    \n        for data in dataloader_t:\n            optimizer.zero_grad()\n            img, mask = data\n            img = img.to(DEVICE)\n            mask = mask.to(DEVICE)\n        \n            outputs = model(img)\n    \n            loss = diceloss(outputs, mask)\n            loss.backward()\n            optimizer.step()\n            scheduler.step()\n            \n            train_loss += loss.item()\n        train_loss /= len(dataloader_t)\n        \n        print(f\"FOLD: {fold}, EPOCH: {epoch + 1}, train_loss: {train_loss}\")\n        \n        ###Validation\n        model.eval()\n        valid_loss = 0\n        \n        for data in dataloader_v:\n            img, mask = data\n            img = img.to(DEVICE)\n            mask = mask.to(DEVICE)\n        \n            outputs = model(img)\n    \n            loss = diceloss(outputs, mask)\n        \n            valid_loss += loss.item()\n        valid_loss /= len(dataloader_v)\n        \n        print(f\"FOLD: {fold}, EPOCH: {epoch + 1}, valid_loss: {valid_loss}\")\n        \n        \n    ###Save model\n    torch.save(model.state_dict(), f\"FOLD{fold}_.pth\")\n    \n    cv_score += valid_loss\n    \ncv_score = cv_score/nfolds","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(f\"CV score is: {cv_score}\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!rm -r ./masks\n!rm -r ./train","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}