{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Cassava Plant Leaf Disease Classification "},{"metadata":{},"cell_type":"markdown","source":"<img style=\"float: left;width:500px\" src=\"data:image/jpeg;base64,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\">"},{"metadata":{},"cell_type":"markdown","source":"## About Cassava Plants\n\nAs the second-largest provider of carbohydrates in Africa, cassava is a key food security crop grown by smallholder farmers because it can withstand harsh conditions. At least 80% of household farms in Sub-Saharan Africa grow this starchy root, but viral diseases are major sources of poor yields."},{"metadata":{},"cell_type":"markdown","source":"## Importing Libraries and Data"},{"metadata":{"trusted":false},"cell_type":"code","source":"import os\nimport glob\nimport json\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport tensorflow as tf\nimport tensorflow.keras as k\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom keras import backend as K \nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.optimizers import RMSprop, Adam, Nadam\nfrom tensorflow.keras.applications import VGG16\nfrom tensorflow.keras.applications import ResNet50\nfrom tensorflow.keras.applications import EfficientNetB4\nfrom tensorflow.keras.applications import EfficientNetB0\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.layers import GlobalAveragePooling2D, Flatten, Dense, Dropout\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau\n\n#install efficientnet that has noisy-student weights\n!pip install -U efficientnet\nimport efficientnet.keras as efn\n\n#Bi-Tempered Logistic Loss\ncwd = os.getcwd()\nos.chdir('../input/bitempered-logistic-loss-direct-upload')\nfrom tf_bi_tempered_loss import BiTemperedLogisticLoss\nos.chdir(cwd)\n\n# remove warnings (most of them are not critical and meant for debugging tensorflow)\nimport warnings\nwarnings.filterwarnings(\"ignore\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Mixed precision to speedup training.\n**You Need GPU compute capability of at least 7.0, Remove the below cell if that isn't the case.For more information refer __[this](https://docs.nvidia.com/deeplearning/performance/mixed-precision-training/index.html)__**"},{"metadata":{"trusted":false},"cell_type":"code","source":"from tensorflow.keras.mixed_precision import experimental as mixed_precision\npolicy = mixed_precision.Policy('mixed_float16')\nmixed_precision.set_policy(policy)","execution_count":null,"outputs":[]},{"metadata":{"trusted":false},"cell_type":"code","source":"work_dir = '../input/cassava-leaf-disease-classification/'\nos.listdir(work_dir) \ntrain_path = '/kaggle/input/cassava-leaf-disease-classification/train_images/'\n\ndata = pd.read_csv(work_dir + 'train.csv')\ndata.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Exploratory Data Analysis"},{"metadata":{},"cell_type":"markdown","source":"**Let's convert the labels to meaningful class names for better understanding.**"},{"metadata":{"trusted":false},"cell_type":"code","source":"f = open(work_dir + 'label_num_to_disease_map.json')\nreal_labels = json.load(f)\nreal_labels = {int(k):v for k,v in real_labels.items()}\n\ndata['class_name'] = data['label'].map(real_labels)\ndata.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Let's see what classes are present in the database.**"},{"metadata":{"trusted":false},"cell_type":"code","source":"pd.DataFrame(real_labels.items(),columns=['Labels','Disease Name'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Let's have a looking on the distribution of the classes in the dataset**"},{"metadata":{"trusted":false},"cell_type":"code","source":"sns.set_style(\"whitegrid\")\nplt.figure(figsize=(15,6))\nsns.countplot(data[\"class_name\"], edgecolor=\"black\", palette=\"mako\")\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":false},"cell_type":"code","source":"perc_CMD = (data['label'].value_counts()[3]/np.sum(data['label'].value_counts()))*100\nprint(perc_CMD)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Key Insights:\n- Cassava Mosaic Disease (CMD) is the dominant class in the training set constituting  **61.5%** of the total data.\n- The dataset is very skewed so there might be a need of upsampling or undersampling."},{"metadata":{},"cell_type":"markdown","source":"### Experiment 1\n\n**The test data for the competetion is private and only available after submitting out kernel. Lets test out whether the test set is skewed as well. For this we will predict all the data to be the dominant class: CMD (3).**"},{"metadata":{"trusted":false},"cell_type":"code","source":"# TEST_DIR = 'test_images/'\n# test_images = os.listdir(TEST_DIR)\n# predictions = []\n\n# for image in test_images:\n#     predictions.append(3)\n\n# sub = pd.DataFrame({'image_id': test_images, 'label': predictions})\n# sub.to_csv('submission.csv', index = False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Surprisingly The score came out to be 0.60,This means even the test set is very much skewed, CMD seems to be a very common disease**\n\n<img style=\"float: left;\" src=\"https://i.imgur.com/ZLZjhxS.png\"></img>\n\n****"},{"metadata":{},"cell_type":"markdown","source":"### Let's Have a look at the images of different classes\n**Label 0 : Cassava Bacterial Blight (CBB)**"},{"metadata":{"trusted":false},"cell_type":"code","source":"df0 = data[data[\"label\"] == 0]\nfiles = df0[\"image_id\"].sample(4).tolist()\n\nplt.figure(figsize=(15,5))\nindex = 0\nfor file in files:\n    image = Image.open(train_path + file)\n    plt.subplot(1, 4, index + 1)\n    plt.imshow(image)\n    plt.axis(\"off\")\n    index += 1\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Label 1: Cassava Brown Streak Disease (CBSD)**"},{"metadata":{"trusted":false},"cell_type":"code","source":"df0 = data[data[\"label\"] == 1]\nfiles = df0[\"image_id\"].sample(4).tolist()\n\nplt.figure(figsize=(15,5))\nindex = 0\nfor file in files:\n    image = Image.open(train_path + file)\n    plt.subplot(1, 4, index + 1)\n    plt.imshow(image)\n    plt.axis(\"off\")\n    index += 1\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Label 2: Cassava Green Mottle (CGM)**"},{"metadata":{"trusted":false},"cell_type":"code","source":"df0 = data[data[\"label\"] == 2]\nfiles = df0[\"image_id\"].sample(4).tolist()\n\nplt.figure(figsize=(15,5))\nindex = 0\nfor file in files:\n    image = Image.open(train_path + file)\n    plt.subplot(1, 4, index + 1)\n    plt.imshow(image)\n    plt.axis(\"off\")\n    index += 1\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Label 3: Cassava Mosaic Disease (CMD)**"},{"metadata":{"trusted":false},"cell_type":"code","source":"df0 = data[data[\"label\"] == 3]\nfiles = df0[\"image_id\"].sample(4).tolist()\n\nplt.figure(figsize=(15,5))\nindex = 0\nfor file in files:\n    image = Image.open(train_path + file)\n    plt.subplot(1, 4, index + 1)\n    plt.imshow(image)\n    plt.axis(\"off\")\n    index += 1\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Label 4: Healthy**"},{"metadata":{"trusted":false},"cell_type":"code","source":"df0 = data[data[\"label\"] == 4]\nfiles = df0[\"image_id\"].sample(4).tolist()\n\nplt.figure(figsize=(15,5))\nindex = 0\nfor file in files:\n    image = Image.open(train_path + file)\n    plt.subplot(1, 4, index + 1)\n    plt.imshow(image)\n    plt.axis(\"off\")\n    index += 1\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Note:** There are many noisy labels in our dataset. To mitigate this we will be using label smoothing and Bi-Tempered Logistic loss, as we will see in the later parts of the notebook."},{"metadata":{"trusted":false},"cell_type":"code","source":"# this is important, adjust image size to fit your memory.\nIMG_SIZE = 512\nsize = (IMG_SIZE,IMG_SIZE)\n# number of classes\nn_CLASS = 5\n# choose batch size according to available VRAM\nBATCH_SIZE = 16  #reduce this if you get a OOM(out of memory error)\n\n# number of epochs\nEPOCHS = 7","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"###  Image Augumentation\n\n#### What is it?\nGenerally in cases where the trainset is really small or mostly similar, we can articficially generate more train and test images by slightly changing the color, random rotation, shifts, shear and flips etc. Be careful not to change the image too much as it can be misleading for our model.\nImage augumentation on small datasets boosts the models performace.\n\n#### Why is it important in this case?\nThis dataset is not very small but not very large either. Mostly I use this because this gave a boost in Leaderboard score and Cross Validation accuracy. This can be experimental as well. It is a good practice to do image augumentation for better generalization."},{"metadata":{},"cell_type":"markdown","source":"**Install ImageDataAugumentor**\n\nThis is only one time step if on a local machine, If on kaggle kernel/colab notebook, do this every new session."},{"metadata":{"trusted":false},"cell_type":"code","source":"!pip install git+https://github.com/mjkvaak/ImageDataAugmentor","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We will use **Albumentations** library, It is a very popular image augumentation library.\nFor a quickstart guide follow [this](https://analyticsindiamag.com/hands-on-guide-to-albumentation/)"},{"metadata":{"trusted":false},"cell_type":"code","source":"from ImageDataAugmentor.image_data_augmentor import *\nimport albumentations as A\n\n\n# add as many alteration you want here, dont meddle with validation data much \n# as we won't be doing TTA(test time augumentation)for now. TTA seems to imrove LB score, but I will skip this here.\n\ntrain_augmentations = A.Compose([\n            A.Transpose(p=0.5),\n            A.HorizontalFlip(p=0.5),\n            A.VerticalFlip(p=0.5),\n            A.HueSaturationValue(\n                hue_shift_limit=0.2,\n                sat_shift_limit=0.2,\n                val_shift_limit=0.2,\n                p=0.5),\n            A.RandomBrightnessContrast(\n                brightness_limit=(-0.1,0.1),\n                contrast_limit=(-0.1, 0.1),\n                p=0.5),\n            A.CoarseDropout(p=0.5),\n            A.Cutout(p=0.5),\n            A.Flip(p=0.5),\n            A.ShiftScaleRotate(p=0.5),\n            A.HueSaturationValue(p=0.5, hue_shift_limit=0.2, sat_shift_limit=0.2, val_shift_limit=0.2),\n            A.RandomBrightnessContrast(p=0.5, brightness_limit=(-0.2,0.2), contrast_limit=(-0.2, 0.2)),\n            A.RandomCrop(IMG_SIZE, IMG_SIZE, p=1),\n#             A.Normalize(\n#                 mean=[0.485, 0.456, 0.406],\n#                 std=[0.229, 0.224, 0.225],\n#                 max_pixel_value=255.0,\n#                 p=1.0)\n            ], p=1)\n\nval_augmentations = A.Compose([\n                A.CenterCrop(IMG_SIZE, IMG_SIZE, p=1),\n#                 A.Normalize(\n#                 mean=[0.485, 0.456, 0.406],\n#                 std=[0.229, 0.224, 0.225],\n#                 max_pixel_value=255.0,\n#                 p=1.0)\n                ], p=1)\n\n# Train and validation data Generators\ndatagen_train = ImageDataAugmentor(augment=train_augmentations)\ndatagen_val = ImageDataAugmentor(augment=val_augmentations)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Now let's feed data to above generator**"},{"metadata":{"trusted":false},"cell_type":"code","source":"def DataGenerator(train,val):\n    train_set = datagen_train.flow_from_dataframe(train,\n                                 directory = train_path,\n                                 seed=7,\n                                 x_col = 'image_id',\n                                 y_col = 'class_name',\n                                 target_size = size,\n                                 class_mode = 'categorical',\n    #                              interpolation = 'nearest',  #avoid this when using albumentation(interpolation integer error)\n                                 shuffle = True,\n                                 batch_size = BATCH_SIZE)\n\n    val_set = datagen_val.flow_from_dataframe(val,\n                                 directory = train_path,\n                                 seed=7,\n                                 x_col = 'image_id',\n                                 y_col = 'class_name',\n                                 target_size = size,\n                                 class_mode = 'categorical',\n    #                              interpolation = 'nearest',\n                                 batch_size = BATCH_SIZE)\n    return train_set,val_set","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Now let's check some images after augumentation**"},{"metadata":{"trusted":false},"cell_type":"code","source":"train_temp = data.iloc[:10]\nval_temp = data.iloc[-10:]\ntrain_set_temp,val_set_temp = DataGenerator(train_temp,val_temp)","execution_count":null,"outputs":[]},{"metadata":{"trusted":false},"cell_type":"code","source":"train_images, _ = next(train_set_temp)\n\nplt.figure(figsize=(14, 14))\nfor i in range(9):\n    image = train_images[i]\n    ax = plt.subplot(3, 3, i + 1)\n    plt.imshow(image)\n    plt.axis(\"off\")\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Note:** While using this to display images beware that your batchsize is less than or equal to the total subplots in the above code."},{"metadata":{"trusted":false},"cell_type":"code","source":"val_images, _ = next(val_set_temp)\n\nplt.figure(figsize=(14, 14))\nfor i in range(9):\n    image = train_images[i]\n    ax = plt.subplot(3, 3, i + 1)\n    plt.imshow(image)\n    plt.axis(\"off\")\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Model Building (Transfer Learning)"},{"metadata":{},"cell_type":"markdown","source":"### Label Smoothing\n\nAs I pointed out earlier these data labels are noisy. Label smooting is a technique useed to cope up with such datasets. We will also see other solutions such as using Bi-Tempered Logistic loss as loss function in place of the categorical cross entropy that we use in general.\n\nLabel smoothing can be used with the inbuilt Categorical_crossentropy function that keras has.\n\n#### How Label smoothing works?\nBasically we say that we arent 100% confident in our labels. For example: For a digit recognition task, an image of 9 will have a one-hot encoded vector as follows [0,0,0,0,0,0,0,0,1]. This makes sense as well.\nLet's say we aren't sure that our labels are correct in all the datapoints. lets say we use label smoothing with a value of 0.11\n\nnew_labels = onehot_labels * (1 - label_smoothing) + label_smoothing / num_classes\n\nnew_labels = [0,0,0,0,0,0,0,0,1]*(0.89)+0.012\nnew_labels = [0.012,0.012,0.012,0.012,0.012,0.012,0.012,0.012,0.902]\n\nThis means that we are 90% confident in our labels.\nThe general rule of thumb is to take label smoothing as 1/n_classes. but this is value that must be derived after several experiments.\n\nFor more info on label smoothing : Refer [this](https://towardsdatascience.com/what-is-label-smoothing-108debd7ef06)\n\n### K- Fold Cross Validation\n\nWe will also use K-Fold cross validation to train our model. We will use the stratifiedKfold function because of the skew in out data. This will preserve the ratio of each class in each fold.\n\n<img style=\"float: left;\" src=\"https://i.imgur.com/N5AWSZk.png\">\n\n\nFor more infomation refer [this](https://medium.com/datadriveninvestor/k-fold-cross-validation-6b8518070833)"},{"metadata":{},"cell_type":"markdown","source":"**Let's create all our models here.**"},{"metadata":{"trusted":false},"cell_type":"code","source":"def create_model_resnet50():\n    \n    model = Sequential()\n    model.add(ResNet50(input_shape = (IMG_SIZE, IMG_SIZE, 3), include_top = False, weights = 'imagenet'))\n    model.add(GlobalAveragePooling2D())\n    model.add(Dense(512, activation = 'relu', bias_regularizer=tf.keras.regularizers.L1L2(l1=0.01, l2=0.001)))\n    model.add(Dropout(0.7))\n    model.add(Dense(n_CLASS, activation = 'softmax',dtype='float32')) #this is very important to use mixed_precision\n    \n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":false},"cell_type":"code","source":"def create_model_vgg16():\n    \n    model = Sequential()\n    model.add(VGG16(input_shape = (IMG_SIZE, IMG_SIZE, 3), include_top = False, weights = 'imagenet'))\n    model.add(GlobalAveragePooling2D())\n    model.add(Dense(512, activation = 'relu', bias_regularizer=tf.keras.regularizers.L1L2(l1=0.01, l2=0.001)))\n    model.add(Dropout(0.5))\n    model.add(Dense(n_CLASS, activation = 'softmax',dtype='float32')) #this is very important to use mixed_precision\n    \n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":false},"cell_type":"code","source":"def create_model_effnetb4():\n    \n    model = Sequential()\n    model.add(efn.EfficientNetB4(input_shape = (IMG_SIZE, IMG_SIZE, 3), include_top = False, weights = 'noisy-student'))\n    model.add(GlobalAveragePooling2D())\n    model.add(Dense(256, activation = 'relu', bias_regularizer=tf.keras.regularizers.L1L2(l1=0.01, l2=0.001)))\n    model.add(Dropout(0.7))\n    model.add(Dense(n_CLASS, activation = 'softmax',dtype='float32')) #this is very important to use mixed_precision\n    \n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":false},"cell_type":"code","source":"def create_model_effnetb0():\n    \n    model = Sequential()\n    model.add(EfficientNetB0(input_shape = (IMG_SIZE, IMG_SIZE, 3), include_top = False, weights = 'imagenet'))\n    model.add(GlobalAveragePooling2D())\n#     model.add(Dense(64, activation = 'relu', bias_regularizer=tf.keras.regularizers.L1L2(l1=0.01, l2=0.001)))\n    model.add(Dropout(0.2))\n    model.add(Dense(n_CLASS, activation = 'softmax',dtype='float32')) #this is very important to use mixed_precision\n    \n    return model","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Crossentropy Loss Function\n\nCross Entropy is defined as:\n\n<img style=\"float: center;\" src=\"https://i.imgur.com/bc5430R.png\"></img><br>\n\nIn our Case n = 5. To understand this more deeply refer [This](https://towardsdatascience.com/cross-entropy-loss-function-f38c4ec8643e)"},{"metadata":{"trusted":false},"cell_type":"code","source":"loss = tf.keras.losses.CategoricalCrossentropy(from_logits = True,\n                                               label_smoothing=0.2,\n                                               name='categorical_crossentropy' )\n# loss = BiTemperedLogisticLoss(t1=0.6, t2=1.4)","execution_count":null,"outputs":[]},{"metadata":{"scrolled":true,"trusted":false},"cell_type":"code","source":"n_splits = 5\nfold_no = 0\n\nkfold = StratifiedKFold(n_splits=n_splits, shuffle=True,random_state=7)\nfor train_index, val_index in kfold.split(data,data['class_name']):\n    print(f\"Fold number : {fold_no+1}/{n_splits}\")\n    train,val = data.iloc[list(train_index)],data.iloc[list(val_index)]\n    train_set,val_set = DataGenerator(train,val)\n    \n    # useful when predicting, we can ensemble best model from each fold. \n    \n    # change models here, rest all is same\n    model = create_model_effnetb4()     \n    \n    \n    model_name = 'effnetb4'\n    fold_name = '-fold.h5'\n    filepath = model_name + str(fold_no+1) + fold_name\n    \n    # Adding callbacks\n    # Earlystopping to save time\n    es = EarlyStopping(monitor='val_loss', mode='min', patience=3,\n                   restore_best_weights=True, verbose=1)\n\n    # Save the model with the minimum validation loss(best model for each fold)\n    checkpoint_cb = ModelCheckpoint(filepath,\n                                save_best_only=True,\n                                monitor = 'val_loss',\n                                mode='min')\n\n    # reduce learning rate\n    reduce_lr = ReduceLROnPlateau(monitor = 'val_loss',\n                              factor = 0.3,\n                              patience = 2,\n                              min_lr = 1e-6,\n                              mode = 'min',\n                              verbose = 1)\n    callbacks=[es, checkpoint_cb, reduce_lr]\n    \n    # We use the Adam optimizer\n    model.compile(optimizer = Adam(learning_rate = 1e-3),\n                                    loss = loss,\n                                    metrics = ['categorical_accuracy']) \n    \n    history = model.fit(x = train_set,\n                         validation_data = val_set,\n                         epochs= EPOCHS,batch_size=BATCH_SIZE,\n                         callbacks=callbacks)\n    fold_no += 1\n    K.clear_session()\n    if (fold_no) == n_splits:\n        print(\"Training Finished\")\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Inference & Submission"},{"metadata":{},"cell_type":"markdown","source":"Make another Notebook for inference as Internet isn't allowed in submission notebook."},{"metadata":{"trusted":false},"cell_type":"code","source":"# models = []\n# image_size = 512\n# n_splits=2\n# model_name = 'effnetb4'\n# fold_name = '-fold.h5'\n# cwd = os.getcwd()\n# os.chdir('../input/newcassavadataset')\n# for i in range(n_splits):\n#     effnetb4 = keras.models.load_model(model_name + str(i+1) + fold_name)\n#     models.append(effnetb4)\n# os.chdir(cwd)\n# TEST_DIR = '../input/cassava-leaf-disease-classification/test_images/'\n# test_images = os.listdir(TEST_DIR)\n# preds = []\n# results = []\n\n# for image_id in test_images:\n#     image = Image.open(os.path.join('../input/cassava-leaf-disease-classification', \"test_images\", image_id))\n#     image = image.resize((image_size, image_size))\n#     image = np.expand_dims(image, axis = 0)\n#     for model in models:\n#         p = model.predict(image)\n#         preds.append(np.argmax(p))\n#         print(p)\n#     res = max(set(preds), key = preds.count)\n#     results.append(res)\n\n# sub = pd.DataFrame({'image_id': test_images, 'label': results})\n# sub.to_csv('submission.csv', index = False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Conclusion and Results"},{"metadata":{},"cell_type":"markdown","source":"| Model Name | Image Size | Loss Metric | Label Smoothing | Validation Set Score | Leaderboard Score | Remarks | K-fold|\n|-|-|-|-|-|-|-|-|\n| Naive Model (Predict all images  as dominant class) | 380 | N/A | N/A | N/A | 0.605 | Test set is skewed too | N/A |\n| ResNet-50 (imagenet weights) | 380 | Cross-Entropy | 0.0001 | 0.8745 | 0.605 | kaggle terminated after 9hrs, image size increase might help, freezing layers can be experimented with. | Test_split=0.2 |\n| ResNet-50 (imagenet) | 380 | Cross-Entropy | 0.0001 | Not Documented | 0.161 | this dataset is very different from imagenet database, freezing layers is useless. | test_split=0.2 |\n| VGG16 (imagenet) | 380 | Cross-Entropy | 0.0001 | 0.8550 | Not Evaluated | Kaggle terminated after 9hrs | test_split=0.2 |\n| EfficientNet B4 (imagenet) | 380 | Cross-Entropy | 0.1 | Not Documented | 0.830 | using 512 might be better. | test_split=0.25 |\n| EfficientNet B4 (imagenet) | 512 | Cross-Entropy | 0.2 | 0.8944 | 0.859 | Can be improved with kfold and little more augumentation | test_split=0.2 |\n| EfficientNet B4 (imagenet) | 512 | Cross-Entropy | 0.2 | 0.9010 | 0.874 | kaggle terminated after 9 hrs, submitted the checkpoint which gave this score | test_split=0.2 |\n| EfficientNet B4 (imagenet) | 512 | Cross-Entropy | 0.1 | Not documented | 0.832 | Using kfold takes a lot of time and training it on kaggle is hard | 4-Fold (No Ensembling) |\n| EfficientNet B0 (imagenet) | 512 | Cross-Entropy | 0.1 | Not Documented | 0.885 (top) | the lighter models work better, but will stick to B4 with kfold | N/A |\n| EfficientNet B4 (imagenet) |512  |Cross-Entropy  |0.2  |Not Documented  | 0.888 | The 3 fold process stopped midway,I used the first checkpointed mmodel to predict which actually gave a very good result |1/2  |"},{"metadata":{},"cell_type":"markdown","source":"## Future Work"},{"metadata":{},"cell_type":"markdown","source":"-  Try noisy student weights instead of imagenet weights.\n-  calculate mean and standard deviation manually for this dataset.\n-  RAdam+Lookahead optimizer instead of Adam\n-  Bi-Tempered Logistic Loss and Focal Cosine Loss instead of categorical cross entropy.\n-  Ensemble different models.\n-  Test Time Augumentation (TTA) is very optional as discussion threads say its useful but can mess things up.\n-  Use the 2019 competetion model to improve the performance"},{"metadata":{},"cell_type":"markdown","source":"## References\n\n* [This awesome notebook that taught me Augumentations in Keras(Definitely Check this out)](https://www.kaggle.com/junyingsg/end-to-end-cassava-disease-classification-in-keras/notebook#Model-Building-and-Selection)\n* [Cross Entropy Made Easy](https://towardsdatascience.com/cross-entropy-loss-function-f38c4ec8643e)\n* [Kfold Reference](https://medium.com/datadriveninvestor/k-fold-cross-validation-6b8518070833)\n* [Label Smoothing](https://towardsdatascience.com/what-is-label-smoothing-108debd7ef06)\n* [More Information on Mixed Precision](https://docs.nvidia.com/deeplearning/performance/mixed-precision-training/index.html)"}],"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}