{"cells":[{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","id":"UE-pbpUHm46s","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load in \n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom glob import glob\nfrom pathlib import Path\nimport cv2\n# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom keras import backend as K\nimport keras\nfrom keras.layers import Dense, Activation, Dropout, BatchNormalization, Input, Flatten, Conv2D, MaxPooling2D\nfrom keras.models import Model, load_model\nfrom keras.optimizers import RMSprop, SGD, Adam\nfrom keras.callbacks import Callback\nfrom keras.applications.resnet50 import ResNet50\nfrom keras.regularizers import l1, l2\nfrom keras.initializers import he_normal\nfrom keras.preprocessing.image import ImageDataGenerator\nfrom keras.callbacks import ReduceLROnPlateau\n\nfrom IPython.display import clear_output\n\nimport os\n\n\n# Any results you write to the current directory are saved as output.","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!mkdir /root/data","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!unzip /kaggle/input/dogs-vs-cats/train.zip -d /root/data\n!unzip /kaggle/input/dogs-vs-cats/test1.zip -d /root/data","execution_count":null,"outputs":[]},{"metadata":{"id":"5JP7R1CudAkn","trusted":true},"cell_type":"code","source":"train_dir = '/root/data/train/'  # the images directory\n!ls $train_dir -U | head -5  # view sample images filenames - bash command","execution_count":null,"outputs":[]},{"metadata":{"id":"OkeqeB8-mDft","trusted":true},"cell_type":"code","source":"images_names = os.listdir(train_dir)  # names of the files in the directory\nimages_num = len(images_names)\nprint(f'Number of images: {images_num}')","execution_count":null,"outputs":[]},{"metadata":{"id":"YNRnmyKFaDrI"},"cell_type":"markdown","source":"Since there's a large number of images given the kernel's resources, we'll just make keras feed the model from the directory directly instead of loading them into an np.array"},{"metadata":{"id":"UykCyUoOhdtt","trusted":true},"cell_type":"code","source":"# dimensions of images to use for plt.imshow\nwidth, height, channels = 256, 256, 3","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","id":"QxcXkBAom46x","trusted":true},"cell_type":"code","source":"images_samples = np.zeros((4, height, width, 3), dtype=int)\nsamples_labels = []\n# get random 4 images\nfor i in range(4):\n    rnd_img = np.random.randint(0, images_num)\n    img_filename = images_names[rnd_img]\n    img_bgr = cv2.imread(train_dir + img_filename)  # loads the images channels in (blue, green, red) order\n    images_samples[i] = cv2.resize(src=img_bgr[:, :, [2, 1, 0]], dsize=(width, height))  # store the random image\n    samples_labels.append(img_filename[:3])  # store the random images' label","execution_count":null,"outputs":[]},{"metadata":{"id":"h_1PUQ5gm46-","trusted":true},"cell_type":"code","source":"# view the 4 samples\nfig, axs = plt.subplots(2, 2, figsize=(10, 10))\nfor ax, img, label in zip(axs.ravel(), images_samples, samples_labels):\n    ax.imshow(img);\n    ax.set_title(f'Class: {label}', size=15);","execution_count":null,"outputs":[]},{"metadata":{"id":"n3ga0IE2aqxl"},"cell_type":"markdown","source":"Note that there are mislabelled data in the dataset so don't worry if you see an image with the wrong label. You just got unlucky while picking random samples."},{"metadata":{"trusted":true},"cell_type":"code","source":"new_train_dir = '/root/train/'  # new directory for traininng data \n!mkdir $new_train_dir","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!ls /root","execution_count":null,"outputs":[]},{"metadata":{"id":"Cqx09j9goa6h","trusted":true},"cell_type":"code","source":"# create subdirectories to use as classes for keras to feed the model - bash command\nos.mkdir(new_train_dir+'dogs')\nos.mkdir(new_train_dir+'cats')","execution_count":null,"outputs":[]},{"metadata":{"id":"QeBAXuTNuR6D","trusted":true},"cell_type":"code","source":"# move the images to their corresponding subdirectories - bash command\n!cp $train_dir/cat.* $new_train_dir/cats/\n!cp $train_dir/dog.* $new_train_dir/dogs/","execution_count":null,"outputs":[]},{"metadata":{"id":"T3r10dlfxGRL","trusted":true},"cell_type":"code","source":"# check the directory - bash command\n!ls $new_train_dir","execution_count":null,"outputs":[]},{"metadata":{"id":"6lMfIeJ8bomL"},"cell_type":"markdown","source":"We'll augment the images to get more training data for the model"},{"metadata":{"id":"EUmdySDHm47h","trusted":true},"cell_type":"code","source":"def scale_images(x):\n  return x / 255\n\n# Augmentation Ranges\ntransform_params = {\n    'featurewise_center': False,\n    'featurewise_std_normalization': False,\n    'samplewise_center': False,\n    'samplewise_std_normalization': False,\n    'rotation_range': 30, \n    'width_shift_range': 0.15, \n    'height_shift_range': 0.15,\n    'horizontal_flip': True,\n    'validation_split': 0.25,\n    'preprocessing_function': scale_images\n}\nimg_gen = ImageDataGenerator(**transform_params) ","execution_count":null,"outputs":[]},{"metadata":{"id":"_lITVFYVm47o","trusted":true},"cell_type":"code","source":"fig, axs = plt.subplots(2, 4, figsize=(20,10))  # let's see 4 augmentation examples\nfig.suptitle('Augmentation Results', size=32)\n\nfor axs_col in range(axs.shape[1]):\n    viz_transoform_params = {  # defined each iteration to get new augmentation values each time\n        'theta': np.random.randint(-transform_params['rotation_range'], transform_params['rotation_range']),\n        'tx': np.random.uniform(0, transform_params['width_shift_range']),\n        'ty': np.random.uniform(0, transform_params['height_shift_range']),\n        'flip_horizontal': np.random.choice([True, False], p=[0.5, 0.5])\n    }\n\n    img = images_samples[axs_col]  # the original image\n    aug_img = img_gen.apply_transform(img, viz_transoform_params)  # the same image after augmentation\n    \n    axs[0, axs_col].imshow(img);\n    axs[0, axs_col].set_title('Original Image', size=15)\n    \n    axs[1, axs_col].imshow(aug_img);\n    axs[1, axs_col].set_title('Augmented Image', size=15)","execution_count":null,"outputs":[]},{"metadata":{"id":"EYegF7sim47z","trusted":true},"cell_type":"code","source":"# a Fully connected layer with activation, batchnorm and dropout\ndef dense_block(x, neurons, layer_no):\n    x = Dense(neurons, kernel_initializer=he_normal(layer_no), name=f'Dense{layer_no}')(x)\n    x = Activation('relu', name=f'Relu{layer_no}')(x)\n    x = BatchNormalization(name=f'BatchNorm{layer_no}')(x)\n    x = Dropout(0.5, name=f'Dropout{layer_no}')(x)\n    return x","execution_count":null,"outputs":[]},{"metadata":{"id":"l1QYGqp0m473","trusted":true},"cell_type":"code","source":"def create_model(shape):\n    input_layer = Input(shape, name='input_layer')  # input layer with given shape\n    \n    # load ResNet50 with initialized weights and remove final dense layers - keep as trainable layers\n    resnet = ResNet50(include_top=False, weights='imagenet', input_tensor=input_layer)\n\n    # dense layers after the ResNet50 initialized layers\n    flat1 = Flatten(name='Flatten')(resnet.output)\n    flat_bn = BatchNormalization()(flat1)\n    drop1 = Dropout(0.5)(flat_bn)\n    dens1 = dense_block(drop1, neurons=512, layer_no=1)\n    dens2 = dense_block(dens1, neurons=256, layer_no=3)\n    \n    dens4 = Dense(1, name='Dense4')(dens2)\n    output_layer = Activation('sigmoid')(dens4)\n    \n    model = Model(inputs=[input_layer], outputs=[output_layer])\n\n    return model","execution_count":null,"outputs":[]},{"metadata":{"id":"35sWNtvlm48P","trusted":true},"cell_type":"code","source":"# used to plot training curves (accuracy, loss) while model is training\nclass Plotter(Callback):\n    def plot(self):  # Updates the graph\n        clear_output(wait=True)\n        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 8))\n        \n        # plot the losses\n        ax1.plot(self.epochs, self.losses, label='train_loss')\n        ax1.plot(self.epochs, self.val_losses, label='val_loss')\n        \n        # plot the accuracies\n        ax2.plot(self.epochs, self.acc, label='train_acc')\n        ax2.plot(self.epochs, self.val_acc, label='val_acc')\n    \n        ax1.set_title(f'Loss vs Epochs')\n        ax1.set_xlabel(\"Epochs\")\n        ax1.set_ylabel(\"Loss\")\n        \n        ax2.set_title(f'Accuracy vs Epochs')\n        ax2.set_xlabel(\"Epoches\")\n        ax2.set_ylabel(\"Accuracy\")\n        \n        ax1.legend()\n        ax2.legend()\n        plt.show()\n        \n        # print out the accuracies at each epoch\n        print(f'Epoch #{self.epochs[-1]+1} >> train_acc={self.acc[-1]*100:.3f}%, train_loss={self.losses[-1]:.5f}')\n        print(f'Epoch #{self.epochs[-1]+1} >> val_acc={self.val_acc[-1]*100:.3f}%, val_loss={self.val_losses[-1]:.5f}')\n        \n    def on_train_begin(self, logs={}):\n        # initialize lists to store values from training\n        self.losses = []\n        self.val_losses = []\n        self.epochs = []\n        self.batch_no = []\n        self.acc = []\n        self.val_acc = []\n    \n    def on_epoch_end(self, epoch, logs={}):\n        # append values from the last epoch\n        self.losses.append(logs.get('loss'))\n        self.val_losses.append(logs.get('val_loss'))\n        self.acc.append(logs.get('acc'))\n        self.val_acc.append(logs.get('val_acc'))\n        self.epochs.append(epoch)\n        self.plot()  # update the graph\n        \n    def on_train_end(self, logs={}):\n        self.plot()\n               \nplotter = Plotter()","execution_count":null,"outputs":[]},{"metadata":{"id":"aoPAkm6ym48L","trusted":true},"cell_type":"code","source":"# used to decrease the learning rate if val_acc doesn't enhance\nplateau_reduce = ReduceLROnPlateau(monitor='val_acc', factor=0.75,\n                              patience=1, min_lr=0.000001)","execution_count":null,"outputs":[]},{"metadata":{"id":"YidIMiyjm48U","trusted":true},"cell_type":"code","source":"callbacks = [plotter, plateau_reduce] ","execution_count":null,"outputs":[]},{"metadata":{"id":"ue17z2dYR-6q","trusted":true},"cell_type":"code","source":"# hyperparameters\nheight, width, channels_num = 256, 256, 3\nlearning_rate = 0.00005\nepochs = 8\nbatch_size = 64  # if increased to 64 you may run out of gpu memory - reduce image size if you want to increase the batch size","execution_count":null,"outputs":[]},{"metadata":{"id":"ne2rrapUm48C","trusted":true},"cell_type":"code","source":"model = create_model((height, width, channels_num))\noptimizer = Adam(learning_rate)\n\nmodel.compile(optimizer=optimizer, loss='binary_crossentropy', metrics=['acc'])\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"id":"cQjS2O0lm48f","trusted":true},"cell_type":"code","source":"# used to feed the model augmented training data after being loaded from the directory\ntrain_gen = img_gen.flow_from_directory(directory=new_train_dir, target_size=(height, width), color_mode='rgb', classes=['cats', 'dogs'], \n                                        class_mode='binary', batch_size=batch_size, shuffle=True, subset='training', interpolation='nearest')\n\n# used to feed the model augmented validation data after being loaded from the directory\nvalid_gen = img_gen.flow_from_directory(directory=new_train_dir, target_size=(height, width), color_mode='rgb', classes=['cats', 'dogs'], \n                                        class_mode='binary', batch_size=batch_size, shuffle=True, subset='validation', interpolation='nearest')\n\n\nmodel.fit_generator(train_gen, validation_data=valid_gen, epochs=epochs, \n                        steps_per_epoch=images_num*0.75//batch_size + 1, \n                        validation_steps=images_num*0.25//batch_size + 1, callbacks=callbacks)\n\n# model.save('my_resnet_model.h5')","execution_count":null,"outputs":[]},{"metadata":{"id":"iesfBnzH9qvw","trusted":true},"cell_type":"code","source":"# check the corresponding classes of the binary encoding\ntrain_gen.class_indices","execution_count":null,"outputs":[]},{"metadata":{"id":"y47X0K8rH0DE","trusted":true},"cell_type":"code","source":"# get first 3 convolutional layers to visualize their activations\nlayers_viz = []\nfor layer in model.layers:\n  if len(layers_viz) < 3:\n    if layer.__class__.__name__ == 'Conv2D':\n      layers_viz.append(layer)\n  else:\n    break\n\n# using keras backend functions to obtain layers activtions - check keras documentation\ninp = model.input\noutputs = [layer.output for layer in layers_viz]\nfunctor = K.function([inp, K.learning_phase()], outputs)\ntest_idx = np.random.randint(0, images_samples.shape[0])\ntest_img = images_samples[test_idx]\ntest_img = test_img.reshape(1, *test_img.shape)\nprint(test_img.shape)\nlayers_outs = functor([test_img, 0])","execution_count":null,"outputs":[]},{"metadata":{"id":"kysiI0XtjVhM","trusted":true},"cell_type":"code","source":"# shapes of the selected layers' activations\nfor layer in layers_outs:\n  print(layer.shape)","execution_count":null,"outputs":[]},{"metadata":{"id":"MXPkF40Ampci","trusted":true},"cell_type":"code","source":"# plots activations in a grid of axes\ndef plot_activations(conv_layer):\n  dim_sqrt = np.sqrt(conv_layer.shape[-1])\n  rows_num = int(dim_sqrt)  # get integer number of rows\n  cols_num = conv_layer.shape[-1] // rows_num\n  fig, axs = plt.subplots(rows_num, cols_num, figsize=(16, 16))\n  for filter_map, ax in zip(range(conv_layer.shape[-1]), axs.ravel()):\n    activations = conv_layer[0, :, :, filter_map]\n    activations = (activations - np.min(activations)) / (np.max(activations - np.min(activations)))  # normalize to give to plt.imshow \n    ax.set_axis_off()\n    ax.imshow(activations, cmap='viridis')","execution_count":null,"outputs":[]},{"metadata":{"id":"1YLEXjZu5-eh","trusted":true},"cell_type":"code","source":"# get a random sample from the 4 samples\ntest_idx = np.random.randint(0, images_samples.shape[0]-1)\ntest_img = images_samples[test_idx]\ntest_img = test_img.reshape(1, *test_img.shape)\nlayers_outs = functor([test_img, 0])  # the output of the keras functor we made - the activations","execution_count":null,"outputs":[]},{"metadata":{"id":"qavSglJdupdU","trusted":true},"cell_type":"code","source":"# the 1st conv layer activations\nconv_layer = layers_outs[0]\nprint(f'Number of Filters: {conv_layer.shape[-1]}')\nplot_activations(conv_layer=conv_layer)","execution_count":null,"outputs":[]},{"metadata":{"id":"dUq62LuvvhTR","trusted":true},"cell_type":"code","source":"# the 2nd conv layer activations\nconv_layer = layers_outs[1]\nprint(f'Number of Filters: {conv_layer.shape[-1]}')\nplot_activations(conv_layer=conv_layer)","execution_count":null,"outputs":[]},{"metadata":{"id":"QPqMAqKfvoM7","trusted":true},"cell_type":"code","source":"# the 3rd conv layer activations\nconv_layer = layers_outs[2]\nprint(f'Number of Filters: {conv_layer.shape[-1]}')\nplot_activations(conv_layer=conv_layer)","execution_count":null,"outputs":[]},{"metadata":{"id":"v7MSCn9b53iq"},"cell_type":"markdown","source":"Test data prediction\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"!ls '/root/data/test1/' -U | head -5","execution_count":null,"outputs":[]},{"metadata":{"id":"help-v8y5-tU","trusted":true},"cell_type":"code","source":"test_dir = '/root/data/test1/'  # the images directory\ntest_images_names = os.listdir(test_dir)  # names of the files in the directory\ntest_images_num = len(test_images_names)\nprint(f'Number of images: {test_images_num}')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_ids = np.apply_along_axis(lambda x: x[0][:-4], axis=0, \n                               arr=np.array(test_images_names).reshape(1, test_images_num))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_ids.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_images_names[4]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_ids[4]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test_ids = test_ids.tolist()","execution_count":null,"outputs":[]},{"metadata":{"id":"svigMtwf-7Fj","trusted":true},"cell_type":"code","source":"parent_dir = '/root/data'\n# generator to feed the test images to model in batches - doesn't augment, just the preprocess function\ntest_gen = ImageDataGenerator(preprocessing_function=scale_images).flow_from_directory(\n                                        directory=parent_dir, target_size=(height, width), color_mode='rgb', classes=['test1'],\n                                        class_mode=None, batch_size=50, shuffle=False, interpolation='nearest')","execution_count":null,"outputs":[]},{"metadata":{"id":"jy_uE2sBDcza","trusted":true},"cell_type":"code","source":"preds = model.predict_generator(test_gen)  # model predictions\nprint(preds[:10])","execution_count":null,"outputs":[]},{"metadata":{"id":"Cx1VXVhuEVqf","trusted":true},"cell_type":"code","source":"binary_preds = (preds >= 0.5).astype(int)  # convert predictions to binary \nprint(binary_preds[:10])","execution_count":null,"outputs":[]},{"metadata":{"id":"Cmcp--HeIh4W","trusted":true},"cell_type":"code","source":"ids_arr = np.array(test_ids).astype(int).reshape(-1, 1)  # reshape ids as rows\nids_arr.shape","execution_count":null,"outputs":[]},{"metadata":{"id":"dBTYKlmEJD4X","trusted":true},"cell_type":"code","source":"sub_data = np.hstack([ids_arr, binary_preds])  # data to put in a dataframe for submission","execution_count":null,"outputs":[]},{"metadata":{"id":"uOG3pr_fEn9W","trusted":true},"cell_type":"code","source":"sub_df = pd.DataFrame(data=sub_data, columns=['id', 'label'])","execution_count":null,"outputs":[]},{"metadata":{"id":"i3W_cEGkEq5l","trusted":true},"cell_type":"code","source":"sub_df.head(5)","execution_count":null,"outputs":[]},{"metadata":{"id":"cWXxbcYBIBoh","trusted":true},"cell_type":"code","source":"sub_df.sort_values(by='id', inplace=True)  # order by id\nsub_df.reset_index(inplace=True, drop=True)  # reset the index\nsub_df.head(5)","execution_count":null,"outputs":[]},{"metadata":{"id":"6xJGvbT_J-jy","trusted":true},"cell_type":"code","source":"# save to csv\nsub_df.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"id":"MY4PJpsNLMaH","trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"accelerator":"GPU","colab":{"name":"dog_vs_cat.ipynb","provenance":[],"collapsed_sections":[],"machine_shape":"hm"},"kernelspec":{"name":"python3","display_name":"Python 3"},"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.7.2"}},"nbformat":4,"nbformat_minor":4}