{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# EDA <a id=\"1\"></a>","metadata":{"id":"wRNLR-MQ2i90"}},{"cell_type":"markdown","source":"## Preparing the ground <a id=\"1.1\"></a>","metadata":{"id":"ufcI1cah2o_T"}},{"cell_type":"markdown","source":"### Install and import necessary libraries","metadata":{"id":"1XvcaAzr2rjY"}},{"cell_type":"code","source":"!pip install -q efficientnet","metadata":{"id":"V8TgyWRwpnKH","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport gc\nimport re\n\nimport cv2\nimport math\nimport numpy as np\nimport scipy as sp\nimport pandas as pd\n\nimport tensorflow as tf\nfrom IPython.display import SVG\nimport efficientnet.tfkeras as efn\nfrom keras.utils import plot_model\nimport tensorflow.keras.layers as L\nfrom keras.utils import model_to_dot\nimport tensorflow.keras.backend as K\nfrom tensorflow.keras.models import Model\nfrom kaggle_datasets import KaggleDatasets\nfrom tensorflow.keras.applications import DenseNet121\n\nimport seaborn as sns\nfrom tqdm import tqdm\nimport matplotlib.cm as cm\nfrom sklearn import metrics\nfrom sklearn.preprocessing import MultiLabelBinarizer\nimport matplotlib.pyplot as plt\nfrom sklearn.utils import shuffle\nfrom sklearn.model_selection import train_test_split\n\ntqdm.pandas()\nimport plotly.express as px\nimport plotly.graph_objects as go\nimport plotly.figure_factory as ff\nfrom plotly.subplots import make_subplots\n\nnp.random.seed(0)\ntf.random.set_seed(0)\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","id":"y4ElXcLopnKO","outputId":"72756d56-48f2-46c7-d8d0-56ec9fc395ce","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Load the data and define hyperparameters","metadata":{"id":"DyZKLcDg2yRi"}},{"cell_type":"code","source":"EPOCHS = 20\nSAMPLE_LEN = 100\nIMAGE_PATH = \"../input/plant-pathology-2021-fgvc8/train_images/\"\n#TEST_PATH = \"../input/plant-pathology-2021-fgvc8/test.csv\"\nTRAIN_PATH = \"../input/plant-pathology-2021-fgvc8/train.csv\"\nSUB_PATH = \"../input/plant-pathology-2021-fgvc8/sample_submission.csv\"\n\n\n\nsub = pd.read_csv(SUB_PATH)\n#test_data = pd.read_csv(TEST_PATH)\ntrain_data = pd.read_csv(TRAIN_PATH)","metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","id":"mneU8D9bpnKS","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data.head()","metadata":{"id":"lSXP0xubpnKW","outputId":"80d177b3-cf7d-4088-90ff-149edfb86295","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#test_data.head()","metadata":{"id":"XISwCeBjpnKa","outputId":"61f70add-a4df-452e-feee-f0ffe0dfac14","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Load sample images","metadata":{"id":"O6DgMHJz293K"}},{"cell_type":"markdown","source":"## Visualize sample leaves <a id=\"1.4\"></a>\n\nNow, I will visualize sample leaves beloning to different categories in the dataset.","metadata":{"id":"Ee2qutMw3FTP"}},{"cell_type":"code","source":"train_data['labels'].value_counts()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(20,12))\nlabels = sns.barplot(train_data.labels.value_counts().index,train_data.labels.value_counts())\nfor item in labels.get_xticklabels():\n    item.set_rotation(45)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_data['labels'] = train_data['labels'].apply(lambda string: string.split(' '))\ntrain_data","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"s = list(train_data['labels'])\nmlb = MultiLabelBinarizer()\ntrainx = pd.DataFrame(mlb.fit_transform(s), columns=mlb.classes_, index=train_data.index)\ntrainx","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels = pd.concat([train_data['image'], trainx], axis=1)\nlabels.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def visualize_leaves(cond=[0, 0, 0, 0, 0, 0], cond_cols=[\"healthy\"], is_cond=True):\n    if not is_cond:\n        cols, rows = 3, min([3, len(train_images)//3])\n        fig, ax = plt.subplots(nrows=rows, ncols=cols, figsize=(30, rows*20/3))\n        for col in range(cols):\n            for row in range(rows):\n                ax[row, col].imshow(train_images.loc[train_images.index[-row*3-col-1]])\n        return None\n        \n    cond_0 = \"complex == {}\".format(cond[0])\n    cond_1 = \"frog_eye_leaf_spot == {}\".format(cond[1])\n    cond_2 = \"healthy == {}\".format(cond[2])\n    cond_3 = \"powdery_mildew == {}\".format(cond[3])\n    cond_4 = \"rust == {}\".format(cond[4])\n    cond_5 = \"scab == {}\".format(cond[5])\n    cond_list = []\n    for col in cond_cols:\n        if col == \"complex\":\n            cond_list.append(cond_0)\n        if col == \"frog_eye_leaf_spot\":\n            cond_list.append(cond_1)\n        if col == \"healthy\":\n            cond_list.append(cond_2)\n        if col == \"powdery_mildew\":\n            cond_list.append(cond_3)\n        if col == \"rust\":\n            cond_list.append(cond_4)\n        if col == \"scab\":\n            cond_list.append(cond_5)\n    \n    data = labels.loc[:100]\n    for cond in cond_list:\n        data = data.query(cond)\n        \n    images = train_images.loc[list(data.index)]\n    cols, rows = 3, min([3, len(images)//3])\n    \n    fig, ax = plt.subplots(nrows=rows, ncols=cols, figsize=(30, rows*20/3))\n    for col in range(cols):\n        for row in range(rows):\n            ax[row, col].imshow(images.loc[images.index[row*3+col]])\n    plt.show()","metadata":{"_kg_hide-input":true,"id":"mQagns98pnKt","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig = px.parallel_categories(labels[['complex', 'frog_eye_leaf_spot', 'healthy', 'powdery_mildew', 'rust','scab']], color=\"healthy\", color_continuous_scale=\"sunset\",\\\n                             title=\"Parallel categories plot of targets\")\nfig","metadata":{"_kg_hide-input":true,"id":"vRDHR4G_pnLs","outputId":"7f935b25-2686-447a-bd57-8cd2429b15d2","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In the above plot, we can see the relationship between all four categories. As expected, it is impossible for a healthy leaf (<code>healthy == 1</code>) to have scab, rust, or multiple diseases. Also, every unhealthy leaf has one of either scab, rust, or multiple diseases. The frequency of each combination can be seen by hovering over the plot.","metadata":{"id":"POYx63MXcfcQ"}},{"cell_type":"markdown","source":"# Image processing and augmentation <a id=\"2\"></a>","metadata":{"id":"aiPKQDkh5hF5"}},{"cell_type":"markdown","source":"## Canny edge detection <a id=\"2.1\"></a>\n\nCanny is a popular edge detection algorithm, and as the name suggests, it detects the edges of objects present in an image. It was developed by John F. Canny in 1986. The algorithm involves several steps.\n\n1. **Noise reduction:** Since edge detection is susceptible to noise in an image, we remove the noise in the image using a 5x5 Gaussian filter.\n\n\n2. **Finding Intensity Gradient of the Image**: The smoothened image is then filtered with a Sobel kernel in both horizontal and vertical directions to get the first derivative in the horizontal (*G<sub>x</sub>*) and vertical (*G<sub>y</sub>*) directions. From these two images, one can find the edge gradient and direction for each pixel:\n\n<center><img src=\"https://i.imgur.com/ntyjTep.png\" width=\"300px\"></center>\n<center><img src=\"https://i.imgur.com/75qDjv6.png\" width=\"260px\"></center>\n\n<br>\n\n3. **Rounding:** The gradient is always perpendicular to edges. So, it is rounded to one of the four angles representing vertical, horizontal and two diagonal directions.\n\n4. **Non-maximum suppression:** After getting the gradient magnitude and direction, a full scan of the image is done to remove any unwanted pixels which may not constitute the edge. For this, we check every pixel for being a local maximum in its neighborhood in the direction of the gradient.\n\n5. **Hysteresis Thresholding:** This stage decides which parts are edges and which are not. For this, we need two threshold values, *minVal* and *maxVal*. Any edges with intensity gradient greater than *maxVal* are considered edges and those lesser than *minVal* are considered non-edges, and discarded. Those who lie between these two thresholds are classified edges or non-edges based on their neighborhood. If they are near “sure-edge” pixels, they are considered edges, and otherwise, they are discarded.\n\nThe result of these five steps is a two-dimensional binary map (0 or 255) indicating the location of edges on the image. Canny edge is demonstrated below with a few leaf images:","metadata":{"id":"jC9wp-8N5kU4"}},{"cell_type":"markdown","source":"\n","metadata":{}},{"cell_type":"code","source":"def edge_and_cut(img):\n    emb_img = img.copy()\n    edges = cv2.Canny(img, 100, 200)\n    edge_coors = []\n    for i in range(edges.shape[0]):\n        for j in range(edges.shape[1]):\n            if edges[i][j] != 0:\n                edge_coors.append((i, j))\n    \n    row_min = edge_coors[np.argsort([coor[0] for coor in edge_coors])[0]][0]\n    row_max = edge_coors[np.argsort([coor[0] for coor in edge_coors])[-1]][0]\n    col_min = edge_coors[np.argsort([coor[1] for coor in edge_coors])[0]][1]\n    col_max = edge_coors[np.argsort([coor[1] for coor in edge_coors])[-1]][1]\n    new_img = img[row_min:row_max, col_min:col_max]\n    \n    emb_img[row_min-10:row_min+10, col_min:col_max] = [255, 0, 0]\n    emb_img[row_max-10:row_max+10, col_min:col_max] = [255, 0, 0]\n    emb_img[row_min:row_max, col_min-10:col_min+10] = [255, 0, 0]\n    emb_img[row_min:row_max, col_max-10:col_max+10] = [255, 0, 0]\n    \n    fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(30, 20))\n    ax[0].imshow(img, cmap='gray')\n    ax[0].set_title('Original Image', fontsize=24)\n    ax[1].imshow(edges, cmap='gray')\n    ax[1].set_title('Canny Edges', fontsize=24)\n    ax[2].imshow(emb_img, cmap='gray')\n    ax[2].set_title('Bounding Box', fontsize=24)\n    plt.show()","metadata":{"_kg_hide-input":true,"id":"uU_iqYaCpnL7","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"edge_and_cut(train_images[3])\nedge_and_cut(train_images[4])\nedge_and_cut(train_images[5])","metadata":{"_kg_hide-input":true,"id":"bYJ0t9kppnL9","outputId":"a2785c32-9151-4d8d-f837-ec9de9de49bf","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The second column of images above contains the Canny edges and the third column contains cropped images. I have taken the Canny edges and used it to predict a bounding box in which the actual leaf is contained. The most extreme edges at the four corners of the image are the vertices of the bounding box. This red box is likely to contain most of if not all of the leaf. These edges and bounding boxes can be used to build more accurate models.","metadata":{}},{"cell_type":"markdown","source":"## Flipping <a id=\"2.2\"></a>\n\nFlipping is a simple transformation that involves index-switching on the image channels. In vertical flipping, the order of rows is exchanged, whereas in vertical flipping, the order of rows is exchanged. Let us assume that *A<sub>ijk</sub>* (of size *(m, n, 3)*) is the image we want to flip. Horizontal and vertical flipping can be represented by the transformations below:\n\n<center><img src=\"https://i.imgur.com/B9y5apl.png\" width=\"135px\"></center>\n<center><img src=\"https://i.imgur.com/eQ1dyvN.png\" width=\"305px\"></center>\n<center><img src=\"https://i.imgur.com/i30LQgq.png\" width=\"305px\"></center>\n<br>\n\nWe can see that the order of columns is exchanged in horizontal flipping. While the *i* and *k* indices remain the same, the *j* index reverses. Whereas, in vertical flipping, the order of rows is exchanged in horizontal flipping. While the *j* and *k* indices remain the same, the *i* index reverses.\n\n","metadata":{"id":"Wh0WHTCC5sL_"}},{"cell_type":"code","source":"def invert(img):\n    fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(30, 20))\n    ax[0].imshow(img)\n    ax[0].set_title('Original Image', fontsize=24)\n    ax[1].imshow(cv2.flip(img, 0))\n    ax[1].set_title('Vertical Flip', fontsize=24)\n    ax[2].imshow(cv2.flip(img, 1))\n    ax[2].set_title('Horizontal Flip', fontsize=24)\n    plt.show()","metadata":{"_kg_hide-input":true,"id":"XPZwEZAepnMA","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"invert(train_images[3])\ninvert(train_images[4])\ninvert(train_images[5])","metadata":{"_kg_hide-input":true,"id":"gaiLRUzopnMD","outputId":"dc8f2538-abb7-4449-f0c9-dedd8a393843","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We can see that the images are simply flipped. All major features in the image remain the same, but to a computer algorithm, the flipped images look completely different. These transformations can be used for data augmentation, making models more robust and accurate.","metadata":{}},{"cell_type":"markdown","source":"## Convolution <a id=\"2.3\"></a>\n\nConvolution is a rather simple algorithm which involves a kernel (a 2D matrix) which moves over the entire image, calculating dot products with each window along the way. The GIF below demonstrates convolution in action.\n\n<center><img src=\"https://i.imgur.com/wYUaqR3.gif\" width=\"450px\"></center>\n\nThe above process can be summarized with an equation, where *f* is the image and *h* is the kernel. The dimensions of *f* are *(m, n)* and the kernel is a square matrix with dimensions smaller than *f*:\n\n<center><img src=\"https://i.imgur.com/9scTOGv.png\" width=\"350px\"></center>\n<br>\n\nIn the above equation, the kernel *h* is moving across the length and breadth of the image. The dot product of *h* with a sub-matrix or window of matrix *f* is taken at each step, hence the double summation (rows and columns). Below I demonstrate the effect of convolution on leaf images.","metadata":{"id":"PqS2I93A5u_R"}},{"cell_type":"code","source":"def conv(img):\n    fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(20, 20))\n    kernel = np.ones((7, 7), np.float32)/25\n    conv = cv2.filter2D(img, -1, kernel)\n    ax[0].imshow(img)\n    ax[0].set_title('Original Image', fontsize=24)\n    ax[1].imshow(conv)\n    ax[1].set_title('Convolved Image', fontsize=24)\n    plt.show()","metadata":{"_kg_hide-input":true,"id":"aa81abmWpnMG","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"conv(train_images[3])\nconv(train_images[4])\nconv(train_images[5])","metadata":{"_kg_hide-input":true,"id":"2rqxzygspnMJ","outputId":"98092ab1-48d2-4ce6-e3a9-0342e31cf91c","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The convolution operator seems to have an apparent \"sunshine\" effect of the images. This may also serve the purpose of augmenting the data, thus helping to build more robust and accurate models. ","metadata":{}},{"cell_type":"markdown","source":"## Blurring <a id=\"2.4\"></a>\n\nBlurring is simply the addition of noise to the image, resulting in a less-clear image. The noise can be sampled from any distribution of choice, as long as the main content in the image does not become invisible. Only the minor details get obfuscated due to blurring. The blurring transformation can be represented using the equation below. \n\n<center><img src=\"https://i.imgur.com/zVM8HCU.png\" width=\"220px\"></center>\n<br>\n\nThe example uses a Gaussian distribution with mean 0 and variance 0.1. Below I demonstrate the effect of blurring on a few leaf images:","metadata":{"id":"xmmHZzUq5xu8"}},{"cell_type":"code","source":"def blur(img):\n    fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(20, 20))\n    ax[0].imshow(img)\n    ax[0].set_title('Original Image', fontsize=24)\n    ax[1].imshow(cv2.blur(img, (100, 100)))\n    ax[1].set_title('Blurred Image', fontsize=24)\n    plt.show()","metadata":{"_kg_hide-input":true,"id":"OcXTa-cxpnMM","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"blur(train_images[3])\nblur(train_images[4])\nblur(train_images[5])","metadata":{"_kg_hide-input":true,"id":"aH5qRDdupnMP","outputId":"5b649688-b7e6-419e-d060-ddb09a291e38","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The transformation clearly blurs the image by removing detailed, low-level features, while retaining the major, high-level features. This is once again a great way to augment images and train more robust models.","metadata":{}},{"cell_type":"markdown","source":"# Modeling <a id=\"3\"></a>","metadata":{"id":"wyfDeAJo6lGL"}},{"cell_type":"markdown","source":"## Preparing the ground <a id=\"3.1\"></a>\n\nBefore we move on to building the models, I will explain the major building blocks in pretrained CV models. Every major ImageNet model has a different architecture, but each one has the common building blocks: **Conv2D, MaxPool, ReLU**. I have already explained the mechanism behind convolution in the previous section, so I will now explain MaxPool and ReLU.\n\n### MaxPool\n\nMax pooling is very similar to convolution, except it involves finding the maximum value in a window instead of finding the dot product of the window with a kernel. Max pooling does not require a kernel and it is very useful in reducing the dimensionality of convolutional feature maps in CNNs. The image below demonstrates the working of MaxPool:\n\n\n<center><img src=\"https://i.imgur.com/rBNMsfi.png\" width=\"400px\"></center>\n<br></br>\n\nThe above example demonstrates max pooling with a window size of *(2, 2)*. This process can be represented with the equation below:\n<br></br>\n.\n\n<center><img src=\"https://i.imgur.com/FRyMNhI.png\" width=\"650px\"></center>\n<br></br>\n\nIn the above equation, the window moves across the image and the maximum value in each winow is calculated. Once again, this process is very important in reducing the complexity of CNNs while retaining features.","metadata":{"id":"J5f4-lkS7lKs"}},{"cell_type":"markdown","source":"### ReLU\n\nReLU is an activation function commonly used in neural network architectures. *ReLU(x)* returns 0 for *x < 0* and *x* otherwise. This function helps introducenon-linearity in the neural network, thus increasing its capacity ot model the image data. The graph and equation of *ReLU* are:\n\n<center><img src=\"https://i.imgur.com/eiRVQBh.png\" width=\"400px\"></center>\n\n<center><img src=\"https://i.imgur.com/0mBFAH0.png\" width=\"400px\"></center>\n<br></br>\n\nAs mentioned earlier, this function is non-linear and helps increase the modeling capacity of the CNN models. Now since we understand the basic building blocks of pretrained images models, let us finetune some pretained ImageNet models on TPU and visualize the results!","metadata":{}},{"cell_type":"markdown","source":"### Setup TPU Config","metadata":{"id":"zOfbl73V6t3p"}},{"cell_type":"code","source":"AUTO = tf.data.experimental.AUTOTUNE\ntpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n\ntf.config.experimental_connect_to_cluster(tpu)\ntf.tpu.experimental.initialize_tpu_system(tpu)\nstrategy = tf.distribute.experimental.TPUStrategy(tpu)\n\nBATCH_SIZE = 16 * strategy.num_replicas_in_sync\nGCS_DS_PATH = KaggleDatasets().get_gcs_path('plant-pathology-2021-fgvc8')\n#GCS_DS_PATH = '../input/plant-pathology-2021-fgvc8/train_images'","metadata":{"id":"2ZC6VPQHpnMR","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"KaggleDatasets().get_gcs_path()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Load labels and paths","metadata":{"id":"SuAHc2hu6-Nu"}},{"cell_type":"code","source":"labels","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels[labels['healthy'] == 1]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def format_path(st):\n#     return GCS_DS_PATH + '/images/' + st \n    return GCS_DS_PATH + '/train_images/' + st \n#test_paths = test_data.image_id.apply(format_path).values\ntrain_paths = labels.image.apply(format_path).values\n\ntrain_labels = (labels.loc[:, 'complex':'scab'].values)","metadata":{"id":"9BALmDtRpnMU","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def decode_image(filename, label=None, image_size=(512, 512)):\n    bits = tf.io.read_file(filename)\n    image = tf.image.decode_jpeg(bits, channels=3)\n    image = tf.cast(image, tf.float32) / 255.0\n    image = tf.image.resize(image, image_size)\n    \n    if label is None:\n        return image\n    else:\n        return image, label\n\ndef data_augment(image, label=None):\n    image = tf.image.random_flip_left_right(image)\n    image = tf.image.random_flip_up_down(image)\n    \n    if label is None:\n        return image\n    else:\n        return image, label","metadata":{"id":"T84Nnc1jpnMW","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Create Dataset objects","metadata":{"id":"tonEhhQ77Knh"}},{"cell_type":"code","source":"train_paths","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices((train_paths, train_labels))\n    .map(decode_image, num_parallel_calls=AUTO)\n    .map(data_augment, num_parallel_calls=AUTO)\n    .repeat()\n    .shuffle(512)\n    .batch(BATCH_SIZE)\n    .prefetch(AUTO)\n)\n\n# valid_dataset = (\n#     tf.data.Dataset\n#     .from_tensor_slices((valid_paths, valid_labels))\n#     .map(decode_image, num_parallel_calls=AUTO)\n#     .batch(BATCH_SIZE)\n#     .cache()\n#     .prefetch(AUTO)\n# )\n\n\n# test_dataset = (\n#     tf.data.Dataset\n#     .from_tensor_slices(test_paths)\n#     .map(decode_image, num_parallel_calls=AUTO)\n#     .batch(BATCH_SIZE)\n# )\n","metadata":{"id":"5rkIRCnupnMZ","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Helper functions","metadata":{"id":"xmirtR2L7TDC"}},{"cell_type":"code","source":"def build_lrfn(lr_start=0.00001, lr_max=0.00005, \n               lr_min=0.00001, lr_rampup_epochs=5, \n               lr_sustain_epochs=0, lr_exp_decay=.8):\n    lr_max = lr_max * strategy.num_replicas_in_sync\n\n    def lrfn(epoch):\n        if epoch < lr_rampup_epochs:\n            lr = (lr_max - lr_start) / lr_rampup_epochs * epoch + lr_start\n        elif epoch < lr_rampup_epochs + lr_sustain_epochs:\n            lr = lr_max\n        else:\n            lr = (lr_max - lr_min) *\\\n                 lr_exp_decay**(epoch - lr_rampup_epochs\\\n                                - lr_sustain_epochs) + lr_min\n        return lr\n    return lrfn","metadata":{"id":"uiiCB9SdpnMc","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Define hyperparameters and callbacks","metadata":{}},{"cell_type":"code","source":"lrfn = build_lrfn()\nSTEPS_PER_EPOCH = train_labels.shape[0] // BATCH_SIZE\nlr_schedule = tf.keras.callbacks.LearningRateScheduler(lrfn, verbose=1)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## DenseNet <a id=\"3.2\"></a>\n\nDensely Connected Convolutional Networks (DenseNets), are a popular CNN-based ImageNet used for a variety of applications, inclusing classification, segmentation, localization, etc. Most models before DenseNet relied solely on network depth for representational power. **Instead of drawing representational power from extremely deep or wide architectures, DenseNets exploit the potential of the network through feature reuse.** This was the main motivation behind the DenseNet architecture. Now let us train DenseNet on leaf images and evaluate its performance.","metadata":{"id":"PRtfn6bJ7qI1"}},{"cell_type":"code","source":"with strategy.scope():\n    model = tf.keras.Sequential([DenseNet121(input_shape=(512, 512, 3),\n                                             weights='imagenet',\n                                             include_top=False),\n                                 L.GlobalAveragePooling2D(),\n                                 L.Dense(train_labels.shape[1],\n                                         activation='softmax')])\n        \n    model.compile(optimizer='adam',\n                  loss = 'categorical_crossentropy',\n                  metrics=['categorical_accuracy'])\n    model.summary()","metadata":{"id":"7fllHhN9pnMe","outputId":"c030160f-118a-43db-f431-0fd6b4379292","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### DenseNet fundamental block","metadata":{}},{"cell_type":"code","source":"SVG(tf.keras.utils.model_to_dot(Model(model.layers[0].input, model.layers[0].layers[13].output), dpi=70).create(prog='dot', format='svg'))","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The above image shows the fundamental block in the DenseNet architecture. The architecture mainly involves Convolution, Maxpooling, ReLU, and concatenation.","metadata":{}},{"cell_type":"markdown","source":"### Visualize model architecture\n\nThe model consists of the DenseNet head (without the top), followed by global average pooling and a dense layer (with softmax) to generate probabilities.","metadata":{}},{"cell_type":"code","source":"SVG(tf.keras.utils.model_to_dot(model, dpi=70).create(prog='dot', format='svg'))","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Train model","metadata":{"id":"V4yafObR7wIo"}},{"cell_type":"code","source":"history = model.fit(train_dataset,\n                    epochs=EPOCHS,\n                    callbacks=[lr_schedule],\n                    steps_per_epoch=STEPS_PER_EPOCH,\n                    validation_data=valid_dataset)","metadata":{"_kg_hide-input":false,"_kg_hide-output":true,"id":"V56dwx6opnMh","outputId":"4fc94f9f-ac22-4386-a9d2-6e3192493b33","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Visualize results","metadata":{"id":"H32GrB0L7ulh"}},{"cell_type":"code","source":"def display_training_curves(training, validation, yaxis):\n    if yaxis == \"loss\":\n        ylabel = \"Loss\"\n        title = \"Loss vs. Epochs\"\n    else:\n        ylabel = \"Accuracy\"\n        title = \"Accuracy vs. Epochs\"\n        \n    fig = go.Figure()\n        \n    fig.add_trace(\n        go.Scatter(x=np.arange(1, EPOCHS+1), mode='lines+markers', y=training, marker=dict(color=\"dodgerblue\"),\n               name=\"Train\"))\n    \n    fig.add_trace(\n        go.Scatter(x=np.arange(1, EPOCHS+1), mode='lines+markers', y=validation, marker=dict(color=\"darkorange\"),\n               name=\"Val\"))\n    \n    fig.update_layout(title_text=title, yaxis_title=ylabel, xaxis_title=\"Epochs\", template=\"plotly_white\")\n    fig.show()","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Scatter plots","metadata":{"id":"U2Rmet4p-ot3"}},{"cell_type":"code","source":"display_training_curves(\n    history.history['categorical_accuracy'], \n    history.history['val_categorical_accuracy'], \n    'accuracy')","metadata":{"_kg_hide-input":true,"id":"dKUl8NckpnMn","outputId":"eb37495e-52fc-4e0f-dc0b-e82e708dcf94","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"From the above plots, we can see that the losses decrease and accuracies increase quite consistently. The training metrics settle down very fast (after 1 or 2 epochs), whereas the validation metrics much greater volatility and start to settle down only after 7-8 epochs. This is expected because validation data is unseen and more diffcult to make predictions on than training data. ","metadata":{}},{"cell_type":"markdown","source":"### Animation (click ▶️)","metadata":{}},{"cell_type":"code","source":"acc_df = pd.DataFrame(np.transpose([[*np.arange(1, EPOCHS+1).tolist()*3], [\"Train\"]*EPOCHS + [\"Val\"]*EPOCHS + [\"Benchmark\"]*EPOCHS,\n                                     history.history['categorical_accuracy'] + history.history['val_categorical_accuracy'] + [1.0]*EPOCHS]))\nacc_df.columns = [\"Epochs\", \"Stage\", \"Accuracy\"]\nfig = px.bar(acc_df, x=\"Accuracy\", y=\"Stage\", animation_frame=\"Epochs\", title=\"Accuracy vs. Epochs\", color='Stage',\n       color_discrete_map={\"Train\":\"dodgerblue\", \"Val\":\"darkorange\", \"Benchmark\":\"seagreen\"}, orientation=\"h\")\n\nfig.update_layout(\n    xaxis = dict(\n        autorange=False,\n        range=[0, 1]\n    )\n)\n\nfig.update_layout(template=\"plotly_white\")","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"From the animations above, we can see the volatility in validation metrics a lot more clearly. The validation metrics oscillate in an erratic fashion until it reaches the 7th epoch and starts to generalize properly.","metadata":{}},{"cell_type":"markdown","source":"### Sample predictions\n\nNow, I will visualize some sample predictions made by the DenseNet model. The <font color=\"red\">red</font> bars represent the model's prediction (maximum probability), the <font color=\"green\">green</font> represent the ground truth (label), and the rest of the bars are <font color=\"blue\">blue</font>. When the model predicts correctly, the prediction bar is <font color=\"green\">green</font>.","metadata":{}},{"cell_type":"code","source":"def process(img):\n    return cv2.resize(img/255.0, (512, 512)).reshape(-1, 512, 512, 3)\ndef predict(img):\n    return model.layers[2](model.layers[1](model.layers[0](process(img)))).numpy()[0]\n\nfig = make_subplots(rows=4, cols=2)\npreds = predict(train_images[2])\n\ncolors = {\"Healthy\":px.colors.qualitative.Plotly[0], \"Scab\":px.colors.qualitative.Plotly[0], \"Rust\":px.colors.qualitative.Plotly[0], \"Multiple diseases\":px.colors.qualitative.Plotly[0]}\nif list.index(preds.tolist(), max(preds)) == 0:\n    pred = \"Healthy\"\nif list.index(preds.tolist(), max(preds)) == 1:\n    pred = \"Scab\"\nif list.index(preds.tolist(), max(preds)) == 2:\n    pred = \"Rust\"\nif list.index(preds.tolist(), max(preds)) == 3:\n    pred = \"Multiple diseases\"\n\ncolors[pred] = px.colors.qualitative.Plotly[1]\ncolors[\"Healthy\"] = \"seagreen\"\ncolors = [colors[val] for val in colors.keys()]\nfig.add_trace(go.Image(z=cv2.resize(train_images[2], (205, 136))), row=1, col=1)\nfig.add_trace(go.Bar(x=[\"Healthy\", \"Multiple diseases\", \"Rust\", \"Scab\"], y=preds, marker=dict(color=colors)), row=1, col=2)\nfig.update_layout(height=1200, width=800, title_text=\"DenseNet Predictions\", showlegend=False)\n\npreds = predict(train_images[0])\ncolors = {\"Healthy\":px.colors.qualitative.Plotly[0], \"Scab\":px.colors.qualitative.Plotly[0], \"Rust\":px.colors.qualitative.Plotly[0], \"Multiple diseases\":px.colors.qualitative.Plotly[0]}\nif list.index(preds.tolist(), max(preds)) == 0:\n    pred = \"Healthy\"\nif list.index(preds.tolist(), max(preds)) == 1:\n    pred = \"Multiple diseases\"\nif list.index(preds.tolist(), max(preds)) == 2:\n    pred = \"Rust\"\nif list.index(preds.tolist(), max(preds)) == 3:\n    pred = \"Scab\"\n    \ncolors[pred] = px.colors.qualitative.Plotly[1]\ncolors[\"Multiple diseases\"] = \"seagreen\"\ncolors = [colors[val] for val in colors.keys()]\nfig.add_trace(go.Image(z=cv2.resize(train_images[0], (205, 136))), row=2, col=1)\nfig.add_trace(go.Bar(x=[\"Healthy\", \"Multiple diseases\", \"Rust\", \"Scab\"], y=preds, marker=dict(color=colors)), row=2, col=2)\n\npreds = predict(train_images[3])\ncolors = {\"Healthy\":px.colors.qualitative.Plotly[0], \"Scab\":px.colors.qualitative.Plotly[0], \"Rust\":px.colors.qualitative.Plotly[0], \"Multiple diseases\":px.colors.qualitative.Plotly[0]}\nif list.index(preds.tolist(), max(preds)) == 0:\n    pred = \"Healthy\"\nif list.index(preds.tolist(), max(preds)) == 1:\n    pred = \"Multiple diseases\"\nif list.index(preds.tolist(), max(preds)) == 2:\n    pred = \"Rust\"\nif list.index(preds.tolist(), max(preds)) == 3:\n    pred = \"Scab\"\n    \ncolors[pred] = px.colors.qualitative.Plotly[1]\ncolors[\"Rust\"] = \"seagreen\"\ncolors = [colors[val] for val in colors.keys()]\nfig.add_trace(go.Image(z=cv2.resize(train_images[3], (205, 136))), row=3, col=1)\nfig.add_trace(go.Bar(x=[\"Healthy\", \"Multiple diseases\", \"Rust\", \"Scab\"], y=preds, marker=dict(color=colors)), row=3, col=2)\n\npreds = predict(train_images[1])\ncolors = {\"Healthy\":px.colors.qualitative.Plotly[0], \"Scab\":px.colors.qualitative.Plotly[0], \"Rust\":px.colors.qualitative.Plotly[0], \"Multiple diseases\":px.colors.qualitative.Plotly[0]}\nif list.index(preds.tolist(), max(preds)) == 0:\n    pred = \"Healthy\"\nif list.index(preds.tolist(), max(preds)) == 1:\n    pred = \"Multiple diseases\"\nif list.index(preds.tolist(), max(preds)) == 2:\n    pred = \"Rust\"\nif list.index(preds.tolist(), max(preds)) == 3:\n    pred = \"Scab\"\n    \ncolors[pred] = px.colors.qualitative.Plotly[1]\ncolors[\"Scab\"] = \"seagreen\"\ncolors = [colors[val] for val in colors.keys()]\nfig.add_trace(go.Image(z=cv2.resize(train_images[1], (205, 136))), row=4, col=1)\nfig.add_trace(go.Bar(x=[\"Healthy\", \"Multiple diseases\", \"Rust\", \"Scab\"], y=preds, marker=dict(color=colors)), row=4, col=2)\n\nfig.update_layout(template=\"plotly_white\")","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_paths = []\ntest_paths.append(GCS_DS_PATH + '/test_images/' + '85f8cb619c66b863.jpg')\ntest_paths.append(GCS_DS_PATH + '/test_images/' + 'ad8770db05586b59.jpg')\ntest_paths.append(GCS_DS_PATH + '/test_images/' + 'c7b03e718489f3ca.jpg')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices(test_paths)\n    .map(decode_image, num_parallel_calls=AUTO)\n    .batch(BATCH_SIZE)\n)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"probs_efn = model.predict(test_dataset, verbose=1)\nprint(probs_efn)\nSUB_PATH = \"../input/plant-pathology-2021-fgvc8/sample_submission.csv\"\nsub = pd.read_csv(SUB_PATH)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"arr = ['complex','frog_eye_leaf_spot','healthy','powdery_mildew','rust','scab']","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub.loc[:, 'labels':] = [arr[np.argmax(aa)] for aa in probs_efn][0]\n#sub.loc[:, 'labels':] = [arr[np.argmax(aa)] for aa in probs_efn][1]\n#sub.loc[:, 'labels':] = [arr[np.argmax(aa)] for aa in probs_efn][2]\nsub.to_csv('./submission.csv', index=False)\nsub.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We can see that DenseNet predicts leaf diseases with great accuracy. No red or blue bars are seen. The probabilities are very polarized (one very high and the rest very low), indicating that the model is making these predictions with great confidence.","metadata":{}},{"cell_type":"markdown","source":"### Generate submission","metadata":{}},{"cell_type":"code","source":"# probs_dnn = model.predict(test_dataset, verbose=1)\n# sub.loc[:, 'healthy':] = probs_dnn\n# sub.to_csv('submission_dnn.csv', index=False)\n# sub.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## EfficientNet <a id=\"3.3\"></a>\n\nEfficientNet is another popular (more recent) CNN-based ImageNet model which achieved the SOTA on several image-based tasks in 2019. EfficientNet performs model scaling in an innovative way to achieve excellent accuracy with significantly fewer parameters. It achieves the same if not greater accuracy than ResNet and DenseNet with a mcuh shallower architecture. Now let us train EfficientNet on leaf images and evaluate its performance.","metadata":{"id":"isy53hSJ72O5"}},{"cell_type":"code","source":"with strategy.scope():\n    model = tf.keras.Sequential([efn.EfficientNetB7(input_shape=(512, 512, 3),\n                                                    weights='imagenet',\n                                                    include_top=False),\n                                 L.GlobalAveragePooling2D(),\n                                 L.Dense(train_labels.shape[1],\n                                         activation='softmax')])\n    \n    \n        \n    model.compile(optimizer='adam',\n                  loss = 'categorical_crossentropy',\n                  metrics=['categorical_accuracy'])\n    model.summary()","metadata":{"id":"x8ELPOLJpnMp","outputId":"03e23ca7-1a98-4a3d-d61b-3edcb1ae0ced","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### EfficientNet fundamental block","metadata":{}},{"cell_type":"code","source":"SVG(tf.keras.utils.model_to_dot(Model(model.layers[0].input, model.layers[0].layers[11].output), dpi=70).create(prog='dot', format='svg'))","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The above image shows the fundamental block in the EfficientNet architecture. This architecture involves more addition and multiplication-based operators than DenseNet. These operations are less parameter-intensive than concatenation, which is much more common in DenseNet. Such transformations help EfficientNet achieve great efficiency (in terms of performance per parameter).","metadata":{}},{"cell_type":"markdown","source":"### Visualize model architecture\n\nThe model consists of the EfficientNet head (without the top), followed by global average pooling and a dense layer (with softmax) to generate probabilities.","metadata":{}},{"cell_type":"code","source":"SVG(tf.keras.utils.model_to_dot(model, dpi=70).create(prog='dot', format='svg'))","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Train model","metadata":{"id":"DKydIBmq76tf"}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"history = model.fit(train_dataset,\n                    epochs=EPOCHS,\n                    callbacks=[lr_schedule],\n                    steps_per_epoch=STEPS_PER_EPOCH)","metadata":{"_kg_hide-input":false,"_kg_hide-output":true,"id":"MHuypFwdpnMr","outputId":"3fde00ef-c8ac-46b4-bd0f-81a1294bf66f","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.save(\"EfficientNet_epoch20_2.h5\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"a","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"history = a","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Visualize results","metadata":{"id":"hus0ZMYP8Ehy"}},{"cell_type":"markdown","source":"### Scatter plots","metadata":{"id":"gTrARyKO-aQP"}},{"cell_type":"code","source":"display_training_curves(\n    history.history['categorical_accuracy'], \n    history.history['val_categorical_accuracy'], \n    'accuracy')","metadata":{"_kg_hide-input":true,"id":"94JJ7UNDpnMu","outputId":"f4331b89-9fbc-47aa-884a-0b5d26df1479","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Animation (click ▶️)","metadata":{}},{"cell_type":"markdown","source":"From the above plots, we can once again see that the losses decrease and accuracies increase quite consistently. The training metrics settle down very fast (after 1 or 2 epochs). In this case, the validation metrics do not show high volatility as compared to the DenseNet model.","metadata":{}},{"cell_type":"code","source":"acc_df = pd.DataFrame(np.transpose([[*np.arange(1, EPOCHS+1).tolist()*3], [\"Train\"]*EPOCHS + [\"Val\"]*EPOCHS + [\"Benchmark\"]*EPOCHS,\n                                     history.history['categorical_accuracy'] + history.history['val_categorical_accuracy'] + [1.0]*EPOCHS]))\nacc_df.columns = [\"Epochs\", \"Stage\", \"Accuracy\"]\nfig = px.bar(acc_df, x=\"Accuracy\", y=\"Stage\", animation_frame=\"Epochs\", title=\"Accuracy vs. Epochs\", color='Stage',\n       color_discrete_map={\"Train\":\"dodgerblue\", \"Val\":\"darkorange\", \"Benchmark\":\"seagreen\"}, orientation=\"h\")\n\nfig.update_layout(\n    xaxis = dict(\n        autorange=False,\n        range=[0, 1]\n    )\n)\n\nfig.update_layout(template=\"plotly_white\")","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"From the animations above, we can see that the validation and training metrics do not show great volatility. They steadily rise towards 1.0.","metadata":{}},{"cell_type":"code","source":"test_paths","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_dataset = (\n    tf.data.Dataset\n    .from_tensor_slices(test_paths)\n    .map(decode_image, num_parallel_calls=AUTO)\n    .batch(BATCH_SIZE)\n)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The model predicts the leaf diseases with great accuracy. The level of performance is similar to that of DenseNet, as the green bars are very common. The red and blue bars are more prominent in the last (fourth) leaf labeled \"multiple diseases\". This is probably because leaves with multiple diseases may show symptoms of rust and scab as well, thus slightly confusing the model.","metadata":{}},{"cell_type":"markdown","source":"### Generate submission","metadata":{}},{"cell_type":"code","source":"probs_efn = model.predict(test_dataset, verbose=1)\nprint(probs_efn)\nSUB_PATH = \"../input/plant-pathology-2021-fgvc8/sample_submission.csv\"\nsub = pd.read_csv(SUB_PATH)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"arr = ['complex','frog_eye_leaf_spot',\t'healthy'\t,'powdery_mildew'\t,'rust',\t'scab']\narr","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub.loc[:, 'labels':] = [arr[np.argmax(aa)] for aa in probs_efn][0]\nsub.to_csv('./submission.csv', index=False)\nsub.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"[arr[np.argmax(aa)] for aa in probs_efn]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sub.loc[:, 'labels':]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## EfficientNet NoisyStudent <a id=\"3.4\"></a>\n\nEfficientNet NoisyStudent, released in 2020, is based on EfficientNet and uses semi-supervised learning on noisy images to learn rich visual representation. It outperformed EfficientNet on several tasks and is the SOTA at the time of writing (March 2020). Now let us train EfficientNet NoisyStudent on leaf images and evaluate its performance.","metadata":{}},{"cell_type":"code","source":"with strategy.scope():\n    model = tf.keras.Sequential([efn.EfficientNetB7(input_shape=(512, 512, 3),\n                                                    weights='noisy-student',\n                                                    include_top=False),\n                                 L.GlobalAveragePooling2D(),\n                                 L.Dense(train_labels.shape[1],\n                                         activation='softmax')])\n    \n    \n        \n    model.compile(optimizer='adam',\n                  loss = 'categorical_crossentropy',\n                  metrics=['categorical_accuracy'])\n    model.summary()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### EfficientNet NoisyStudent","metadata":{}},{"cell_type":"code","source":"SVG(tf.keras.utils.model_to_dot(Model(model.layers[0].input, model.layers[0].layers[11].output), dpi=70).create(prog='dot', format='svg'))","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The above image shows the fundamental block in the EfficientNet NoisyStudent architecture. This model has the same architecture as EfficientNet. Only the weights are different, as they are obtained through semi-supervision.","metadata":{}},{"cell_type":"markdown","source":"### Visualize model architecture\n\nThe model consists of the EfficientNet NoisyStudent head (without the top), followed by global average pooling and a dense layer (with softmax) to generate probabilities.","metadata":{}},{"cell_type":"code","source":"SVG(tf.keras.utils.model_to_dot(model, dpi=70).create(prog='dot', format='svg'))","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Train model","metadata":{}},{"cell_type":"code","source":"history = model.fit(train_dataset,\n                    epochs=EPOCHS,\n                    callbacks=[lr_schedule],\n                    steps_per_epoch=STEPS_PER_EPOCH,\n                    validation_data=valid_dataset)","metadata":{"_kg_hide-input":false,"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Visualize results","metadata":{}},{"cell_type":"markdown","source":"### Scatter plots","metadata":{}},{"cell_type":"code","source":"display_training_curves(\n    history.history['categorical_accuracy'], \n    'accuracy')","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"From the above plots, we can see that the losses decrease and accuracies increase quite consistently. The training metrics settle down very fast (after 1 or 2 epochs), whereas the validation metrics much greater volatility and start to settle down only after 12-13 epochs (similar to DenseNet). This is expected because validation data is unseen and more diffcult to make predictions on than training data. ","metadata":{}},{"cell_type":"markdown","source":"### Animation (click ▶️)","metadata":{}},{"cell_type":"code","source":"acc_df = pd.DataFrame(np.transpose([[*np.arange(1, EPOCHS+1).tolist()*3], [\"Train\"]*EPOCHS + [\"Val\"]*EPOCHS + [\"Benchmark\"]*EPOCHS,\n                                     history.history['categorical_accuracy'] + history.history['val_categorical_accuracy'] + [1.0]*EPOCHS]))\nacc_df.columns = [\"Epochs\", \"Stage\", \"Accuracy\"]\nfig = px.bar(acc_df, x=\"Accuracy\", y=\"Stage\", animation_frame=\"Epochs\", title=\"Accuracy vs. Epochs\", color='Stage',\n       color_discrete_map={\"Train\":\"dodgerblue\", \"Val\":\"darkorange\", \"Benchmark\":\"seagreen\"}, orientation=\"h\")\n\nfig.update_layout(\n    xaxis = dict(\n        autorange=False,\n        range=[0, 1]\n    )\n)\n\nfig.update_layout(template=\"plotly_white\")","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]}]}