{"cells":[{"metadata":{},"cell_type":"markdown","source":"## Comprehensive beginners guide to CNN:\n\nCNN(Convolution Neural Networks) are very popular and well used specially when it comes to pattern or shape recognitions in images. \n\nSince, this problem asks for a hand-written digit recognition or in other words pattern recognition I have used CNN. There are several advantages of using CNN which are described later and are easy to use. I have used a Keras model. \n\nI hope this will give a good idea of how Convolution Neural Nets work.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"### Importing libraires and packages:","execution_count":null},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import confusion_matrix\nfrom sklearn.model_selection import train_test_split\n\nfrom tensorflow.keras.models import Sequential, Model, load_model\nfrom tensorflow.keras.layers import Conv2D, MaxPool2D, Flatten, Dense\nfrom tensorflow.keras import optimizers\nfrom keras.utils.np_utils import to_categorical\n\nimport os","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Reading the data from Input folder:","execution_count":null},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"train = pd.read_csv(\"/kaggle/input/digit-recognizer/train.csv\")\ntest = pd.read_csv(\"/kaggle/input/digit-recognizer/test.csv\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Analyzing the data:","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"print(train.shape)\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# The 'label' column is separated from the rest in the training set\n\nlabel = train['label'].to_numpy()\ntrain['label'].value_counts().plot(kind='bar')\ntrain = train.drop(columns=['label']).to_numpy()\ntest = test.to_numpy()\n\n# The reshaping is important as need to put it in form of an image of dimension 28x28.\n# The extra 1 denotes it is an gray scale image. For an RGB image it would be 3\ntrain=np.reshape(train,(42000,28,28,1))\ntest=np.reshape(test,(28000,28,28,1))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The plot above shows the occurence of each of the digits in the training set. \n\nThere is not much of a difference between frequency of each of the digit. So, the dataset can be assumed to be not skewed and therefore good.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"### Data Visualization:","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"#Visualizing few random digits from the training set\n\nfig=plt.figure(figsize=(10, 4))\ncolumns = 6\nrows = 1\nfor i in range(columns*rows):\n    fig.add_subplot(rows, columns, i+1)\n    plt.axis('off')\n    img1 = train[i+1000]\n    plt.imshow(np.squeeze(img1,axis=2), cmap=\"gray\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Data Preprocessing:\n\n##### Normalizing:\nThis is a very important step in any image or feature based classification. \nWithout normalisation of data some features get much more importance to others leading to lower accuracy compared to when normalized.\n\nThere are generally 2 very popular ways of normalizing:\n\ni) Dividing the values with the maximum value that can occur (for gray scale it is 255) --\n\n      This will lead values from 0 to 1\n      \nii) Subtracting the mean of the values and then dividing it with the standard deviation --\n\n      This will lead values from -1 to 1","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"#Here I have used the second method, the first method is equally applicable:\n\nmeu = train.mean()\nsig = train.std()\ntrain = (train - meu)/sig\n\nmeu = test.mean()\nsig = test.std()\ntest = (test - meu)/sig","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"##### One-Hot encoding:\n\nIt is popular way to categorising data and is widely used whenever we do classification for more than 2 classes but is equally appliable for binary(2 class) classification as well.\n\nA one-hot vector is a vector which is 0 in most dimensions, and 1 in a single dimension. In this case, the n-th digit will be represented as a vector which is 1 in the n-th dimension.\n\nIn this problem, since we classify 10 digits(0 to 9) we have 10:\n\nFor example, 3 would be represented as [0,0,0,1,0,0,0,0,0,0] ","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Labels before one-hot encoding:\")\nprint(label[5:8])\nprint(\"Labels after one-hot encoding:\")\nprint(to_categorical(label)[5:8])\n\nlabel = to_categorical(label)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Spilitting the training set to get validation results\n\nX_train, X_val, Y_train, Y_val = train_test_split(train, label, test_size=0.1, random_state=0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Modelling the CNN:\n\n##### Design idea:\n\nDesigning a CNN is a very important step towards classification result.\n\nA common practice in CNN is \"Increase the Dimension - Reduce the size\"\n\nHere, dimension is the number of channels. Initially it was 1(gray-scale), after first Convolution I am making it 32 and then I am making it 64\n\n##### Placing Layers:\n\nA CNN architecture always looks like a repeatation of Convolution-Activation-Pooling Combination followed by Dense and Dropout(optional) layers for the final classification.\n\n##### Choosing values:\n\nThe choice of loss function is very important. In this question, since I am performing categorisation I will be choosing \"categorical_cross entropy\". There are many more options which have there special uses.\n\nThe activation of the final layer is chosen as \"Softmax\" since we break it into 10 categories(with each having its own prediction). If the output layer had only 1 neuron then \"Softmax\" is irrelavant. (You can try training and see the results)","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"model = Sequential()\n\nmodel.add(Conv2D(32,kernel_size=(3,3),activation=\"relu\",input_shape=(28,28,1)))\nmodel.add(MaxPool2D(pool_size=(2,2),strides=2))\nmodel.add(Conv2D(64,kernel_size=(3,3),activation=\"relu\"))\nmodel.add(MaxPool2D(pool_size=(2,2),strides=2))\nmodel.add(Flatten())\nmodel.add(Dense(512,activation=\"relu\"))\nmodel.add(Dense(10,activation=\"softmax\"))\nmodel.compile(loss=\"categorical_crossentropy\",optimizer=\"adam\", metrics=[\"accuracy\"])\n\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.fit(X_train,Y_train, epochs=10, validation_data=(X_val,Y_val))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"result = model.evaluate(X_val, Y_val, batch_size = 32)\nprint(\"Loss on validation: %f \\nAccuracy on validation: %f \" %(result[0] ,result[1]*100))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Visualising the output:\n\nLet us check the Confusion Matrix which will give us the idea of the predictions.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"ylabel = model.predict_classes(X_val)\nytest = [np.argmax(y, axis=None, out=None) for y in Y_val]\nconf_matrix = confusion_matrix(ytest, ylabel)\nsns.heatmap(conf_matrix, cmap=\"YlGnBu\",annot=True, fmt='g')\nplt.xlabel('predicted value')\nplt.ylabel('actual value')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"##### Displaying some of the predictions:","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"#Displaying the picture along with the predictions\n\nfig=plt.figure(figsize=(15, 6))\ncolumns = 6\nrows = 3\nfor i in range(columns*rows):\n    fig.add_subplot(rows, columns, i+1)\n    plt.axis('off')\n    img1 = X_val[i]\n    value ='%1.0f'%(ylabel[i])\n    plt.text(5, 25,value,color='black',fontsize=12,bbox=dict(facecolor='yellow'))\n    plt.imshow(np.squeeze(img1,axis=2), cmap=\"gray\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Checking some mis-classifications:\n\nAs most of the results are correct let us check some incorrect predictions that were being made and we might be able to figure out the reason.\n","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"# Let me print both the prediction and the actual values on \n# the images which will help in better visualisation\n\ny_mis = [i for i in range(len(ytest)) if ytest[i]!=ylabel[i]]\nprint(y_mis) #indices of the mis-classifications\n\nfig=plt.figure(figsize=(15, 6))\ncolumns = 6\nrows = 3\nx=0\nfor i in range(columns*rows):\n    fig.add_subplot(rows, columns, i+1)\n    plt.axis('off')\n    img1 = X_val[y_mis[x]]\n    predicted_value ='%1.0f'%(ylabel[y_mis[x]])\n    plt.text(5, 25,predicted_value,color='black',fontsize=12,bbox=dict(facecolor='yellow'))\n    actual_value ='%1.0f'%(ytest[y_mis[x]])\n    plt.text(20, 25,actual_value,color='white',fontsize=12,bbox=dict(facecolor='green'))\n    plt.imshow(np.squeeze(img1,axis=2), cmap=\"gray\")\n    x=x+1","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"As we can see above that the mis-classifications are very debatable as they could have been predicted either ways even with human eyes.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"### Visualising Convolution Layers:\n\nLet us see how the Convolution layers work to produce a classification","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"img=X_val[8]\nfig=plt.figure(figsize=(2,2))\nplt.imshow(np.squeeze(img,axis=2), cmap=\"gray\")\nplt.show()\n\nimg = np.expand_dims(img, axis=0)\nconv1_output = Model(inputs=model.input, outputs=model.get_layer('conv2d').output)\nconv2_output = Model(inputs=model.input, outputs=model.get_layer('conv2d_1').output)\nconv1_features = conv1_output.predict(img)\nconv2_features = conv2_output.predict(img)\n\nimport matplotlib.image as mpimg\n\nfig=plt.figure(figsize=(14,7))\ncolumns = 8\nrows = 4\n\nprint(\"Output for each filter of first Convolution Layer\")\nfor i in range(columns*rows):\n    #img = mpimg.imread()\n    fig.add_subplot(rows, columns, i+1)\n    plt.axis('off')\n    plt.title('filter'+str(i))\n    plt.imshow(conv1_features[0, :, :, i], cmap='gray')\nplt.show()\n\nfig=plt.figure(figsize=(14,7))\nprint(\"Output for each filter of second Convolution Layer\")\nfor i in range(columns*rows):\n    #img = mpimg.imread()\n    fig.add_subplot(rows, columns, i+1)\n    plt.axis('off')\n    plt.title('filter'+str(i))\n    plt.imshow(conv2_features[0, :, :, i], cmap='gray')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### Inference from above output:\n\nIt can be seen that the first layer of convolution observes edge features or backgroud or sudden changes that can be obtained by certain well known image-processing convolution kernels such as sobel or laplacian. \n\nHowever, the second convolution layer observes edges and corners which cannot be explained very well. And this is true if we go even further the convolution output cannot be explained but they capture relevant feature which makes CNN very useful for image classification","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"### Submitting Predictions to Kaggle:","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"answers = model.predict_classes(test, verbose=0)\nsubmissions=pd.DataFrame({\"ImageId\": list(range(1,len(answers)+1)),\"Label\": answers})\nsubmissions.to_csv(\"submission.csv\", index=False, header=True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Thankyou for reading till the end. If it has helped you understand Convolution please concider upvoting. Thankyou.","execution_count":null}],"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}