{"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":"# <center>Cats Vs Dogs</center>\n\n\n","metadata":{}},{"cell_type":"markdown","source":"  ","metadata":{}},{"cell_type":"markdown","source":"### <center>In this notebook, we will write a convolutional neural network(CNN) to classify images containing a dog or a cat. It is easy for people, dogs and cats. However, the computer will be a little more difficult</center>","metadata":{}},{"cell_type":"markdown","source":"![](https://pixelz.cc/wp-content/uploads/2018/10/dogs-and-cats-uhd-4k-wallpaper-768x432.jpg)\n\n\n\n----------------------------","metadata":{}},{"cell_type":"markdown","source":"\nFirst we import the necessary libraries","metadata":{}},{"cell_type":"code","source":"import os\nimport zipfile\nimport pandas as pd\nimport numpy as np\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix\nfrom sklearn.metrics import ConfusionMatrixDisplay\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n%matplotlib inline\n\nfrom tensorflow.keras import layers\nfrom tensorflow.keras import models\nfrom tensorflow.keras import regularizers\nfrom tensorflow.keras import optimizers\nfrom tensorflow.keras.callbacks import EarlyStopping\nfrom tensorflow.keras.preprocessing import image\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator, load_img","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:51:38.008581Z","iopub.execute_input":"2022-01-28T19:51:38.009416Z","iopub.status.idle":"2022-01-28T19:51:45.044623Z","shell.execute_reply.started":"2022-01-28T19:51:38.009323Z","shell.execute_reply":"2022-01-28T19:51:45.043585Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <center>Loading data</center>","metadata":{}},{"cell_type":"markdown","source":"To begin with, we will create a **path** variable that will be responsible for the path to our data. This is done for more convenient and faster path output.\n\nNext, using **listdir**, we will load the list of image names from the path directory","metadata":{}},{"cell_type":"code","source":"import zipfile\nwith zipfile.ZipFile(\"../input/dogs-vs-cats/train.zip\",'r') as z:\n    z.extractall(\".\")","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:51:45.046947Z","iopub.execute_input":"2022-01-28T19:51:45.047476Z","iopub.status.idle":"2022-01-28T19:51:58.299765Z","shell.execute_reply.started":"2022-01-28T19:51:45.047431Z","shell.execute_reply":"2022-01-28T19:51:58.298821Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"path = '/kaggle/working/train/'\nfilenames = os.listdir(path)\nfilenames[:5]","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:51:58.301484Z","iopub.execute_input":"2022-01-28T19:51:58.301812Z","iopub.status.idle":"2022-01-28T19:51:58.329287Z","shell.execute_reply.started":"2022-01-28T19:51:58.301771Z","shell.execute_reply":"2022-01-28T19:51:58.328464Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Since in the future I will be converting our data to a **Dataframe**, we will create a target variable responsible for the picture class: **cat** and **dog**","metadata":{}},{"cell_type":"code","source":"label = []\nfor filename in filenames:\n    if filename.split('.')[0] =='cat':\n        label.append('cat')\n    else:\n        label.append('dog')","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:51:58.331648Z","iopub.execute_input":"2022-01-28T19:51:58.332013Z","iopub.status.idle":"2022-01-28T19:51:58.352674Z","shell.execute_reply.started":"2022-01-28T19:51:58.331971Z","shell.execute_reply":"2022-01-28T19:51:58.351450Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df = pd.DataFrame({\n                   'name':filenames,\n                   'label':label\n                 })","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:51:58.354429Z","iopub.execute_input":"2022-01-28T19:51:58.355399Z","iopub.status.idle":"2022-01-28T19:51:58.370957Z","shell.execute_reply.started":"2022-01-28T19:51:58.355354Z","shell.execute_reply":"2022-01-28T19:51:58.369770Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.head()","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:51:58.373038Z","iopub.execute_input":"2022-01-28T19:51:58.373305Z","iopub.status.idle":"2022-01-28T19:51:58.392445Z","shell.execute_reply.started":"2022-01-28T19:51:58.373276Z","shell.execute_reply":"2022-01-28T19:51:58.391490Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's see on the histogram whether the **label**  is correct sorted all the photos into classes","metadata":{}},{"cell_type":"code","source":"print(df['label'].value_counts())\nsns.countplot(data=df, x=df['label']);","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:51:58.393904Z","iopub.execute_input":"2022-01-28T19:51:58.394607Z","iopub.status.idle":"2022-01-28T19:51:58.664168Z","shell.execute_reply.started":"2022-01-28T19:51:58.394564Z","shell.execute_reply":"2022-01-28T19:51:58.663157Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Great, we have an equal number of cat and dog classes\n\nLet's extract a couple of images from the data (I manually looked at the images in the directory and chose the most presentable animals in the photos)","metadata":{}},{"cell_type":"code","source":"load_img(path+'cat.10009.jpg')","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:51:58.665977Z","iopub.execute_input":"2022-01-28T19:51:58.666265Z","iopub.status.idle":"2022-01-28T19:51:58.782094Z","shell.execute_reply.started":"2022-01-28T19:51:58.666225Z","shell.execute_reply":"2022-01-28T19:51:58.781211Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"load_img(path+'dog.1283.jpg')","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:51:58.783247Z","iopub.execute_input":"2022-01-28T19:51:58.783555Z","iopub.status.idle":"2022-01-28T19:51:58.888710Z","shell.execute_reply.started":"2022-01-28T19:51:58.783511Z","shell.execute_reply":"2022-01-28T19:51:58.887932Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <center>Base model</center>","metadata":{}},{"cell_type":"markdown","source":"First, let's write the basic architecture of **CNN**\n\nIn this model, we use 5 consecutive blocks from **Conv2D** and **MaxPooling2d** with different filter depths\n\nSince we have a classification task, after 5 blocks we will transform our data into a 1D tensor and apply Dense layers,\n\nthe last **Dense layer** should have 1 layer and **activation='sigmoid'** since we have a binary classification task\n\n**input_shape** we will set **(256, 256, 3)** as the base for further use of the generator without resizing the image\n","metadata":{}},{"cell_type":"code","source":"model = models.Sequential()\nmodel.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(256, 256, 3)))\nmodel.add(layers.MaxPooling2D((2, 2)))\n\nmodel.add(layers.Conv2D(64, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\n\nmodel.add(layers.Conv2D(64, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\n\nmodel.add(layers.Conv2D(128, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\n\nmodel.add(layers.Conv2D(128, (3, 3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2, 2)))\n\nmodel.add(layers.Flatten())\nmodel.add(layers.Dense(512, activation='relu'))\nmodel.add(layers.Dense(1, activation='sigmoid'))","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:51:58.892662Z","iopub.execute_input":"2022-01-28T19:51:58.893467Z","iopub.status.idle":"2022-01-28T19:52:01.994353Z","shell.execute_reply.started":"2022-01-28T19:51:58.893427Z","shell.execute_reply":"2022-01-28T19:52:01.993362Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.summary()","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:52:01.995689Z","iopub.execute_input":"2022-01-28T19:52:01.996598Z","iopub.status.idle":"2022-01-28T19:52:02.014761Z","shell.execute_reply.started":"2022-01-28T19:52:01.996552Z","shell.execute_reply":"2022-01-28T19:52:02.013921Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n----------------\n\nGreat, using **optimizer='Adam'** as the most basic and recommended\n\n**loss='binary_crossentropy'** since we have a binary classification\n\n**metrics='acc'** since we have the same number of classes and accuracy is a suitable metric","metadata":{}},{"cell_type":"code","source":"model.compile(optimizer='Adam', loss='binary_crossentropy', metrics='acc')","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:52:02.016361Z","iopub.execute_input":"2022-01-28T19:52:02.016678Z","iopub.status.idle":"2022-01-28T19:52:02.032775Z","shell.execute_reply.started":"2022-01-28T19:52:02.016634Z","shell.execute_reply":"2022-01-28T19:52:02.031671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# <center>Data preprocessing: splitting data into train, test, val</center>","metadata":{}},{"cell_type":"markdown","source":"Since the competition has already ended and I will not be able to submit the test data that is attached to the dataset, so we will divide our sample into 3 parts: **train, test, val**.\n\nWe will use proportions **train:test:val** - **8:1:1**\n\nOn the **train** sample, we will train our model\n\nOn the **val** sample, we will check the ability of our model to generalize to unknown data\n\nOn the **test** sample, we will make the final prediction","metadata":{}},{"cell_type":"markdown","source":"-----------------\n\nTo get an equal number of classes when splitting the data, I will use **stratify**. With it, our classes will be related as **1:1**","metadata":{}},{"cell_type":"code","source":"train, test_val = train_test_split(df, test_size=0.2, stratify=df['label'], random_state=17)","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:52:02.034069Z","iopub.execute_input":"2022-01-28T19:52:02.034596Z","iopub.status.idle":"2022-01-28T19:52:02.073859Z","shell.execute_reply.started":"2022-01-28T19:52:02.034518Z","shell.execute_reply":"2022-01-28T19:52:02.072875Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test, val = train_test_split(test_val, test_size=0.5,  stratify=test_val['label'], random_state=17)","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:52:02.076527Z","iopub.execute_input":"2022-01-28T19:52:02.077095Z","iopub.status.idle":"2022-01-28T19:52:02.091704Z","shell.execute_reply.started":"2022-01-28T19:52:02.077049Z","shell.execute_reply":"2022-01-28T19:52:02.090544Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print('train size:', train.shape[0],\n      '\\nvalidation size:', val.shape[0],\n      '\\ntest size:', test.shape[0],     \n     )","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:52:02.093421Z","iopub.execute_input":"2022-01-28T19:52:02.093780Z","iopub.status.idle":"2022-01-28T19:52:02.103062Z","shell.execute_reply.started":"2022-01-28T19:52:02.093738Z","shell.execute_reply":"2022-01-28T19:52:02.101676Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print('train labels:\\n',train['label'].value_counts(),\n      '\\n\\nvalidataion labels:\\n',val['label'].value_counts(),\n      '\\n\\ntest labels:\\n',test['label'].value_counts(),\n      sep='')","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:52:02.105170Z","iopub.execute_input":"2022-01-28T19:52:02.105880Z","iopub.status.idle":"2022-01-28T19:52:02.121884Z","shell.execute_reply.started":"2022-01-28T19:52:02.105830Z","shell.execute_reply":"2022-01-28T19:52:02.120790Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Great, we see that all the data is split in a ratio of 8:1:1 and with the same ratio of classes","metadata":{}},{"cell_type":"markdown","source":"#  <center>Data Preprocessing: Data normalization</center>","metadata":{}},{"cell_type":"markdown","source":"Let's apply an **ImageDataGenerator** to our data to make it look like an **input_shape** for our model.\n\nNeural networks need to receive scaled data as input, for this we apply **rescale=1./255**\n\nIn this case, the image size is not specified, because **flow_from_dataframe** creates **target_size=(256, 256)** and default **color_mode='rgb'**\n\nin case of changing the size of the input tensors and using other values, we would have to manually specify the dimensions and depth of the image\n\n----------------\n\nUsually at this stage, the process of image **augmentation** is done.\n\nAugmentation is the process of generating artificial images using rotations, mirroring, shifts and many other different methods based on existing ones.\n\nThis is one of the methods of dealing with overfitting of the model, if necessary, I will apply it in the future.\n\n","metadata":{}},{"cell_type":"code","source":"train_gen = ImageDataGenerator(rescale=1./255)\ntrain_data = train_gen.flow_from_dataframe(train,\n                                           directory=path,\n                                           x_col='name',\n                                           y_col='label',\n                                           class_mode='binary',\n                                           seed=17                                          \n                                          )\n\nval_gen = ImageDataGenerator(rescale=1./255)\nval_data = val_gen.flow_from_dataframe(val,\n                                       directory=path,\n                                       x_col='name',\n                                       y_col='label',\n                                       class_mode='binary',\n                                       seed=17  \n                                      )","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:52:02.123826Z","iopub.execute_input":"2022-01-28T19:52:02.124563Z","iopub.status.idle":"2022-01-28T19:52:02.389560Z","shell.execute_reply.started":"2022-01-28T19:52:02.124490Z","shell.execute_reply":"2022-01-28T19:52:02.388555Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I would also like to say about **batch_size**\n\nIn this case, I decided to use the standard **batch_size=32**, but at the expense of this, use fewer epochs for training.\n\nThe larger the batch_size value, the less time it takes to train one epoch and the more epochs are needed to get good results.\n\nThe number of iterations in one epoch is **train_size(20000)/batch_size(32)=625**","metadata":{}},{"cell_type":"markdown","source":"# <center>Base model training</center>","metadata":{}},{"cell_type":"code","source":"history = model.fit(train_data,\n                    validation_data = val_data,\n                    epochs=10\n                   )","metadata":{"execution":{"iopub.status.busy":"2022-01-28T19:52:02.391250Z","iopub.execute_input":"2022-01-28T19:52:02.392142Z","iopub.status.idle":"2022-01-28T20:07:04.703771Z","shell.execute_reply.started":"2022-01-28T19:52:02.392105Z","shell.execute_reply":"2022-01-28T20:07:04.702737Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss = history.history['loss']\nval_loss = history.history['val_loss']\n\nplt.figure(figsize=(15,8))\nplt.plot(loss, label='Train loss')\nplt.plot(val_loss,'--', label='Val loss')\nplt.title('Training and validation loss')\nplt.xticks(np.arange(0,10))\nplt.yticks(np.arange(0, 0.7, 0.05));\nplt.grid()\nplt.legend();","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:04.709053Z","iopub.execute_input":"2022-01-28T20:07:04.711596Z","iopub.status.idle":"2022-01-28T20:07:05.122597Z","shell.execute_reply.started":"2022-01-28T20:07:04.711540Z","shell.execute_reply":"2022-01-28T20:07:05.121595Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The plot clearly shows that after the **5th** epoch, val loss stopped decreasing, but went up, we see the problem of **overfitting**\n\nFirst, let's start expanding our data with **augmentation**.","metadata":{}},{"cell_type":"markdown","source":"#  <center>Data Preprocessing : Augmentation</center>","metadata":{}},{"cell_type":"markdown","source":"As I wrote above, augmentation is the process of generating artificial images using rotations, mirroring, shifts and other methods based on existing data.\n\nLet me show examples of augmentation with photos of cats and dogs that I showed above.","metadata":{}},{"cell_type":"code","source":"aug_gen = ImageDataGenerator(rescale = 1./255,\n                               shear_range = 0.2,\n                               zoom_range = 0.2,\n                               rotation_range=40,\n                               width_shift_range=0.2,\n                               height_shift_range=0.2,\n                               horizontal_flip=True,\n                               fill_mode='nearest'\n                              )","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:05.124101Z","iopub.execute_input":"2022-01-28T20:07:05.124911Z","iopub.status.idle":"2022-01-28T20:07:05.132618Z","shell.execute_reply.started":"2022-01-28T20:07:05.124844Z","shell.execute_reply":"2022-01-28T20:07:05.131233Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_cat = load_img(path+'cat.10009.jpg')\nimg_dog = load_img(path+'dog.1283.jpg')\n\nimg_cat_arr = image.img_to_array(img_cat)\nimg_cat_arr = img_cat_arr.reshape((1,)+img_cat_arr.shape)\n\nimg_dog_arr = image.img_to_array(img_dog)\nimg_dog_arr = img_dog_arr.reshape((1,)+ img_dog_arr.shape)","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:05.134301Z","iopub.execute_input":"2022-01-28T20:07:05.135332Z","iopub.status.idle":"2022-01-28T20:07:05.159872Z","shell.execute_reply.started":"2022-01-28T20:07:05.135285Z","shell.execute_reply":"2022-01-28T20:07:05.158907Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"aug_images_cat = aug_gen.flow(img_cat_arr, batch_size=1)\naug_images_dog = aug_gen.flow(img_dog_arr, batch_size=1)","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:05.161639Z","iopub.execute_input":"2022-01-28T20:07:05.161964Z","iopub.status.idle":"2022-01-28T20:07:05.169184Z","shell.execute_reply.started":"2022-01-28T20:07:05.161919Z","shell.execute_reply":"2022-01-28T20:07:05.167900Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(15,8))\nplt.subplot(141)\nplt.imshow(img_cat)\nplt.title(\"original\")\ni=2\nfor batch in aug_images_cat:\n    plt.subplot(14*10+i)\n    plt.imshow(image.array_to_img(batch[0]))\n    plt.title(\"augmented\")\n    i += 1\n    if i % 5 == 0:\n        break","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:05.170799Z","iopub.execute_input":"2022-01-28T20:07:05.171862Z","iopub.status.idle":"2022-01-28T20:07:06.186416Z","shell.execute_reply.started":"2022-01-28T20:07:05.171828Z","shell.execute_reply":"2022-01-28T20:07:06.185440Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.figure(figsize=(15,8))\nplt.subplot(141)\nplt.imshow(img_dog)\nplt.title(\"original\")\ni=2\nfor batch in aug_images_dog:\n    plt.subplot(14*10+i)\n    plt.imshow(image.array_to_img(batch[0]))\n    plt.title(\"augmented\")\n    i += 1\n    if i % 5 == 0:\n        break","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:06.188195Z","iopub.execute_input":"2022-01-28T20:07:06.188737Z","iopub.status.idle":"2022-01-28T20:07:07.155568Z","shell.execute_reply.started":"2022-01-28T20:07:06.188695Z","shell.execute_reply":"2022-01-28T20:07:07.154608Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Great, now let's apply augmentation to our **train data**.\n\nIt is important to apply **augmentation only to train data**, not to val and test. We need to provide our model with a large sample of images so that it can learn to recognize patterns in images.\n\nAlso in the new network, I'm going to change the size of incoming images to **(224x224)**, this size is most often used as input in many CNN networks, let's do the same.\n\nSince we decided to change the input image size, we also need to change the generation for val_data","metadata":{}},{"cell_type":"code","source":"train_data = aug_gen.flow_from_dataframe(train,\n                                         directory=path,\n                                         x_col='name',\n                                         y_col='label',\n                                         class_mode='binary',\n                                         target_size=(224,224),\n                                         seed=17\n                                        )\n\nval_data = val_gen.flow_from_dataframe(val,\n                                       directory=path,\n                                       x_col='name',\n                                       y_col='label',\n                                       class_mode='binary',\n                                       target_size=(224,224),\n                                       seed=17  \n                                      )","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:07.157279Z","iopub.execute_input":"2022-01-28T20:07:07.157834Z","iopub.status.idle":"2022-01-28T20:07:07.440110Z","shell.execute_reply.started":"2022-01-28T20:07:07.157790Z","shell.execute_reply":"2022-01-28T20:07:07.439089Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Ok, now let's improve our model","metadata":{}},{"cell_type":"markdown","source":"#  <center>Model tuning</center>","metadata":{}},{"cell_type":"markdown","source":"Our base model looked like this","metadata":{}},{"cell_type":"code","source":"model.summary()","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:07.441981Z","iopub.execute_input":"2022-01-28T20:07:07.442584Z","iopub.status.idle":"2022-01-28T20:07:07.460053Z","shell.execute_reply.started":"2022-01-28T20:07:07.442513Z","shell.execute_reply":"2022-01-28T20:07:07.459112Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"It is important to understand that a strong fight against an **overfitting** problem can turn into an **underfitting** problem.\n\nPerhaps the augmentation is already enough to solve the problem of overfitting our model, but I want to add some more examples of hyperparameter regularization:\n\n\n- Let's try to apply **l2 regularization** with a small coefficient to the Dense layer\n\n\n- Add a **Dropout layer** with a small value before the last output layer","metadata":{}},{"cell_type":"code","source":"best_model = models.Sequential()\n\nbest_model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(224, 224, 3)))\nbest_model.add(layers.MaxPooling2D((2, 2)))\n\nbest_model.add(layers.Conv2D(64, (3, 3), activation='relu'))\nbest_model.add(layers.MaxPooling2D((2, 2)))\n\nbest_model.add(layers.Conv2D(64, (3, 3), activation='relu'))\nbest_model.add(layers.MaxPooling2D((2, 2)))\n\nbest_model.add(layers.Conv2D(128, (3, 3), activation='relu'))\nbest_model.add(layers.MaxPooling2D((2, 2)))\n\nbest_model.add(layers.Conv2D(128, (3, 3), activation='relu'))\nbest_model.add(layers.MaxPooling2D((2, 2)))\n\nbest_model.add(layers.Flatten())\nbest_model.add(layers.Dense(512, activation='relu', kernel_regularizer=regularizers.l2(0.001)))\nbest_model.add(layers.Dropout(0.2))\nbest_model.add(layers.Dense(1, activation='sigmoid'))","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:07.461696Z","iopub.execute_input":"2022-01-28T20:07:07.462099Z","iopub.status.idle":"2022-01-28T20:07:07.571171Z","shell.execute_reply.started":"2022-01-28T20:07:07.462053Z","shell.execute_reply":"2022-01-28T20:07:07.570169Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"best_model.summary()","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:07.576834Z","iopub.execute_input":"2022-01-28T20:07:07.577096Z","iopub.status.idle":"2022-01-28T20:07:07.595185Z","shell.execute_reply.started":"2022-01-28T20:07:07.577063Z","shell.execute_reply":"2022-01-28T20:07:07.594034Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's try to directly set the **learning_rate** to the optimizer, take a value slightly less than the default.\n\nThus, we guarantee that our optimizer will not get stuck in the local minimum of the function, however, for this we reduce the **learning rate**","metadata":{}},{"cell_type":"code","source":"best_model.compile(optimizer=optimizers.Adam(learning_rate=5e-4), loss='binary_crossentropy', metrics='acc')","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:07.597832Z","iopub.execute_input":"2022-01-28T20:07:07.598175Z","iopub.status.idle":"2022-01-28T20:07:07.610984Z","shell.execute_reply.started":"2022-01-28T20:07:07.598127Z","shell.execute_reply":"2022-01-28T20:07:07.609601Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#  <center>Tuned model training</center>","metadata":{}},{"cell_type":"markdown","source":"Let's increase the number of **epochs to 60**, most likely the model will need many more epochs than in the previous version\n\nWe will also add **callbacks** that will stop training if **val_loss** has not changed more than **0.001** over the past **5 epochs**","metadata":{}},{"cell_type":"code","source":"history_2 = best_model.fit(train_data,\n                           validation_data = val_data,\n                           epochs=60,\n                           callbacks=[EarlyStopping(monitor='val_acc', min_delta=0.001, patience=5, verbose=1)]\n                          )","metadata":{"execution":{"iopub.status.busy":"2022-01-28T20:07:07.612997Z","iopub.execute_input":"2022-01-28T20:07:07.614354Z","iopub.status.idle":"2022-01-28T22:59:12.801227Z","shell.execute_reply.started":"2022-01-28T20:07:07.614302Z","shell.execute_reply":"2022-01-28T22:59:12.799459Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Okay, our network stopped after **38** epochs as the **callback** fired.\n\nPerhaps after a few epochs, **val_acc** would begin to grow, but this accuracy suits me, since this notebook was not written to find the best results, but to work out and understand working with CNN in Keras.\n\nLet's take a look at the accuracy plot","metadata":{}},{"cell_type":"code","source":"loss = history_2.history['acc']\nval_loss = history_2.history['val_acc']\n\nplt.figure(figsize=(15,8))\nplt.plot(loss, label='Train acc')\nplt.plot(val_loss,'--', label='Val acc')\nplt.title('Training and validation accuracy')\nplt.yticks(np.arange(0.5, 1, 0.05))\nplt.xticks(np.arange(0, 26))\nplt.grid()\nplt.legend();","metadata":{"execution":{"iopub.status.busy":"2022-01-28T22:59:12.802892Z","iopub.execute_input":"2022-01-28T22:59:12.803303Z","iopub.status.idle":"2022-01-28T22:59:13.216620Z","shell.execute_reply.started":"2022-01-28T22:59:12.803259Z","shell.execute_reply":"2022-01-28T22:59:13.215488Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Compared to our first model, we really got rid of the **overfitting** problem. Now our metrics on the chart grow in proportion to each other\n\nLet's save the model and apply it to the test data","metadata":{}},{"cell_type":"code","source":"best_model.save('best_model_cat_vs_dog.h5')","metadata":{"execution":{"iopub.status.busy":"2022-01-28T22:59:13.218210Z","iopub.execute_input":"2022-01-28T22:59:13.220730Z","iopub.status.idle":"2022-01-28T22:59:13.316395Z","shell.execute_reply.started":"2022-01-28T22:59:13.220680Z","shell.execute_reply":"2022-01-28T22:59:13.315406Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#   <center>Result on test data</center>","metadata":{}},{"cell_type":"markdown","source":"First, we need to convert **test data** to the same form as **val** using a **generator**\n\n**IMPORTANT!!!** It is necessary to set **shuffle=False** in order to avoid data mixing!!!","metadata":{}},{"cell_type":"code","source":"test_data = val_gen.flow_from_dataframe(test,\n                                        directory=path,\n                                        x_col='name',\n                                        y_col='label',\n                                        class_mode='binary',\n                                        target_size=(224,224),\n                                        shuffle=False,\n                                        seed=17  \n                                       )","metadata":{"execution":{"iopub.status.busy":"2022-01-28T22:59:13.318368Z","iopub.execute_input":"2022-01-28T22:59:13.318769Z","iopub.status.idle":"2022-01-28T22:59:13.362738Z","shell.execute_reply.started":"2022-01-28T22:59:13.318698Z","shell.execute_reply":"2022-01-28T22:59:13.361409Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_pred = best_model.predict(test_data)","metadata":{"execution":{"iopub.status.busy":"2022-01-28T22:59:13.364699Z","iopub.execute_input":"2022-01-28T22:59:13.365020Z","iopub.status.idle":"2022-01-28T22:59:21.177852Z","shell.execute_reply.started":"2022-01-28T22:59:13.364980Z","shell.execute_reply":"2022-01-28T22:59:21.176830Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pred_label = test_pred > 0.5\ntrue_label = test_data.classes","metadata":{"execution":{"iopub.status.busy":"2022-01-28T22:59:21.181541Z","iopub.execute_input":"2022-01-28T22:59:21.182420Z","iopub.status.idle":"2022-01-28T22:59:21.191181Z","shell.execute_reply.started":"2022-01-28T22:59:21.182372Z","shell.execute_reply":"2022-01-28T22:59:21.189925Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"ConfusionMatrixDisplay(confusion_matrix(true_label, pred_label), display_labels=test_data.class_indices).plot();","metadata":{"execution":{"iopub.status.busy":"2022-01-28T22:59:21.192923Z","iopub.execute_input":"2022-01-28T22:59:21.193251Z","iopub.status.idle":"2022-01-28T22:59:21.542806Z","shell.execute_reply.started":"2022-01-28T22:59:21.193209Z","shell.execute_reply":"2022-01-28T22:59:21.541565Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"best_model.evaluate(test_data)","metadata":{"execution":{"iopub.status.busy":"2022-01-28T22:59:21.548278Z","iopub.execute_input":"2022-01-28T22:59:21.551067Z","iopub.status.idle":"2022-01-28T22:59:30.398196Z","shell.execute_reply.started":"2022-01-28T22:59:21.551018Z","shell.execute_reply":"2022-01-28T22:59:30.397262Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Great, we have **95% on test data**\n\nIt is possible that the model could be trained for high accuracy results, however, the purpose of this notebook was to learn how to work with **Keras** and **CNN**\n\n\n-----------------","metadata":{}},{"cell_type":"markdown","source":"![](https://aurora.ekof.bg.ac.rs/~s160748/331.jpg)","metadata":{}},{"cell_type":"markdown","source":"##  <center>Thank you for watching this is my project, I will be grateful if you upvoted and give feedback about my work in the comments. I want to improve my skills, and if you find any mistakes in the work, please tell me about it.</center>","metadata":{"execution":{"iopub.status.busy":"2022-01-28T23:00:32.445044Z","iopub.execute_input":"2022-01-28T23:00:32.445342Z","iopub.status.idle":"2022-01-28T23:00:32.450746Z","shell.execute_reply.started":"2022-01-28T23:00:32.445310Z","shell.execute_reply":"2022-01-28T23:00:32.449424Z"}}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}