{"cells":[{"metadata":{"_uuid":"9c689c199cc6ce7f1642937c00d544c5b6aadcd4"},"cell_type":"markdown","source":"# Fastai Darknet Model"},{"metadata":{"_uuid":"563ca3755eb71b5b455ba05c67198a25da8bc29c"},"cell_type":"markdown","source":"This kernel implements a Darknet model for the HUman Protein Atals comp.\nIt uses the 4 separated images as input, concatenating them in a 4 channel RGBY image. To do so, I modified the first convolution in the Darknet implementation from 3-->4 channels.\nThe model trained is a Darknet_small. Better results can be achieved with computing power and a deeper network (Darknet53 for instance, check yolo [website](https://pjreddie.com/darknet/yolo/) )\n\nThis kernel uses the fastai library version 0.7 and one cycle training schedule.  No transforms are used.\n\nTo be able to train in the limited computing power of kallge, I trained a 128 model, then a 256 and finally this one, but only one epoch."},{"metadata":{"trusted":true,"_uuid":"14da00fd2d9e39a4b2a3024375fb8e30e92bc10a"},"cell_type":"code","source":"from fastai.conv_learner import *\nfrom fastai.dataset import *\n\nfrom pathlib import Path\nimport json\ntorch.cuda.set_device(0)\nfrom pathlib import Path\ntorch.cuda.set_device(0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"1e729ca03fa499e20193ba865b8491f63f765b6f"},"cell_type":"code","source":"ls /kaggle/input/","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9460811993269fc27cf5214f8483a38b429b3027"},"cell_type":"markdown","source":"sample : can be used to train in smaller dataset. For debug set sample to 3000 for instance."},{"metadata":{"trusted":true,"_uuid":"712cf41b95d08b64eb6a2eacce63007a753cb010"},"cell_type":"code","source":"MASKS = 'train.csv'\nSUB = 'sample_submission.csv'\nTRAIN = Path('train/')\nTEST = Path('test/')\nPATH = Path('/kaggle/input/human-protein-atlas-image-classification/')\nTMP = Path('/kaggle/working/tmp/')\nMODEL = Path('/kaggle/working/model/')\nPRETRAINED = '/kaggle/input/4-channel-darknet-sz-256/model/256'\nseg = pd.read_csv(PATH/MASKS).set_index('Id')\nsample_sub = pd.read_csv(PATH/SUB).set_index('Id')\n\nsample= len(seg)\nseg.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4a9fd1df3c6948cc66f7acd8bd1981ff40487563"},"cell_type":"code","source":"train_names_png = [TRAIN/f for f in os.listdir(PATH/TRAIN)]\ntrain_names = list(seg.index.values)\ntrain_names_sample = list(seg.index.values)[0:sample]\ntest_names_png = [TEST/f for f in os.listdir(PATH/TEST)]\ntest_names = list(sample_sub.index.values)\ntest_names_sample = list(sample_sub.index.values)[0:sample]\nlen(train_names_sample), len(test_names)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"30faaaa4a7623a7cc66ab7e703b1497277f7a194"},"cell_type":"code","source":"TMP.mkdir(exist_ok=True)\nMODEL.mkdir(exist_ok=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"30cf4c4eff0e3104892bf0348dcb2d71168ca078"},"cell_type":"code","source":"def rgba_open(fname, path=PATH, sz=128):\n    '''open RGBA image from 4 different 1-channel files.\n    return: numpy array [4, sz, sz]'''\n    flags = cv2.IMREAD_GRAYSCALE\n    red = cv2.imread(str(path/(fname+ '_red.png')), flags)\n    blue = cv2.imread(str(path/(fname+ '_blue.png')), flags)\n    green = cv2.imread(str(path/(fname+ '_green.png')), flags)\n    yellow = cv2.imread(str(path/(fname+ '_yellow.png')),flags)\n    im = np.array([red, green, blue, yellow], dtype=np.float32)\n    if sz==512:\n        return im/255\n    else:\n        rgba = cv2.resize(np.rollaxis(im, 0,3), (sz, sz), interpolation = cv2.INTER_CUBIC)\n        return np.rollaxis(rgba, 2,0)/255\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"63e35180874bc24a13e5971376a3330aa3c5af36"},"cell_type":"markdown","source":"This is to be able to use a smaller data sample to debug, sample=31072 is the full dataset. "},{"metadata":{"trusted":true,"_uuid":"98f15fe713bf01f0eda87b9fc9abeee71a7d4341"},"cell_type":"code","source":"seg2 = seg.iloc[0:sample]\nval_idxs = get_cv_idxs(sample)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b1fa9d0c61c898f96de89520691d0547a84cdb61"},"cell_type":"code","source":"class CustomDataset(FilesDataset):\n    def __init__(self, fnames, y, transform, path, sz):\n        self.y=y\n        self.fnames = fnames\n        self.sz = sz\n        assert(len(fnames)==len(y))\n        super().__init__(fnames, transform, path)\n        \n    def get_x(self, i): \n        return rgba_open(self.fnames[i], self.path, self.sz)\n        \n    def get_y(self, i):\n        return self.y[i]\n    def get_sz(self): return self.sz\n    def get_c(self): return 28\n    @property\n    def is_multi(self):\n        return True","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ff438d8b31506b767858d47000023daa4234419c"},"cell_type":"markdown","source":"Compute `y` vector of targets."},{"metadata":{"trusted":true,"_uuid":"5c254c0d62f95403ff325d391d0cd20a36088907"},"cell_type":"code","source":"indexes = seg2.Target.apply(str.split)\ny = np.zeros((sample, 28))\nfor i in range(sample):\n    y[i,np.array(indexes[i], dtype=int)]=1","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4f4fc9a6beb90e931b07a785481c34c3d27de9f6"},"cell_type":"code","source":"len(train_names_sample),  y.shape, y.dtype","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4bb91eb8375953649d680e6de6e2dfd2e35c812d"},"cell_type":"code","source":"((val_x,trn_x),(val_y,trn_y)) = split_by_idx(val_idxs, np.array(train_names_sample), y)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"94c96e20381707a9a0136077f1bb1848dcc89fee"},"cell_type":"code","source":"# tfms = tfms_from_model(resnet34, sz=sz, crop_type=CropType.NO, aug_tfms=[])\ndef get_data(sz=128, bs=32):\n    datasets = ImageData.get_ds(CustomDataset, (trn_x,trn_y), (val_x,val_y), sz=sz, tfms=(None,None), path=PATH/TRAIN)\n    datasets[4] = CustomDataset(test_names, test_names, None, PATH/TEST, sz)\n    return ImageData(PATH, datasets, bs=bs, num_workers=4, classes=28)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"bbe34804be938bc25180924b788edd592b17a960"},"cell_type":"markdown","source":"## Darknet Model definition"},{"metadata":{"_uuid":"ff6858eca46ed5f419e5cf708f0f365e809bb8dc"},"cell_type":"markdown","source":"One improvement could be to train in `Darknet([1, 2, 4, 8, 8, 4])` for instance."},{"metadata":{"trusted":true,"_uuid":"609ed9ac9f334c3f8d6a5817d7165b37ad577b55"},"cell_type":"code","source":"class ConvBN(nn.Module):\n    \"convolutional layer then batchnorm\"\n\n    def __init__(self, ch_in, ch_out, kernel_size = 3, stride=1, padding=0):\n        super().__init__()\n        self.conv = nn.Conv2d(ch_in, ch_out, kernel_size=kernel_size, stride=stride, padding=padding, bias=False)\n        self.bn = nn.BatchNorm2d(ch_out, momentum=0.01)\n        self.relu = nn.LeakyReLU(0.1, inplace=True)\n\n    def forward(self, x): return self.relu(self.bn(self.conv(x)))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"5ef4bbf6735ec0d37ae75d0be793a6151397a975"},"cell_type":"code","source":"class DarknetBlock(nn.Module):\n    def __init__(self, ch_in):\n        super().__init__()\n        ch_hid = ch_in//2\n        self.conv1 = ConvBN(ch_in, ch_hid, kernel_size=1, stride=1, padding=0)\n        self.conv2 = ConvBN(ch_hid, ch_in, kernel_size=3, stride=1, padding=1)\n\n    def forward(self, x): return self.conv2(self.conv1(x)) + x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"0f4c788136691caa9becf37b4c34673174971394"},"cell_type":"code","source":"class Darknet(nn.Module):\n    \"Replicates the darknet classifier from the YOLOv3 paper (table 1)\"\n\n    def make_group_layer(self, ch_in, num_blocks, stride=1):\n        layers = [ConvBN(ch_in,ch_in*2,stride=stride)]\n        for i in range(num_blocks): layers.append(DarknetBlock(ch_in*2))\n        return layers\n\n    def __init__(self, num_blocks, num_classes=1000, start_nf=32):\n        super().__init__()\n        nf = start_nf\n        layers = [ConvBN(4, nf, kernel_size=3, stride=1, padding=1)]\n        for i,nb in enumerate(num_blocks):\n            layers += self.make_group_layer(nf, nb, stride=(1 if i==1 else 2))\n            nf *= 2\n        layers += [nn.AdaptiveAvgPool2d(1), Flatten(), nn.Linear(nf, num_classes)]\n#         layers += [nn.Sigmoid()]\n        self.layers = nn.Sequential(*layers)\n\n    def forward(self, x): return self.layers(x)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"95f67039dc8eeb837b0657e8fdddf5a68ffc27b6"},"cell_type":"code","source":"class FocalLoss(nn.Module):\n    def __init__(self, gamma=2):\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\n        max_val = (-input).clamp(min=0)\n        loss = input - input * target + max_val + \\\n            ((-max_val).exp() + (-input - max_val).exp()).log()\n\n        invprobs = F.logsigmoid(-input * (target * 2.0 - 1.0))\n        loss = (invprobs * self.gamma).exp() * loss\n        \n        return loss.sum(dim=1).mean()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"cf833a0b6c43365f545ea1c363ea7bcd4b1f1741"},"cell_type":"code","source":"from sklearn.metrics import fbeta_score\nimport warnings\n\ndef f1_(preds, targs, start=0.17, end=0.24, step=0.01):\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\")\n        return max([fbeta_score(targs, (preds>th), 1, average='samples')\n                    for th in np.arange(start,end,step)])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"8a5d689a7836ef37f60634c6e62dcefe3ecb3d74"},"cell_type":"code","source":"m = Darknet([1, 2, 4, 4, 3], 28).cuda()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"cd8945abdfd63e42cd0b2ca7d64b5ea11a8f98b6"},"cell_type":"code","source":"m","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e7c71330642450266a632658b344cf0bd45df223"},"cell_type":"code","source":"md = get_data(512,8)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"9b0b404ac3941ef5a80f58426df3002b84b3098c"},"cell_type":"code","source":"learn = Learner.from_model_data(m, md, tmp_name=TMP, models_name=MODEL)\nlearn.crit = FocalLoss()\nlearn.opt_fn = optim.Adam\nlearn.metrics = [f1_]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"66be78d58ac912d84c7df7a4fe7ffac82e44d7c5"},"cell_type":"markdown","source":"loading 256 model"},{"metadata":{"trusted":true,"_uuid":"5211b66ba70a483415d154f8e465d3d113b50ad1"},"cell_type":"code","source":"learn.load(PRETRAINED)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"16e3bf6f18fe788ddf487a1493ac1963c633a4ec"},"cell_type":"code","source":"lr = 1E-2","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"b6c318d785a8f6fbbc6ba080357ce375f927a8c6"},"cell_type":"markdown","source":"Train with one cycle policy"},{"metadata":{"trusted":true,"_uuid":"549667fe39a3d057ab919d24d1ebd05e4597b678"},"cell_type":"code","source":"learn.fit(lr/10,1,cycle_len=1,use_clr_beta=(10,10, 0.85, 0.9))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"cd37111e79b85b2332ea843c29cceb59c383b6f2"},"cell_type":"code","source":"learn.save('512')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"bcbad45ed5c5a2b570298644278b563a6f108956"},"cell_type":"code","source":"learn.load('512')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"0cf15d2a2a6803eb506de41e4486beb0cc3f9721"},"cell_type":"markdown","source":"## Predictions and submission file"},{"metadata":{"_uuid":"1ec8c5fd0ec1b88f60d2466c9ace7343866b924c"},"cell_type":"markdown","source":"We first get predictions on the validation set with corresponding targets. We will use this to compute an \"optimised\" set of thresholds"},{"metadata":{"trusted":true,"_uuid":"7560000ad2fe5f7415a13632af8e8dec1e0613fb"},"cell_type":"code","source":"p_v, t_v = learn.predict_with_targs()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"f2b7abba273abab2c15578f3ff2950dd2736c021"},"cell_type":"code","source":"def sigmoid(a):\n    return 1/(1+np.exp(-a))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a30bc4a3a8b587789b661298a4f7f15b415493fa"},"cell_type":"code","source":"sp_v = sigmoid(p_v) #compute the sigmoid of the network output","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6e3fda0b72c372c49b83768698ae3408d3d1cd4d"},"cell_type":"markdown","source":"Helper optimisation functions for F1 metric"},{"metadata":{"trusted":true,"_uuid":"50d42e26b2640da083b9059ccca667d622c630a8"},"cell_type":"code","source":"def f1_np(y_pred, y_true, threshold=0.5):\n    '''numpy f1 metric'''\n    y_pred = (y_pred>threshold).astype(int)\n    TP = (y_pred*y_true).sum(1)\n    prec = TP/(y_pred.sum(1)+1e-7)\n    rec = TP/(y_true.sum(1)+1e-7)\n    res = 2*prec*rec/(prec+rec+1e-7)\n    return res.mean()\n\n\ndef f1_n(y_pred, y_true, thresh, n, default=0.5):\n    '''partial f1 function for index n'''\n    threshold = default * np.ones(y_pred.shape[1])\n    threshold[n]=thresh\n    return f1_np(y_pred, y_true, threshold)\n\ndef find_thresh(y_pred, y_true):\n    '''brute force thresh finder'''\n    ths = []\n    for i in range(y_pred.shape[1]):\n        aux = []\n        for th in np.linspace(0,1,100):\n            aux += [f1_n(y_pred, y_true, th, i)]\n        ths += [np.array(aux).argmax()/100]\n    return np.array(ths)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"62084e380950d3a94598ef2c39464cc77d860e84"},"cell_type":"code","source":"ths = find_thresh(sp_v, t_v); ths","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8440abac069165425106ac1ace3b1202e60cb150"},"cell_type":"markdown","source":"Before optim: `f1 = 0.32`after `f1= 0.48`"},{"metadata":{"trusted":true,"_uuid":"cecd369bd9ae4785ccf5712af6fdf889ea7e8a36"},"cell_type":"code","source":"f1_np(sp_v, t_v, 0.5), f1_np(sp_v, t_v, ths)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"07642cfefa9de7ff75c6c1fa0c3114d114dfb7cd"},"cell_type":"code","source":"preds = learn.predict(is_test=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"aaf9de6a4d83f28f9c82e090a736e5f4c54c268c"},"cell_type":"markdown","source":"A threshold optimisation can immrove this results a lot."},{"metadata":{"trusted":true,"_uuid":"d14f7637c015996f27b0da58efc502b772e43e2f"},"cell_type":"code","source":"preds = sigmoid(preds)\nthreshold = ths\nprint(preds.shape)\nclasses = np.array([str(n) for n in range(28)])\nres = np.array([\" \".join(classes[(np.where(pp>threshold))])for pp in preds])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4c717a5305006a95b73829b9b67f3851af2fa6af"},"cell_type":"code","source":"filenames = np.array([os.path.basename(fn).split('.')[0] for fn in test_names])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"01bedf77ceb3fd749ea7715e9a53eb6d74f5cf83"},"cell_type":"code","source":"res.shape, filenames.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"30c553b5d14355abf7a67ca6964266e46c906179"},"cell_type":"code","source":"frame = pd.DataFrame(np.array([filenames, res]).T, columns = ['Id','Predicted'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e93ecf56b1b17e5708072e3410c4c0c86aee4b2c"},"cell_type":"code","source":"frame.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"24785f511a021607bda3b2dea7516fa48323adb8"},"cell_type":"code","source":"frame.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}