{"cells":[{"metadata":{"_cell_guid":"764fe135-c09e-9769-5794-500867154d93","_uuid":"74b068a639dac72ba57c94e5c8b741d2f69822f4"},"cell_type":"markdown","source":"Starter EDA and ConvNet implementation using Keras. \n\nInspiration for this notebook comes from this [Keras blog post](https://blog.keras.io/building-powerful-image-classification-models-using-very-little-data.html) and the [VGG ConvNet paper](https://arxiv.org/pdf/1409.1556.pdf). \n"},{"metadata":{"_cell_guid":"3d458c15-e131-f3c4-f756-843d6454bb37","_uuid":"6669ca36d6d0739ee1f1228bebc997c070f32e1e","trusted":true},"cell_type":"code","source":"import os, cv2, random\nimport numpy as np\nimport pandas as pd\n\nimport matplotlib.pyplot as plt\nfrom matplotlib import ticker\nimport seaborn as sns\n%matplotlib inline \n\nfrom keras.models import Sequential\nfrom keras.layers import Input, Dropout, Flatten, Convolution2D, MaxPooling2D, Dense, Activation, Conv2D\nfrom keras.optimizers import RMSprop, Adam\nfrom keras.callbacks import ModelCheckpoint, Callback, EarlyStopping, ModelCheckpoint\nfrom keras.utils import np_utils","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"04681981-55ac-7820-a0ae-38f98c851c39","_uuid":"f8bb25c695b5d3a58602db2f95cb36f5356adbc1"},"cell_type":"markdown","source":"## Preparing the Data\n\nThis function resizes the images to 64x64 and samples 2000 images (8%) of the data to run efficiently as a Kaggle Kernel. I also separated cats and dogs for exploratory analysis. "},{"metadata":{"_cell_guid":"663d335e-1b84-a8cb-19ee-8f04839cf4e5","_uuid":"ace73f0b823ed856ce888b13bab6d512d7edbb2d","trusted":true},"cell_type":"code","source":"TRAIN_DIR = '../input/train/'\nTEST_DIR = '../input/test/'\n\nROWS = 180\nCOLS = 180\nCHANNELS = 3\n\ntrain_images = [TRAIN_DIR+i for i in os.listdir(TRAIN_DIR)] # use this for full dataset\ntrain_dogs =   [TRAIN_DIR+i for i in os.listdir(TRAIN_DIR) if 'dog' in i]\ntrain_cats =   [TRAIN_DIR+i for i in os.listdir(TRAIN_DIR) if 'cat' in i]\n\ntest_images =  [TEST_DIR+i for i in os.listdir(TEST_DIR)]\n\n\n# slice datasets for memory efficiency on Kaggle Kernels, delete if using full dataset\n#train_images = train_dogs[:7000] + train_cats[:7000]\ntrain_images = train_dogs + train_cats\nrandom.shuffle(train_images)\n#test_images =  test_images[:25]\n\ndef read_image(file_path):\n    img = cv2.imread(file_path, cv2.IMREAD_COLOR) #cv2.IMREAD_GRAYSCALE\n    return cv2.resize(img, (ROWS, COLS), interpolation=cv2.INTER_CUBIC)\n\n\ndef prep_data(images):\n    count = len(images)\n    data = np.ndarray((count, CHANNELS, ROWS, COLS), dtype=np.uint8)\n\n    for i, image_file in enumerate(images):\n        image = read_image(image_file)\n        data[i] = image.T\n        if i%250 == 0: print('Processed {} of {}'.format(i, count))\n    \n    return data\n\ntrain = prep_data(train_images)\ntest = prep_data(test_images)\n\nprint(\"Train shape: {}\".format(train.shape))\nprint(\"Test shape: {}\".format(test.shape))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"ffc3e0a7e67a9a9e5fb39632b8cccfd95e925d32"},"cell_type":"code","source":"len(train_dogs)","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"ed0fc95a-53bc-3517-245d-cf1f5122bc2d","_uuid":"f2af7dc735ab7273ebeb35b9c7bb83c77695dd24"},"cell_type":"markdown","source":"### Generating the Labels\n\nWe're dealing with a binary classification problem here - (1) dog (0) cat. The lables can be created by looping over the file names in the train directory. It's nice to see the training data is perfectly balanced. "},{"metadata":{"_cell_guid":"c0f2fbf6-8d78-7d42-6579-5486a36c1e60","_uuid":"ca1f6aa565403c6f209709c4f338db9a0df1911a","trusted":true},"cell_type":"code","source":"labels = []\nfor i in train_images:\n    if 'dog' in i:\n        labels.append(1)\n    else:\n        labels.append(0)\n\nsns.countplot(labels)\n#sns.plt.title('Cats and Dogs')","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"4bb0bd2a-0512-aa72-c628-5b6ac946d97c","_uuid":"bb6cee185fe789a34deb07336ba36b427103495b"},"cell_type":"markdown","source":"### Checking out Cats and Dogs\nA quick side-by-side comparison of the animals."},{"metadata":{"_cell_guid":"0b7c79ae-6543-2ed8-e3f5-af4f5beb9cf7","_uuid":"4a235114b4e9af5fca379c0430c3cb6881ab400f","trusted":true},"cell_type":"code","source":"def show_cats_and_dogs(idx):\n    cat = read_image(train_cats[idx])\n    dog = read_image(train_dogs[idx])\n    pair = np.concatenate((cat, dog), axis=1)\n    plt.figure(figsize=(10,5))\n    plt.imshow(pair)\n    plt.show()\n    \nfor idx in range(0,5):\n    show_cats_and_dogs(idx)","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"599a11ba-aaa4-8266-03ea-d816518e2a81","_uuid":"fed390bf0a9dda75720d2e89fbfd57f416489f46"},"cell_type":"markdown","source":"### Your Average Cat and Dog Photo\n\nJust for fun, the mean pixel values for cats and dogs. I can almost see a resemblance, a ghost, if you will..."},{"metadata":{"_cell_guid":"7d3c6eb7-c370-9752-67c8-c780bab14abe","_uuid":"18dafa3d8a3c9d9bf8ade82d5bb9802c7eb5b7fc","trusted":true},"cell_type":"code","source":"dog_avg = np.array([dog[0].T for i, dog in enumerate(train) if labels[i]==1]).mean(axis=0)\nplt.imshow(dog_avg)\nplt.title('Your Average Dog')","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"a5a93063-de92-18ce-b48f-68a193e90002","_uuid":"0dc65e2475b8925c6f3667109944c6c878abf07e","trusted":true},"cell_type":"code","source":"cat_avg = np.array([cat[0].T for i, cat in enumerate(train) if labels[i]==0]).mean(axis=0)\nplt.imshow(cat_avg)\nplt.title('Your Average Cat')","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"51e403b6-bcfc-b4fa-3770-9850cc86bae3","_uuid":"3ca2972aeb05aa7cae04230ec6847243593c16af"},"cell_type":"markdown","source":"## CatdogNet-16\n\nA scaled down version of the VGG-16, with a few notable changes.\n\n- Number of convolution filters cut in half, fully connected (dense) layers scaled down. \n- Optimizer changed to `RMSprop`. \n- Output layer activation set to `sigmoid` for binary crossentropy. \n- Some layers commented out for efficiency.\n\nThe full network takes about 80s per epoch on a GTX1070 (or 2hr+ on CPU) on the full dataset.  (This script only trains on 8% of the 25K images. )"},{"metadata":{"trusted":true,"_uuid":"47a6554acfc3c071c8b3a4e3a28d1df70253fe53"},"cell_type":"code","source":"from keras.layers import LeakyReLU","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"4c477612-41d3-3112-5176-3c4ad9633080","_uuid":"b63a0fe325a4c4f6d3d0ed9531e8e52a8f5b62e6","trusted":true},"cell_type":"code","source":"optimizer = RMSprop(lr=0.00001)\nobjective = 'mse'\n\n\ndef catdog():\n    \n    model = Sequential()\n\n    model.add(Conv2D(32, (3, 3), border_mode='same', input_shape=(3, ROWS, COLS), activation='relu',data_format='channels_first'))\n    model.add(Conv2D(32, (3, 3), border_mode='same', activation='relu'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    #model.add(Dropout(0.3))\n\n    model.add(Conv2D(64, (3, 3), border_mode='same', activation='relu'))\n    model.add(Conv2D(64, (3, 3), border_mode='same', activation='relu'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    \n    model.add(Conv2D(128, (3, 3), border_mode='same', activation='relu'))\n    model.add(Conv2D(128, (3, 3), border_mode='same', activation='relu'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    #model.add(Dropout(0.3))\n    model.add(Conv2D(256, (3, 3), border_mode='same', activation='relu'))\n    model.add(Conv2D(256, (3, 3), border_mode='same', activation='relu'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    #model.add(Dropout(0.3))\n    model.add(Conv2D(512, (3, 3), border_mode='same', activation='relu'))\n    model.add(Conv2D(512, (3, 3), border_mode='same', activation='relu'))\n#     model.add(Convolution2D(256, 3, 3, border_mode='same', activation='relu'))\n    model.add(MaxPooling2D(pool_size=(2, 2)))\n    #model.add(Dropout(0.3))\n    #model.add(Conv2D(256, (3, 3), border_mode='same', activation='relu'))\n    #model.add(Conv2D(256, (3, 3), border_mode='same', activation='relu'))\n     #model.add(Convolution2D(256, 3, 3, border_mode='same', activation='relu'))\n    #model.add(MaxPooling2D(pool_size=(2, 2)))\n    #model.add(Dropout(0.3))\n    #model.add(Convolution2D(256, 3, 3, border_mode='same', activation='relu'))\n    #model.add(Convolution2D(256, 3, 3, border_mode='same', activation='relu'))\n#     model.add(Convolution2D(256, 3, 3, border_mode='same', activation='relu'))\n    #model.add(MaxPooling2D(pool_size=(2, 2)))\n\n    model.add(Flatten())\n    model.add(Dense(4096, activation='relu'))\n    model.add(Dropout(0.5))\n    \n    model.add(Dense(2048, activation='relu'))\n    model.add(Dropout(0.5))\n\n    model.add(Dense(1))\n    model.add(Activation('sigmoid'))\n\n    model.compile(loss=objective, optimizer=optimizer, metrics=['accuracy'])\n    return model\n\n\nmodel = catdog()\nmodel.summary()","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"b7fbf156-2268-62ce-5fed-52a09af5e6f7","_uuid":"0aafe34f13539a436e6aadb18cbf528f212ab8dd"},"cell_type":"markdown","source":"### Train and Predict\n\nI'm using Keras's early stopping callback to end training when the validation loss stops improving, otherwise the model will overfit. I will also be tracking the loss history on each epoch to visualize the overfitting trend. \n\nNote: A slice of 1000 images was used to fit the model for CPU efficency. The model's perfrmance improves significantly when used on the entire dataset. "},{"metadata":{"_cell_guid":"ca613169-4b41-e9d5-27c7-6629f8e035c8","_uuid":"200386560dc8373517b1adfc01b402dd00a6ed6e","trusted":true},"cell_type":"code","source":"nb_epoch = 25\nbatch_size = 30\n\n## Callback for loss logging per epoch\nclass LossHistory(Callback):\n    def on_train_begin(self, logs={}):\n        self.losses = []\n        self.val_losses = []\n        \n    def on_epoch_end(self, batch, logs={}):\n        self.losses.append(logs.get('loss'))\n        self.val_losses.append(logs.get('val_loss'))\n\nearly_stopping = EarlyStopping(monitor='val_loss', patience=7, verbose=1, mode='auto')     \ncheck_point = ModelCheckpoint(filepath='my_model.h5', \n                               verbose=1, save_best_only=True, mode='auto')\n        \ndef run_catdog():\n    \n    history = LossHistory()\n    model.fit(train, labels, batch_size=batch_size, epochs=nb_epoch,\n              validation_split=0.25, verbose=1, shuffle=True, callbacks=[history, early_stopping, check_point])\n    \n\n    predictions = model.predict(test, verbose=0)\n    return predictions, history\n\npredictions, history = run_catdog()","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"6266d01c-9d4c-a1dc-8cb9-ce7b3107b536","_uuid":"bd4a46f2844724ef198cf8477b4ea6e01fb0ea36","trusted":true},"cell_type":"code","source":"loss = history.losses\nval_loss = history.val_losses\n\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.title('VGG-16 Loss Trend')\nplt.plot(loss, 'blue', label='Training Loss')\nplt.plot(val_loss, 'green', label='Validation Loss')\nplt.xticks(range(0,nb_epoch)[0::2])\nplt.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"d9382e7d-31db-0d75-6876-aaccb126e4f6","_uuid":"36b8fb5e9fa0999eec89515be5a83e79004ce1f0"},"cell_type":"markdown","source":"## How'd We Do?\n\nI'm pretty sure I can distinguish a cat from a dog 100% of the time, but how confident is the model?...\n\nTip: Run on the full dataset with a GPU for a LB logloss of ~0.4 and accuracy at approx 90%. "},{"metadata":{"_cell_guid":"c665d01b-a5a7-2f7e-e5d4-09e108920e40","_uuid":"3a11f4c68b16f9c6cdd10284b9481938914c8897","trusted":true},"cell_type":"code","source":"for i in range(0,10):\n    if predictions[i, 0] >= 0.5: \n        print('I am {:.2%} sure this is a Dog'.format(predictions[i][0]))\n    else: \n        print('I am {:.2%} sure this is a Cat'.format(1-predictions[i][0]))\n        \n    plt.imshow(test[i].T)\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"daef9d1da5f12756271cc4425d503caf37965cb8"},"cell_type":"code","source":"predictions\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"25497f1ccc74908ee08e599a1a5ca3ed6be70fbe"},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"_change_revision":0,"_is_fork":false,"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}