{"cells":[{"metadata":{},"cell_type":"markdown","source":"# [<center>Dogs Vs Cats</center>](https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition)","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"* Public and private score : 0.04758\n* I've already been tried with resnet, vgg16 and inception. I'm gonna try something different with nasnet, inception resnet and exception. \n* This time my accuracy is almost 99%. Essential tips are included at the top of the code.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"* calculate the total time spent at the end of this notebook","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import time\nstart = time.time()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Import Packages","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nimport os\nfrom keras.layers import Dense, Flatten, Dropout, Lambda, Input, Concatenate, concatenate\nfrom keras.models import Model\nfrom keras.applications import *\nfrom keras.preprocessing.image import ImageDataGenerator, load_img, img_to_array\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom keras.callbacks import ModelCheckpoint\nfrom keras import regularizers","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# See which directories have you got","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"for dirname, _, filenames in os.walk('../input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Unzip the zip files","execution_count":null},{"metadata":{"trusted":true,"collapsed":true},"cell_type":"code","source":"!unzip ../input/dogs-vs-cats-redux-kernels-edition/train.zip -d train\n!unzip ../input/dogs-vs-cats-redux-kernels-edition/test.zip -d test","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Check out where my output files reside","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"print(os.listdir('../'))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Manually labelling 2 different datasets and save them into labels\n\nfile.split('.')[0] means - \n* the particular filename is a String separated by dots.\n* line.split(\".\")[0] returns the 1st item of the array. (the actual file looks like \"cat.100.jpg\")","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"I'm gonna label the data, so that in train test split I won't have to use stratification.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"filenames = os.listdir(\"../working/train/train\")\n\nlabels = []\nfor file in filenames:\n    category = file.split('.')[0]\n    if category == 'cat':\n        labels.append('cat')\n    else:\n        labels.append('dog')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* In case of train_test_split, allowed inputs are lists, numpy arrays, scipy-sparse matrices or pandas dataframes. I'm using pandas dataframes. \n* Our dependent variable will be label and filename will act as an independent variable.","execution_count":null},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"df = pd.DataFrame({\n    'filename': filenames,\n    'label': labels\n})","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df.head(10)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Make sure that the labels are proportionately equal before and after the train_test_split","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_class_counts(df):\n    grp = df.groupby(['label']).nunique()\n    return {key: grp[key] for key in list(grp.keys())}\n\ndef get_class_proportions(df):\n    class_counts = get_class_counts(df)\n    return {val[0]: round(val[1]/df.shape[0],4) for val in class_counts.items()}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Dataset class counts\", get_class_counts(df))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* We can see the classes are divided equally into 0.5.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Dataset class proportions\", get_class_proportions(df))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train Test Splitting","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"* Divide the labels according to \"label\" stratification.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df, validation_df = train_test_split(df, \n                                           test_size=0.1, \n                                           stratify=df['label'],\n                                           random_state = 42)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Confirm that the labels of both training and validation sets are equally divided.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"print(\"Train data class proportions : \", get_class_proportions(train_df))\nprint(\"Validation data class proportions : \", get_class_proportions(validation_df))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.head(10)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* We can see, there is no need of indexing, so I'm dropping it.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = train_df.reset_index(drop=True)\n\nvalidation_df = validation_df.reset_index(drop=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.head(10)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"batch_size = 64\ntrain_num = len(train_df)\nvalidation_num = len(validation_df)\n\nprint(\"The number of training set is {}\".format(train_num))\nprint(\"The number of validation set is {}\".format(validation_num))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Preprocessing\n\n1. Augmentation\n2. Flow_from_dataframe","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Specify DataFrame or Directory ","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"* Since we're gonna use two different types of multi input model with flow from directory, I'm using two generator.\n* In case of generator, \"yield\" is used instead of \"return\". Cz, we need to generate image on the fly, which iterates over the loop once.","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def two_image_generator(generator, df, directory, batch_size,\n                        x_col = 'filename', y_col = None, model = None, shuffle = False,\n                        img_size1 = (224, 224), img_size2 = (299,299)):\n    gen1 = generator.flow_from_dataframe(\n        df,\n        directory,\n        x_col = x_col,\n        y_col = y_col,\n        target_size = img_size1,\n        class_mode = model,\n        batch_size = batch_size,\n        shuffle = shuffle,\n        seed = 1)\n    gen2 = generator.flow_from_dataframe(\n        df,\n        directory,\n        x_col = x_col,\n        y_col = y_col,\n        target_size = img_size2,\n        class_mode = model,\n        batch_size = batch_size,\n        shuffle = shuffle,\n        seed = 1)\n    \n    while True:\n        X1i = gen1.next()\n        X2i = gen2.next()\n        \n        if y_col:\n            yield [X1i[0], X2i[0]], X1i[1]  #X1i[1] is the label\n        else:\n            yield [X1i, X2i]\n        ","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Test if the generator generates same images with two different sizes(225 & 300)","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"ex_df = pd.DataFrame()\nex_df['filename'] = filenames[:5]\nex_df['label'] = labels[:5]\nex_df.head()\n\ntrain_aug_datagen = ImageDataGenerator(\n    rotation_range = 20,\n    shear_range = 0.1,\n    zoom_range = 0.2,\n    width_shift_range = 0.1,\n    height_shift_range = 0.1,\n    horizontal_flip = True\n)\ne1 = two_image_generator(train_aug_datagen, ex_df, '../working/train/train/',\n                                      batch_size = 2, y_col = 'label',\n                                      model = 'binary', shuffle = True)\n\nfig = plt.figure(figsize = (10,10))\nbatches = 0\nrows = 5\ncols = 5\ni = 0\nj = 0\nindices_a = [1, 2, 3, 4, 5, 11, 12, 13, 14, 15]\nindices_b = [6, 7, 8, 9, 10, 16, 17, 18, 19, 20]\nfor [x_batch, x_batch2], y_batch in e1:\n    for image in x_batch:\n        fig.add_subplot(rows, cols, indices_a[i])\n        i += 1\n        plt.imshow(image.astype('uint8'))\n        \n    for image in x_batch2:\n        fig.add_subplot(rows, cols, indices_b[j])\n        j += 1\n        plt.imshow(image.astype('uint8'))\n    \n    batches += 1\n    if batches >= 6:\n        break\nplt.show()\n\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Add data augmentation","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"train_aug_datagen = ImageDataGenerator(\n    rotation_range = 20,\n    shear_range = 0.1,\n    zoom_range = 0.2,\n    width_shift_range = 0.1,\n    height_shift_range = 0.1,\n    horizontal_flip = True\n)\ntrain_generator = two_image_generator(train_aug_datagen, train_df, '../working/train/train/',\n                                      batch_size = batch_size, y_col = 'label',\n                                      model = 'binary', shuffle = True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* Adding augmented data will not improve the accuracy of the validation. Which is why, augmetation on validation dataset is kind of superfluous","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"validation_datagen = ImageDataGenerator()\n\nvalidation_generator = two_image_generator(validation_datagen, validation_df,\n                                           '../working/train/train/', batch_size = batch_size,\n                                           y_col = 'label',model = 'binary', shuffle = True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Modeling\n\n1. Create model architecture\n2. Compile\n3. callbacks\n4. fit_generator","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"def create_base_model(MODEL, img_size, lambda_fun = None):\n    inp = Input(shape = (img_size[0], img_size[1], 3))\n    x = inp\n    if lambda_fun:\n        x = Lambda(lambda_fun)(x)\n    \n    base_model = MODEL(input_tensor = x, weights = 'imagenet', include_top = False, pooling = 'avg')\n        \n    model = Model(inp, base_model.output)\n    return model","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> You can get the idea of different models from here : https://keras.io/api/applications/","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Model generation","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"#define vgg + resnet50 + densenet\nmodel1 = create_base_model(nasnet.NASNetLarge, (224, 224), nasnet.preprocess_input)\nmodel2 = create_base_model(inception_resnet_v2.InceptionResNetV2, (299, 299), inception_resnet_v2.preprocess_input)\nmodel3 = create_base_model(xception.Xception, (299, 299), xception.preprocess_input)\nmodel1.trainable = False\nmodel2.trainable = False\nmodel3.trainable = False\n\ninpA = Input(shape = (224, 224, 3))\ninpB = Input(shape = (299, 299, 3))\nout1 = model1(inpA)\nout2 = model2(inpA)\nout3 = model3(inpB)\n\nx = Concatenate()([out1, out2, out3])                \nx = Dropout(0.6)(x)\nx = Dense(1, activation='sigmoid')(x)\nmultiple_pretained_model = Model([inpA, inpB], x)\n\nmultiple_pretained_model.compile(loss = 'binary_crossentropy',\n                          optimizer = 'rmsprop',\n                          metrics = ['accuracy'])\n\nmultiple_pretained_model.summary()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* best weight will be stored in dogcat.weights.best.hdf5","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Callback","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"checkpointer = ModelCheckpoint(filepath='dogcat.weights.best.hdf5', verbose=1, \n                               save_best_only=True, save_weights_only=True)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Fit model","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\nmultiple_pretained_model.fit_generator(\n    train_generator,\n    epochs = 3,\n    steps_per_epoch = train_num // batch_size,\n    validation_data = validation_generator,\n    validation_steps = validation_num // batch_size,\n    verbose = 1,\n    callbacks = [checkpointer]\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"multiple_pretained_model.load_weights('dogcat.weights.best.hdf5')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Preprocessing test data","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"test_filenames = os.listdir(\"../working/test/test\")\ntest_df = pd.DataFrame({\n    'filename': test_filenames\n})\n\nnum_test = len(test_df)\n\ntest_datagen = ImageDataGenerator()\n\ntest_generator = two_image_generator(test_datagen, test_df, '../working/test/test/', batch_size = batch_size)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Prediction","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"prediction = multiple_pretained_model.predict_generator(test_generator, \n                                         steps=np.ceil(num_test/batch_size))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* values smaller than 0.005 become 0.005, and values larger than 0.995 become 0.995.","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"# Clipping predicted result","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"prediction = prediction.clip(min = 0.005, max = 0.995)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Submission","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"submission_df = pd.read_csv('../input/dogs-vs-cats-redux-kernels-edition/sample_submission.csv')\n\nfor i, fname in enumerate(test_filenames):\n    index = int(fname[fname.rfind('/')+1:fname.rfind('.')])\n    submission_df.at[index-1, 'label'] = prediction[i]\nsubmission_df.to_csv('Cats&DogsSubmission.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"## print run time\nend = time.time()\nprint(round((end-start),2), \"seconds\")","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}