{"metadata": {"language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "mimetype": "text/x-python", "version": "3.6.3", "nbconvert_exporter": "python", "name": "python", "file_extension": ".py", "pygments_lexer": "ipython3"}, "kernelspec": {"name": "python3", "display_name": "Python 3", "language": "python"}}, "nbformat": 4, "nbformat_minor": 1, "cells": [{"metadata": {"_uuid": "123fe7add5cf374838433d2a590e6274786e90a1", "_cell_guid": "f1f7c25c-bcbc-4821-bdba-6adc4fcf372c"}, "cell_type": "markdown", "source": ["# Kaggle Dog breed\n", "Classify dog breed in Kaggle competition"]}, {"outputs": [], "metadata": {"_uuid": "3f62c0bb82c59a0d01cfc524b5178c9a2ef2172d", "_cell_guid": "b3b3ac67-bb99-4b70-815b-5a18aa1653a3"}, "cell_type": "code", "execution_count": null, "source": ["!ls ../input/dog-breed-identification"]}, {"outputs": [], "metadata": {"_uuid": "6f15efbc97e36e0facc4086e2a3e032993f1f77a", "_cell_guid": "dd350147-ce99-44df-8611-d7ae6f263b60"}, "cell_type": "code", "execution_count": null, "source": ["import numpy as np\n", "\n", "original_train_dir = '../input/dog-breed-identification/train'\n", "original_test_dir = '../input/dog-breed-identification/test'\n", "train_labels = np.loadtxt('../input/dog-breed-identification/labels.csv', delimiter=',', dtype=str, skiprows=1)\n", "# Remove missing data, this image was missing on my dataset?\n", "# train_labels = train_labels[train_labels[:, 0] != '000bec180eb18c7604dcecc8fe0dba07']\n", "clazzes, counts = np.unique(train_labels[:, 1], return_counts=True)\n", "print(\"Some classes with count:\")\n", "print(np.asarray((clazzes, counts)).T[0:10])\n", "print(\"Number of class: %d\" % clazzes.size)"]}, {"metadata": {"_uuid": "4e18d30888434da18f4f6e75c0e4d68fbabee110", "_cell_guid": "be7fc614-b3d4-433d-a129-99bbe7bd82ed"}, "cell_type": "markdown", "source": ["## Copy data\n", "Keras has `ImageDataGenerator` with `flow_from_directory` as a source to make data augmentation. Below code will copy image to separate folder according to class name, which will be fed to ImageGenerator."]}, {"outputs": [], "metadata": {"_uuid": "54e4d6b679bf912e792a27f3ffec607844cb6712", "collapsed": true, "_cell_guid": "31de5330-ff1f-40f8-975f-c6d3255a0977"}, "cell_type": "code", "execution_count": null, "source": ["import os, shutil\n", "\n", "def mkdirIfNotExist(directory):\n", "    if not os.path.exists(directory):\n", "        os.mkdir(directory)\n", "    return directory\n", "\n", "base_dir = mkdirIfNotExist('./data_gen')\n", "train_dir = mkdirIfNotExist(os.path.join(base_dir, 'train'))\n", "validation_dir = mkdirIfNotExist(os.path.join(base_dir, 'validation'))\n", "test_dir = mkdirIfNotExist(os.path.join(base_dir, 'test'))\n", "for clazz in clazzes[:]:\n", "    mkdirIfNotExist(os.path.join(train_dir, clazz))\n", "    mkdirIfNotExist(os.path.join(validation_dir, clazz))"]}, {"outputs": [], "metadata": {"_uuid": "7c383a5f280796c482f9b40bf0d960e1a888c6a0", "_cell_guid": "75833aa2-5c38-42b9-b725-9b054e8c1499"}, "cell_type": "code", "execution_count": null, "source": ["def copyIfNotExist(fnames, src_dir, dst_dir):\n", "    nCopied = 0\n", "    for fname in fnames:\n", "        src = os.path.join(src_dir, fname)\n", "        dst = os.path.join(dst_dir, fname)\n", "        if not os.path.exists(dst):\n", "            shutil.copyfile(src, dst)\n", "            nCopied += 1\n", "    if nCopied > 0:\n", "        print(\"Copied %d to %s\" % (nCopied, dst_dir))\n", "\n", "# This will split available labeled data to train-validation sets\n", "train_ratio = 0.7\n", "for clazz in clazzes[:]:\n", "    fnames = train_labels[train_labels[:, 1] == clazz][:,0]\n", "    fnames = ['{}.jpg'.format(name) for name in fnames]\n", "    idx = int(len(fnames)*(1-train_ratio))\n", "    val_fnames = fnames[:idx]\n", "    train_fnames = fnames[idx:]\n", "    train_class_dir = os.path.join(train_dir, clazz)\n", "    validation_class_dir = os.path.join(validation_dir, clazz)\n", "    copyIfNotExist(train_fnames, original_train_dir, train_class_dir)\n", "    copyIfNotExist(val_fnames, original_train_dir, validation_class_dir)"]}, {"metadata": {"_uuid": "42ea25dac2ddfb1c88210e050558580b5f39de87", "_cell_guid": "1853c311-7b71-4d2b-87dd-52999fb731e5"}, "cell_type": "markdown", "source": ["## Data augmentation\n", "I found out that using input image size as 299x299 is important for using pre-trained model with Xception. I tried with lower rescale size (249x249) and data is kind of bottleneck in 75% of accuracy. 299x299 give accuracy about 82%"]}, {"outputs": [], "metadata": {"_uuid": "c7f21db8612539a20fe3f81a9106d8a35add9b34", "_cell_guid": "02a8bb8d-b14c-4a4c-a365-c76f615f2626"}, "cell_type": "code", "execution_count": null, "source": ["from keras.preprocessing.image import ImageDataGenerator\n", "img_width ,img_height = 299, 299\n", "batch_size = 16\n", "\n", "train_datagen = ImageDataGenerator(\n", "    rescale=1./255,\n", "    rotation_range=40,\n", "    width_shift_range=0.2,\n", "    height_shift_range=0.2,\n", "    horizontal_flip=True,\n", "    shear_range=0.1,\n", "    zoom_range=0.1\n", ")\n", "test_datagen = ImageDataGenerator(rescale=1./255)\n", "\n", "train_generator = train_datagen.flow_from_directory(\n", "        train_dir,\n", "        target_size=(img_width, img_height),\n", "        batch_size=batch_size,\n", "        class_mode='categorical')\n", "total_train_image_count = train_generator.samples\n", "class_count = train_generator.num_class\n", "\n", "validation_generator = test_datagen.flow_from_directory(\n", "        validation_dir,\n", "        target_size=(img_width, img_height),\n", "        batch_size=batch_size,\n", "        class_mode='categorical',\n", "        shuffle=False)\n", "total_val_image_count = train_generator.samples"]}, {"metadata": {"_uuid": "506797efb59be129479bb24cc2ba58b95df6f9a9"}, "cell_type": "markdown", "source": ["Display some images after doing augmentation"]}, {"outputs": [], "metadata": {"_uuid": "7266dae38b4a0ed6567b183e050e22ffee1d289f", "_cell_guid": "735c7b55-d82c-4234-b19c-87259b96af4d"}, "cell_type": "code", "execution_count": null, "source": ["from keras.preprocessing import image\n", "import matplotlib.pyplot as plt\n", "\n", "train_first_dir = os.path.join(train_dir, clazzes[0])\n", "fnames = [os.path.join(train_first_dir, fname) for fname in os.listdir(train_first_dir)]\n", "\n", "img_path = fnames[3]\n", "img = image.load_img(img_path, target_size=(img_width, img_height))\n", "x = image.img_to_array(img)\n", "x = x.reshape((1,) + x.shape)\n", "\n", "i = 0\n", "for batch in train_datagen.flow(x, batch_size=1):\n", "    plt.figure(i)\n", "    imgplot = plt.imshow(image.array_to_img(batch[0]))\n", "    i += 1\n", "    if i % 4 == 0:\n", "        break\n", "\n", "plt.show()"]}, {"metadata": {"_uuid": "eb65e4eb0b7d00b66153bf73a725206d62d24793", "_cell_guid": "292311c7-e4a2-4d95-b816-cc4becd0de50"}, "cell_type": "markdown", "source": ["## Extract feature with pretrained model\n", "Kaggle doesn't allow to download model from outside. I copied Xception model as dataset and copy to `.keras/models`, where Keras can find and use it."]}, {"outputs": [], "metadata": {"_uuid": "0759dd1dac24bfa27520c3fff6c9459a13e4fb6c", "collapsed": true, "_cell_guid": "57aa2bc8-c341-4f46-8f18-50402d36b6df"}, "cell_type": "code", "execution_count": null, "source": ["cache_dir = os.path.expanduser(os.path.join('~', '.keras'))\n", "if not os.path.exists(cache_dir):\n", "    os.mkdir(cache_dir)\n", "models_dir = os.path.join(cache_dir, 'models')\n", "if not os.path.exists(models_dir):\n", "    os.mkdir(models_dir)"]}, {"outputs": [], "metadata": {"_uuid": "a6d9b7eb60d275ba87711ca1e4db3145674ae097", "collapsed": true, "_cell_guid": "4f4a6461-7eab-46bd-8b70-a5452a153364"}, "cell_type": "code", "execution_count": null, "source": ["!cp ../input/keras-pretrained-models/* ~/.keras/models/"]}, {"outputs": [], "metadata": {"_uuid": "524446ce79813f659be25c8156daaad1151e78e7", "_cell_guid": "73a49191-6a4f-43d1-81ee-eb28fba5b09c"}, "cell_type": "code", "execution_count": null, "source": ["!ls ~/.keras/models"]}, {"outputs": [], "metadata": {"_uuid": "73d6887805a9891a4741f7238c24c9589663acd4", "collapsed": true, "_cell_guid": "49cc9f5c-a443-4ffa-9ab9-e9f0eaecf2b2"}, "cell_type": "code", "execution_count": null, "source": ["from keras.applications.xception import Xception\n", "\n", "conv_base = Xception(weights='imagenet',\n", "                     include_top=False,\n", "                     input_shape=(img_width, img_height, 3))\n", "conv_base.trainable = False"]}, {"metadata": {"_uuid": "d982c13fbf39fe34d713d6a7e5fc256a55df85a9", "_cell_guid": "bd2851e9-61dd-402a-8cd3-708553d19f4c"}, "cell_type": "markdown", "source": ["## Define Neural Net\n", "Define neural net with customized last layer."]}, {"outputs": [], "metadata": {"_uuid": "b330c325cd44c80542f1470a6172fc832593ec3e", "_cell_guid": "61ae534e-9ccc-4cc5-b4b7-0e9f642465b4"}, "cell_type": "code", "execution_count": null, "source": ["from keras import layers, models, regularizers, optimizers\n", "from keras.models import Sequential,  Model\n", "from keras.layers import Flatten, Dense, Dropout\n", "\n", "model = models.Sequential()\n", "model.add(conv_base)\n", "model.add(layers.Flatten())\n", "model.add(layers.Dense(512, activation='relu'))\n", "model.add(layers.Dense(class_count, activation='sigmoid'))\n", "\n", "model.compile(loss='categorical_crossentropy',\n", "              optimizer=optimizers.SGD(lr=1e-4, momentum=0.90),\n", "              metrics=['acc'])\n", "model.summary()"]}, {"metadata": {"_uuid": "794c5bc428b0edbfb8d77a7d0ace6a0f1c600805", "_cell_guid": "4ee40583-bf87-4056-b83a-334724a7e286"}, "cell_type": "markdown", "source": ["## Train model\n", "Only run with limit data due to resource constraint in Kaggle server."]}, {"outputs": [], "metadata": {"_uuid": "fbb922d0898d5dd12d8e4e79d37abcbac38eb0a1", "_cell_guid": "ce54b26c-312f-496c-a348-3a156a8c0bfc", "scrolled": true}, "cell_type": "code", "execution_count": null, "source": ["from time import strftime\n", "\n", "history = model.fit_generator(\n", "      train_generator,\n", "##       steps_per_epoch=int(total_train_image_count / batch_size),\n", "      steps_per_epoch=1,\n", "      epochs=1,\n", "      validation_data=validation_generator,\n", "##      validation_steps=int(total_val_image_count / batch_size)\n", "      validation_steps=1\n", ")\n", "\n", "# time_str = strftime(\"%Y%m%d_%H%M%S\")\n", "# model.save('dog_breed_pretrain_xception_299_{}.h5py'.format(time_str))"]}, {"metadata": {"_uuid": "59cb1d985a7db7d49488461ba0302320ca5e0116", "_cell_guid": "2e30bb18-f6ca-44cb-84ff-c5ea0b40d22f"}, "cell_type": "markdown", "source": ["## Evaluation"]}, {"outputs": [], "metadata": {"_uuid": "945166b31c7d2519c197659ee7a759daed98d48a", "_cell_guid": "015b7b2d-3d02-4933-bc09-0fe8d4d248c0"}, "cell_type": "code", "execution_count": null, "source": ["import matplotlib.pyplot as plt\n", "\n", "acc = history.history['acc']\n", "val_acc = history.history['val_acc']\n", "loss = history.history['loss']\n", "val_loss = history.history['val_loss']\n", "\n", "epochs = range(len(acc))\n", "\n", "plt.plot(epochs, acc, 'bo')\n", "plt.plot(epochs, val_acc, 'b')\n", "plt.title('Training and validation accuracy')\n", "\n", "plt.figure()\n", "\n", "plt.plot(epochs, loss, 'bo')\n", "plt.plot(epochs, val_loss, 'b')\n", "plt.title('Training and validation loss')\n", "\n", "plt.show()"]}, {"metadata": {"_uuid": "6fac506a53c09abdcc0c5de3224bb093167cecd1"}, "cell_type": "markdown", "source": ["## Make prediction\n", "Make prediction and create submit file. But it is slow on Kaggle server so I disabled them."]}, {"outputs": [], "metadata": {"_uuid": "b965aa24e979b16a6bbc65e32a0aca3b96238905", "_cell_guid": "1ce13473-e6b8-45a3-9985-c0f46e1f4a70"}, "cell_type": "code", "execution_count": null, "source": ["from keras.preprocessing import image\n", "import numpy as np\n", "\n", "def load_test_image(fpath):\n", "    img = image.load_img(fpath, target_size=(img_width, img_height))\n", "    x = image.img_to_array(img)\n", "    return x\n", "\n", "test_labels = np.loadtxt('../input/dog-breed-identification/sample_submission.csv', delimiter=',', dtype=str, skiprows=1)\n", "test_images = []\n", "test_names = test_labels[:,0]\n", "# Slow on Kaggle server\n", "#for test_name in test_names:\n", "#    fname = '{}.jpg'.format(test_name)\n", "#    data = load_test_image(os.path.join(original_test_dir, fname))\n", "#    test_images.append(data)\n", "\n", "test_images = np.asarray(test_images)\n", "test_images = test_images.astype('float32')\n", "test_images /= 255\n", "print(test_images.shape)"]}, {"outputs": [], "metadata": {"_uuid": "95ed070f69e17aecbcbc9dd74b8889342e3bc1d9", "collapsed": true, "_cell_guid": "cf4b2495-1bc5-4be9-904d-280c601abba7"}, "cell_type": "code", "execution_count": null, "source": ["# Slow on Kaggle server\n", "# predictions = model.predict(test_images, verbose=1)"]}, {"metadata": {"_uuid": "3add8fa4e71f7b37cf4d023f3b8978122bafd6a3"}, "cell_type": "markdown", "source": ["## Prepare submit data"]}, {"outputs": [], "metadata": {"_uuid": "0c4a51faacb4e4b6668aec7753f4ac2aff47dd7d", "_cell_guid": "d8eedf99-e407-4523-8c99-73613d4c787e"}, "cell_type": "code", "execution_count": null, "source": ["import pandas as pd\n", "class_indices = sorted([ [k,v] for k, v in train_generator.class_indices.items() ], key=lambda c : c[1])\n", "columns = [b[0] for b in class_indices]\n", "# No prediction, no\n", "# df = pd.DataFrame(predictions,columns=columns)\n", "# df = df.assign(id = test_names)\n", "# print(df.head())\n", "\n", "# df.to_csv(\"submit.csv\", index=False)"]}, {"outputs": [], "metadata": {"_uuid": "109e0ec4817c8f6f7f33eb49bf42c5510f9937b9", "collapsed": true, "_cell_guid": "a9901b62-da5f-4699-8470-891c15ee6c7f"}, "cell_type": "code", "execution_count": null, "source": []}]}