{"cells":[{"metadata":{},"cell_type":"markdown","source":"This notebook pretends to use `fastai2` library to generate something similar as @iafoss did [here](https://www.kaggle.com/iafoss/panda-concat-tile-pooling-starter-0-79-lb). In order to do it, I reused part of his code together with this [notebook](https://www.kaggle.com/mnpinto/fastai2-working-with-3d-data/notebook) that uses fastai2 for working with \"3D\" images. For model training, I will use @drhabib's 2nd place model which can be found [here](https://github.com/DrHB/PANDA-2nd-place-solution) but only using the classification head."},{"metadata":{"trusted":true},"cell_type":"code","source":"from fastai.vision.all import *","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"TRAIN = '../input/panda-16x128x128-tiles-data/train/'\npath = Path('../input/panda-16x128x128-tiles-data/train/')\n#path_small = path[:100]\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"LABELS = '../input/prostate-cancer-grade-assessment/train.csv'\ndf = pd.read_csv(LABELS)\ndf","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"files = sorted(set([p for p in os.listdir(TRAIN)]))\nfiles_id = [p[:32] for p in files]\ndf_2 = pd.DataFrame({'file': files, 'image_id': files_id})\nresult = pd.merge(df_2, df, on=\"image_id\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"result","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"result['path'] = TRAIN + result['file']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"result","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"result['isup_grade'].unique()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The following code uses the \"3D\" approach to build a `[bs x N x C x H X W]` Dataloader similar to the fastai1 version. In this case, however, it randomly sample a specific number of images. Moreover, in case some images does not have enough tiles (in this case n<12), it repeats some of them."},{"metadata":{"trusted":true},"cell_type":"code","source":"def int2float(o:TensorImage):\n    return o.float().div_(255.)\n\nclass ImageSequence(fastuple):\n    @classmethod\n    def create(cls, fns): return cls(tuple(PILImage.create(f) for f in fns))\n\ndef ImageSequenceBlock(): \n    return TransformBlock(type_tfms=ImageSequence.create, batch_tfms=int2float)\n\nclass SequenceGetItems():\n    def __init__(self, filename_col, sequence_id_col, label_col, n_img):\n        self.fn = filename_col\n        self.seq = sequence_id_col\n        self.label = label_col\n        self.n_img = n_img\n        \n    def __call__(self, df):\n        data = []\n        for fn in progress_bar(df[self.seq].unique()):\n            similar = df[self.seq] == fn\n            similar = df.loc[similar]\n            similar = similar.sample(self.n_img, replace=True)\n            fns = similar[self.fn].tolist()\n            lbl = similar[self.label].values[0]\n            data.append([*fns, lbl])\n        return data\n\ndef create_batch(data):\n    xs, ys = [], []\n    for d in data:\n        xs.append(d[0])\n        ys.append(d[1])\n    xs = torch.cat([TensorImage(torch.cat([im[None] for im in x], dim=0))[None] for x in xs], dim=0)\n    ys = torch.cat([y[None] for y in ys], dim=0)\n    return TensorImage(xs), TensorCategory(ys)\n\ndef show_sequence_batch(max_n=2, n_img=10):\n    xb, yb = dls.one_batch()\n    fig, axes = plt.subplots(ncols=n_img, nrows=max_n, figsize=(12,6), dpi=120)\n    for i in range(max_n):\n        xs, ys = xb[i], yb[i]\n        for j, x in enumerate(xs):\n            axes[i,j].imshow(x.permute(1,2,0).cpu().numpy())\n            axes[i,j].set_title(ys.item())\n            axes[i,j].axis('off')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"n_img = 49\nbs = 8\n\ndblock = DataBlock(\n    blocks    = (ImageSequenceBlock, CategoryBlock),\n    get_items = SequenceGetItems('path', 'image_id', 'isup_grade', n_img), \n    get_x     = lambda t : t[:-1],\n    get_y     = lambda t : t[-1],\n    splitter  = RandomSplitter(valid_pct=0.2, seed=2020))\n\ndls = dblock.dataloaders(result, bs=bs, create_batch=create_batch)\nshow_sequence_batch(n_img=n_img)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dls.vocab","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model\n\nHere, I used the same model with some changes to fit the new `Dataloader`"},{"metadata":{"trusted":true},"cell_type":"code","source":"class CustomEnd(nn.Module):\n    def __init__(self, scaler = SigmoidRange(-1, 6.0)):\n        super().__init__()\n        self.scaler_ = scaler\n        \n    def forward(self, x):\n        classif = x[:, :-1]\n        regress = self.scaler_ (x[:, -1])\n        return classif, regress\n    \ndef make_divisible(v, divisor=8, min_value=None):\n    min_value = min_value or divisor\n    new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)\n   # Make sure that round down does not go down by more than 10%.\n    if new_v < 0.9 * v:\n        new_v += divisor\n    return new_v\n\ndef sigmoid(x, inplace: bool = False):\n    return x.sigmoid_() if inplace else x.sigmoid()\n\nclass SqueezeExcite(nn.Module):\n    def __init__(self, in_chs, se_ratio=0.25, reduced_base_chs=None,\n             act_layer=nn.ReLU, gate_fn=sigmoid, divisor=1, **_):\n        super(SqueezeExcite, self).__init__()\n        self.gate_fn = gate_fn\n        reduced_chs = make_divisible((reduced_base_chs or in_chs) * se_ratio, divisor)\n        self.avg_pool = nn.AdaptiveAvgPool2d(1)\n        self.conv_reduce = nn.Conv2d(in_chs, reduced_chs, 1, bias=True)\n        self.act1 = act_layer(inplace=True)\n        self.conv_expand = nn.Conv2d(reduced_chs, in_chs, 1, bias=True)\n    def forward(self, x):\n        x_se = self.avg_pool(x)\n        x_se = self.conv_reduce(x_se)\n        x_se = self.act1(x_se)\n        x_se = self.conv_expand(x_se)\n        x = x * self.gate_fn(x_se)\n        return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class Model(nn.Module):\n    def __init__(self, N=49):\n        super().__init__()\n        m = resnet34(pretrained=True)\n        self.enc = nn.Sequential(*list(m.children())[:-2])       \n        nc = list(m.children())[-1].in_features\n        self.cb = SqueezeExcite(nc)\n        self.head = nn.Sequential(AdaptiveConcatPool2d(),\n                                  Flatten(),\n                                  nn.Linear(2*nc,512),\n                                  nn.ReLU(inplace=True),\n                                  nn.Dropout(0.4),\n                                  nn.Linear(512,6))\n                                   \n                                  #,CustomEnd())\n        \n        self.N=N\n        \n    def forward(self, x):\n        shape = x.shape\n        n = shape[1]\n        x = x.view(-1,shape[2],shape[3],shape[4])\n        x = self.enc(x)\n        shape = x.shape\n        x = x.view(-1, n, x.shape[1], x.shape[2], x.shape[3]).permute(0, 2, 1, 3, 4).contiguous().\\\n        view(-1, x.shape[1], x.shape[2] * n, x.shape[3])\n        x = x.view(x.shape[0], x.shape[1], x.shape[2]//int(np.sqrt(self.N)), -1)\n        x = self.cb(x)\n        x = self.head(x)\n        return x#[1]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = Model()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn = Learner(dls, model, metrics=[accuracy, CohenKappa(weights='quadratic')]).to_fp16()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.fine_tune(5, freeze_epochs=5, cbs=[EarlyStoppingCallback(monitor='cohen_kappa_score', patience=5), \n                     SaveModelCallback(monitor='cohen_kappa_score', with_opt=True, fname='RN34SE_fastai2')])","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}