{"cells":[{"metadata":{},"cell_type":"markdown","source":"**Dealing with the data**"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# first thing is extracting the files\nimport os, shutil, zipfile\n\ndata = ['train', 'test1']\n\nfor el in data:\n    with zipfile.ZipFile('../input/dogs-vs-cats/' + el + \".zip\", \"r\") as z:\n        z.extractall(\".\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#shutil.rmtree('/kaggle/working/small') # deletes the 'small' directory, for debugging purposes","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"path = os.getcwd()\nprint (\"The current working directory is %s\" % path)\n\noriginal_dataset_dir = '/kaggle/working' # where the data was extracted\nbase_dir = '/kaggle/working/small' # directory for the smaller dataset we'll be using (just for less runtime)\nos.mkdir(base_dir)\n\ntrain_dir = os.path.join(base_dir, 'train')\nos.mkdir(train_dir)\nvalidation_dir = os.path.join(base_dir, 'validation')\nos.mkdir(validation_dir)\ntest_dir = os.path.join(base_dir, 'test')\nos.mkdir(test_dir)\n\ntrain_cats_dir = os.path.join(train_dir, 'cats')\nos.mkdir(train_cats_dir)\ntrain_dogs_dir = os.path.join(train_dir, 'dogs')\nos.mkdir(train_dogs_dir)\n\nvalidation_cats_dir = os.path.join(validation_dir, 'cats')\nos.mkdir(validation_cats_dir)\nvalidation_dogs_dir = os.path.join(validation_dir, 'dogs')\nos.mkdir(validation_dogs_dir)\n\ntest_cats_dir = os.path.join(test_dir, 'cats')\nos.mkdir(test_cats_dir)\ntest_dogs_dir = os.path.join(test_dir, 'dogs')\nos.mkdir(test_dogs_dir)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"#print(os.listdir(\"/kaggle/working/train/\"))\n\noriginal_dataset_dir = '/kaggle/working/train' # for the smaller sample size we use only part of the training set\n\n# out of the total 25'000 images of cats n dogs (12'500 for each class), we'll be using, for each class:\n# just 1'000 images for training, 500 for validation and 500 for testing\nfnames = ['cat.{}.jpg'.format(i) for i in range(1000)]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir, fname)\n    dst = os.path.join(train_cats_dir, fname)\n    shutil.copyfile(src, dst)\n    \nfnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir, fname)\n    dst = os.path.join(validation_cats_dir, fname)\n    shutil.copyfile(src, dst)\n\nfnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir, fname)\n    dst = os.path.join(test_cats_dir, fname)\n    shutil.copyfile(src, dst)\n    \n# dogs\nfnames = ['dog.{}.jpg'.format(i) for i in range(1000)]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir, fname)\n    dst = os.path.join(train_dogs_dir, fname)\n    shutil.copyfile(src, dst)\n    \nfnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir, fname)\n    dst = os.path.join(validation_dogs_dir, fname)\n    shutil.copyfile(src, dst)\n\nfnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]\nfor fname in fnames:\n    src = os.path.join(original_dataset_dir, fname)\n    dst = os.path.join(test_dogs_dir, fname)\n    shutil.copyfile(src, dst)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Building the network**"},{"metadata":{"trusted":true},"cell_type":"code","source":"# since we're having a binary classification problem, the network will end with a single unit (Dense layer size 1) with a sigmoid function\nfrom keras import models\nfrom keras import layers\nfrom keras import optimizers\n\nmodel = models.Sequential()\nmodel.add(layers.Conv2D(32, (3,3), activation='relu', input_shape=(150,150,3)))\nmodel.add(layers.MaxPooling2D((2,2)))\nmodel.add(layers.Conv2D(64, (3,3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2,2)))\nmodel.add(layers.Conv2D(128, (3,3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2,2)))\nmodel.add(layers.Conv2D(128, (3,3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2,2)))\nmodel.add(layers.Flatten())\nmodel.add(layers.Dense(512, activation='relu'))\nmodel.add(layers.Dense(1, activation='sigmoid'))\n\nmodel.summary()\n\n# since we ended the network with a single layer, the loss will be calculted using binary crossentropy\nmodel.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"if we look at the model summary we'll see that in the output shape, the amount of filters (depth size) is going up according to the values we put on each Conv2D layer. after the Conv2D we pass the output into a MaxPooling2D layer which 'cuts' the resolution of the image by half. on the next Conv2D layer we then make the image even deeper, increasing the number of filter, in order to balance this, we ought to also lower the resolution through pooling.\n\neach time we increase the number of filters we also have to downsize the image afterwards. this way it's possible for the next filters to identify more abstract patterns (that are seen even in a lower resolution)."},{"metadata":{},"cell_type":"markdown","source":"**Data preprocessing**\n\nwe have to format the data into floating point tensors in order to feed it into the network, while also scaling the pixels of the image from a 0-255 range to a 0-1 interval."},{"metadata":{"trusted":true},"cell_type":"code","source":"from keras.preprocessing.image import ImageDataGenerator\n# contains generators that can be used to automatically convert images into tensors\n# the generator object acts as an iterator and works with the yield operator:\ndef generator():\n    i = 0\n    while True:\n        i += 1\n        yield i\n        \n# creating the generators\ntrain_datagen = ImageDataGenerator(rescale=1./255)\ntest_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_generator = train_datagen.flow_from_directory(train_dir, # img\n                                                    target_size=(150,150), # resize images to 150x150\n                                                    batch_size=20,\n                                                    class_mode='binary') # using binary crossentropy calls for binary labels\n\nvalidation_generator = test_datagen.flow_from_directory(validation_dir,\n                                                       target_size=(150,150),\n                                                       batch_size=20,\n                                                       class_mode='binary')\n                                                    \nfor data_batch, labels_batch in train_generator:\n    print('data batch shape: ', data_batch.shape) # (20, 150, 150, 3)\n    print('label batch shape: ', labels_batch.shape) # (20,)\n    break # because it generates the data indefinetely\n    \n\n# we'll fit the model using the data generator through the fit_generator method. it expects as its first input a generator that yields batches of inputs and targets\n# steps_per_epoch is an argument that determines how many batches are required for an epoch to pass\n# after steps_per_epoch gradient descent steps we go to the next epoch (each batch is a gradient descent step). since each batch is 20 samples, after 100 we'll have the data\nhistory = model.fit_generator(train_generator, steps_per_epoch=100, epochs=30,\n                             validation_data=validation_generator, validation_steps=50)\n\nmodel.save('cats_vs_dogs.h5')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nplt.style.use('ggplot')\n\nacc = history.history['acc']\nval_acc = history.history['val_acc']\nloss = history.history['loss']\nval_loss = history.history['val_loss']\n\nepochs = range(1, len(acc) + 1)\n\nplt.plot(epochs, acc, 'b', label='training acc')\nplt.plot(epochs, val_acc, 'r', label='validation acc')\nplt.title('accuracy')\nplt.legend()\n\nplt.figure()\n\nplt.plot(epochs, loss, 'b', label='training loss')\nplt.plot(epochs, val_loss, 'r', label='validation loss')\nplt.title('loss')\nplt.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Data augmentation**\n\nwe clearly have a lot of overfitting, and much of it is due to the (purposefully) small sample size. we'll get around this by using some augmentation techniques"},{"metadata":{"trusted":true},"cell_type":"code","source":"datagen = ImageDataGenerator(rotation_range=40, # range 0-180 within which to rotate images\n                            width_shift_range=0.2,\n                            height_shift_range=0.2, # randomly translate pictures vertically and horizontally\n                            shear_range=0.2, # shearing transformations (kinda like, making the picture at an angle)\n                            zoom_range=0.2,\n                            horizontal_flip=True,\n                            fill_mode='nearest') # how to fill new pixels\n\n# just to be able to look at the images with the transformations:\nfrom keras.preprocessing import image # module for image processing\n\nfnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]\nimg_path = fnames[420] # the example image chosen for augmentation\nimg = image.load_img(img_path, target_size=(150,150)) # resizing just like before\n\nx = image.img_to_array(img) # to shape (150,150,3)\nx = x.reshape((1,) + x.shape)\n\ni = 0\nfor batch in datagen.flow(x, batch_size=1): # cuz we just wanna demo the transformations of 1 pic\n    plt.figure(i)\n    imgplot = plt.imshow(image.array_to_img(batch[0]))\n    i += 1\n    if i % 3 == 0:\n        break\n        \nplt.show()\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# while this helps, the inputs will be heavily correlated, to ensure that we get a smaller overfit we'll add a dropout layer right before the Dense layer:\nmodel = models.Sequential()\nmodel.add(layers.Conv2D(32, (3,3), activation='relu', input_shape=(150,150,3)))\nmodel.add(layers.MaxPooling2D((2,2)))\nmodel.add(layers.Conv2D(64, (3,3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2,2)))\nmodel.add(layers.Conv2D(128, (3,3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2,2)))\nmodel.add(layers.Conv2D(128, (3,3), activation='relu'))\nmodel.add(layers.MaxPooling2D((2,2)))\nmodel.add(layers.Flatten())\nmodel.add(layers.Dropout(0.5))\nmodel.add(layers.Dense(512, activation='relu'))\nmodel.add(layers.Dense(1, activation='sigmoid'))\n\nmodel.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# finally, we'll train the network using the data augmentation and dropout:\ntrain_datagen = ImageDataGenerator(rescale=1./255,\n                                   rotation_range=40,\n                                   width_shift_range=0.2,\n                                   height_shift_range=0.2,\n                                   shear_range=0.2,\n                                   zoom_range=0.2,\n                                   horizontal_flip=True)\n\ntest_datagen = ImageDataGenerator(rescale=1./255) # the test/validation data shall not be augmented\n\ntrain_generator = train_datagen.flow_from_directory(train_dir,\n                                                   target_size=(150,150),\n                                                   batch_size=32,\n                                                   class_mode='binary')\n\nvalidation_generator = test_datagen.flow_from_directory(validation_dir,\n                                                       target_size=(150,150),\n                                                       batch_size=32,\n                                                       class_mode='binary')\n\nhistory = model.fit_generator(train_generator, steps_per_epoch=100, epochs=100,\n                             validation_data=validation_generator, validation_steps=50)\n\nmodel.save('cats_vs_dogs_aug.h5')\n\n# we get way better results, but an even better way to improve the accuracy is by using a pretrained model :)","execution_count":null,"outputs":[]}],"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}