{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Cassava Leaf Disease Classification - Competition Introduction, EDA & Simple Model \n\n\n\n### Competition Summary\nThe objective of this compeition is to identify the type of disease present on a Cassava Leaf image. There are 4 classes of unhealthy and 1 healthy for a total of 5 label classes across a total of  21,367 images. According to the competition description farmers often take pictures with their phone, hwoever we don't know if these images are taken with farmers mobiles. Finally we expect ~15,000 images in the test set. \n\n\n### Notebook Summary Findings\n- Training data is imbalanced with Cassava Mosaic Disease (CMD) being the most common disease present\n- Images range in type from close up pictures of leaves, to those with many leaves, and several with extra data (i.e. sky, houses, hands, etc)\n- Cassava Brown Streak Disease (CBSD) can occur as aymptomatic in the leaves. The disease is only present in the roots.\n- In several diseases a varaity of symptoms exist, not always manifesting in the same way.\n- Caccava Mosaic Disease (CMD) includes symptom intensity varaible with onset time\n- The Cassava Green Mottle (CGM) spectral profile in red and green is distinct from other clases.\n\n### Contents\n1. [Data Overview](#data-overview)\n2. [Disease Types](#disease-types)\n   - 2a. [Healthy](#healthy-leaf)\n   - 2b. [Cassava Bacterial Blight (CBB)](#cassava-bacterial-blight)\n   - 2c. [Cassava Brown Streak Disease (CBSD)](#cassava-brown-streak-disease)\n   - 2e. [Cassava Green Mottle (CGM)](#cassava-green-mottle)\n   - 2f. [Cassava Mosaic Disease (CMD)](#caccava-mosaic-disease)\n3. [Model Building & Training](#modelling)\n\n\n### References\n1. [Cassava Lead Diseases: Overview](https://www.kaggle.com/c/cassava-leaf-disease-classification/discussion/198143)","attachments":{}},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"import os\nimport json\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom sklearn.model_selection import StratifiedKFold\nfrom imblearn.over_sampling import RandomOverSampler\n\nimport tensorflow as tf\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.applications import DenseNet201\n\nimport cv2","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<a id=\"balance-data\"></a>\n# Data Overview"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"PATH_IMGS = \"../input/cassava-leaf-disease-classification/train_images\"\ntrain_df = pd.read_csv(\"../input/cassava-leaf-disease-classification/train.csv\")\n\nwith open(\"../input/cassava-leaf-disease-classification/label_num_to_disease_map.json\") as f:\n    class_names = json.loads(f.read())\nf.close()\n\ntrain_df[\"label_name\"] = train_df['label'].apply(lambda x: class_names[str(x)])\ntrain_df.label = train_df.label.astype(str)\n\nprint(\"Total training samples: \", len(train_df))\ntrain_df.head(10)","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"fig = plt.figure()\ntrain_df.groupby(\"label_name\")[\"label_name\"].count().plot(kind= \"bar\", title= \"Frequency of image label classes\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We see this is an imbalanced dataset. What seems interesting is that it is not the healthy class that is dominant."},{"metadata":{},"cell_type":"markdown","source":"Considering the possibility that images were not taken in a standard way it is worth checking the image resolution to see if they are stadnardized. Below we see that all images have a standard shape of 600 by 800 pixels with 3 channels."},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"img_shapes = list()\nfor (_, row) in train_df.iterrows():\n    img_path = os.path.join(PATH_IMGS, row.image_id)\n    img = cv2.imread(img_path)\n    img_shapes.append(img.shape)\n\nprint(\"Unique image shapes in training set:\", set(img_shapes))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<a id=\"disease-types\"></a>\n# Cassava Disease Profiles\nEach disease profile includes a descrption of characterists (primary symptoms, aditional symptoms, and descrption), a collection of images of the disease, and a color density chart of a random sample of 500 images from the respective class.\n\n<a id=\"healthy-leaf\"></a>\n## Healthy Leaf\nA healthy casava plant is a perennial shrub characterized by brown/purple woodish stems, and lobed leaves reachin gup to 30 cm in lenth. Originally from south america the plant can reach 4 meters high and is usally harvested 9 -12 months after planting.\n\nBelow we see a sample of images and the color density of healthy leaves. Interesting to note that we notice leaf defects even on healthy leaves like that in the top left image.\n"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def load_image(image_id):\n    image = cv2.imread(os.path.join(PATH_IMGS, image_id))\n    return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\ndef view_imgs(df, seed=99):\n    \n    fig = plt.figure(figsize=(18, 10))\n    samples = df.sample(6, random_state=seed)\n\n    for idx, (_, row) in enumerate(samples.iterrows()):\n\n        plt.subplot(3, 3, idx+1)\n        \n        image = load_image(row.image_id)\n\n        plt.imshow(image)\n        plt.title(f\"Class Label: {row.label_name}\", fontsize=12)\n        plt.axis(\"off\")\n\ndef plot_images_histogram(imgs, name):\n    fig = plt.figure(figsize=(5,4))\n    #average histogram of image sample\n    red_values = np.stack([img[:, :, 0].ravel() for img in imgs]).mean(axis=0)\n    green_values = np.stack([img[:, :, 1].ravel() for img in imgs]).mean(axis=0)\n    blue_values = np.stack([img[:, :, 2].ravel() for img in imgs]).mean(axis=0)\n    \n    sns.kdeplot(red_values, alpha=0.5, color='red')\n    sns.kdeplot(green_values, alpha=0.5, color='green')\n    sns.kdeplot(blue_values, alpha=0.5, color='blue')\n    plt.title(f\"Color density: {name}\")\n    plt.show()        \n\n    \nhealthy = train_df[train_df.label==\"4\"]\nview_imgs(healthy)\n\nimgs = [load_image(img) for img in healthy.sample(500, random_state=99).image_id.values]\nplot_images_histogram(imgs, healthy.label_name.unique()[0])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<a id=\"cassava-bacterial-blight\"></a>\n## Cassava Bacterial Blight (CBB)\n\n- **Primary symptoms:** \n    - blight, wilting, dieback, and vascular necrosis\n- **Additional symptoms:**\n    - Pools of extruded gum along cuts in the stem, and leave cross veins\n    - Gum forms as both golden liquid and hardened amber deposits\n- **Description:**\n    - Visible angular necrotic spotting forms on the leaves with a chlorotic ring encircling the spots\n    - Spots range from moist brown lesions restricted to the bottom of the plant, to encasing the whole plant and killing entire leaves\n[Source](https://en.wikipedia.org/wiki/Bacterial_blight_of_cassava)"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"cbb = train_df[train_df.label==\"0\"]\nview_imgs(cbb)\n\nimgs = [load_image(img) for img in cbb.sample(500, random_state=99).image_id.values]\nplot_images_histogram(imgs, cbb.label_name.unique()[0])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<a id=\"cassava-brown-streak-disease\"></a>\n## Cassava Brown Streak Disease (CBSD) \n\n\n- **Primary symptoms:** \n    - Sever chlorosis and necrosis on infected leaves, with a yellowish, mottled appearance\n- **Additional symptoms:**\n    - Brown streaks on stems of plant\n    - Dry brown-black necrotic rot of the cassava tuber ranging from a small lesion to the whole root\n    - Root constriction from tuber rot and stunted growth\n- **Description:**\n    - Chlorosis may be associated with the veins, spanning from the mid vein, secondary and tertiary veins, or rather in blotches unconnected to veins\n    - Leaf symptoms vary greatly depending on a variety of factors including growing conditions, plant age, and virus species\n    - Affected plants do not always possess all symptoms except for those that are severely affected \n    - Plant leaves may be asymptomatic, with disease only affecting the tubers.[Source](https://en.wikipedia.org/wiki/Cassava_brown_streak_virus_disease)"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"cbsd = train_df[train_df.label==\"1\"]\nview_imgs(cbsd)\n\nimgs = [load_image(img) for img in cbsd.sample(500, random_state=99).image_id.values]\nplot_images_histogram(imgs, cbsd.label_name.unique()[0])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<a id=\"cassava-green-mottle\"></a>\n## Cassava Green Mottle (CGM)\n- **Primary symptoms:** \n    - Leaves puckered with yellow spots (can be faint or distinct), green mosaic patterns, and twisted margins.\n    - Shoots usually appear helthy\n- **Additional symptoms:**\n    - Plant severly stunted\n    - Edible roots abset or small and woody\n- **Description:**\n    - Yellow patterns on leaves range from small dots to iggregular yellow and green patches\n    - Leaf margins are also often distorted [Source](https://www.pestnet.org/fact_sheets/cassava_green_mottle_068.htm)"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"cgm = train_df[train_df.label==\"2\"]\nview_imgs(cgm)\n\nimgs = [load_image(img) for img in cgm.sample(500, random_state=99).image_id.values]\nplot_images_histogram(imgs, cgm.label_name.unique()[0])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<a id=\"caccava-mosaic-disease\"></a>\n## Caccava Mosaic Disease (CMD)\n- **Primary symptoms:** \n    - Chlorotic mosaic of the leaves, leaf distortion, and stunted growth\n- **Additional symptoms:**\n    - Leaf stalks have characteristic S-shape\n- **Description:**\n    - Rapid symptom onset correlates with plant recovery\n    - Slow development of disease correlates with plant death [Source](https://en.wikipedia.org/wiki/Cassava_mosaic_virus)"},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"cmd = train_df[train_df.label==\"3\"]\nview_imgs(cmd)\n\nimgs = [load_image(img) for img in cmd.sample(500, random_state=99).image_id.values]\nplot_images_histogram(imgs, cmd.label_name.unique()[0])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<a id=\"modelling\"></a>\n# Modelling\n\nAs a baseline model we will generate a balanced dataset and use DenseNet201 as the foundation. There appears to be a lot of natural varaition in the images so no image augmentation will be added. However based on the color densities and varaity of image types we could think about adding image augmentation in the form of zoom, verticle flip, and color filters for browns and reds.\n\nNote: I've used ROC AUC as metric because the model appears to have a bug with accruacy. Suggestions welcome on how to fix it :)\n\n### Steps:\n1. Random oversampling of dataset\n2. Add folds with Stratified KFold\n3. Inialize image data generator and train model"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def balance_set(df, x_cols, y_cols):\n    ros = RandomOverSampler(random_state=42)\n\n    x_multi, y_multi = ros.fit_resample(df[x_cols], df[y_cols].values)\n    data = pd.concat([x_multi, pd.DataFrame(y_multi, columns= y_cols)], axis=1)\n    return data\n\ntrain_df_bal = balance_set(train_df, train_df.drop(\"label\", axis=1).columns, [\"label\"])\n\nskf = StratifiedKFold(n_splits=5)\nfor fold, (trn, val) in enumerate(skf.split(train_df_bal, train_df_bal.label)):\n    train_df_bal.loc[val, \"skfold\"] = fold\n    \ntrain_df_bal.skfold = train_df_bal.skfold.astype(int)\n\ntrain_df_bal[train_df_bal.skfold == 0].groupby(\"label\")[\"image_id\"].count().plot(kind='bar', title=\"Class distribution after balancing\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def make_image_gen(df, fold):\n\n    img_gen = ImageDataGenerator(rescale=1./255)\n\n    trn_df = df[df.skfold != fold]\n    val_df = df[df.skfold == fold]\n    \n    print(\"Train length: \", trn_df.shape[0])\n    print(\"Val length: \", val_df.shape[0])\n    \n    train_gen = img_gen.flow_from_dataframe(trn_df,\n                                            directory=PATH_IMGS,\n                                            x_col = \"image_id\",\n                                            y_col = \"label\",\n                                            target_size = (200,200),\n                                            class_mode=\"categorical\",\n                                            batch_size=64,\n                                            shuffle=True,\n                                            seed=99)\n\n    val_gen = img_gen.flow_from_dataframe(val_df,\n                                          directory=PATH_IMGS,\n                                          x_col = \"image_id\",\n                                          y_col = \"label\",\n                                          target_size = (200,200),\n                                          class_mode=\"categorical\",\n                                          batch_size=64,\n                                          shuffle=True,\n                                          seed=99)\n    return train_gen, val_gen","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def make_model(trainable_weights=False):\n    tf.keras.backend.clear_session()\n\n    densenet = DenseNet201(input_shape = (200, 200, 3), weights=\"imagenet\", include_top=False)\n\n    N_CLASSES = len(train_df.label.unique())\n\n    for layer in densenet.layers:\n        layer.trainable=trainable_weights\n    \n    model = tf.keras.models.Sequential([\n        densenet,\n        tf.keras.layers.GlobalAveragePooling2D(),\n        tf.keras.layers.Dropout(0.3),\n        tf.keras.layers.Dense(128, activation='relu'),\n        tf.keras.layers.Dropout(0.3),\n        tf.keras.layers.Dense(N_CLASSES, activation='softmax')\n    ])\n\n    accuracy = tf.keras.metrics.Accuracy()\n    roc_auc = tf.keras.metrics.AUC()\n\n    optimizer = tf.keras.optimizers.Adam(lr=0.0005)\n    \n    model.compile(optimizer=optimizer, loss=\"categorical_crossentropy\", metrics=[accuracy, roc_auc])\n\n    return model\n\nprint(\"Baseline Model Summary\")\nmake_model(trainable_weights=False).summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"TRAIN_ALL_FOLDS = True\nif not TRAIN_ALL_FOLDS:\n    folds = [0]\nelse:\n    folds = train_df_bal.skfold.unique()\n\nprint(\"Folds: \", folds)\ntrain_results = dict()\n\nfor fold in folds:\n    \n    print(f\"Fold {fold}\")\n    train_gen, val_gen = make_image_gen(train_df_bal, fold)\n    \n    model = make_model(trainable_weights=False)\n\n    print(\"Model training\")\n    history = model.fit_generator(train_gen, \n                                  validation_data=val_gen, \n                                  epochs=5, \n                                  steps_per_epoch=len(train_gen), \n                                  validation_steps=len(val_gen))\n    \n    train_results[f\"fold_{fold}\"] = history.history\n    \n    model.save(f\"./densenet_baseline_fold_{fold}.h5\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Work in progress..."}],"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}