{"cells":[{"metadata":{"_cell_guid":"cb800806-0ba0-436e-b2f8-76e7738c9e0b","_uuid":"b818bb4268f7d8d25d5935356128ff4151be6dd4"},"cell_type":"markdown","source":"# Cats and Dogs Redux\n\nThe model is \"light-weight\" version of the  VGG16 architecture wit **360,865** trainable parameters: \n\n1) Fewer blocks\n\n2) Replaced regular convolution layer by **Dilated convolution** layer [https://arxiv.org/abs/1511.07122]\n\n3) Use of **swish** activation function on the Fully connected layer [ https://arxiv.org/abs/1710.05941]\n\nAlthough (2) results in a lower training and validation loss, the improvement using swish activation is marginal. \n\nAfter 8 Epochs, the model shows a validation accuracy around 82%. Pretty good! \nThis result can be further improved by reducing the high variance of the model: the training loss is much smaller than the validation loss.\n\nOne could include data augmentation in the batch generator so to control the overfitting to the training dataset."},{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":false,"collapsed":true},"cell_type":"code","source":"# Required libraries\nfrom keras.layers import Conv2D, Activation, MaxPooling2D, Flatten, Dense, Dropout\nfrom keras import backend as K\nfrom keras.utils.generic_utils import get_custom_objects\nfrom keras.models import Sequential\nfrom keras.optimizers import Adam\nimport numpy as np\nimport cv2\nimport glob\nimport matplotlib.pylab as plt\nimport hashlib\nimport random\nfrom PIL import Image\nimport seaborn\n\n#set random seed\nnp.random.seed(72)\n","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"0f1b2a78-fb7f-426b-9f06-e14e65fd12fe","collapsed":true,"_uuid":"57cbc1b8ab29791c379e09335029acb219e02e52","trusted":false},"cell_type":"code","source":"#####\n# Get the list of the filenames that contains substring \"cat\" or \"dog\n# Remove duplicate images\n####\n\ndef get_list_images(folder):\n    '''\n    get list of filenames in a directory, as  2 dictionaries\n    return a dictionary with keys: \"cats/dogs\" and values: list of all filenames for the 2 pet category\n    '''\n    files = glob.glob(folder+\"/*.*\")\n    \n    cats = [fname for fname in files if \"cat\" in fname]\n    dogs = [fname for fname in files if \"dog\" in fname]\n    return {\"dogs\": dogs, \"cats\": cats}\n\n\ndef remove_duplicate_images(ls_files):\n    \"\"\"\n    Search for duplicate images\n    \"\"\"\n    #key=hash_image, value=[list of fnames]\n    unique_imgs = dict()\n    duplicates = dict()\n    for fname in ls_files:\n        img = cv2.imread(fname, cv2.IMREAD_COLOR) #cv2.IMREAD_GRAYSCALE\n        digest_img = hashlib.sha224(img).hexdigest()\n        if digest_img not in unique_imgs.keys() and digest_img not in duplicates.keys():\n            unique_imgs[digest_img] = fname\n        elif digest_img in unique_imgs.keys() and digest_img in duplicates.keys():\n            duplicates[digest_img].append(fname)\n        elif digest_img in unique_imgs.keys() and digest_img not in duplicates.keys():\n            #first encounter of this duplicate\n            duplicates[digest_img] = [fname]\n            \n    return list(unique_imgs.values()), list(duplicates.values())","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"7cba78a2-970d-4206-b522-f4d8a3ff27f0","_uuid":"d2da6b4019b0f57fad36612857e7bbb097f346bd","trusted":false,"collapsed":true},"cell_type":"code","source":"#####\n# Build train and validation dataset\n#####\n\ndata_dict = get_list_images(\"../input/train\")\ncats, duplicates_cats = remove_duplicate_images(data_dict[\"cats\"])\ndogs, duplicates_dogs = remove_duplicate_images(data_dict[\"dogs\"])\n\n#Merge cats and dogs unique imgs\nx = dogs + cats\ny = [0] * len(dogs) + [1] * len(cats)\ndata = list(zip(x, y))\nidx = np.arange(0, len(data), 1)\nprint(\"Number of images of Cats: {} | Dogs: {}\".format( len(cats), len(dogs)))\nprint(\"Total Number of unique images to use on model: {}\".format(len(data)))\n\n#Create training and validation set with split train:validation = 3:1\nnp.random.shuffle(idx)\nsplit_ratio = 0.25\nvalid_sz = int( 0.25 * len(idx) )\ntrain_idx, validation_idx = idx[0:-valid_sz], idx[-valid_sz::]\nprint(\"Set size Training: {} | Validation: {}\".format(len(train_idx), len(validation_idx)))\n\n#Build train dataset and validation dataset\ntrain_data = [ data[i] for i in train_idx  ]\nvalidation_data = [ data[i] for i in validation_idx ]","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"c719e1e4-7077-4b28-b367-94aacc60a793","_uuid":"5d79bc82cd6b385841f450af98cba038893c0e2a","trusted":false,"collapsed":true},"cell_type":"code","source":"######\n#Visualize a few examples\n######\n\nselected_idx = np.random.choice(idx, 10)\n\nf, axarr = plt.subplots(2, 5, figsize=(40, 20))\nfor i, sel_id in enumerate(selected_idx):\n    fname = data[sel_id][0]\n    img =  plt.imread( fname )\n    row, col = i//5, np.clip(i%5, 0, 4)\n    axarr[row, col].imshow(img)\n    f.subplots_adjust(hspace=0)\n    axarr[row, col].set_title(fname)\n    axarr[row, col].axis('off')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":false,"collapsed":true},"cell_type":"code","source":"######\n# Visualize duplicate images\n#######\n\ndef show_duplicates(list_duplicates, label_name=\"dogs\", verbose=True):\n    if verbose:\n        print()\n        print(\"Duplicate images from {} class\".format(label_name))\n        print(\"Number of Images of {} with duplicates: {}\".format(label_name, len(list_duplicates) ))\n\n    f, axarr = plt.subplots( len(list_duplicates), sharex=True, sharey=True, figsize=(20, 10))\n    for i, fname in enumerate(list_duplicates):\n        axarr[i].set_title(\"This image is repeated {} times\".format(len(fname) + 1))\n        img =  plt.imread( fname[0] )\n        axarr[i].imshow(img)\n        axarr[i].axis(\"off\")\n        f.subplots_adjust(hspace=0)\n    plt.show()\n    \nshow_duplicates(duplicates_dogs, label_name=\"dogs\")\nshow_duplicates(duplicates_cats, label_name=\"cats\")","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"bc3cc63b-4a16-44be-b28f-d04f78155a98","_uuid":"845afeb8f2c4486c5facdef00b75ce2cbe56c9be","trusted":false,"collapsed":true},"cell_type":"code","source":"######\n# We will be feeding resized images to the model, where width=height. \n# Some images will be distorted.\n# Let's check that class cats and dogs have about the same distribution of height/width\n# ratio\n#######\n\nhwratio_cats, hwratio_dogs = list(), list()\n\nfor fname in cats:\n    img = plt.imread(fname)\n    hwratio_cats.append(img.shape[0] / img.shape[1])\n    \nfor fname in dogs:\n    img = plt.imread(fname)\n    hwratio_dogs.append(img.shape[0] / img.shape[1])\n\nplt.figure(figsize=(15,5))\nplt.hist(hwratio_cats, bins=100, label=\"Height/Width Ratio for cats\", alpha=0.5)\nplt.hist(hwratio_dogs, bins=100, label=\"Height/Width Ratio for dogs\", alpha=0.5)\nplt.xlabel(\"Image ratio height/width\")\nplt.ylabel(\"Frequency\")\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"0c1146e4-6169-4ae6-80c8-ab6ebf2ef3c5","collapsed":true,"_uuid":"1c12bcda156c7148136de31eab1bab42ad09b0c3","trusted":false},"cell_type":"code","source":"def batch_gen(batch_sz, data, target_sz):\n    indexes = np.arange(0, len(data), 1)\n    start = 0\n    while 1:\n        x_batch = np.zeros( (batch_sz, img_sz, img_sz, 3), dtype=np.float32)\n        y_batch = np.zeros( (batch_sz, 1), dtype=np.float32)\n\n        for row in range(batch_sz):\n            sample = data[ indexes[row + start] ]\n            img = cv2.imread(sample[0], cv2.IMREAD_COLOR)\n            img = cv2.resize(img, (target_sz, target_sz), interpolation=cv2.INTER_CUBIC)\n            #############\n            # Add a few lines for data augmentation with affine distorsions\n            ######\n            img_arr = np.asarray(img)\n            x_batch[row] = (img_arr[:,:,:]-128.)/ 128.\n            y_batch[row, 0] = sample[1]\n        start += batch_sz\n        if start + batch_sz > len(indexes):\n            np.random.shuffle(indexes)\n            start = 0\n        yield x_batch, y_batch","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"970fa246-d790-4ee0-9683-c908240812ec","collapsed":true,"_uuid":"79ae731ce08fc69b521ccf23443d492b388a6329","trusted":false},"cell_type":"code","source":"def swish_activation(x):\n    '''\n    Keras implementation of swish https://arxiv.org/abs/1710.05941\n    '''\n    return (K.sigmoid(x) * x)\n\nget_custom_objects().update({'swish': Activation(swish_activation)})\n\n\ndef binary_classifier(img_sz, img_ch):\n    model = Sequential()\n    #image size = (32, 32, 3)\n    model.add(Conv2D(32, (3, 3), activation='relu', padding=\"same\", input_shape=(img_sz, img_sz, img_ch)))\n    model.add(Conv2D(32, (3, 3), padding=\"same\", activation='relu'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    #image size = (16, 16, 32)\n    model.add(Conv2D(64, (3, 3), activation='relu', padding=\"same\"))\n    model.add(Conv2D(64, (3, 3), padding=\"same\", activation='relu'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    #image size = (16, 16, 64)\n    model.add(Conv2D(128, (3, 3), dilation_rate=(2, 2), activation='relu', padding=\"same\"))\n    model.add(Conv2D(128, (3, 3), padding=\"valid\", activation='relu'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    #image size = (7, 7, 128)\n    model.add(Flatten())\n    model.add(Dense(64, activation=swish_activation))\n    #model.add(Dense(512, activation=swish_activation))\n    model.add(Dropout(0.4))\n    model.add(Dense(1, activation='sigmoid'))\n    return model\n","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"e33e578e-d68a-4e9b-a0be-2fb18fb68b1b","_uuid":"31680f4343afff92e6bde6bc88b6e181c2d2a566","trusted":false,"collapsed":true},"cell_type":"code","source":"# Parameters\nbatch_sz = 128\nimg_sz = 32\nn_epochs = 8\nlr = 0.0005\n\nmodel = binary_classifier(img_sz, 3)\nadam = Adam(lr=lr)\nmodel.compile(loss='binary_crossentropy', optimizer=adam, metrics=['accuracy'])\nsteps_per_epoch = len(train_data) // batch_sz\nvalidation_steps = len(validation_data) // batch_sz\n\nmodel.summary()\nmodel.fit_generator(batch_gen(batch_sz, train_data, img_sz), validation_data=batch_gen(batch_sz, validation_data, img_sz),\\\n                    steps_per_epoch=steps_per_epoch, epochs=n_epochs, validation_steps = validation_steps, verbose=True)","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"b686037d-6a52-4995-83b8-b9f5de11276a","collapsed":true,"_uuid":"3681659308c75d5cef55595aea94f012c8246334","trusted":false},"cell_type":"code","source":"######\n# Let's run prediction on a few examples of the test set\n######\n\n#Build Test set\ntest_files = glob.glob(\"../input/test/*.*\")\n\n\nfor i in range(10):\n    idx = np.random.choice( np.arange(0, len(test_files), 1) )\n    img = cv2.imread( test_files[idx], cv2.IMREAD_COLOR)\n    img_resized = cv2.resize(img, (img_sz, img_sz), interpolation=cv2.INTER_CUBIC)\n    img_arr = (np.asarray(img_resized) - 128.)/128.\n    prob = model.predict(img_arr.reshape(1, img_sz, img_sz, 3))[0][0]\n    plt.imshow(img)\n    plt.title(\"Probability that's a cat: {:.2f}%\".format(prob*100) )\n    plt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"8736679f-8892-4467-bc95-f315ecd1fabb","collapsed":true,"_uuid":"e5ade4aff187a28a6003ffa8a2d9bfdd43d1026e","trusted":false},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"416355fc-5dfe-4663-ac92-123ca73dd398","collapsed":true,"_uuid":"98f85221878cac8b256441e5229d6ffd19c4c46e","trusted":false},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","version":"3.6.4","name":"python","nbconvert_exporter":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":1}