{"cells":[{"metadata":{},"cell_type":"markdown","source":"## Introduction\n\nMisdiagnosis of the many diseases impacting agricultural crops can lead to misuse of chemicals and more outbreaks with significant economic loss and environmental impacts. Current disease diagnosis based on human is time-consuming and expensive, and although computer-vision based models have the promise to increase efficiency."},{"metadata":{},"cell_type":"markdown","source":"## Specific Objectives\n\nObjectives of ‘Cassava Leaf Disease Classification Challenge’ is to train a model using images of training dataset to 1) Accurately classify a given image from testing dataset into different diseased category or a healthy leaf;  2) Address depth perception—angle, light, shade, physiological age of the leaf because as an added challenge, effective solutions for farmers must perform well under significant constraints, since African farmers may only have access to mobile-quality cameras with low-bandwidth; "},{"metadata":{},"cell_type":"markdown","source":"## Data\n\n### Data Description\nGiven a photo of an Cassava leaf, can you accurately assess its health? This competition will challenge you to classify each cassava image into four disease categories or a fifth category indicating a healthy leaf. "},{"metadata":{},"cell_type":"markdown","source":"### Files\n\n***train.csv***\n\n``image_id``: The id defined for each training image.\n\n``label``: Number defined for each disease.\n\n***test.csv***\n\n``image_id``: The id defined for each testing image.\n\n-------------------------------------------------------\n\n***train images***\n\nTraining images with name as image id and in jpg format.\n\n***test images***\n\nTesting images with name as image id and in jpg format.\n\n-------------------------------------------------------\n\n***label_num_to_disease_map.json***\n\nDisease name encoded as label number in train.csv "},{"metadata":{},"cell_type":"markdown","source":"## Importing required fastai modules and packages\n\npackage fastai.vision provides infinite number of functionalities to deal with computer vision problems like image classification, image segmentation etc..."},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install ../input/timm-library/timm-0.3.1-py3-none-any.whl","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# Data Processing\nimport numpy as np \nimport pandas as pd\n\n# Model\nfrom fastai.vision.all import *\nimport torchvision.models as models \n\n# utils\nimport json","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"path = Path('../input/cassava-leaf-disease-classification')\nos.listdir(path)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Lets take a look at the CSV.\ntrain = pd.read_csv(path/\"train.csv\")\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Okay let's check how many images are available in the training dataset:"},{"metadata":{"trusted":true},"cell_type":"code","source":"len(train)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Hopefully, we can develop a highly-predictive, robust, and generalizable model with this dataset.\n\nLet's check the distribution of the different classes:"},{"metadata":{"trusted":true},"cell_type":"code","source":"train['label'].hist(figsize = (10, 5))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"From above plot, we have 5 labels (4 diseases and 1 healthy):\n\n1. Cassava Bacterial Blight (CBB)\n2. Cassava Brown Streak Disease (CBSD)\n3. Cassava Green Mottle (CGM)\n4. Cassava Mosaic Disease (CMD)\n5. Healthy\n\nIn this case label 3, Cassava Mosaic Disease (CMD) is the most common label. This imbalance may have to be addressed with a weighted loss function or oversampling."},{"metadata":{},"cell_type":"markdown","source":"Let us define classes as follows"},{"metadata":{"trusted":true},"cell_type":"code","source":"f = open (path/'label_num_to_disease_map.json', \"r\") \n\ndata = json.loads(f.read()) \nprint(data)\nf.close()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Add complete path column to image_id of train.csv"},{"metadata":{"trusted":true},"cell_type":"code","source":"train['path'] = train['image_id'].map(lambda x:path/'train_images'/x)\ntrain = train.drop(columns=['image_id'])\ntrain = train.sample(frac=1).reset_index(drop=True) #shuffle dataframe\ntrain.head(10)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### DataBlock\n\nFirst let's define item and batch transforms and we'll come up with some basic data augmentations.\n\nOur ``item_tfms`` should ensure everything is ready to go into a batch, so we will use Resize.\n\nOur ``batch_tfms`` should apply any extra augmentations we may want. We'll use RandomResizedCropGPU, aug_transforms, and apply our Normalize:\n\nWe will normalize our data based on ImageNet, since that is what our pretrained model was trained with"},{"metadata":{"trusted":true},"cell_type":"code","source":"item_tfms = RandomResizedCrop(460, min_scale=0.75, ratio=(1.,1.))\nbatch_tfms = [*aug_transforms(size=224, max_warp=0), Normalize.from_stats(*imagenet_stats)]\nbs=32","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dls = ImageDataLoaders.from_df(train, #pass in train DataFrame\n                               valid_pct=0.2, #80-20 train-validation random split\n                               seed=999, #seed\n                               label_col=0, #label is in the first column of the DataFrame\n                               fn_col=1, #filename/path is in the second column of the DataFrame\n                               bs=bs, #pass in batch size\n                               item_tfms=item_tfms, #pass in item_tfms\n                               batch_tfms=batch_tfms)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dls.show_batch()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Model \n\nIn this competition we are going to use ``timm`` library by Ross Wightman. Let's add pretrained ``EfficientNet-B3`` model's weight. \n\nas we are going to use timm  we won't be using ``cnn_learner`` insted we will be using ``timm_learner`` because ``cnn_learner`` doesn't support the models from ``timm`` library. We could use ``resnet/18/34/50`` pretrained model, and we'll be using the ``accuracy metric`` as this is how this competition will grade our results with. We can also use ``mixed precision`` very easily, along with ``Label Smoothing``"},{"metadata":{"trusted":true},"cell_type":"code","source":"if not os.path.exists('/root/.cache/torch/hub/checkpoints/'):\n        os.makedirs('/root/.cache/torch/hub/checkpoints/')\n\n!cp '../input/../input/efficientnet-b3/tf_efficientnet_b3_ns-9d44bf68.pth' '/root/.cache/torch/hub/checkpoints/tf_efficientnet_b3_ns-9d44bf68.pth'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from timm import create_model\nfrom fastai.vision.learner import _update_first_layer\n\ndef create_timm_body(arch:str, pretrained=True, cut=None, n_in=3):\n    \"Creates a body from any model in the `timm` library.\"\n    model = create_model(arch, pretrained=pretrained, num_classes=0, global_pool='')\n    _update_first_layer(model, n_in, pretrained)\n    if cut is None:\n        ll = list(enumerate(model.children()))\n        cut = next(i for i,o in reversed(ll) if has_pool_type(o))\n    if isinstance(cut, int): return nn.Sequential(*list(model.children())[:cut])\n    elif callable(cut): return cut(model)\n    else: raise NamedError(\"cut must be either integer or function\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def create_timm_model(arch:str, n_out, cut=None, pretrained=True, n_in=3, init=nn.init.kaiming_normal_, custom_head=None,\n                     concat_pool=True, **kwargs):\n    \"Create custom architecture using `arch`, `n_in` and `n_out` from the `timm` library\"\n    body = create_timm_body(arch, pretrained, None, n_in)\n    if custom_head is None:\n        nf = num_features_model(nn.Sequential(*body.children())) * (2 if concat_pool else 1)\n        head = create_head(nf, n_out, concat_pool=concat_pool, **kwargs)\n    else: head = custom_head\n    model = nn.Sequential(body, head)\n    if init is not None: apply_init(model[1], init)\n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def timm_learner(dls, arch:str, loss_func=None, pretrained=True, cut=None, splitter=None,\n                y_range=None, config=None, n_out=None, normalize=True, **kwargs):\n    \"Build a convnet style learner from `dls` and `arch` using the `timm` library\"\n    if config is None: config = {}\n    if n_out is None: n_out = get_c(dls)\n    assert n_out, \"`n_out` is not defined, and could not be inferred from data, set `dls.c` or pass `n_out`\"\n    if y_range is None and 'y_range' in config: y_range = config.pop('y_range')\n    model = create_timm_model(arch, n_out, default_split, pretrained, y_range=y_range, **config)\n    learn = Learner(dls, model, loss_func=loss_func, splitter=default_split, **kwargs)\n    if pretrained: learn.freeze()\n    return learn","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learner = timm_learner(dls, \n                    'tf_efficientnet_b3_ns', \n                     opt_func = ranger,\n                     loss_func=LabelSmoothingCrossEntropy(),\n                     metrics = [accuracy]).to_native_fp16()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Finding the learning rate\n\nFastai provide functionality to find optimized learning rate. In order to find the optimized learning rate, the method ``lr_find()`` can be used. The method plot() defined on recorder can be used to plot a line plot between Loss vs. learning Rate. Fastai provide the suggestion also for optimize learning rate"},{"metadata":{"trusted":true},"cell_type":"code","source":"learner.model_dir = '/kaggle/working/models'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learner.lr_find()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"For fastai, the best way to train a model is to train the frozen pretrained model for a single epoch then train the whole pretrained model for several epochs.\n\nAs shown above, the optimal learning rate for training the frozen model is where the loss is decreasing quickly around ~1e-1. To be safe, we will use high weight decay to help prevent overfitting. We will also use another common state-of-the-art training technique: mixup."},{"metadata":{"trusted":true},"cell_type":"code","source":"learner.freeze()\nlearner.fit_flat_cos(1,1e-1, wd=0.5, cbs=[MixUp()])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learner.save('stage-1')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learner = learner.load('stage-1')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learner.unfreeze()\nlearner.lr_find()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learner.unfreeze()\nlearner.fit_flat_cos(10,5e-3,pct_start=0,cbs=[MixUp()])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learner.recorder.plot_loss()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"put the model back to fp32, and now we can export the model if we want to use later (i.e. for an inference)."},{"metadata":{"trusted":true},"cell_type":"code","source":"learner = learner.to_native_fp32()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learner.save('stage-2')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learner.export()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"fastai also comes with some additional utilities like checking the confusion matrix:"},{"metadata":{"trusted":true},"cell_type":"code","source":"interp = ClassificationInterpretation.from_learner(learner)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"interp.plot_confusion_matrix()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Inference\nIt's very simple to perform inference with fastai. The dls.test_dl function allows you to create test dataloader using the same pipeline defined earlier."},{"metadata":{"trusted":true},"cell_type":"code","source":"sample = pd.read_csv(path/'sample_submission.csv')\nsample","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"_sample = sample.copy()\n_sample['path'] = _sample['image_id'].map(lambda x:path/'test_images'/x)\n_sample = _sample.drop(columns=['image_id'])\ntest_dl = dls.test_dl(_sample)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_dl.show_batch()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Now let's pass the dataloader to the model and get predictions. We will use a common inference technique known as test-time augmentation (average predictions when passing in various augmented versions of the test image). This is also implemented in fastai. Let's do 8x TTA"},{"metadata":{"trusted":true},"cell_type":"code","source":"preds, _ = learner.tta(dl=test_dl, n=8, beta=0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Let's make a submission with these predictions!"},{"metadata":{"trusted":true},"cell_type":"code","source":"sample['label'] = preds.argmax(dim=-1).numpy()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample.to_csv('submission.csv',index=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### And Done, let's submit our submission.csv file "},{"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}