{"cells":[{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5"},"cell_type":"markdown","source":"# Kannada MNIST: A Simple CNN Tutorial with App.\n**Benan AKCA** <a id=\"0\"></a>\n\n[![cover.jpg](https://i.postimg.cc/HkVhwrMb/cover.jpg)](https://postimg.cc/QFZmsx0M)\n\n1. [__INTRODUCTION__](#1)\n    \n2. [__CONVOLUTIONAL NEURAL NETWORKS__](#100)\n    1. [__Convolution Layer__](#110)\n        * [Steps of Convolution Operation](#111)\n        * [Stride](#112)\n        * [An Example of Convolution Operation](#113)\n        * [Why Do We Use Filters?](#114)       \n    2. [__Padding__](#130)\n        * [Why do we use Padding?](#131)\n        * [Equation of Calculating Output Dimension](#132)\n        * [Types of Padding in Keras](#133)\n    3. [__Pooling Layer__](#150)\n        * [Hyper Parameters of Pooling Operation](#151)\n        * [Why do we use Pooling?](#152)\n        * [Types of Pooling in Keras](#153)\n    4. [__Batch Normalization Layer__](#160)\n    5. [__Drop Out Layer__](#180)\n    \n3. [__APPLICATION WITH CNN__](#200)\n    1. [__Import Modules__](#210)\n    2. [__Understanding the Data__](#220)\n    3. [__Data Preprocessing__](#230)\n        * [Normalizing the Data](#231)\n        * [Train and Test Splitting](#232)\n        * [Reshape Data to Appropriate Sizes ](#233)\n    4. [__Building and Training a CNN Model__](#250)  \n        * [Model - Build](#251)\n        * [Model - Compile](#252)\n            * [Categorical Cross Entropy](#253)\n            * [Optimizer](#255)\n            * [On-the-Fly Data Augmentation](#257)\n        * [Model - Fit](#259)\n    5. [__Evaluation of the Model__](#270) \n        * [Accuracy and Loss Curves](#271) * \n        * [Test Set Accuracy Score](#272)\n        * [Confusion Matrix](#273)\n        * [F1 Score Calculation](#274)\n        * [Evaluate with Another Dataset](#275)\n        * [Submit for Competition](#275)\n6. [__CONCLUSION__](#290)"},{"metadata":{},"cell_type":"markdown","source":"# ** 2. Introduction** <a id=\"1\"></a>\n<mark>[Return Contents](#0)\n    \n* Hello everyone, first of all, I am pleased to present this work to you. This kernel was prepared within the scope of handwriting recognition contest using the Kannada MNIST data set and in the competition, it managed to be among the __top 2%__. \n* Without further ado, I would like to give you brief information about the content of the study.\n\nThis study consists of two main parts. \n\n1. Basic Theoretical Knowledge; \n\n   * Causal approaches,\n   * Visual expression\n   * Answers of the questions that may arise in your mind about CNN.\n   \n   \n2. Practicing with an Application;\n\n   * Handwriting recognition with the Kannada handwritten digits dataset which is different than the MNIST handwritten digits dataset.   \n   * In the application part, I also gave some theoretical information with visual details according to the necessity.\n  \n  \n  \n  <font color='green'>\n    \n#### If you will find it useful, I would appreciate it if you upvote.\n#### Then let's begin...\n  [![smile.jpg](https://i.postimg.cc/0jVh4z64/smile.jpg)](https://postimg.cc/fS0HtTf7)\n"},{"metadata":{},"cell_type":"markdown","source":"# ** 2. Convolutional Neural Networks** <a id=\"100\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{},"cell_type":"markdown","source":"* In Deep Learning, Convolutional Neural Network (CNN, or ConvNet) is a class of deep neural networks, most commonly applied to analyzing visual imagery.\n* It has applications in image and video recognition, recommender systems, image classification, medical image analysis, and natural language processing\n* The name “Convolutional Neural Network” indicates that the network employs a mathematical operation called convolution. Convolution is a specialized kind of linear operation. Convolutional networks are simply neural networks that use convolution in place of general matrix multiplication in at least one of their layers\n* A convolutional neural network consists of one or several convolutional layers, followed by some fully connected layers of neurons like in classical multilayer feedforward neural networks.\n* The hidden layers of a CNN typically consist of convolutional layers, pooling layers, fully connected layers and normalization layers. "},{"metadata":{},"cell_type":"markdown","source":"## **Convolution Operation** <a id=\"110\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{},"cell_type":"markdown","source":"[![conv-sample.jpg](https://i.postimg.cc/Bngpn2F1/conv-sample.jpg)](https://postimg.cc/QFF1690X)\n* You can think of left one as the “input” signal (or image), and the other (called the kernel) as a “filter” on the input image, producing an output image (so convolution takes two matrix as input and produces a third as output). \n\n#### **Steps of convulation operation** <a id=\"111\"></a> \n1. Take the element wise production of the upper left corner sub-matrix and filter\n2. Then sum all row and column to obtain a single value\n3. Result is the upper corner cell's value of your new output matrix. (output(0,0))\n4. After that, slide the filter over \"S\" column on main image and do the same thing. You have your output(0,1) value.\n\n#### **Stride** <a id=\"112\"></a>\n* Sliding amount is declared by stride (S) constant. \n* According to problem stride content may vary change and this will directly effect on output dimensions.\n* Calculation of output matrix dimension for $l$'th layer:\n\\begin{equation}\nn^{l} = \\frac{n^{l-1} - f}{s}+1 \\\\\n\\text{output} =  n^l x n^l\n\\end{equation}\nWhere;\n * $l$ demonstrates the layer number.\n * $n^{l}$ is the horizontal/vertical dimension of output matrix.\n * $n^{l-1}$ is the horizontal/vertical dimension of input matrix.\n * $f$ is the horizontal/vertical dimension of filter (kernel) matrix.\n * $s$ is the stride constant. Using for determine how many columns will kernel shift to right side in order to calculate output matrixes next cell."},{"metadata":{},"cell_type":"markdown","source":"### **An Example of Convolution Operation** <a id=\"113\"></a>\n<mark>[Return Contents](#0)\n    \n[![conv2.png](https://i.postimg.cc/g2cXdGn9/conv2.png)](https://postimg.cc/F7qsV5yG)\n* As you see above we have 5x5 image matrix and 3x3 filter matrix,\n* If we choose the stride (S) parameter as 1 then our output matrix would be 3x3 matrix\n* If we choose the stride (S) parameter as 2 then our output matrix would be 2x2 matrix\n\n\\begin{equation}\nn^{l} = \\frac{5 - 3}{1}+1 = 3\\\\\n\\text{output} =  3 x 3\\\\\n\\text{  }\\\\\nn^{l} = \\frac{5 - 3}{2}+1 = 2\\\\\n\\text{output} =  2 x 2\\\\\n\\end{equation}"},{"metadata":{},"cell_type":"markdown","source":"* If the entrance picture is colored (RGB) as below, the 2nd and 3rd dimensions will also be available. (6x6x3)\n* In this case, we will convolute the filter 1 to have three different values from 3 channels and will sum the results to obtain a single number.\n* The important factor in here is filter1 has the same values for all three-channel (demonstrated as different colors) he has.\n* On the example above we use only one filter but in real applications, there will be more filters used at the same time as below.\n* The output matrix width(w) and height(h) sizes are generally the same in applications but it can be different also.\n* So output images are a matrix in $w$x$h$x$c$ dimension.\n* $w$ and $h$ is calculated as above equation and the channel dimension of the matrix is the same as filter number used on convolution. You can imagine that after the convolution operation each filter creates a different layer on the matrix as below."},{"metadata":{},"cell_type":"markdown","source":"[![convolution-with-multiple-filters2.png](https://i.postimg.cc/1RVhTfzX/convolution-with-multiple-filters2.png)](https://postimg.cc/DmhM4fL3)"},{"metadata":{},"cell_type":"markdown","source":"### **Why do we use Filters?** <a id=\"114\"></a>\n<mark>[Return Contents](#0)\n* To answer this question first we should ask another and more general question;\n* What is our main purpose?\n* In this kernel our main purpose is as you know recognition handwrite patterns, but for simplicity, let us think about cats.\n* When you see a cat face picture how do you understand that is a cat? It should have solid patterns on its face right? \n   * Solid patterns like eye shape, nose curve, mustaches, etc.\n   * Our brain recognizes some parts of the face separately and combines them to give a final decision. The same explanation is acceptable for CNN filters.\n* Each filter has a job; some of them detect the edges (Sobel filter as on the example), some of them detect just the horizontal lines. \n* The task of the filter becomes more complex in the deep layers of the evolutionary network. For example, the first layer of the network can detect just __horizontal lines__ while deeper layers of the network __can detect the nose or eye__ and again ongoing deeper layers __can detect even the human face.__\n* To sum up filters are used to extract the features of the image. After feature extraction, we use a fully connected layer for classification according to extracted features.\n\n"},{"metadata":{},"cell_type":"markdown","source":"## **Padding** <a id=\"130\"></a>\n<mark>[Return Contents](#0)\n\n    "},{"metadata":{},"cell_type":"markdown","source":"##### Padding refers to the process of symmetrically adding values to the input matrix. \n\n[![padding.png](https://i.postimg.cc/MK6S0DXG/padding.png)](https://postimg.cc/QVywhcQL)"},{"metadata":{},"cell_type":"markdown","source":"### **__Why do we use Padding?__** <a id=\"131\"></a>\n<mark>[Return Contents](#0)    \n1. The information at the edges is convoluted only once, while the information in the middle is convoluted 3 times with a 3x3 filter. This results in the loss of information on the edges. Edge information can be convoluted multiple times thanks to padding.\n2. The output matrix dimension getting smaller whenever we use convolution operation. Padding mostly used in designing the CNN layers when the dimensions of the input volume need to be preserved in the output volume (**Same Padding**).\n    \n### Equation of calculating output dimension changes with using padding process as below <a id=\"132\"></a>\n\n\\begin{equation}\nn^{l} = \\frac{n^{l-1} + 2p - f}{s}+1 \\\\\n\\text{output} =  n^l x n^l\n\\end{equation}\n"},{"metadata":{},"cell_type":"markdown","source":"### **Types of Padding in Keras** <a id=\"133\"></a> \n<mark>[Return Contents](#0)  \n* Valid : No padding\n* Same  : Choosing a $p$ value on above equation to obtain output matrix in same dimension as like input matrix."},{"metadata":{},"cell_type":"markdown","source":"## **Pooling** <a id=\"150\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{},"cell_type":"markdown","source":"* Pooling refers to the process of dividing a matrix into pools and select from every pool only one value as representing that pool.\n* Despite the convolution operation Pooling has not any weight because you don't use any filter to operation.\n* It is about only selecting one value from small groups."},{"metadata":{},"cell_type":"markdown","source":"[![pooling.png](https://i.postimg.cc/qRwcsqPF/pooling.png)](https://postimg.cc/ph9nxWXQ)"},{"metadata":{},"cell_type":"markdown","source":"### **Hyper Parameters of Pooling Operation** <a id=\"151\"></a>\n1. Pooling Size\n2. Stride"},{"metadata":{},"cell_type":"markdown","source":"* Same as convolution operation, for example in the above pic. there is $2$x$2$ pooling size and stride = 2 (sliding on matrix by 2 cell)"},{"metadata":{},"cell_type":"markdown","source":"### **Why do we use Pooling?** <a id=\"152\"></a>\n<mark>[Return Contents](#0)\n1. To gain robustness on feature extraction;\n    * Pooling prevents the model from over-training by discarding the unnecessary data relative to the selected value.\n2. To speed up the computation;\n    * Reducing the size of the representation for increasing the speed of the computation."},{"metadata":{},"cell_type":"markdown","source":"### **Types of Pooling in Keras** <a id=\"153\"></a>\n<mark>[Return Contents](#0)\n    \n* Max Pooling: The operation of selecting the maximum value from each mini-group (pool) as the picture above\n* Average Pooling: The operation of selecting the average value from each mini-group."},{"metadata":{},"cell_type":"markdown","source":"[![CNN.jpg](https://i.postimg.cc/6qr7MkGP/CNN.jpg)](https://postimg.cc/Lgs6X0Dk)]\n"},{"metadata":{},"cell_type":"markdown","source":"  1. Filter Matrices containing a specific pattern\n  2. In the Conv layer, the filters are shifted over the image by the number of strides and convolution is performed.\n  3. The matrix obtained as a result of the 3rd process is called a feature map. Here it can be observed in which regions of the matrix in the previous layer how much data is available for the filter pattern.\n  4. Numerically high values are indicative of pattern compliance.\n  5. A feature map can be considered as the new picture we have for the next CNN layer. We can now detect the patterns in this picture with a new filter. This is the 2nd layer of CNN. We can increase the number of layers with this logic."},{"metadata":{},"cell_type":"markdown","source":"## **Batch Normalization** <a id=\"160\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{},"cell_type":"markdown","source":"#### **What is Batch Norm?** <a id=\"161\"></a>\n<mark>[Return Contents](#0)\n    \n* Batch Normalization is a method used to normalize the input of a hidden layer by adjusting and scaling the activations.\n* The method consists of performing normalization on each neuron of a specific layer on each training mini-batch.\n* Normalized neurons of $l$'th layer will have a specific mean and variance for the problem according to trained $\\gamma$ and $\\beta$ parameters.\n* The algorithm of the batch norm is shown below;\n\n[![batch-norm.png](https://i.postimg.cc/6QpmDb6K/batch-norm.png)](https://postimg.cc/ts8k65Vv)"},{"metadata":{},"cell_type":"markdown","source":"#### **Why do we use Batch Norm?** <a id=\"161\"></a>\n<mark>[Return Contents](#0)\n    \n* At artificial neural networks distribution of the inputs changes for each layer during training since the weight parameters of the previous layers change. This slows down training by requiring lower learning rates and careful parameter initialization, making it difficult to train models with non-linear saturation.\n* In short terms we use Batch Norm;\n    1. To increase the learning speed of the model.\n    2. To use much higher learning rates and be less careful about initialization\n    3. To regularize the network (side positive effect)"},{"metadata":{},"cell_type":"markdown","source":"#### **Some notes about Batch Norm** <a id=\"162\"></a>\n<mark>[Return Contents](#0)\n    \n* The parameters $\\gamma$ and $\\beta$ are learnable parameters. Therefore, you just initialize them and with backpropagation or other optimization algorithm they will be optimized like weight parameters.\n* Changing the distribution of each layer’s inputs during training, because of the enhancement of parameters of the previous layers calling covariate shifting at the original paper.\n* You can read the original paper written by Sergey Ioffe and Christian Szegedy from [here](https://arxiv.org/pdf/1502.03167.pdf)."},{"metadata":{},"cell_type":"markdown","source":"## **Drop Out** <a id=\"180\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{},"cell_type":"markdown","source":"* Drop out is an effective regularization method.\n* During training, some number of layer outputs are randomly ignored or “dropped out.” This has the effect of making the layer look-like and be treated-like a layer with a different number of nodes and connectivity to the prior layer. In effect, each update to a layer during training is performed with a different “view” of the configured layer.\n* __Why do we use Drop Out?__ The answer is to prevent overfitting...\n* It can be used with most types of layers, such as dense fully connected layers, convolutional layers, and recurrent layers such as the long short-term memory network layer.\n[![images.jpg](https://i.postimg.cc/Bn3X9rhW/images.jpg)](https://postimg.cc/hfCSV5WM)"},{"metadata":{},"cell_type":"markdown","source":"[![Types-of-Learning-in-Machine-Learning.jpg](https://i.postimg.cc/L6kp6RLT/Types-of-Learning-in-Machine-Learning.jpg)](https://postimg.cc/vcmj3JPg)"},{"metadata":{},"cell_type":"markdown","source":"# ** 3. An Image Classification Application with CNN** <a id=\"200\"></a>\n<mark>[Return Contents](#0)\n<hr>"},{"metadata":{},"cell_type":"markdown","source":"## **Import Modules** <a id=\"210\"></a>"},{"metadata":{"trusted":true},"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport matplotlib.pyplot as plt # data visualization\nimport seaborn as sns # data visualization\n\nfrom sklearn.model_selection import train_test_split\nfrom keras.utils.np_utils import to_categorical\n\nimport tensorflow as tf\nfrom tensorflow.keras import Sequential\nfrom tensorflow.keras.layers import Conv2D, Dropout, Dense, Flatten, BatchNormalization, MaxPooling2D,LeakyReLU\nfrom tensorflow.keras.optimizers import RMSprop,Nadam,Adadelta\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.regularizers import l2\n\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* With this code below you can check if the kernel use GPU or not."},{"metadata":{"trusted":true},"cell_type":"code","source":"tf.test.gpu_device_name()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## **Understanding the Data** <a id=\"220\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{},"cell_type":"markdown","source":"* We have training and test set CSV files,\n* In order to evaluate the generalization skill of the model we will split our training set into training and validation sets.\n* After training the data, Kaggle will evaluate the final performance of our data with test set predictions."},{"metadata":{},"cell_type":"markdown","source":"* Let's read csv files"},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"raw_train = pd.read_csv('../input/Kannada-MNIST/train.csv')\nraw_test = pd.read_csv('../input/Kannada-MNIST/test.csv')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* We have $28$x$28$ dimension handwritten pics.\n* Dataset has been already flattened and has 784-pixel values for each pic.\n* Totaly we have $60000$ pics in training set."},{"metadata":{"scrolled":true,"trusted":true},"cell_type":"code","source":"raw_train.iloc[[0,-1],[1,-1]] # First and last values of dataset","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* It is important to know the distribution of data according to the labels they have.\n* This data set is __homogeneously__ distributed as you see below."},{"metadata":{"trusted":true},"cell_type":"code","source":"num = raw_train.label.value_counts()\nsns.barplot(num.index,num)\nnumbers = num.index.values","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* __If the data wasn't homogeneously__ distributed what would we do?\n    1. Then we could use data augmentation techniques to generate new data for low quantity labels,\n    2. Or if we have enough data we can discard some high quantity labels"},{"metadata":{},"cell_type":"markdown","source":"#### **Image of Handwritten Character** <a id=\"2\"></a>\n<mark>[Return Contents](#0)\n<hr>\n    \n * An overview of a picture\n * You can change the $num$ variable to see other numbers."},{"metadata":{"trusted":true},"cell_type":"code","source":"num=6\nnumber = raw_train.iloc[num,1:].values.reshape(28,28)\nprint(\"Picture of \"+ str(num) + \"in Kannada style\")\nplt.imshow(number, cmap=plt.get_cmap('gray'))\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## **Data Preprocessing** <a id=\"230\"></a>\n### **Normalizing Data** <a id=\"231\"></a>\n<mark>[Return Contents](#0)\n<hr>\n* What is normalizing? Normalization means that adjusting values measured on different scales to a notionally common scale.\n* Why should you normalize the data?  With a normalized data weight values reach optimum value faster.\n* On image processing applications generally we normalize data to 0-1 scale with dividing data to 255.\n* Because each pixel in every sample of training set has integer values from 0 to 255.\n* In order to normalize training set data, we need to convert x to float type."},{"metadata":{"trusted":true},"cell_type":"code","source":"x = raw_train.iloc[:, 1:].values.astype('float32') / 255\ny = raw_train.iloc[:, 0] # labels","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### **Cross Validation - Training- Validation Set Split** <a id=\"232\"></a>\n<mark>[Return Contents](#0)\n    \n* In order to measure the generalization ability of the model, we train the data with the training set and make the model arrangement according to the error value in the validation set. In addition, we determine the final performance of the model with the test set. \n* The reason for using the test set on the final evaluation is the model would have a bias on the validation set because we developed the model according to the validation set performance. So a kind of overfitting on the validation set is formed.\n* This Kernel is prepared on a Kaggle competition dataset. They give us a training set for training the model and a test set without labels for prediction. As we don't have the labels we don't know the final performance of the test set until we submit our predictions.\n* To evaluate the model we need to split our training set into training and validation set.\n    \n* For I prefer to split\n    * Training set - $\\%80$\n    * Validation set - $\\%20$\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"x_train, x_val, y_train, y_val = train_test_split(x, y, test_size = 0.2, random_state=42) ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### **Reshape Data to Fit Model** <a id=\"233\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{},"cell_type":"markdown","source":"> * In order to feed the CNN model we need to reshape our $54000$x$784$ flatten image data to $54000$x$28$x$28$x$1$ dimensions.;"},{"metadata":{"trusted":true},"cell_type":"code","source":"x_train.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"x_train = x_train.reshape(-1, 28, 28,1)\nx_val = x_val.reshape(-1, 28, 28,1)\ny_train = to_categorical(y_train)\ny_val = to_categorical(y_val)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* You can leave one unknown dimension as -1.\n* Whenever numpy has -1 value on reshape method it will calculate the dimension which denoted with -1 automatically."},{"metadata":{},"cell_type":"markdown","source":"## **Building and Training a CNN Model** <a id=\"250\"></a>\n<mark>[Return Contents](#0)\n<hr>\n* On Keras Sequential Networks there is three-stage for training building, compiling and fitting the model.\n    \n#### **Model - Build** <a id=\"251\"></a>\n\n    \n* On building stage you specify the architecture of the model mainly.\n* You can decide the [Filter](#110) size and [Padding](#150) type you will use on [Convolution](#110) operations and add [Pooling](#150), Batch Normalization, Dropout, activation function layers with build section."},{"metadata":{"trusted":true},"cell_type":"code","source":"model = tf.keras.models.Sequential([\n    tf.keras.layers.Conv2D(64, (3,3), padding='same', input_shape=(28, 28, 1)),\n    tf.keras.layers.BatchNormalization(momentum=0.9, epsilon=1e-5, gamma_initializer=\"uniform\"),\n    tf.keras.layers.LeakyReLU(alpha=0.1),\n    tf.keras.layers.Conv2D(64,  (3,3), padding='same'),\n    tf.keras.layers.BatchNormalization(momentum=0.9, epsilon=1e-5, gamma_initializer=\"uniform\"),\n    tf.keras.layers.LeakyReLU(alpha=0.1),\n    tf.keras.layers.Conv2D(64,  (3,3), padding='same'),\n    tf.keras.layers.BatchNormalization(momentum=0.9, epsilon=1e-5, gamma_initializer=\"uniform\"),\n    tf.keras.layers.LeakyReLU(alpha=0.1),\n\n    tf.keras.layers.MaxPooling2D(2, 2),\n    \n    tf.keras.layers.Conv2D(128, (3,3), padding='same'),\n    tf.keras.layers.BatchNormalization(momentum=0.9, epsilon=1e-5, gamma_initializer=\"uniform\"),\n    tf.keras.layers.LeakyReLU(alpha=0.1),\n    tf.keras.layers.Conv2D(128, (3,3), padding='same'),\n    tf.keras.layers.BatchNormalization(momentum=0.9, epsilon=1e-5, gamma_initializer=\"uniform\"),\n    tf.keras.layers.LeakyReLU(alpha=0.1),\n    tf.keras.layers.Conv2D(128, (3,3), padding='same'),\n    tf.keras.layers.BatchNormalization(momentum=0.9, epsilon=1e-5, gamma_initializer=\"uniform\"),\n    tf.keras.layers.LeakyReLU(alpha=0.1),\n    \n    tf.keras.layers.MaxPooling2D(2,2),\n    \n    tf.keras.layers.Conv2D(256, (3,3), padding='same'),\n    tf.keras.layers.BatchNormalization(momentum=0.9, epsilon=1e-5, gamma_initializer=\"uniform\"),\n    tf.keras.layers.LeakyReLU(alpha=0.1),\n    tf.keras.layers.Conv2D(256, (3,3), padding='same'),\n    tf.keras.layers.BatchNormalization(momentum=0.9, epsilon=1e-5, gamma_initializer=\"uniform\"),##\n    tf.keras.layers.LeakyReLU(alpha=0.1),\n\n    tf.keras.layers.MaxPooling2D(2,2),\n    \n    \n    tf.keras.layers.Flatten(),\n    tf.keras.layers.Dense(256),\n    tf.keras.layers.LeakyReLU(alpha=0.1),\n    \n    tf.keras.layers.BatchNormalization(),\n    tf.keras.layers.Dense(10, activation='softmax')\n])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### **Model - Compile** <a id=\"252\"></a>\n<mark>[Return Contents](#0)\n    \n* On Compile Section we specify the loss function, optimizer algorithm and metric to use for evaluating the model."},{"metadata":{"trusted":true},"cell_type":"code","source":"optimizer = RMSprop(learning_rate=0.0015,###########\n    rho=0.9,\n    momentum=0.1,\n    epsilon=1e-07,\n    centered=True,\n    name='RMSprop')\nmodel.compile(loss='categorical_crossentropy',\n              optimizer=optimizer,\n              metrics=['accuracy'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### **Compile ->Loss Function ->Categorical Crossentropy** <a id=\"253\"></a>\n<mark>[Return Contents](#0)\n    \n* The Categorical Crossentropy Loss Function computes between network predictions and target values for multiclass classification problems.\n\n* The loss is calculated using the following formula;\n\\begin{equation}\nLoss = -\\frac{1}{m}\\sum_{i=1}^{m}\\sum_{k=1}^{K} Y^i_klog(\\hat{Y^i_k})(1-Y^i_k)log(1-\\hat{Y^i_k}) \\\\\n\\end{equation}\n\nwhere $k$ demonstrates class, $i$ demonstrates sample number, $\\hat{Y_c}$ is the predicted value, $Y_c$ is the ground truth value, $m$ is the sample number in a batch and $K$ is the total number of classes.\n\n* Why do we use log? Because cross-entropy function penalize bigger difference more and smaller difference less as mentioned below.\n\n[![cross-entropy.png](https://i.postimg.cc/qvwNHJpL/cross-entropy.png)](https://postimg.cc/687WdN22)"},{"metadata":{},"cell_type":"markdown","source":"#### **Compile -> Optimizer** <a id=\"255\"></a>\n<mark>[Return Contents](#0)\n* To optimize the weight values of the network we should choose an optimizer algorithm. \n* In the first lessons of artificial learning, the gradient descent algorithm is taught as the optimization algorithm used in backpropagation. \n* Over time, the gradient descent algorithm was developed and the algorithms that achieved faster and more accurate results were obtained. \n* Some of them are ADAGRAD, ADAM, ADAMAX, NADAM and RMSPROP. \n* These algorithms use techniques such as adaptive learning rate and momentum to achieve the global minimum.\n* If you want to take a more detailed look at Gradient Descent algorithms, [here](https://ruder.io/optimizing-gradient-descent/) is a very nice overview article written by [Sebastian Ruder](https://ruder.io/optimizing-gradient-descent/).\n\n* __As you see below__ while Stochastic Gradient Descent (SGD) which is a basic gradient descent algorithm cannot escape the saddle point, more advanced algorithms escape the saddle point __at different speeds.__\n"},{"metadata":{},"cell_type":"markdown","source":"![CNN.jpg](https://ruder.io/content/images/2016/09/saddle_point_evaluation_optimizers.gif)\n![CNN.jpg](https://ruder.io/content/images/2016/09/contours_evaluation_optimizers.gif)"},{"metadata":{"trusted":true},"cell_type":"code","source":"batch_size = 1024\nnum_classes = 10\nepochs = 60\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### **On-the-Fly Data Augmentation** <a id=\"257\"></a>\n<mark>[Return Contents](#0)\n* On classification tasks on image datasets data augmentation is a common way to increase the generalization of the model. \n\n[![Plot-of-Images-Generated-with-a-Rotation-Augmentation.png](https://i.postimg.cc/m2JBtVsK/Plot-of-Images-Generated-with-a-Rotation-Augmentation.png)](https://postimg.cc/3dXPqXWZ)\n\n* With the ImageDataGenerator on Keras, we can handle this objective easily.\n* [Here](https://www.pyimagesearch.com/2019/07/08/keras-imagedatagenerator-and-data-augmentation/) is a comprehensive and inspiring article about data augmentation and ImageDataGenerator written by [Adrian Rosebrock](https://www.pyimagesearch.com/2019/07/08/keras-imagedatagenerator-and-data-augmentation/) who is the author of PyImageSearch a very instructive web-site about computer vision.\n* __By changing__ some of the properties of the image from the code below, __you can observe what changes are happening in the dataset__. \n* With this observation, you can roughly specify the range you should choose."},{"metadata":{"trusted":true},"cell_type":"code","source":"# An observation code for our dataset\ndatagen_try = ImageDataGenerator(rotation_range=15,\n                             width_shift_range = 0.25,\n                             height_shift_range = 0.25,\n                             shear_range = 25,\n                             zoom_range = 0.4,)\n# fit parameters from data\ndatagen_try.fit(x_train)\n# configure batch size and retrieve one batch of images\nfor x_batch, y_batch in datagen_try.flow(x_train, y_train, batch_size=9):\n\t# create a grid of 3x3 images\n\tfor i in range(0, 9):\n\t\tplt.subplot(330 + 1 + i)\n\t\tplt.imshow(x_batch[i].reshape(28, 28), cmap=plt.get_cmap('gray'))\n\t# show the plot\n\tplt.show()\n\tbreak","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"datagen_train = ImageDataGenerator(rotation_range = 10,\n                                   width_shift_range = 0.25,\n                                   height_shift_range = 0.25,\n                                   shear_range = 10,\n                                   zoom_range = 0.1,\n                                   horizontal_flip = False)\n\ndatagen_val = ImageDataGenerator() \n\n\nstep_train = x_train.shape[0] // batch_size\nstep_val = x_val.shape[0] // batch_size\n\nlearning_rate_reduction = tf.keras.callbacks.ReduceLROnPlateau( \n    monitor='loss',    # Quantity to be monitored.\n    factor=0.25,       # Factor by which the learning rate will be reduced. new_lr = lr * factor\n    patience=2,        # The number of epochs with no improvement after which learning rate will be reduced.\n    verbose=1,         # 0: quiet - 1: update messages.\n    mode=\"auto\",       # {auto, min, max}. In min mode, lr will be reduced when the quantity monitored has stopped decreasing; \n                       # in the max mode it will be reduced when the quantity monitored has stopped increasing; \n                       # in auto mode, the direction is automatically inferred from the name of the monitored quantity.\n    min_delta=0.0001,  # threshold for measuring the new optimum, to only focus on significant changes.\n    cooldown=0,        # number of epochs to wait before resuming normal operation after learning rate (lr) has been reduced.\n    min_lr=0.00001     # lower bound on the learning rate.\n    )\n\nes = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=300, restore_best_weights=True)\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### **Model - Fit** <a id=\"259\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{"trusted":true},"cell_type":"code","source":"history = model.fit_generator(datagen_train.flow(x_train, y_train, batch_size=batch_size),\n                              steps_per_epoch=len(x_train)//batch_size,\n                              epochs=epochs,\n                              validation_data=(x_val, y_val),\n                              validation_steps=50,\n                              callbacks=[learning_rate_reduction, es],\n                              verbose=2)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## **Evaluation of the Model** <a id=\"270\"></a>\n### **Accuracy and Loss Curves** <a id=\"271\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{},"cell_type":"markdown","source":"* On classification, accuracy metric is calculated as below;\n\n\\begin{equation}\nclassification~accuracy = \\frac{correct~predictions}{total~predictions} * 100 \\\\\n\\end{equation}"},{"metadata":{"trusted":true},"cell_type":"code","source":"tf.keras.utils.plot_model(model, to_file=\"model.png\", show_shapes=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.plot(history.history['accuracy'])\nplt.plot(history.history['val_accuracy'])\nplt.title('Model accuracy')\nplt.ylabel('Accuracy')\nplt.xlabel('Epoch')\nplt.legend(['Train', 'Validation'], loc='upper left')\nplt.show()\n\n# Plot training & validation loss values\nplt.plot(history.history['loss'])\nplt.plot(history.history['val_loss'])\nplt.title('Model loss')\nplt.ylabel('Loss')\nplt.xlabel('Epoch')\nplt.legend(['Train', 'Validation'], loc='upper left')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### **Test Set Accuracy Score** <a id=\"272\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{"trusted":true},"cell_type":"code","source":"model.evaluate(x_val, y_val, verbose=2);","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### **Confusion Matrix** <a id=\"273\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{"trusted":true},"cell_type":"code","source":"y_predicted = model.predict(x_val)\ny_grand_truth = y_val\ny_predicted = np.argmax(y_predicted,axis=1)\ny_grand_truth = np.argmax(y_grand_truth,axis=1)\nfrom sklearn.metrics import confusion_matrix\ncm = confusion_matrix(y_grand_truth, y_predicted)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* If 90% of the data set is cat image and 10% is dog image, your accuracy will be 90% even if you estimate the entire test set as a cat.\n* But in another aspect, the model's success in predicting dogs is 0%.\n* In this context, accuracy may not always give us realistic information about the actual performance of the model.\n* The confusion matrix shows how confused your classification model is for which classes by detailing the relationship between actual class and predicted class.\n* If there is an anomaly something like above mentioned, you can specify the problem with confusion matrix and improve accuracy by various methods like adding more data for a specific class, etc."},{"metadata":{"trusted":true},"cell_type":"code","source":"f, ax = plt.subplots(figsize=(10,10))\nsns.heatmap(cm,fmt=\".0f\", annot=True,linewidths=0.1, linecolor=\"purple\", ax=ax)\nplt.xlabel(\"Predicted\")\nplt.ylabel(\"Grand Truth\")\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### **F1 Score Calculation** <a id=\"274\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{},"cell_type":"markdown","source":"* As mentioned above, [accuracy](#271) gives a general idea about the performance of the model but does not provide any information about the model's trends.\n* In classification algorithms, it is important to analyze the false predictions of the model.\n* There are 2 kinds of false predictions; \n  * __Predicted as \"1\" but Ground Truth is \"0\" (False Positive)__\n  * __Predicted as \"0\" but Ground Truth is \"1\" (False Negative)__\n\n[![f1.png](https://i.postimg.cc/1X1h0vM1/f1.png)](https://postimg.cc/Hczhd4f6)\n* The F1 score creates a success performance metric, taking into account both of these incorrect prediction performances as well as the true positive predictions\n* Since our problem has more than 2 classes, we calculated the F1 score for each class on a one-to-all basis.\n* [Here](https://towardsdatascience.com/multi-class-metrics-made-simple-part-ii-the-f1-score-ebe8b2c2ca1) is a nice article on how to calculate the F1 score in multiple classes, supported by examples."},{"metadata":{"trusted":true},"cell_type":"code","source":"scores = np.zeros((10,3))\ndef calc_F1(num):\n  TP = cm[num,num]\n  FN = np.sum(cm[num,:])-cm[num,num]\n  FP = np.sum(cm[:,num])-cm[num,num]\n  precision = TP/(TP+FP)\n  recall = TP/(TP+FN)\n  F1_score = 2*(recall * precision) / (recall + precision)\n  return precision, recall, F1_score\nfor i in range(10):\n   precision, recall, F1_score = calc_F1(i)\n   scores[i,:] = precision, recall, F1_score\nscores_frame = pd.DataFrame(scores,columns=[\"Precision\", \"Recall\", \"F1 Score\"], index=[list(range(0, 10))])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"f, ax = plt.subplots(figsize = (4,6))\nax.set_title('Number Scores')\nsns.heatmap(scores_frame, annot=True, fmt=\".3f\", linewidths=0.5, cmap=\"PuBu\", cbar=True, ax=ax)\nbottom, top = ax.get_ylim()\nplt.ylabel(\"\")\nax.set_ylim(bottom + 0.5, top - 0.5)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### **Evaluate with Another Dataset** <a id=\"275\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{"trusted":true},"cell_type":"code","source":"raw_dig = pd.read_csv(\"../input/Kannada-MNIST/Dig-MNIST.csv\")\nraw_dig.head()\nx_dig = raw_dig.iloc[:, 1:].values.astype('float32') / 255\ny_dig = raw_dig.iloc[:, 0].values\n\nx_dig = x_dig.reshape(-1,28,28,1)\ny_dig = to_categorical(y_dig)\nmodel.evaluate(x_dig, y_dig, verbose=2)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### **Submit for Competition** <a id=\"276\"></a>\n<mark>[Return Contents](#0)"},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_sub=pd.read_csv('../input/Kannada-MNIST/sample_submission.csv')\nraw_test_id=raw_test.id\nraw_test=raw_test.drop(\"id\",axis=\"columns\")\nraw_test=raw_test / 255\ntest=raw_test.values.reshape(-1,28,28,1)\ntest.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sub=model.predict(test)     ##making prediction\nsub=np.argmax(sub,axis=1) ##changing the prediction intro labels\n\nsample_sub['label']=sub\nsample_sub.to_csv('submission.csv',index=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# **Conclusion** <a id=\"276\"></a>\n<mark>[Return Contents](#0)\n<font color='green'>\n* Please do not hesitate to comment and ask questions.\n* If you found it useful, I would appreciate it if you upvote    \n    \n    [![smile.jpg](https://i.postimg.cc/0jVh4z64/smile.jpg)](https://postimg.cc/fS0HtTf7)\n"}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.5"}},"nbformat":4,"nbformat_minor":1}