{"metadata": {"language_info": {"mimetype": "text/x-python", "name": "python", "codemirror_mode": {"version": 3, "name": "ipython"}, "nbconvert_exporter": "python", "version": "3.6.3", "pygments_lexer": "ipython3", "file_extension": ".py"}, "kernelspec": {"language": "python", "display_name": "Python 3", "name": "python3"}}, "nbformat_minor": 1, "cells": [{"source": ["#\n", "# Finetune the Inception V3 network on the CDiscount dataset.\n", "#\n", "# Taken from https://keras.io/applications/#usage-examples-for-image-classification-models"], "metadata": {"collapsed": true, "_uuid": "048aa22da72c9c668092459f8260a64b5eb091e7", "_cell_guid": "9e121e07-1ee7-4780-b6d2-5624db105456"}, "execution_count": null, "cell_type": "code", "outputs": []}, {"source": ["import os\n", "import pickle\n", "import itertools\n", "import io\n", "import time\n", "import bson\n", "import threading\n", "\n", "import pandas as pd\n", "from scipy.misc import imread\n", "import numpy as np\n", "from sklearn.preprocessing import LabelEncoder\n", "from keras.applications.inception_v3 import InceptionV3\n", "from keras.preprocessing import image\n", "from keras.models import Model\n", "from keras.layers import Dense, GlobalAveragePooling2D\n", "from keras import backend as K\n", "import keras"], "metadata": {"_uuid": "14f230e2206ad3ea7e3604d5b1389176366a40e2", "_cell_guid": "fe03a23f-e3cc-4afa-8cf9-5753064c55e4"}, "execution_count": null, "cell_type": "code", "outputs": []}, {"source": ["def create_model(num_classes=None):\n", "    # create the base pre-trained model\n", "    base_model = InceptionV3(weights='imagenet', include_top=False)\n", "    \n", "    # add a global spatial average pooling layer\n", "    x = base_model.output\n", "    x = GlobalAveragePooling2D()(x)\n", "    # let's add a fully-connected layer\n", "    x = Dense(4096, activation='relu')(x)\n", "    # and a logistic layer -- let's say we have 200 classes\n", "    predictions = Dense(num_classes, activation='softmax')(x)\n", "\n", "    # this is the model we will train\n", "    model = Model(inputs=base_model.input, outputs=predictions)\n", "\n", "    # first: train only the top layers (which were randomly initialized)\n", "    # i.e. freeze all convolutional InceptionV3 layers\n", "    for layer in base_model.layers:\n", "        layer.trainable = False\n", "\n", "    # compile the model (should be done *after* setting layers to non-trainable)\n", "    model.compile(optimizer='rmsprop', loss='sparse_categorical_crossentropy', metrics=['accuracy'])\n", "    \n", "    return model"], "metadata": {"collapsed": true, "_uuid": "8e31d211d2f890281137afe9c880246bb7dff329", "_cell_guid": "15ceb308-4366-43ea-925a-50ab899c1166"}, "execution_count": null, "cell_type": "code", "outputs": []}, {"source": ["def grouper(n, iterable):\n", "    '''\n", "    Given an iterable, it'll return size n chunks per iteration.\n", "    Handles the last chunk too.\n", "    '''\n", "    it = iter(iterable)\n", "    while True:\n", "        chunk = tuple(itertools.islice(it, n))\n", "        if not chunk:\n", "            return\n", "        yield chunk\n", "\n", "class threadsafe_iter:\n", "    \"\"\"\n", "    Takes an iterator/generator and makes it thread-safe by\n", "    serializing call to the `next` method of given iterator/generator.\n", "    \"\"\"\n", "    def __init__(self, it):\n", "        self.it = it\n", "        self.lock = threading.Lock()\n", "\n", "    def __iter__(self):\n", "        return self\n", "\n", "    def __next__(self):\n", "        with self.lock:\n", "            return self.it.__next__()\n", "\n", "def threadsafe_generator(f):\n", "    \"\"\"\n", "    A decorator that takes a generator function and makes it thread-safe.\n", "    \"\"\"\n", "    def g(*a, **kw):\n", "        return threadsafe_iter(f(*a, **kw))\n", "    return g\n", "\n", "@threadsafe_generator\n", "def get_features_label(documents, batch_size=32, return_labels=True):\n", "    '''\n", "    Given a document return X, y\n", "    \n", "    X is scaled to [0, 1] and consists of all images contained in document.\n", "    y is given an integer encoding.\n", "    '''\n", "    \n", "    \n", "    for batch in grouper(batch_size, documents): \n", "        images = []\n", "        labels = []\n", "\n", "        for document in batch:\n", "            category = document.get('category_id', '')\n", "            img = document.get('imgs')[0]\n", "            data = io.BytesIO(img.get('picture', None))\n", "            im = imread(data)\n", "\n", "            if category:    \n", "                label = labelencoder.transform([category])\n", "            else:\n", "                label = None\n", "\n", "            im = im.astype('float32') / 255.0\n", "\n", "            images.append(im)\n", "            labels.append(label)\n", "\n", "        if return_labels:\n", "            yield np.array(images), np.array(labels)\n", "        else:\n", "            yield np.array(images)"], "metadata": {"collapsed": true, "_uuid": "a1628af466356fe668ef32d54c89a0df95989e45", "_cell_guid": "4a0d2963-3895-4c0d-9f0f-cf34cb25202d"}, "execution_count": null, "cell_type": "code", "outputs": []}, {"source": ["if os.path.isfile('labelencoder.pkl'):\n", "    with open('labelencoder.pkl', 'rb') as f:\n", "        labelencoder = pickle.load(f)\n", "    categories = pd.read_csv('categories.csv')\n", "    \n", "else:\n", "    # Get the category ID for each document in the training set.\n", "    documents = bson.decode_file_iter(open('../input/train.bson', 'rb'))\n", "    categories = [(d['_id'], d['category_id']) for d in documents]\n", "    categories = pd.DataFrame(categories, columns=['id', 'cat'])\n", "\n", "    # Create a label encoder for all the labels found\n", "    labelencoder = LabelEncoder()\n", "    labelencoder.fit(categories.cat.unique().ravel())\n", "    \n", "    with open('labelencoder.pkl', 'wb') as f:\n", "        pickle.dump(labelencoder, f)\n", "        \n", "    categories.to_csv('categories.csv')"], "metadata": {"_uuid": "11d0313d1dd4bf0443da8691e954ae35221453e3", "_cell_guid": "0d4334f9-dade-43c6-8d5e-5165b132ba2d"}, "execution_count": null, "cell_type": "code", "outputs": []}, {"source": ["# load the previous model\n", "\n", "try:\n", "    inception = keras.models.load_model('inceptionv3-finetune.h5')\n", "except:\n", "    inception = create_model(num_classes=len(labelencoder.classes_))\n", "\n", "# So we can look at the progress on Tensorboard\n", "callback = keras.callbacks.TensorBoard(\n", "    log_dir='./logs/inception/2/{}'.format(time.time())\n", ")\n", "\n", "generator = get_features_label(bson.decode_file_iter(open('../input/train.bson', 'rb')))\n", "\n", "# docs says train for a few epocs (LOL!)\n", "# Each step is 32 images.\n", "\n", "# 200 epochs x  500 steps x 32 images -> 3 200 000 images / ~7M\n", "inception.fit_generator(\n", "    generator=generator,\n", "    epochs=320,\n", "    steps_per_epoch=500,\n", "    callbacks=[callback],\n", "    validation_data=generator,\n", "    validation_steps=50\n", ")\n", "\n", "inception.save('inceptionv3-finetune.h5')"], "metadata": {"scrolled": false, "_uuid": "f093c3966645c903bf8cf0d6f7db20eb30a01730", "_cell_guid": "8392290f-5db9-4d7f-a233-06c69c7656d5"}, "execution_count": null, "cell_type": "code", "outputs": []}, {"source": ["# we chose to train the top 2 inception blocks, i.e. we will freeze\n", "# the first 249 layers and unfreeze the rest:\n", "for layer in inception.layers[:249]:\n", "    layer.trainable = False\n", "for layer in inception.layers[249:]:\n", "    layer.trainable = True\n", "\n", "# we need to recompile the model for these modifications to take effect\n", "# we use SGD with a low learning rate\n", "from keras.optimizers import SGD\n", "inception.compile(optimizer=SGD(lr=0.00001, momentum=0.9),\n", "                            loss='sparse_categorical_crossentropy',\n", "                            metrics=['accuracy'])\n", "\n", "# we train our model again (this time fine-tuning the top 2 inception blocks\n", "# alongside the top Dense layers\n", "# So we can look at the progress on Tensorboard\n", "callback = keras.callbacks.TensorBoard(\n", "    log_dir='./logs/inception/{}'.format(time.time())\n", ")\n", "\n", "generator = get_features_label(bson.decode_file_iter(open('data/train.bson', 'rb')))\n", "\n", "# docs says train for a few epocs (LOL!)\n", "# Each step is 32 images.\n", "\n", "# 200 epochs x  steps x 32 images -> 320 000 images / ~7M\n", "inception.fit_generator(\n", "    generator=generator,\n", "    epochs=320,\n", "    steps_per_epoch=500,\n", "    callbacks=[callback],\n", "    validation_data=generator,\n", "    validation_steps=50\n", ")\n", "\n", "inception.save('inceptionv3-finetune-2.h5')"], "metadata": {"collapsed": true, "_uuid": "bd0275ddb057130bb6e914cf9c6025dabd48025b", "_cell_guid": "dec3b114-b26a-4a95-9cb9-adeca3306b07", "scrolled": false}, "execution_count": null, "cell_type": "code", "outputs": []}, {"source": ["%%time\n", "# create a submission\n", "\n", "generator = get_features_label(bson.decode_file_iter(open('data/test.bson', 'rb')), return_labels=False)\n", "\n", "predictions = []\n", "\n", "for i, batch in enumerate(generator):\n", "    output = inception.predict(batch)\n", "    labels = labelencoder.inverse_transform(output.argmax(axis=1))\n", "    predictions.extend(labels.tolist())\n", "    \n", "    if i and (i % 200 == 0):\n", "        print(\"{} images predicted.\".format(len(predictions)))"], "metadata": {"collapsed": true, "_uuid": "4aa5ae154bbab61267cf9c9b43bddfa081e07e42", "_cell_guid": "f59ea09d-90cc-42d3-9902-8708410231f1"}, "execution_count": null, "cell_type": "code", "outputs": []}, {"source": ["with open('predictions.pkl', 'wb') as pf:\n", "    pickle.dump(predictions, pf)\n", "\n", "submission = pd.read_csv('data/sample_submission.csv')\n", "submission.category_id = predictions\n", "submission.to_csv('submission.csv', index=False)"], "metadata": {"collapsed": true, "_uuid": "031e3cf0c814a3fa4de8f7afd90192235f8daf30", "_cell_guid": "802fc454-bb43-4920-8cef-d4ffae932d91", "scrolled": false}, "execution_count": null, "cell_type": "code", "outputs": []}, {"source": [], "metadata": {"collapsed": true, "_uuid": "4fba9aeb3b328c261d187dbd8d223f9c3fbea373", "_cell_guid": "6cf0c65b-47f0-4e45-a51c-3b3849a3fbf5"}, "execution_count": null, "cell_type": "code", "outputs": []}], "nbformat": 4}