{"cells":[{"metadata":{},"cell_type":"markdown","source":"Fastai vision on iWildCam 2020 data resized to 256x256.\nResnet50 + mixup + TTA"},{"metadata":{},"cell_type":"markdown","source":"## Data pre-processing"},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"import os\n\nimport pandas as pd\nfrom matplotlib import pyplot as plt\n\nfrom fastai import *\nfrom fastai.vision import *\n\nimport json\n\n%matplotlib inline","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Read in training dataset."},{"metadata":{"trusted":true},"cell_type":"code","source":"test_images = os.listdir(\"../input/iwildcam2020-256/256_images/test/images/\")\ntrain_images = os.listdir(\"../input/iwildcam2020-256/256_images/train/images/\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"with open(r'/kaggle/input/iwildcam-2020-fgvc7/iwildcam2020_train_annotations.json') as json_file:\n    train_data = json.load(json_file)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train = pd.DataFrame({'id': [item['id'] for item in train_data['annotations']],\n                         'category_id': [item['category_id'] for item in train_data['annotations']],\n                         'image_id': [item['image_id'] for item in train_data['annotations']],\n                         'location': [item['location'] for item in train_data['images']],\n                         'file_name': [item['file_name'] for item in train_data['images']]})\n\ndf_train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train = df_train[df_train['file_name'].isin(train_images)]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train.shape","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Split training validation using categories. 70% of category entries in training set."},{"metadata":{"trusted":true},"cell_type":"code","source":"cat_images = dict()\ncat_count = dict()\n\nannotations = train_data['annotations']\n_images = train_data['images']\nfor i, annotation in enumerate(annotations):\n    _img = annotation['image_id']\n    cat = annotation['category_id']\n    \n    imgs = cat_images.get(cat, None)\n    if imgs is None:\n        cat_images[cat] = [{'image_id': _img, 'category': cat}]\n    else:\n        cat_images[cat].append({'image_id': _img, 'category': cat})\n        \n    count = cat_count.get(cat, 0)\n    if count == 0:\n        cat_count[cat] = 1\n    else:\n        cat_count[cat] += 1\n        \nn_train = dict()\nn_val = dict()\n\nfor cat, count in cat_count.items():\n    _train = math.floor(count * 0.70)\n    if _train < 1:\n        _train = 1\n    _val = count - _train\n    n_train[cat] = _train\n    n_val[cat] = _val\n\ntrain_images = []\nval_images = []\nfor cat in cat_images.keys():\n    random.shuffle(cat_images[cat])\n    train_images += cat_images[cat][:n_train[cat]]\n    val_images += cat_images[cat][n_train[cat]:]\n\nval_img_dt = pd.DataFrame(val_images)\n\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Tag validation set."},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train['is_valid'] = np.where(df_train.image_id.isin(val_img_dt['image_id']), True, False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"Check what is location split."},{"metadata":{"trusted":true},"cell_type":"code","source":"loc_valid = df_train.loc[(df_train['is_valid'] == True)].location.unique()\nloc_train = df_train.loc[(df_train['is_valid'] == False)].location.unique()\n\nloc_valid.shape\ndf_train.category_id.unique().shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train.groupby('is_valid').size()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Remove corrupted images."},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train.drop(df_train.loc[df_train['file_name']=='87022118-21bc-11ea-a13a-137349068a90.jpg'].index, inplace=True)\ndf_train.drop(df_train.loc[df_train['file_name']=='8792549a-21bc-11ea-a13a-137349068a90.jpg'].index, inplace=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train.category_id.unique().shape","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Read test data."},{"metadata":{"trusted":true},"cell_type":"code","source":"with open(r'/kaggle/input/iwildcam-2020-fgvc7/iwildcam2020_test_information.json') as f:\n    test_data = json.load(f)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_test = pd.DataFrame.from_records(test_data['images'])\ndf_test.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Modelling part"},{"metadata":{},"cell_type":"markdown","source":"I'm creating a ImageDataBunch in a few steps. DataBunch is an object that the model needs.\nFirst I create an `ImageList` with training and test data.\nSecondly I define the transformations that will be applied to the pictures.\nI say that labels for the training come from the dataframe and are stored in `category_id` column. I add the test set.\nFinally I can create the databunch. Apply transofrmations to the data, resize all pictures to 128x128, add reflection padding. I want to use a batch size `bs` of 256 images, and normalize the data with `imagenet_stats`."},{"metadata":{"trusted":true},"cell_type":"code","source":"train, test = [ImageList.from_df(df, path='../input/iwildcam2020-256/256_images/', cols='file_name', folder=folder, suffix='') \n               for df, folder in zip([df_train, df_test], ['train/images', 'test/images'])]\ntrfm = get_transforms(max_rotate=20, max_zoom=1.3, max_lighting=0.4, max_warp=0.4,\n                      p_affine=1., p_lighting=1.)\nsrc = (train.use_partial_data(1)\n        .split_from_df(col='is_valid')\n        .label_from_df(cols='category_id')\n        .add_test(test))\ndata = (src.transform(trfm, size = 128, padding_mode = 'reflection')\n        .databunch(path=Path('.'), bs = 256).normalize(imagenet_stats))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(data.classes)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"org_classes = pd.DataFrame({\"org_category\": data.classes})\norg_classes['Category'] = org_classes.index","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def _plot(i,j,ax):\n    x,y = data.train_ds[1]\n    x.show(ax, y=y)\n\nplot_multi(_plot, 3, 3, figsize=(8,8))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Show batch."},{"metadata":{"trusted":true},"cell_type":"code","source":"data.show_batch()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Show number of categories in the data."},{"metadata":{"trusted":true},"cell_type":"code","source":"data.c","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"I use transfer learning. This means I will use a pre-trained model in this case Resnet50 and adapt it to my dataset. In transfer learning we keep the convolutionals layers: body or the backbone with their weigths pre-trained on ImageNet and only define a new head. I use the head defined by the fastai library.\n\nI use accuracy as the metric to print. I add mixup. Model won't be trained on actual photos, but on random combinations of them."},{"metadata":{"trusted":true},"cell_type":"code","source":"learn = cnn_learner(data, base_arch=models.resnet50, metrics=accuracy).mixup()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The most important parameter to set is learning rate which is the step size in the optimization to reach the loss minimum. To find the learning rate I use `lr_find`. What it does is it starts with a very small lr, increases it with every batch and records the loss. Then the lr values are ploted against the losses."},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.lr_find()\nlearn.recorder.plot(suggestion=True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Recommended methods choosing the LR:\n * at the steepest decline of loss\n * 10x prior to the minimum loss. "},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.recorder.min_grad_lr","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Fastai.vision module divides the architecture in 3 groups and trains them with variable learning rates depending on what you input. (Starting layers usually don't require large variations in parameters)\n\nAdditionally, if you use 'fit_one_cycle', all the groups will have learning rate annealing with their respective variable learning.\nFirst I freeze the body weights and only train the head."},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.fit_one_cycle(10, slice(0.01))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"After the random weights in the head are trained a bit, we can unfreeze the weights in the whole network and train everything."},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.unfreeze()\nlearn.lr_find()\nlearn.recorder.plot(suggestion=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.fit_one_cycle(10, slice(1e-5, 1e-4))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"interp = ClassificationInterpretation.from_learner(learn)\ninterp.plot_confusion_matrix(figsize=(12,12), dpi=60)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Make predictions on the test set using test time augmentation. TTA makes 4 predictions using the transforms of the training set and averages them. "},{"metadata":{"trusted":true},"cell_type":"code","source":"preds,y = learn.TTA(ds_type=DatasetType.Test)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"pred_csv = pd.DataFrame(preds.numpy())\npred_csv['Id'] = learn.data.test_ds.items\npred_csv.to_csv(\"outout_preds.csv\", index = False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission = pd.read_csv('../input/iwildcam-2020-fgvc7/sample_submission.csv')\nid_list = list(submission.Id)\npred_list = list(np.argmax(preds.numpy(), axis=1))\npred_dict = dict((key, value.item()) for (key, value) in zip(learn.data.test_ds.items,pred_list))\npred_ordered = [pred_dict['../input/iwildcam2020-256/256_images/test/images/' + id + '.jpg'] for id in id_list]\nsubmission_with_idx = pd.DataFrame({'Id':id_list,'Category':pred_ordered})\nsubmission_fixed_labels = pd.merge(submission_with_idx, org_classes, on = 'Category', how='left')\nsubmission_fixed_labels = submission_fixed_labels.drop(['Category'], axis = 1)\nsubmission_fixed_labels.rename(columns={'org_category': 'Category'}, inplace=True)\n\nsubmission_fixed_labels.to_csv(\"submission.csv\".format(Category),index = False)\nprint(\"Done\")","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}