{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"#importing required libraries\n\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\nimport tensorflow as tf\nfrom keras import Sequential\nfrom keras.layers import Dense,Dropout, Flatten, BatchNormalization,Activation\nfrom keras.layers import Lambda, Input, GlobalAveragePooling2D\nfrom keras.optimizers import Adam, SGD\nfrom keras.utils import to_categorical\nfrom keras.models import Model\nfrom keras.preprocessing.image import load_img\nfrom keras.callbacks import ReduceLROnPlateau,EarlyStopping, ModelCheckpoint\nfrom keras.preprocessing.image import ImageDataGenerator\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom tqdm.autonotebook import tqdm\n\nfrom sklearn.model_selection import train_test_split\n\n\nimport os\nimport gc\n\n\n# import os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Checking if 'GPU' is aailable or not\n\nprint('Yes !! GPU is available' if tf.config.list_physical_devices('GPU') else 'GPU in not available !')\nprint(tf.config.list_physical_devices('GPU') )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(tf.__version__)\nprint(tf.keras.__version__)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# set seed\nseed = 42\n\ndef seed_everything(seed):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    tf.random.set_seed(seed)\n    \nseed_everything(seed)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"#directory of train and test images\n\ntrain_dir = '../input/cassava-leaf-disease-classification/train_images/'\ntest_dir = '../input/cassava-leaf-disease-classification/test_images/'","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv('../input/cassava-leaf-disease-classification/train.csv')\ntrain_df.head(10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"if len(os.listdir(train_dir)) == len(train_df['image_id']):\n    print('Number of image ids in train.csv file matches with the actual number of images present in train folder')\nelse:\n    print('Number of image ids in train.csv file does not match with the actual number of images present in train folder')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Observation:\n1. Images of train folder are present in the same order as that of train.csv file."},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.info()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import json\n\nwith open('../input/cassava-leaf-disease-classification/label_num_to_disease_map.json') as f:\n    classes = json.load(f)\n    \nclasses","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Observation:\n1. We have 5 different clases\n2. Classes 0 - 3 ---> represents diseases \n3. Class 4 ---> Helthy leaf\n4. We don't have any null values"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df['class_name'] = train_df['label'].astype('str').map(classes) #We are converting the data type of 'label' from 'int' to 'str' as map requires 'str' format for mapping\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Potting the categorical ratio\n\n#function to plot bar height information\n\ndef barh(ax):\n    \n    for p in ax.patches:\n        val = p.get_height() #height of the bar\n        x = p.get_x()+ p.get_width()/2 # x- position \n        y = p.get_x()+ p.get_height()+100 #y-position\n        ax.annotate(round(val,2),(x,y))\n    \n#Plotting the class distribution in a descending order\n\nplt.figure(figsize = (15,7))\nax0 =sns.countplot(x=train_df['class_name'],order=train_df['class_name'].value_counts().index )\nbarh(ax0)\nplt.show()   ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Observation:\n1. We have class imbalance here.i.e. label 'Cassa Mosaic Disease(CMD) has 13158 images while othe classes have less than 3000 images.\n2. We will be using stratify= train_df.label.values while spliting for validation data"},{"metadata":{"trusted":true},"cell_type":"code","source":"from plotly.express import pie\n\nclass_val = pd.DataFrame(train_df['class_name'].value_counts())\nprint(class_val)\n\nfig = pie(class_val,values ='class_name', names = list(class_val.index), title = 'Image Class distribution ')\nfig.show()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Observation:\n1. We have atleast 1000 images per class or category"},{"metadata":{},"cell_type":"markdown","source":"### Reading images from thr train DIR"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.shape","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Taking data in batches\n\nAs we have limited ram memory and huge number of images, We will be taking images in batches and will extract features"},{"metadata":{"trusted":true},"cell_type":"code","source":"#list of img_dir accoring to train.csv file\nimg_dir =[]\nfor ix,img_id in enumerate(tqdm(train_df['image_id'].values)):\n    img_dir.append(os.path.join(train_dir,img_id))\n\ntrain_df['img_dir'] = img_dir #creatinga new column\ntrain_df= train_df.astype('str') #datagen requires the target value in str format\ntrain_df.head()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Creating train and validation dataset ( as we can not apply validation_split =0.2 when we are working with ImageDataGenerator )\n\ntrain_df, val_df = train_test_split(train_df, test_size = 0.2, random_state = 100,\n                                    stratify = train_df['label'].values) # stratify as we have class imbalance\ntrain_df.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#validation dataframe shape\nval_df.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Hyperparameters\nbatch_size= 32 # Batch size > 32 will cause ResourceExhaustedError during model.fit()\nepochs=20\nlearn_rate=0.001\n# sgd=SGD(lr=learn_rate,momentum=.9,nesterov=False)\nadam=Adam(lr=learn_rate, beta_1=0.9, beta_2=0.999, epsilon=None,  amsgrad=False)\n\ninput_shape = (300,300,3)\nn_classes = len(classes)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# #using Xception \n\n# from keras.applications.xception import Xception, preprocess_input\n# # xception_preprocessor = preprocess_input\n# xception = Xception(include_top= False, weights = 'imagenet',\n#                        input_shape = input_shape,\n#                        classes = n_classes) ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Data augmentation:\n1. Data augmentation encompasses a wide range of techniques used to generate “new” training samples from the original ones by applying random jitters and perturbations (but at the same time ensuring that the class labels of the data are not changed).\n2. The basic idea behind the augmentation is to train the model on all kind of possible transformations of an image\n3. Here we are using flow_from_dataframe. This is because we have limited ram and we need to get images in batches with respect to the image_id available in the train.csv file"},{"metadata":{"trusted":true},"cell_type":"code","source":"# # we are defining ImageDataGenerator\n\n# datagen = ImageDataGenerator(horizontal_flip = True,\n#                             vertical_flip = True,\n#                              zoom_range = 0.2,\n# #                              shear_range = 0.2,\n#                              rescale = 1.0/255,  # Ar RGB colors are presented in 0-155 range (1 pixel = 8 bits, since each bit can be 1 or 0, 8 bits info 2^8 = 256 , 0-255 , total 256)\n# #                              width_shift_range = 0.2,\n# #                              height_shift_range = 0.2,\n#                              fill_mode = 'nearest',\n# #                              preprocessing_function = preprocess_input\n                             \n#                             ) \n# datagen_val = ImageDataGenerator(#preprocessing_function = preprocess_input,\n#                                  rescale = 1.0/255) # as we don not need all transformation during validation\n\n# datagen_pred = ImageDataGenerator(#preprocessing_function = preprocess_input,\n#                                   rescale = 1.0/255 )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"datagen = ImageDataGenerator(\n                    rotation_range = 30,\n                    width_shift_range = 0.2,\n                    height_shift_range = 0.2,\n                    shear_range = 0.2,\n                    zoom_range = 0.2,\n                    brightness_range = [0.5,1.5],\n                    horizontal_flip = True,\n                    vertical_flip = True,\n                    fill_mode = 'nearest'\n)\n\ndatagen_val = ImageDataGenerator()\ndatagen_pred = ImageDataGenerator()\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#we can use datagen from dataframe : https://vijayabhaskar96.medium.com/tutorial-on-keras-imagedatagenerator-with-flow-from-dataframe-8bd5776e45c1\n\ntrain_generator= datagen.flow_from_dataframe(dataframe=train_df, directory=train_dir, x_col=\"image_id\", y_col=\"label\",\n                                            class_mode=\"sparse\", target_size=(300,300), batch_size=batch_size,shuffle = True,\n                                             seed = seed,interpolation = \"nearest\",\n                                            color_mode = 'rgb')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"val_generator = datagen_val.flow_from_dataframe(dataframe=val_df, directory=train_dir, x_col=\"image_id\", y_col=\"label\",\n                                            class_mode=\"sparse\", target_size=(300,300), batch_size=batch_size,shuffle = False,\n                                                seed = seed,interpolation = \"nearest\",\n                                                \n                                               color_mode = 'rgb')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#test datagen\n\npred_datagen = datagen_pred.flow_from_directory(\"../input/cassava-leaf-disease-classification/\",\n                                               batch_size = 1, # as we want all images in one batch during prediction\n                                               target_size = (300,300),\n                                                classes=['test_images'], # https://kylewbanks.com/blog/loading-unlabeled-images-with-imagedatagenerator-flowfromdirectory-keras\n                                               color_mode ='rgb',\n                                              seed = seed\n                                               ) ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#plotting some images from image generator https://www.analyticsvidhya.com/blog/2020/08/image-augmentation-on-the-fly-using-keras-imagedatagenerator/\n\nfig,ax = plt.subplots(nrows=1,ncols=5,figsize=(16,16))\n\n\nfor i in range (5):\n    \n    image = next(train_generator)[0][0] # getting images\n    \n    image = np.squeeze(image) # changing size from (1, 200, 200, 3) to (200, 200, 3) for plotting the image\n    \n    ax[i].imshow(image)\n    ax[i].axis('off')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Stop training when the validation loss metric has stopped decreasing for 5 epochs.\nearly_stopping = EarlyStopping(monitor = 'val_loss',\n                               patience = 5,\n                               mode = 'min',\n                               restore_best_weights = True)\n\n# Save the model with the minimum validation loss\ncheckpoint = ModelCheckpoint('best_model.hdf5', \n                             monitor = 'val_loss',\n                             verbose = 1,\n                             mode = 'min', \n                             save_best_only = True)\n# reduce learning rate\nreduce_lr = ReduceLROnPlateau(monitor = 'val_loss',\n                              factor = 0.2,\n                              patience = 3,\n                              min_lr = 0.001,\n                              mode = 'min',\n                              verbose = 1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Model Building:\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"from tensorflow.keras.applications import EfficientNetB3\neffB3 = EfficientNetB3(input_shape = input_shape, include_top = False, weights = 'imagenet')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#creating a function to build the FC by taking the base model and return the final model\n\ndef build_model(base_modelx):\n    \n#     for layer in base_modelx.layers:\n#         layer.trainable = False\n    \n    model = Sequential(base_modelx)\n#     model.add(Flatten())\n    model.add(GlobalAveragePooling2D())\n#     model.add(Dropout(0.3))\n    model.add(Dense(256,activation ='relu'))#, kernel_regularizer = tf.keras.regularizers.l2(0.01)))\n#     model.add(BatchNormalization())\n#     model.add(Dropout(0.5))\n#     model.add(Dense(512,activation = 'relu',kernel_regularizer = tf.keras.regularizers.l2(0.01)))\n#     model.add(BatchNormalization())\n#     model.add(Dropout(0.3))\n#     model.add(Dense(256,activation = 'relu'))\n#     model.add(Dropout(0.2))\n#     model.add(Dense(128,activation = 'relu'))\n#     model.add(Dropout(0.15))\n    model.add(Dense(n_classes,activation='softmax'))\n    \n    print(model.summary())\n    \n    model.compile(loss = 'sparse_categorical_crossentropy',optimizer = adam,metrics =['accuracy'])\n    \n    return model","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = build_model(effB3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"STEP_SIZE_TRAIN = train_generator.n//train_generator.batch_size\nSTEP_SIZE_VALID = val_generator.n//val_generator.batch_size","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"history = model.fit(train_generator,\n                    validation_data = val_generator,\n                    epochs = epochs,\n                    steps_per_epoch = STEP_SIZE_TRAIN,\n                    validation_steps = STEP_SIZE_VALID,\n                    callbacks = [early_stopping, checkpoint, reduce_lr]\n                   )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# (model.history.history)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Ploting acc and loss\n\ndef plot_result(modelx):\n    results = pd.DataFrame({'epochs':list(range(1,len(modelx.history.history['accuracy'])+1)),'Training_acc':modelx.history.history['accuracy'],'Validation_acc':modelx.history.history['val_accuracy'],\n                          'Training_loss':modelx.history.history['loss'],'Validation_loss':modelx.history.history['val_loss']})\n\n    plt.figure(figsize=(12,5))\n    sns.lineplot(x = 'epochs', y ='Training_acc', data = results, color='r' )\n    sns.lineplot(x = 'epochs', y ='Validation_acc', data = results, color='blue' )\n    plt.title('Training Accuracy vs Validation Accuracy')\n    plt.show()\n\n    plt.figure(figsize=(12,5))\n    sns.lineplot(x = 'epochs', y ='Training_loss', data = results, color='r' )\n    sns.lineplot(x = 'epochs', y ='Validation_loss', data = results, color='blue' )\n    plt.title('Training Loss vs Validation Loss')\n    plt.show()\n\nplot_result(model)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.save(\"final_model_effB3.h5\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"pred = model.predict(pred_datagen)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# First prediction\nprint(pred[0])\nprint(f\"Max value (probability of prediction): {np.max(pred[0])}\") # the max probability value predicted by the model\nprint(f\"Sum: {np.sum(pred[0])}\") # because we used softmax activation in our model, this will be close to 1\nprint(f\"Max index: {np.argmax(pred[0])}\") # the index of where the max value in predictions[0] occurs\nprint(f\"Predicted label: {classes[str(np.argmax(pred[0]))]}\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Creating submission file"},{"metadata":{"trusted":true},"cell_type":"code","source":"sub = pd.DataFrame(columns =['image_id','label'])\nsub","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub['image_id'] = [os.path.splitext(path)[0]+'.jpg' for path in os.listdir(test_dir)]\nsub['label']= np.argmax(pred,axis=1)\n\nsub\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#submitting\n\nsub.to_csv('submission.csv',index= None)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### NOTE:\n1. This competition requires no internet connection. Hence we will save the model and use that in the inference notebook.\n2. The model can be fine tuned to get better accuracy\n"},{"metadata":{},"cell_type":"markdown","source":"Inference notebook: https://www.kaggle.com/deepakat002/efficientnetb3-inference-0-860"},{"metadata":{},"cell_type":"markdown","source":"### Please give a upvote if you like this notebook :)"},{"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}