{"cells":[{"metadata":{},"cell_type":"markdown","source":"# CASSAVA LEAF DISEASE CLASSIFICATION\n\nThis is starter training notebook for tensorflow users for this competition. This notebook is run with internet 'on' for downloading pretrained models.\n\n- It uses tf.data for loading data for training\n- model is trained using transfer learning."},{"metadata":{},"cell_type":"markdown","source":"Importing required dependencies"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport os\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.model_selection import train_test_split","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Getting Path to csv files:\n\n- train.csv\n- submission.csv\n\nsubmission.csv is loaded only for checking format of submission file"},{"metadata":{"trusted":true},"cell_type":"code","source":"for dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        if filename.endswith(\".csv\"):\n            print(os.path.join(dirname, filename))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission_file =\"/kaggle/input/cassava-leaf-disease-classification/sample_submission.csv\"\ntrain_path =\"/kaggle/input/cassava-leaf-disease-classification/train.csv\"\ntrain_images_path = \"../input/cassava-leaf-disease-classification/train_images\"","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Reading CSV files and viewing their heads"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"train_data = pd.read_csv(train_path)\nsubmission = pd.read_csv(submission_file)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_data.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Checking class imbalance "},{"metadata":{"trusted":true},"cell_type":"code","source":"train_data.label.value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.countplot(\"label\", data=train_data)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"From above plot we can see that dataset has more images of class 3 (Cassava Mosaic Disease (CMD)), So we need to have care that we split data in stratified manner."},{"metadata":{},"cell_type":"markdown","source":"## Splitting data into training and validation"},{"metadata":{"trusted":true},"cell_type":"code","source":"trainX,valX,trainY,valY = train_test_split(train_data.iloc[:,0].values,\n                                             train_data.iloc[:,1].values,\n                                             stratify= train_data.iloc[:,1].values,\n                                             random_state=11,\n                                             test_size=0.2\n                                            )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"num_train_images = len(trainX)\nnum_eval_images = len(valX)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Number of train images: \",num_train_images)\nprint(\"Number of validation images: \",num_eval_images)\nprint(\"Shape of train data: \",trainX.shape)\nprint(\"Shape of validation data: \",valX.shape)\nprint(\"Shape of train targets: \",trainY.shape)\nprint(\"Shape of validation targets: \",valY.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train = pd.DataFrame.from_dict({\"image_id\":trainX, \"label\":trainY})\ndf_val = pd.DataFrame.from_dict({\"image_id\":valX, \"label\":valY})","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train.label.value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.countplot(\"label\", data=df_train)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_val.label.value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sns.countplot(\"label\", data=df_val)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"In above plots we can see both train and validation data which we have splitted have same distribution of labels which we want."},{"metadata":{},"cell_type":"markdown","source":"## Setting Parameters here"},{"metadata":{"trusted":true},"cell_type":"code","source":"EPOCHS=5\nBATCH_SIZE=32\nIMAGE_DIM=(224,224)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Helper Functions\n\n- get_path_of_image : for getting full path to image from its name\n- load_tf_image : loading and normalizing image and converting to tensor\n- generate_tf_dataset : generate tf dataset"},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_path_of_image(image):\n    return os.path.join(train_images_path,image)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def load_tf_image(image_path,dim):\n    image = tf.io.read_file(image_path)\n    image = tf.image.decode_jpeg(image,channels=3)\n    image = tf.image.resize(image,dim)\n    image = tf.image.convert_image_dtype(image, tf.float32)\n    image = image/255.0\n    return image","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def generate_tf_dataset(X,Y,image_size):\n    X = [get_path_of_image(str(x)) for x in X]\n    datasetX = tf.data.Dataset.from_tensor_slices(X).map(\n            lambda path: load_tf_image(path,image_size),\n            num_parallel_calls=tf.data.experimental.AUTOTUNE\n    )\n    datasetY = tf.data.Dataset.from_tensor_slices(tf.keras.utils.to_categorical(Y))\n    dataset = tf.data.Dataset.zip((datasetX,datasetY))\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.repeat()\n    dataset = dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)\n    return dataset","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def plot_images_grid(data,num_rows=1,class_names=None):\n    images, labels = data\n    n=len(images)\n    labels = np.argmax(labels.numpy(), axis=1)\n    if n > 1:\n        num_cols=np.ceil(n/num_rows)\n        fig,axes=plt.subplots(ncols=int(num_cols),nrows=int(num_rows))\n        axes=axes.flatten()\n        fig.set_size_inches((20,20))\n        for i,image in enumerate(images):\n            axes[i].imshow(image.numpy())\n            axes[i].axis('off')\n            axes[i].set_title(class_names[str(labels[i])])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Setting up train and validation tf dataset\n\nCreate tf datasets using tf.data for training and validation. A single element of these datasets return *(Image,Label)* where Image = *(batch_size,image_width,image_height,channels)* and Label = *(batch_size,)*. "},{"metadata":{"trusted":true},"cell_type":"code","source":"train_dataset=generate_tf_dataset(trainX,trainY,IMAGE_DIM)\nprint(train_dataset.element_spec)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"eval_dataset=generate_tf_dataset(valX,valY,IMAGE_DIM)\nprint(eval_dataset.element_spec)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Plotting and Visualizing images"},{"metadata":{"trusted":true},"cell_type":"code","source":"image_classes = {\n    \"0\":\"Cassava Bacterial Blight (CBB)\",\n    \"1\":\"Cassava Brown Streak Disease (CBSD)\",\n    \"2\":\"Cassava Green Mottle (CGM)\",\n    \"3\":\"Cassava Mosaic Disease (CMD)\",\n    \"4\":\"Healthy\"\n}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_images_grid(next(iter(train_dataset.take(1))),class_names=image_classes,num_rows=8)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plot_images_grid(next(iter(eval_dataset.take(1))),class_names=image_classes,num_rows=8)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Training Model\n\nWe use InceptionResnetV2 pretrained model trained on imagenet, chop off its last classification layers (Dense Layers) and finetune it."},{"metadata":{"trusted":true},"cell_type":"code","source":"pretrained = tf.keras.applications.InceptionResNetV2(\n                include_top=False, weights='imagenet',input_shape=(*IMAGE_DIM,3)\n            )\npretrained.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = tf.keras.Sequential([\n    pretrained,\n    tf.keras.layers.GlobalAveragePooling2D(),\n    tf.keras.layers.Dropout(0.2),\n    tf.keras.layers.Dense(512),\n    tf.keras.layers.LeakyReLU(),\n    tf.keras.layers.Dropout(0.2),\n    tf.keras.layers.Dense(128),\n    tf.keras.layers.LeakyReLU(),\n    tf.keras.layers.Dense(len(image_classes),activation=\"softmax\")\n])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.compile(loss=\"categorical_crossentropy\",optimizer=\"adam\",metrics=[\"accuracy\"])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Callbacks for:\n- model_checkpointing- For checpointing model with best validation accuracy.\n- early_stop- Stop training of model if model's validation accuracy did not improved in last 5 steps\n- reduce_lr- reduce learning rate if validation accuracy did not improved in last 2 steps."},{"metadata":{"trusted":true},"cell_type":"code","source":"checkpoint_path=\"best_checkpoint\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model_checkpoint=tf.keras.callbacks.ModelCheckpoint(checkpoint_path,monitor=\"val_accuracy\",\n                                                    save_best_only=True,mode=\"max\",\n                                                    save_weights_only=True,\n                                                    verbose=1)\nearly_stop=tf.keras.callbacks.EarlyStopping(monitor=\"val_accuracy\",patience=5,\n                                            mode=\"max\", verbose=1)\nreduce_lr = tf.keras.callbacks.ReduceLROnPlateau(monitor='val_accuracy',mode=\"max\",\n                                                 factor=0.2,patience=2, \n                                                 min_lr=0.001, verbose=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"callbacks=[model_checkpoint,early_stop,reduce_lr]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"model training happens here with fit method. It takes tf datasets and steps."},{"metadata":{"trusted":true},"cell_type":"code","source":"history=model.fit(train_dataset,\n                  epochs=EPOCHS,\n                  steps_per_epoch=num_train_images//BATCH_SIZE,\n                  validation_data=eval_dataset,\n                  validation_steps=num_eval_images//BATCH_SIZE,\n                  callbacks=callbacks)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Loading best model checkpoint"},{"metadata":{"trusted":true},"cell_type":"code","source":"if os.path.isfile(checkpoint_path):\n    model.load_weights(checkpoint_path)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Predicting test data\n\nThis competition provide test data during submission for scoring and notebook with internet disabled. So create a new inference notebook for submission."},{"metadata":{},"cell_type":"markdown","source":"## What to do next?\n\n- Add data augmentation for better generalization of model, we can use [albumentations](https://albumentations.ai/) or tensorflow image augmentation explained [here](https://www.tensorflow.org/tutorials/images/data_augmentation).\n\n- Tune hyperparmeters and check performance.\n\n- Use different pretrained models like Xception, InceptionV3, Vgg, EfficientNet, Resnet etc. find list of some pretrained models [here](https://www.tensorflow.org/api_docs/python/tf/keras/applications)\n\n- Use K folds for cross validation (we have already using stratified hold_out_split cross validation in this notebook) \n\n- Using different trained models and create ensemble using Voting Classification, Model Stacking or Blending.\n\n- Do not just limited to these use your intitution for feature engineering."},{"metadata":{},"cell_type":"markdown","source":"### ALL THE BEST 👍"},{"metadata":{},"cell_type":"markdown","source":"#### Thanks for tuning till last and consider upvoting ✔✔."}],"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}