{"cells":[{"metadata":{"_uuid":"d94c329b3763059c253c940ad9f389b45c93bf93"},"cell_type":"markdown","source":"To wrap up, I take this kernel by @asanakoev and combine it with my idea here about a better classification method to see if it really works.\n\nResults:\n\n- Better training time (since we only used 68% of data): 5509s vs 8196s.'\n\n- A bit better accuracy, the threshold wasn't fine-tuned, it was taken from my original resnet18 kernel, so it can get better."},{"metadata":{"_uuid":"88e20188ddffce29fc4c3af7b6fc09bb48cc1fee"},"cell_type":"markdown","source":"## Whales recognition ResNet baseline"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"!pip install fastai==0.7.0 --no-deps\n!pip install torch==0.4.1 torchvision==0.2.1\nfrom fastai.conv_learner import *\nfrom fastai.dataset import *\nfrom tqdm import tqdm\nimport pandas as pd\nimport numpy as np\nimport os\nfrom sklearn.model_selection import train_test_split, StratifiedShuffleSplit\nimport matplotlib.pyplot as plt\nimport math\n\n\nMODEL_NAME = 'Resnet50'\nTRAIN = '../input/humpback-whale-identification/train/'\nTEST = '../input/humpback-whale-identification/test/'\nLABELS = '../input/humpback-whale-identification/train.csv'\nSAMPLE_SUB = '../input/humpback-whale-identification/sample_submission.csv'\nBBOX = '../input/generating-whale-bounding-boxes/bounding_boxes.csv'\n# Backbone architecture\narch = resnet50\n# Number of workers for data preprocessing\nnum_workers = 4","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"bf2a5c5342e855974efaeb7fe5c2b90f2cf636cf"},"cell_type":"markdown","source":"Next, we prapare out dataset to work with Fastai's pipeline."},{"metadata":{"trusted":true,"_uuid":"d9adfc15b56c7f80f291c66dc6d6f38d4d55e6a2"},"cell_type":"code","source":"df = pd.read_csv(LABELS).set_index('Image')\nnew_whale_df = df[df.Id == \"new_whale\"] # only new_whale dataset\ntrain_df = df[~(df.Id == \"new_whale\")] # no new_whale dataset, used for training\nunique_labels = np.unique(train_df.Id.values)\n\nlabels_dict = dict()\nlabels_list = []\nfor i in range(len(unique_labels)):\n    labels_dict[unique_labels[i]] = i\n    labels_list.append(unique_labels[i])\nprint(\"Number of classes: {}\".format(len(unique_labels)))\ntrain_df.Id = train_df.Id.apply(lambda x: labels_dict[x])\ntrain_labels = np.asarray(train_df.Id.values)\ntest_names = [f for f in os.listdir(TEST)]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6a910097d19053c50d60ea7ee9496ed2a55746e2"},"cell_type":"markdown","source":"Let's draw a simple histogram to see the sample-per-class distribution."},{"metadata":{"trusted":true,"_uuid":"ddef1744553be7723709a1e14253612a18c6f7e2"},"cell_type":"code","source":"labels_count = train_df.Id.value_counts()\n\nplt.figure(figsize=(18, 4))\nplt.subplot(121)\n_, _,_ = plt.hist(labels_count.values)\nplt.ylabel(\"frequency\")\nplt.xlabel(\"class size\")\n\nplt.title('class distribution; log scale')\nlabels_count.head()\n\nplt.subplot(122)\n_ = plt.plot(labels_count[1:].values)\nplt.title('w/o class new_whale; log scale')\nplt.xlabel(\"class\")\nplt.ylabel(\"log(size)\")\nplt.gca().set_yscale('log')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a494334eac4b3e32e4756395dd2167ee94368f86"},"cell_type":"code","source":"train_df['image_name'] = train_df.index\nbbox_df = pd.read_csv(BBOX).set_index('Image')\n\nrs = np.random.RandomState(42) # set random seed to be equal to the sense of life\nperm = rs.permutation(len(train_df))\n\ntr_n = train_df['image_name'].values\n# Yes, we will validate on the subset of training data\nval_n = train_df['image_name'].values[perm][:1000]\n\nprint('Train/val:', len(tr_n), len(val_n))\nprint('Train classes', len(train_df.loc[tr_n].Id.unique()))\nprint('Val classes', len(train_df.loc[val_n].Id.unique()))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"94af91d70819db979d39a4d77b2e30493498978b"},"cell_type":"code","source":"class HWIDataset(FilesDataset):\n    def __init__(self, fnames, path, transform):\n        self.train_df = train_df\n        super().__init__(fnames, transform, path)\n\n    def get_x(self, i):\n        img = open_image(os.path.join(self.path, self.fnames[i]))\n        bbox = bbox_df.loc[self.fnames[i]]\n        x0, y0, x1, y1 = bbox['x0'], bbox['y0'], bbox['x1'],  bbox['y1']\n        if not (x0 >= x1 or y0 >= y1):\n            img = img[y0:y1, x0:x1,:]\n        img = cv2.resize(img, (self.sz, self.sz))\n        return img\n\n    def get_y(self, i):\n        if (self.path == TEST): return 0\n        return self.train_df.loc[self.fnames[i]]['Id']\n\n    def get_c(self):\n        return len(unique_labels)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"140dfae2b41cbe4f770f8d80fcaba0ebc772e983"},"cell_type":"code","source":"class RandomLighting(Transform):\n    def __init__(self, b, c, tfm_y=TfmType.NO):\n        super().__init__(tfm_y)\n        self.b, self.c = b, c\n\n    def set_state(self):\n        self.store.b_rand = rand0(self.b)\n        self.store.c_rand = rand0(self.c)\n\n    def do_transform(self, x, is_y):\n        if is_y and self.tfm_y != TfmType.PIXEL: return x  # add this line to fix the bug\n        b = self.store.b_rand\n        c = self.store.c_rand\n        c = -1 / (c - 1) if c < 0 else c + 1\n        x = lighting(x, b, c)\n        return x\n    \ndef get_data(sz, batch_size):\n    \"\"\"\n    Read data and do augmentations\n    \"\"\"\n    aug_tfms = [RandomRotateZoom(deg=20, zoom=2, stretch=1),\n                RandomLighting(0.2, 0.2, tfm_y=TfmType.NO),\n                RandomBlur(blur_strengths=3,tfm_y=TfmType.NO),\n                RandomFlip(tfm_y=TfmType.NO)]\n    tfms = tfms_from_model(arch, sz, crop_type=CropType.NO, tfm_y=TfmType.NO,\n                           aug_tfms=aug_tfms)\n    ds = ImageData.get_ds(HWIDataset, (tr_n[:-(len(tr_n) % batch_size)], TRAIN),\n                          (val_n, TRAIN), tfms, test=(test_names, TEST))\n    md = ImageData(\"./\", ds, batch_size, num_workers=num_workers, classes=None)\n    return md\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"f8258255beb8fb608abb8a292b07c7161580007e"},"cell_type":"code","source":"image_size = 384\nbatch_size = 32\nmd = get_data(image_size, batch_size)\nextra_fc_layers_size = []\nlearn = ConvLearner.pretrained(arch, md, xtra_fc=extra_fc_layers_size) \nlearn.opt_fn = optim.Adam","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"scrolled":false,"_uuid":"ab2d2e0e0a6a15f38a983993486316040e9afb38"},"cell_type":"code","source":"print('Number of layer groups:', len(learn.get_layer_groups()), '\\t(first 2 groups is pretrained backbone)')\nprint('This is our extra thin on top of the backbone Resnet50 architecture:')\nlearn.get_layer_groups()[2]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ddc1f6a3bf90cf20ffdf566fd852a5f15022c9e2"},"cell_type":"markdown","source":"# Nothing fancy, just train in here\nWe start by training only the newly initialized weights (classifier), then unfreeze the model and finetune the pretrained weights"},{"metadata":{"trusted":true,"_uuid":"f3118d5e2dbe61c8d51d0e33642ea5bb0b516a54","scrolled":true},"cell_type":"code","source":"base_lr = 1e-4 # lr for the backbone\nfc_lr = 1e-3 # lr for the classifer\n\nlrs = [base_lr, base_lr, fc_lr]\n# Freeze backbone and train the classifier for 2 epochs\nlearn.fit(lrs=lrs, n_cycle=2, cycle_len=None)\n\n# Unfreeze backbone and continue training for 9 epochs\nlearn.unfreeze()\nlearn.fit(lrs, n_cycle=16, cycle_len=None)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"eabe555ace20e3b36c6266432108d31de1e58282"},"cell_type":"markdown","source":"## No Fuss Prediction with 8 TTA"},{"metadata":{"trusted":true,"_uuid":"3ed2aa4395dd4f573aeaad5ac25a5f4f5078354f"},"cell_type":"code","source":"best_th = 0.38","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"6cbfaedbad6bac01b06d87eaf3723dd260b7a51e"},"cell_type":"code","source":"preds_t,y_t = learn.TTA(is_test=True,n_aug=8)\npreds_t = np.stack(preds_t, axis=-1)\npreds_t = np.exp(preds_t)\npreds_t = preds_t.mean(axis=-1)\npreds_t = np.concatenate([np.zeros((preds_t.shape[0],1))+best_th, preds_t],axis=1)\nnp.save(\"preds.npy\",preds_t)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"82b4e79d2a05267790200de4860fd25a0a669f7f"},"cell_type":"markdown","source":"Finally, our submission."},{"metadata":{"trusted":true,"_uuid":"f5fbd91e970d375debc3270ebd5b08bb41eeb66e"},"cell_type":"code","source":"sample_df = pd.read_csv(SAMPLE_SUB)\nsample_list = list(sample_df.Image)\nlabels_list = [\"new_whale\"]+labels_list\npred_list = [[labels_list[i] for i in p.argsort()[-5:][::-1]] for p in preds_t]\npred_dic = dict((key, value) for (key, value) in zip(learn.data.test_ds.fnames,pred_list))\npred_list_cor = [' '.join(pred_dic[id]) for id in sample_list]\ndf = pd.DataFrame({'Image':sample_list,'Id': pred_list_cor})\ndf.to_csv('submission_{}.csv'.format(MODEL_NAME), header=True, index=False)\ndf.head()","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}