{"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"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":30512,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<center><b> In this notebook, I have tried my best to explain all the concepts of convolutional neural networks (CNN) with the help of illustrations and in-depth descriptions. </b></center>\n\n<hr>","metadata":{}},{"cell_type":"markdown","source":"# Importing required libraries\n\nThe following libraries are imported at the beginning of the code:\n\n- `numpy`: a library for numerical computing in Python\n- `pandas`: a library for data manipulation and analysis\n- `os`: a module for interacting with the operating system\n- `matplotlib.pyplot`: a module for creating visualizations in Python\n\n","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nimport matplotlib.pyplot as plt\nimport os","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2023-06-17T18:32:30.233145Z","iopub.execute_input":"2023-06-17T18:32:30.234008Z","iopub.status.idle":"2023-06-17T18:32:30.239181Z","shell.execute_reply.started":"2023-06-17T18:32:30.233953Z","shell.execute_reply":"2023-06-17T18:32:30.237856Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n# Loading and Visualizing Data\nIn this section, we will be loading and visualizing the dataset. \nWe will first use pandas to read the csv file\n\n","metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a"}},{"cell_type":"code","source":"#First we read the training dataset\ntrain = pd.read_csv(\"../input/digit-recognizer/train.csv\") \ntrain.head()\nprint(f\"Shape of training dataset is: {train.shape}\")","metadata":{"_uuid":"6884db4361a209f639cd6f3bf15231d271ecf563","execution":{"iopub.status.busy":"2023-06-17T18:26:45.717125Z","iopub.execute_input":"2023-06-17T18:26:45.719509Z","iopub.status.idle":"2023-06-17T18:26:50.501056Z","shell.execute_reply.started":"2023-06-17T18:26:45.719461Z","shell.execute_reply":"2023-06-17T18:26:50.499171Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Now we read testing dataset \ntest = pd.read_csv(\"../input/digit-recognizer/test.csv\") \ntest.head()\nprint(f\"Shape of testing dataset is: {test.shape}\")","metadata":{"_uuid":"d7ab3a0db0c87aef2bb65fae5f564ff4b1260a96","execution":{"iopub.status.busy":"2023-06-17T18:26:50.502599Z","iopub.execute_input":"2023-06-17T18:26:50.502928Z","iopub.status.idle":"2023-06-17T18:26:52.553277Z","shell.execute_reply.started":"2023-06-17T18:26:50.502894Z","shell.execute_reply":"2023-06-17T18:26:52.552332Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Now, we extract labels\nY_train = train[\"label\"]\n# Drop 'label' column so that we can directly extract training pixel values in the required format\ntrain_temp=train.drop(labels = [\"label\"],axis = 1) \nX_train = train_temp.values.reshape((-1,28,28)).astype(np.uint8)[:,:,:,None]\n","metadata":{"_uuid":"318d94f6e935a6d8210d0ac03707dfa6e3946475","execution":{"iopub.status.busy":"2023-06-17T18:26:52.555711Z","iopub.execute_input":"2023-06-17T18:26:52.555985Z","iopub.status.idle":"2023-06-17T18:26:52.668707Z","shell.execute_reply.started":"2023-06-17T18:26:52.555961Z","shell.execute_reply":"2023-06-17T18:26:52.667594Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Visualize number of digit classes\nplt.figure(figsize=(15, 7))\ncount = Y_train.value_counts()\nplt.bar(count.index, count.values, color='teal')\nplt.title('Number of Digit Classes')\nplt.show()","metadata":{"_uuid":"8902b0312e6c047596cf27ebba554a68b82604b2","execution":{"iopub.status.busy":"2023-06-17T18:26:52.670286Z","iopub.execute_input":"2023-06-17T18:26:52.670711Z","iopub.status.idle":"2023-06-17T18:26:52.959963Z","shell.execute_reply.started":"2023-06-17T18:26:52.670678Z","shell.execute_reply":"2023-06-17T18:26:52.959078Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"_,ax = plt.subplots(3,3,figsize=(6,6))\n\n\n\nfor i,( image,label) in enumerate(zip(X_train,Y_train)) :\n    \n    \n    ax[i//3][i%3].imshow(image.squeeze())\n    ax[i//3][i%3].axis('off')\n    ax[i//3][i%3].set_title(f\"Label: {label}\",y=-0.3)\n    if(i==8): break\nplt.subplots_adjust(hspace=0.5)\nplt.show()","metadata":{"_uuid":"4c9d3a1467cf82a3dff04967a9846906f9758ed4","execution":{"iopub.status.busy":"2023-06-17T18:26:52.961340Z","iopub.execute_input":"2023-06-17T18:26:52.962304Z","iopub.status.idle":"2023-06-17T18:26:53.261159Z","shell.execute_reply.started":"2023-06-17T18:26:52.962269Z","shell.execute_reply":"2023-06-17T18:26:53.260260Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n# Preprocessing Steps for Image Classification using CNNs\n## Normalization\nNormalization is performed on the images to reduce the effect of illumination differences. Grayscale normalization is commonly used as it helps CNNs to work faster.\n\n## Reshape\nThe input images are of size 28 x 28. Therefore, we reshape all the data to 28x28x1 3D matrices. Keras requires an extra dimension at the end, which corresponds to channels. As the images are grayscale, only one channel is used.\n\n## Label Encoding\nLabel encoding is performed to encode the labels to one-hot vectors. For example, the label \"2\" is encoded as [0,0,1,0,0,0,0,0,0,0], and the label \"4\" is encoded as [0,0,0,0,1,0,0,0,0,0]. This allows us to use categorical cross-entropy as the loss function in our CNN mode","metadata":{"_uuid":"74e341f4845b42101182eda3c990e3dc4dc64dff"}},{"cell_type":"markdown","source":"We will first normalize the data","metadata":{}},{"cell_type":"code","source":"X_train = X_train / 255.0\ntest = test / 255.0\n","metadata":{"_uuid":"f39e537f15757b5da3363c138a33e50bd78bbf49","execution":{"iopub.status.busy":"2023-06-17T18:26:53.262605Z","iopub.execute_input":"2023-06-17T18:26:53.263187Z","iopub.status.idle":"2023-06-17T18:26:53.429416Z","shell.execute_reply.started":"2023-06-17T18:26:53.263154Z","shell.execute_reply":"2023-06-17T18:26:53.428446Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now we print and observe the shapes","metadata":{}},{"cell_type":"code","source":"print(\"Training images shape: \",X_train.shape)\nprint(\"Test images shape: \",test.shape)","metadata":{"execution":{"iopub.status.busy":"2023-06-17T18:26:53.430701Z","iopub.execute_input":"2023-06-17T18:26:53.431053Z","iopub.status.idle":"2023-06-17T18:26:53.437246Z","shell.execute_reply.started":"2023-06-17T18:26:53.431020Z","shell.execute_reply":"2023-06-17T18:26:53.436274Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Test images haven't been reshaped. We thus first reshape them","metadata":{}},{"cell_type":"code","source":"test = test.values.reshape(-1,28,28,1)\n","metadata":{"_uuid":"d524f6de086928158a76c54d1685a92c1802e230","execution":{"iopub.status.busy":"2023-06-17T18:26:53.438831Z","iopub.execute_input":"2023-06-17T18:26:53.439449Z","iopub.status.idle":"2023-06-17T18:26:53.446319Z","shell.execute_reply.started":"2023-06-17T18:26:53.439417Z","shell.execute_reply":"2023-06-17T18:26:53.445426Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(\"Training images shape: \",X_train.shape)\nprint(\"Test images shape: \",test.shape)","metadata":{"execution":{"iopub.status.busy":"2023-06-17T18:26:53.450617Z","iopub.execute_input":"2023-06-17T18:26:53.451566Z","iopub.status.idle":"2023-06-17T18:26:53.458149Z","shell.execute_reply.started":"2023-06-17T18:26:53.451400Z","shell.execute_reply":"2023-06-17T18:26:53.457456Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now we perform one-hot encoding for the labels","metadata":{}},{"cell_type":"code","source":"from keras.utils.np_utils import to_categorical #Used for one-hot encoding\nY_train = to_categorical(Y_train, num_classes = 10)","metadata":{"_uuid":"d15d35ca439dce194a96f4442c7a1c085ce24d28","execution":{"iopub.status.busy":"2023-06-17T18:26:53.459331Z","iopub.execute_input":"2023-06-17T18:26:53.460081Z","iopub.status.idle":"2023-06-17T18:27:01.662397Z","shell.execute_reply.started":"2023-06-17T18:26:53.460046Z","shell.execute_reply":"2023-06-17T18:27:01.661421Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n# Train Test Split\n We will be splitting the data into training and testing set using sklearn's train_test_split function\n* test split size is 10% while train split size is 90%.\n","metadata":{"_uuid":"111d4a1e66d823363f890f892bba39d692479663"}},{"cell_type":"code","source":"# Split the train and the validation set for the fitting\nfrom sklearn.model_selection import train_test_split\nX_train, X_test, Y_train, Y_test = train_test_split(X_train, Y_train, test_size = 0.1, random_state=2)\nprint(\"Training images set shape\",X_train.shape)\nprint(\"Testing images set shape\",X_train.shape)\nprint(\"Training labels shape\",Y_train.shape)\nprint(\"Validaation labels shape\",Y_test.shape)","metadata":{"_uuid":"882d9dfb1bee94b2ec1e83911e3e334994d74f4b","execution":{"iopub.status.busy":"2023-06-17T18:27:01.663799Z","iopub.execute_input":"2023-06-17T18:27:01.664600Z","iopub.status.idle":"2023-06-17T18:27:02.188723Z","shell.execute_reply.started":"2023-06-17T18:27:01.664563Z","shell.execute_reply":"2023-06-17T18:27:02.187612Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Convolutional Neural Network\nConvolutional Neural Networks (CNNs) are a type of deep learning algorithm used for image classification and object detection. They are widely used in computer vision applications due to their ability to automatically learn and extract features from images. CNNs consist of a series of convolutional layers that apply filters to the input image, followed by pooling layers that downsample the output of the convolutional layers.\n\nHere is an example of a CNN architecture for image classification:\n* <a href=\"https://ibb.co/kV1j9p\"><img src=\"https://preview.ibb.co/nRkBpp/gec2.jpg\" alt=\"gec2\" border=\"0\"></a>","metadata":{"_uuid":"caf4b9f7fcae153ac1d4c0246b1defc9d9d30776"}},{"cell_type":"markdown","source":"### Understanding Convolution Operation\nConvolution operation is an essential technique used in convolutional neural networks (CNNs) for image classification. It involves using a feature detector/kernel/filter of a certain size (e.g., 3x3, 5x5, or 7x7) to detect specific features in an image, such as edges or shapes. The feature detector convolves or slides over the input image, performing element-wise multiplication of the corresponding pixel values. This results in a feature map or convolved feature that highlights the detected feature in the image. We use multiple feature detectors to create multiple feature maps, which are important for accurate classification. The stride is the number of steps that the feature detector takes while navigating over the input image. The size of the input image is reduced during convolution, which is useful for faster processing, but it also results in some loss of information.\n\n <a href=\"https://imgbb.com/\"><img src=\"https://image.ibb.co/m4FQC9/gec.jpg\" alt=\"gec\" border=\"0\"></a><br>\n After applying the convolutional layer, ReLU activation function is used to introduce non-linearity in the model. This helps in breaking the linearity of the image data and improving the model's ability to capture non-linear features in the image (as most of the data is non-linear in nature, we need to introduce back non-linearity)\n <br>\n <a href=\"https://ibb.co/mVZih9\"><img src=\"https://preview.ibb.co/gbcQvU/RELU.jpg\" alt=\"RELU\" border=\"0\"></a>","metadata":{"_uuid":"682cb6f5144794038c0bef1dc0814dde0f7fe6c0"}},{"cell_type":"markdown","source":"### Preserving the Image Dimensions (Padding)\nCNNs use convolutions to extract features from images, and each convolution reduces the size of the input volume. Same padding is used in the early layers of a CNN to prevent this reduction in size and preserve important information about the original input, which helps to extract low-level features effectively. Essentially, same padding ensures that the output size of a convolutional layer is the same as the input size.\n<br> <a href=\"https://ibb.co/jUPkUp\"><img src=\"https://preview.ibb.co/noH5Up/padding.jpg\" alt=\"padding\" border=\"0\"></a>","metadata":{"_uuid":"3af8fb49243719ad70016b64cd4dff6e62c413d9"}},{"cell_type":"markdown","source":"\n### Max Pooling\nMax pooling is an essential component of Convolutional Neural Networks (CNNs) that helps in down-sampling or sub-sampling of the input image. This process helps to reduce the number of parameters in the network, which in turn helps control overfitting. Additionally, max pooling also makes the detection of features invariant to scale or orientation changes, which is crucial in image recognition tasks. By reducing the amount of parameters and computation required in the network, max pooling helps to improve the overall efficiency and accuracy of the model.\n\n. \n<a href=\"https://ibb.co/ckTjN9\"><img src=\"https://preview.ibb.co/gsNYFU/maxpool.jpg\" alt=\"maxpool\" border=\"0\"></a>","metadata":{"_uuid":"6694b8ea366f974d6bd055a2915a5dc06f7a96bb"}},{"cell_type":"markdown","source":"\n### Flattening\nDuring the flattening process, the spatial dimensions of the input volume are collapsed into a single dimension while preserving the total number of elements in the array. This operation is necessary because fully connected layers require a 1D input, while convolutional and pooling layers operate on 2D or 3D inputs.\n<br> <a href=\"https://imgbb.com/\"><img src=\"https://image.ibb.co/c7eVvU/flattenigng.jpg\" alt=\"flattenigng\" border=\"0\"></a>","metadata":{"_uuid":"c2fb971fc473c9104a27975242a36f8e9183742c"}},{"cell_type":"markdown","source":"\n### Fully Connected Layer (FCC)\nThe fully connected layer (FCC) is a type of layer in a neural network where each neuron is connected to every neuron in the previous layer. This means that the FCC receives input from all the neurons in the previous layer\n<br> <a href=\"https://ibb.co/hsS14p\"><img src=\"https://preview.ibb.co/evzsAU/fullyc.jpg\" alt=\"fullyc\" border=\"0\"></a>","metadata":{"_uuid":"f437407cf18fdfacf8626baa5e76ed5d64be1a11","trusted":true}},{"cell_type":"markdown","source":"\n# Implementing CNN Model","metadata":{"_uuid":"7b290eb3f3b111d6841f8e4093277be1bde05078","trusted":true}},{"cell_type":"code","source":"# \nfrom sklearn.metrics import confusion_matrix\nimport itertools\n\nfrom keras.utils.np_utils import to_categorical # convert to one-hot-encoding\nfrom keras.models import Sequential\nfrom keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPool2D\nfrom keras.optimizers import RMSprop,Adam\nfrom keras.preprocessing.image import ImageDataGenerator\nfrom keras.callbacks import ReduceLROnPlateau\n\nmodel = Sequential()\n\nmodel.add(Conv2D(filters = 8, kernel_size = (5,5),padding = 'same',  activation ='relu', input_shape = (28,28,1)))\nmodel.add(MaxPool2D(pool_size=(2,2)))\nmodel.add(Dropout(0.25))\n\nmodel.add(Conv2D(filters = 16, kernel_size = (3,3),padding = 'same', activation ='relu'))\nmodel.add(MaxPool2D(pool_size=(2,2), strides=(2,2)))\nmodel.add(Dropout(0.25))\n\n#FCC Part\nmodel.add(Flatten())\nmodel.add(Dense(256, activation = \"relu\"))\nmodel.add(Dropout(0.5))\nmodel.add(Dense(10, activation = \"softmax\"))","metadata":{"_uuid":"c441d7b3852cee5d3636272d4da2f96b169f81ac","execution":{"iopub.status.busy":"2023-06-17T18:27:02.190310Z","iopub.execute_input":"2023-06-17T18:27:02.190738Z","iopub.status.idle":"2023-06-17T18:27:05.345270Z","shell.execute_reply.started":"2023-06-17T18:27:02.190704Z","shell.execute_reply":"2023-06-17T18:27:05.344328Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n# Defining Optimizer & The Loss function   \nWe would be using Adam's Optimizer & Cateogorical Cross entropy loss function\n","metadata":{"_uuid":"9a432df491777ca0019db6f6b972581f2c5bd9f9"}},{"cell_type":"markdown","source":"Adam optimizer is an algorithm that dynamically adjusts the learning rate for each parameter in a neural network based on the estimates of the gradients. It helps improve the training process by efficiently updating the network parameters. While categorical crossentropy loss function is a loss function used to assess the accuracy of a machine learning model when dealing with multiple classes or categories. It quantifies how well the model's predicted probabilities align with the actual true labels.","metadata":{}},{"cell_type":"code","source":"# Define the optimizer\noptimizer = Adam(lr=0.001, beta_1=0.9, beta_2=0.999)\n#Now we compile the model\n\nmodel.compile(optimizer = optimizer , loss = \"categorical_crossentropy\", metrics=[\"accuracy\"])","metadata":{"_uuid":"607a02b42636e3115a1ac7a8edcadf61cf5ea1b0","execution":{"iopub.status.busy":"2023-06-17T18:27:05.346620Z","iopub.execute_input":"2023-06-17T18:27:05.346969Z","iopub.status.idle":"2023-06-17T18:27:05.367089Z","shell.execute_reply.started":"2023-06-17T18:27:05.346920Z","shell.execute_reply":"2023-06-17T18:27:05.366242Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<a id=\"13\"></a>\n\n","metadata":{"_uuid":"91bf542a979c5b191c534876186bf31e70ec7f06"}},{"cell_type":"markdown","source":"<a id=\"14\"></a>\n### Epochs and Batch Size\n An epoch refers to a complete iteration or pass through the entire dataset during the training of a machine learning model,ie. in this process, the model makes predictions on each data point, calculates the error, and updates its internal parameters to improve its performance.\n \n","metadata":{"_uuid":"d5d8fea4cbb9b53cf1bb5089357a33dc179ee981"}},{"cell_type":"code","source":"epochs=10","metadata":{"execution":{"iopub.status.busy":"2023-06-17T18:27:05.369869Z","iopub.execute_input":"2023-06-17T18:27:05.370442Z","iopub.status.idle":"2023-06-17T18:27:05.375689Z","shell.execute_reply.started":"2023-06-17T18:27:05.370406Z","shell.execute_reply":"2023-06-17T18:27:05.374611Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In machine learning, we often process data in batches rather than all at once. Batch size refers to the number of data points or examples we process together in each batch during training. Instead of updating the model after seeing every single example, which could be time-consuming, we update it after processing a batch of examples.","metadata":{}},{"cell_type":"code","source":"batch_size = 32","metadata":{"_uuid":"a237feb5e53ecbc8799101cb6e699877faafde77","execution":{"iopub.status.busy":"2023-06-17T18:27:05.377980Z","iopub.execute_input":"2023-06-17T18:27:05.378552Z","iopub.status.idle":"2023-06-17T18:27:05.384373Z","shell.execute_reply.started":"2023-06-17T18:27:05.378521Z","shell.execute_reply":"2023-06-17T18:27:05.383327Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n# Data Augmentation\nTo address the issue of overfitting, it is important to increase the size of our handwritten digit dataset artificially. This can be achieved through a technique called data augmentation. Data augmentation involves applying small transformations to the training data in order to replicate the natural variations observed in handwritten digits<br>\n<a href=\"https://ibb.co/k24CUp\"><img src=\"https://preview.ibb.co/nMxXUp/augment.jpg\" alt=\"augment\" border=\"0\"></a>\n    \n","metadata":{"_uuid":"e87c60e45759e0c52c2b72ee562fdb964c8008d5"}},{"cell_type":"code","source":"# Data augmentation\ndatagen = ImageDataGenerator(\n    featurewise_std_normalization=False,\n    samplewise_std_normalization=False,\n    zca_whitening=False,\n    rotation_range=5,\n    zoom_range=0.1,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    horizontal_flip=False,\n    vertical_flip=False\n)\n\ndatagen.fit(X_train)\n","metadata":{"_uuid":"c339fa7d06e9b73a519ca661bed19de482707d1f","execution":{"iopub.status.busy":"2023-06-17T18:27:05.385977Z","iopub.execute_input":"2023-06-17T18:27:05.386375Z","iopub.status.idle":"2023-06-17T18:27:05.486593Z","shell.execute_reply.started":"2023-06-17T18:27:05.386345Z","shell.execute_reply":"2023-06-17T18:27:05.485446Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Fitting The Model\nWhen we talk about \"fitting\" a neural network, we are referring to the process of training the network on a given dataset. It involves iteratively adjusting the network's parameters (weights and biases) based on the input data and corresponding target values.","metadata":{"_uuid":"e42e43f1b3b9e6da0d99dafff8f4a4b514a07f4c"}},{"cell_type":"code","source":"history = model.fit_generator(datagen.flow(X_train,Y_train, batch_size=batch_size), epochs = epochs, validation_data = (X_test,Y_test))","metadata":{"_uuid":"4b2957bb8976a25cdbbbdc3110d68c5035a9773c","execution":{"iopub.status.busy":"2023-06-17T18:27:05.488166Z","iopub.execute_input":"2023-06-17T18:27:05.488521Z","iopub.status.idle":"2023-06-17T18:30:10.165083Z","shell.execute_reply.started":"2023-06-17T18:27:05.488487Z","shell.execute_reply":"2023-06-17T18:30:10.164155Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n# Evaluate model performance\nWe would visualizing the loss on the validation set as a function of number of epochs.\nWe will also plot a few sample predictions","metadata":{"_uuid":"debd5424728e11aa30a8513ac7b4f7377193a2da"}},{"cell_type":"code","source":"\nplt.plot(history.history['val_loss'], label=\"validation loss\")\nplt.title(\"Test Loss\")\nplt.xlabel(\"Number of Epochs\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.show()","metadata":{"_uuid":"180a06f7ae01e69117c6c8258411cfe1b9b7b991","execution":{"iopub.status.busy":"2023-06-17T18:31:18.142232Z","iopub.execute_input":"2023-06-17T18:31:18.142880Z","iopub.status.idle":"2023-06-17T18:31:18.418842Z","shell.execute_reply.started":"2023-06-17T18:31:18.142844Z","shell.execute_reply":"2023-06-17T18:31:18.417879Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Now plotting a few sample predictions","metadata":{}},{"cell_type":"code","source":"_,ax = plt.subplots(3,3,figsize=(6,6))\nfor i, image in enumerate(X_test):\n\n    image = np.expand_dims(image,0)\n    output = model(image)\n    \n    pred = np.argmax(output, axis=1, keepdims=True)\n    \n    ax[i//3][i%3].imshow(image.squeeze())\n    ax[i//3][i%3].axis('off')\n    ax[i//3][i%3].set_title(f\"Prediction: {pred[0][0]}\",y=-0.3)\n    if(i==8): break\nplt.subplots_adjust(hspace=0.5)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-06-17T18:39:08.531803Z","iopub.execute_input":"2023-06-17T18:39:08.532169Z","iopub.status.idle":"2023-06-17T18:39:08.905883Z","shell.execute_reply.started":"2023-06-17T18:39:08.532138Z","shell.execute_reply":"2023-06-17T18:39:08.905011Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"<hr>","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}