{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Cassava Leaf Disease Classification - Exploratory Data Analysis\n\nQuick Exploratory Data Analysis for [Cassava Leaf Disease Classification](https://www.kaggle.com/c/cassava-leaf-disease-classification) challenge    \n\nThis competition will challenge you to distinguish between several diseases that cause material harm to the food supply of many African countries. In some cases the main remedy is to burn the infected plants to prevent further spread, which can make a rapid automated turnaround quite useful to the farmers."},{"metadata":{"trusted":true},"cell_type":"code","source":"%%capture\n!pip install ../input/timm-pip/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":"#import Libraries\n\nimport numpy as np\nimport os\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport json\n\nimport tensorflow as tf\n\nimport tensorflow as tf\nimport torchvision\nfrom fastai.vision.all import *\nimport timm\n\nfrom pathlib import Path\n\nfrom tqdm import tqdm\n\nimport plotly.graph_objects as go\nimport plotly_express as px\nimport plotly.figure_factory as ff\nfrom plotly.subplots import make_subplots\n\nimport cv2\n\nfrom plotly.offline import init_notebook_mode\ninit_notebook_mode()\n\ndataset_path = os.path.normpath(\"../input/cassava-leaf-disease-classification\")\nos.listdir(dataset_path)\n\nBASE_DIR = \"../input/cassava-leaf-disease-classification/\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Checking Tensorflow Version\ntf.__version__","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Explorative Data Analysis"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"#loading dataset inkl. first look\ntrain_df = pd.read_csv(dataset_path + \"/\" + 'train.csv')\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Total amount of images\nlen_df = len(train_df)\nprint(f\"There are {len_df} images in the dataset\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#All the differnet labels in the dataset\nwith open(os.path.join(BASE_DIR, \"label_num_to_disease_map.json\")) as file:\n    map_classes = json.loads(file.read())\n    map_classes = {int(k) : v for k, v in map_classes.items()}\n\nprint(\"All the differnet labels in the dataset:\")\nprint(json.dumps(map_classes, indent=4))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Image Shape in width, height and RGB-Channel\nimg_shapes = {}\nfor image_name in os.listdir(os.path.join(BASE_DIR, \"train_images\"))[:1]:\n    image = cv2.imread(os.path.join(BASE_DIR, \"train_images\", image_name))\n    img_shapes[image.shape] = img_shapes.get(image.shape, 0) + 1\n\nprint(\"The images have the shape (width, height, RGB-channel): \", img_shapes)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Adding Columns with Classname & Path to the training dataframe\ntrain_df = pd.read_csv(os.path.join(BASE_DIR, \"train.csv\"))\n\ntrain_df[\"class_name\"] = train_df[\"label\"].map(map_classes)\n\ntrain_df['path'] = train_df['image_id'].map(lambda x:dataset_path +\"/\" \"train_images\" + \"/\" + \"x\")\ntrain_df = train_df.drop(columns=['image_id'])\ntrain_df = train_df.sample(frac=1).reset_index(drop=True) #shuffle dataframe","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.figure(figsize=(8, 4))\nplt.title(\"Images per Class\")\nsns.countplot(y=\"class_name\", data=train_df);","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"base_path = Path('../input/cassava-leaf-disease-classification')\ntrain_img_dir = base_path /'train_images'\ntest_img_dir = base_path /'test_images'\n\ntrain_df = pd.read_csv(base_path/'train.csv')\ndiseaseMapping = pd.read_json(base_path/'label_num_to_disease_map.json', typ='series')\n\ntrain_images = os.listdir(base_path/'train_images/')\ntest_images = os.listdir(base_path/'test_images/')\n\nmappingDict = diseaseMapping.to_dict()\n\ntrain_df = train_df.replace(mappingDict)\n\nhealthyImages = train_df[train_df['label'] == 'Healthy']['image_id'].to_list()\ncbbImages = train_df[train_df['label'] == 'Cassava Bacterial Blight (CBB)']['image_id'].to_list()\ncbsdImages = train_df[train_df['label'] == 'Cassava Brown Streak Disease (CBSD)']['image_id'].to_list()\ncgmImages = train_df[train_df['label'] == 'Cassava Green Mottle (CGM)']['image_id'].to_list()\ncmdImages = train_df[train_df['label'] == 'Cassava Mosaic Disease (CMD)']['image_id'].to_list()\n\nlabelCounts = train_df['label'].value_counts().reset_index()\nlabelCounts.columns = ['Label', 'Number of Observations']\n\ntrain = pd.read_csv('../input/cassava-leaf-disease-classification/train.csv')\ntrain['disease'] = train.label.map({0:\"Cassava Bacterial Blight (CBB)\",\n1:\"Cassava Brown Streak Disease (CBSD)\",\n2:\"Cassava Green Mottle (CGM)\",\n3:\"Cassava Mosaic Disease (CMD)\",\n4:\"Healthy\"})\ndiseases = train.disease.value_counts()\n\nfig = px.pie(diseases,\n             values='disease',\n             names=diseases.index,\n             #color_discrete_sequence=orange_black,\n             hole=.3)\nfig.update_traces(textinfo='percent+label', pull=0.05)\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Class CMD has by far the most images in the dataset, so it is unbalanced."},{"metadata":{"trusted":true},"cell_type":"code","source":"#defining plot functions for RGB Plots\n\ndef showImages(images):\n\n    # Extract 9 random images from it\n    random_images = [np.random.choice(images) for i in range(9)]\n\n    # Adjust the size of your images\n    plt.figure(figsize=(10,8))\n\n    # Iterate and plot random images\n    for i in range(9):\n        plt.subplot(3, 3, i + 1)\n        img = plt.imread(train_img_dir/random_images[i])\n        plt.imshow(img, cmap='gray')\n        plt.axis('off')\n\n    # Adjust subplot parameters to give specified padding\n    plt.tight_layout()\n    \ndef showHistogram(sample_img, title):\n    f = plt.figure(figsize=(16,8))\n    f.add_subplot(1,2, 1)\n\n    raw_image = plt.imread(train_img_dir/sample_img)\n    plt.imshow(raw_image, cmap='gray')\n    plt.colorbar()\n    plt.title(title)\n    print(f\"Image dimensions:  {raw_image.shape[0],raw_image.shape[1]}\")\n    print(f\"Maximum pixel value : {raw_image.max():.1f} ; Minimum pixel value:{raw_image.min():.1f}\")\n    print(f\"Mean value of the pixels : {raw_image.mean():.1f} ; Standard deviation : {raw_image.std():.1f}\")\n\n    f.add_subplot(1,2, 2)\n\n    #_ = plt.hist(raw_image.ravel(),bins = 256, color = 'orange',)\n    _ = plt.hist(raw_image[:, :, 0].ravel(), bins = 256, color = 'red', alpha = 0.5)\n    _ = plt.hist(raw_image[:, :, 1].ravel(), bins = 256, color = 'Green', alpha = 0.5)\n    _ = plt.hist(raw_image[:, :, 2].ravel(), bins = 256, color = 'Blue', alpha = 0.5)\n    _ = plt.xlabel('Intensity Value')\n    _ = plt.ylabel('Count')\n    _ = plt.legend(['Red_Channel', 'Green_Channel', 'Blue_Channel'])\n    plt.show()\n    \n    \n    \ndef load_image(image_id):\n    image = cv2.imread(str(train_img_dir/image_id))\n    return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n\ndef showChannelDistribution(images, leafType):\n    imageArray = [load_image(image_id) for image_id in images]\n    \n    red_values = [np.mean(imageArray[idx][:, :, 0]) for idx in range(len(imageArray))]\n    green_values = [np.mean(imageArray[idx][:, :, 1]) for idx in range(len(imageArray))]\n    blue_values = [np.mean(imageArray[idx][:, :, 2]) for idx in range(len(imageArray))]\n    values = [np.mean(imageArray[idx]) for idx in range(len(imageArray))]\n    \n    hist_data = [red_values, green_values, blue_values, values]\n    group_labels = ['Red', 'Green', 'Blue', 'All']\n\n    fig = ff.create_distplot(hist_data, group_labels,colors = ['red', 'green','blue','grey'])\n    fig.update_layout(template = 'plotly_white', title_text = f'Channel Distribution - {leafType}')\n    fig.show()\n    return hist_data\n\ndef showBoxPlot(histData, leafType):\n    figData = []\n    for i, name in zip(range(3), ['Red', 'Green', 'Blue']):\n        trace = go.Box(y = histData[i], name = name, boxpoints='all', marker_color  = name)\n        figData.append(trace)\n\n    fig = go.Figure(figData)\n    fig.update_layout(title_text = f'Pixel Intensity Distribution - {leafType}', template = 'plotly_white')\n    fig.show() \n    \n    \n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"showImages(healthyImages)\n#showImages(cmdImages)\n#showImages(cgmImages)\n#showImages(cbsdImages)\n#showImages(cbbImages)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"A batch of nine healthy images"},{"metadata":{"trusted":true},"cell_type":"code","source":"showHistogram(healthyImages[0], 'Healthy Image')\n#showHistogram(cmdImages[0], 'CMD Image')\n#showHistogram(cgmImages[0], 'CGM Image')\n#showHistogram(cbsdImages[0], 'CBSD Image')\n#showHistogram(cbbImages[0], 'CBB Image')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"raw","source":"These are the RGB-Channel Values of a sample healthy image."},{"metadata":{"trusted":true},"cell_type":"code","source":"data = showChannelDistribution(healthyImages, 'Healthy')\n#data = showChannelDistribution(cbbImages, 'CBB Images')\n#data = showChannelDistribution(cbsdImages, 'CBSD Images')\n#data = showChannelDistribution(cgmImages, 'CGM Images')\n#data = showChannelDistribution(cmdImages, 'CMD Images')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"These are the RGB-Channel Values of all Healthy Images."},{"metadata":{"trusted":true},"cell_type":"code","source":"showBoxPlot(data, 'Healthy Leaves')\n#showBoxPlot(data, 'CBB Images')\n#showBoxPlot(data, 'CBSD Images')\n#showBoxPlot(data, 'CGM Images')\n#showBoxPlot(data, 'CMD Images')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Insights:\n* CGM types images have the highest median RGB values\n* CBB type images have the lowest median RGB values\n* The channel intensity median values follow this trend G>R>B"},{"metadata":{"trusted":true},"cell_type":"code","source":"channelIntensityDf = pd.DataFrame(\n    {\n        'Leaf Type' : ['Healthy', 'CBB','CBSD', 'CGM', 'CMD'], \n        'Red Channel Mean' : [108,102,106,113,110],\n        'Green Channel Mean' : [126,117,123,128,128],\n        'Blue Channel Mean' : [80,66,72,85,80]\n    }\n)\n\nchannelIntensityDf.style.background_gradient(cmap='Greens', axis = 0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model Development"},{"metadata":{"trusted":true},"cell_type":"code","source":"set_seed(999)\n\ndataset_path = Path('../input/cassava-leaf-disease-classification')\nos.listdir(dataset_path)\n\nBASE_DIR = \"../input/cassava-leaf-disease-classification/\"\n\ntrain_df = pd.read_csv(\"../input/cassava-leaf-disease-classification/train.csv\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(os.path.join(BASE_DIR, \"train.csv\"))\n\ntrain_df[\"class_name\"] = train_df[\"label\"].map(map_classes)\n\ntrain_df['path'] = train_df['image_id'].map(lambda x:dataset_path/'train_images'/x)\ntrain_df = train_df.drop(columns=['image_id'])\ntrain_df = train_df.sample(frac=1).reset_index(drop=True) #shuffle dataframe\n\ntrain_df = train_df[[\"label\", \"path\"]]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from PIL import Image\n\nim = Image.open(train_df['path'][1])\nwidth, height = im.size\nprint(width,height) ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Image Augmentation to increase the training dataset**"},{"metadata":{"trusted":true},"cell_type":"code","source":"item_tfms = RandomResizedCrop(224, min_scale=0.75, ratio=(1.,1.))\nbatch_tfms = [*aug_transforms(size=224, max_warp=0), Normalize.from_stats(*imagenet_stats)]\nbs=64","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Defining an image data loader for the model**"},{"metadata":{"trusted":true},"cell_type":"code","source":"dls = ImageDataLoaders.from_df(train_df, #pass in train DataFrame\n                               valid_pct=0.1, #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) #pass in batch_tfms","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Random Example Batch: \")\ndls.show_batch()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Making pretrained weights work without needing to find the default filename\nif not os.path.exists('/root/.cache/torch/hub/checkpoints/'):\n        os.makedirs('/root/.cache/torch/hub/checkpoints/')\n!cp '../input/timmefficientnet/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":"# Config of the timm learner\nfrom 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\")\n        \ndef 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\n\ndef 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":{},"cell_type":"markdown","source":"**Building the model**"},{"metadata":{"trusted":true},"cell_type":"code","source":"learn = 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":{"trusted":true},"cell_type":"code","source":"learn.lr_find()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Finding the right learningrate with the lowest loss**"},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.freeze()\nlearn.fit_flat_cos(1,1e-1, wd=0.5, cbs=[MixUp()])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.save('stage-1')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn = learn.load('stage-1')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.recorder.plot_loss()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.unfreeze()\nlearn.lr_find()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Unfreeze the model and again finding the learning rate with the lowest loss**\n\n**and Train for 10 epochs, since this seams to be the optimal amount**"},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.unfreeze()\nlearn.fit_flat_cos(15,2e-2,pct_start=0,cbs=[MixUp()])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.recorder.plot_loss()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn = learn.to_native_fp32()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.save('stage-2')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"learn.export()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"interp = ClassificationInterpretation.from_learner(learn)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"interp.plot_confusion_matrix()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_df = pd.read_csv(dataset_path/'sample_submission.csv')\nsample_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"_sample_df = sample_df.copy()\n_sample_df['path'] = _sample_df['image_id'].map(lambda x:dataset_path/'test_images'/x)\n_sample_df = _sample_df.drop(columns=['image_id'])\ntest_dl = dls.test_dl(_sample_df)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_dl.show_batch()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"preds, _ = learn.tta(dl=test_dl, n=8, beta=0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_df['label'] = preds.argmax(dim=-1).numpy()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_df.to_csv('submission.csv',index=False)","execution_count":null,"outputs":[]},{"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}