{"cells":[{"metadata":{"_uuid":"43a300127cae0aeadf4e02060c07eab10f5ae823"},"cell_type":"markdown","source":"# Localization context with Keras\n\nIn the discussions about this competition I noticed some remarks that the country information does not increase the LB. I decided to go a little other direction and to wrap *countries* into a *continent* feature and tried to compare the approaches. This notebook covers:\n* creating train/valid/test generators based on a list of sub generators for each class\n* option of adding context data to custom CNN model: country or continent of competitor\n* little insight into localization contexted images\n* defining a proper model, depending on the selected context mode\n* models training and comparison\n* making predictions\n\nAt first, import necessary libs, define settings. Note that **additional package _(pycountry-convert)_** was installed."},{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_kg_hide-output":true,"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"import os\nimport cv2\nimport json\nimport time\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\nfrom enum import Enum\nimport matplotlib\n%matplotlib inline\nimport matplotlib.pyplot as plt\nfrom sklearn.preprocessing import LabelEncoder, OneHotEncoder\n\n!pip install pycountry-convert\nimport pycountry_convert\n\nimport keras\nimport tensorflow as tf\nfrom keras.metrics import top_k_categorical_accuracy\nfrom keras.backend.tensorflow_backend import set_session\n\nconfig = tf.ConfigProto()\nconfig.gpu_options.allow_growth = True\nconfig.log_device_placement = True \nsess = tf.Session(config=config)\nset_session(sess) ","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"252200efbe7d07385ac5814ca59f5861d39dc20b"},"cell_type":"markdown","source":"## Settings, statistics, helpers\nInitial training settings. Take care of *num_classes x (items_in_class+1) > batch_size*."},{"metadata":{"_uuid":"96f16821ff4705379599873a90b144620df8f99a","trusted":true},"cell_type":"code","source":"num_classes = 340 #30 will be set later\nitems_in_class = 75\nbatch_size = 2048\ntraining_percent = 0.4 # how much data to take (40%)\nvalid_percent = 0.05 # size of the validation set (5%)\nsize = 32 # size of image: 32x32px\n\nmy_class_path = \"../input/train_simplified/\"\nclass_paths = os.listdir(\"../input/train_simplified/\")\ncat_names = [item[:-4] for item in class_paths] # take file names, remove '.csv' extension\ncat_names.sort(key=lambda x: str.lower(x)) # sort names of classes regardless capital letters\n\nUNKNOWN_COUNTRY = 'YYY'\nUNKNOWN_CONTINENT = 'XXX'\n\ncntntns = ['AF', 'AS', 'EU', 'NA', 'OC', 'SA']+[UNKNOWN_CONTINENT] # list of continents\n\n# mode indicating whether to consider localization context\nclass ContextMode(Enum):\n    no_context = 1\n    country_context = 2\n    continent_context = 3","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"632a73fed610db72e374f65796189c9c71add67d"},"cell_type":"markdown","source":"This section delievers some statistics of the classes files and extracts all of the countries present in the available training data set."},{"metadata":{"_uuid":"1f267fee1557d27c121e4a13e0f2f2c0d1602570","trusted":true},"cell_type":"code","source":"data_countries_codes = []\nfile_lengths = []\nrecog_stat_list = []\n\nfor _name in tqdm(class_paths[0:num_classes]):\n    # use keep_default_na=False to prevent pandas parsing country code 'NA' as nan\n    df = pd.read_csv(my_class_path+_name, keep_default_na=False)\n    file_lengths.append(len(df)-1) # number of samples in file (header not counted)\n    \n    recog_stat = df['recognized'].value_counts()\n    recog_stat_list.append(100.0*recog_stat[1]/(recog_stat[0]+recog_stat[1])) # % of recognized images in class\n    \n    ccode_stat = df['countrycode'].unique()    \n    set1 = set(data_countries_codes)\n    set2 = set(ccode_stat)\n    any_new = set2-set1\n    data_countries_codes += list(any_new) # list of unique country two letter codes               \n    \n# set size of validation set for each class\nvalid_lengths=[int(leng*valid_percent) for leng in file_lengths]\n    \nprint('Average file length: {:.0f}+-{:.0f} lines\\nRecognized images: {:.0f}+-{:.0f}%\\nUnique countries in data: {}'.format(np.mean(file_lengths), np.std(file_lengths), np.mean(recog_stat_list), np.std(recog_stat_list), len(data_countries_codes)))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"78daf4e6f3911305146ee3fb76a61993caf72153","_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"myhist=plt.hist(file_lengths, bins=50)\nmyhist=plt.ylabel('number of classes[n]')\nmyhist=plt.xlabel('items in class [n]')","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"06c7cf1d878c9e2aa9d36a082b0fa2bef7b365f2"},"cell_type":"markdown","source":"Using **pycountry-convert** we can associate two-letter country names encoded in **iso-3166-1** standard with appropriate continent. Following lines should help us catching the exceptions (codes that do not match with the standard). Note that here I make a union of iso-3166-1 codes with codes from the training data but one can stick to the selected set only."},{"metadata":{"trusted":true,"_uuid":"fae327ca8a10d99d5de270539911a4a1abcd8c7c"},"cell_type":"code","source":"valid_countries_dict = pycountry_convert.map_countries(cn_name_format=\"default\")\nvalid_country_codes = list(set([value['alpha_2'] for key, value in valid_countries_dict.items()]))\nall_country_codes= set(valid_country_codes) | set(data_countries_codes) | set([UNKNOWN_COUNTRY])","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"bb39b813216b07676c97603f8064914c588599aa"},"cell_type":"markdown","source":"If received country code is not in our *all_country_codes* set let's assign it to UNKNOWN_COUNTRY. Also set the continent as unknown for unexpected countries (which can come with the testing set)."},{"metadata":{"trusted":true,"_uuid":"6927f3854c3b153acf4461caa3653b44e1a12330"},"cell_type":"code","source":"def ValidateCountry(country_code):    \n    return country_code if country_code in all_country_codes else UNKNOWN_COUNTRY\n\ndef ValidateContinent(country_code):\n    try:\n        return pycountry_convert.country_alpha2_to_continent_code(country_code)\n    except:\n        return UNKNOWN_CONTINENT","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d36c0268376853f485ca018a26cc55d693cf82b8"},"cell_type":"markdown","source":"And let's test them:"},{"metadata":{"_uuid":"1e600086b8ee66ee221ad7a8c6764650806de9ec","trusted":true},"cell_type":"code","source":"data_countries_codes = [ValidateCountry(_cntry) for _cntry in data_countries_codes]\ncont_codes = [ValidateContinent(_cntry) for _cntry in data_countries_codes]\nprint(\"First 10 countries:    {}\".format(data_countries_codes[0:10]))\nprint(\"Associated continents: {}\\n\".format(cont_codes[0:10]))\nprint(\"Unique continents:     {}\".format(set(cont_codes))) # has to match defined cntntns list","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"fbad3e1e1d4d67c8aa449b1e37ff79b372f5ef3a"},"cell_type":"markdown","source":"Function **draw_cv2**, which I have first spotted in [@beluga](https://www.kaggle.com/gaborfodor) kernels as [here [1]](https://www.kaggle.com/gaborfodor/black-white-cnn-lb-0-77) or [here [2]](https://www.kaggle.com/gaborfodor/greyscale-mobilenet-animals) (in the second example each stroke color gets lighter). It is very nice, however it is the main bottleneck in terms of execution time within my solution (at least in the way I call it from the generator). Parallelization tries with splitting the em.drawings.values into few tables, each given to other process in a pool did not help me so far."},{"metadata":{"_uuid":"1749d9f5cc04f31354089324b5883ba5730b06c3","trusted":true},"cell_type":"code","source":"#---drawing images: ref [1,2]--------------------------------------\ndef draw_cv2(raw_strokes):\n    size = 32\n    lw = 6\n    BASE_SIZE = 256\n    img = np.zeros((BASE_SIZE, BASE_SIZE), np.uint8)    \n    for stroke_no, stroke in enumerate(raw_strokes):\n        line_intensity = 255 - min(stroke_no, 10) * 10\n        for i in range(len(stroke[0]) - 1):\n            _ = cv2.line(img, (stroke[0][i], stroke[1][i]), (stroke[0][i + 1], stroke[1][i + 1]), line_intensity, lw)            \n    if size != BASE_SIZE:\n        return cv2.resize(img, (size, size))\n    return img","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"494f575b6b92d6ffd872c8bb07d83cfe6f53ff6d"},"cell_type":"markdown","source":"### Generators\nThe following cell defines a sub generator, dedicated for just one file/class. When running the notebook  for all 340 classes, 340 sub generators for training and 340 for validation will be produced."},{"metadata":{"_uuid":"b3f19ec137146c744223d47b35979f3c1dc7d5c4","trusted":true},"cell_type":"code","source":"num_classes = 30\n\ndef items_gen(_id, isTraining):\n    _path = my_class_path + cat_names[_id] + '.csv'\n    \n    if isTraining: # skip the first valid_percent lines of the file\n        start_index, end_index = 1, valid_lengths[_id]        \n        if training_percent<1: # or select only last training_ratio lines\n            start_index, end_index = 1, int(file_lengths[_id]*(1-training_percent))                    \n    else: # validation: skip the last 1-valid_percent lines of the file\n        start_index, end_index = valid_lengths[_id], file_lengths[_id]\n    \n    while True:\n        # skiprows-> generator will chunk within the specified range, depending on its type [training/validation]\n        for _chunk in pd.read_csv(_path, chunksize=items_in_class +1, usecols=['drawing', 'recognized', 'word', 'countrycode'],skiprows=range(start_index, end_index), keep_default_na=False):\n            yield _chunk","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"ff9d167cc4ac9846673f8bb807b2b7812c0bd094"},"cell_type":"markdown","source":"Let's create a list of sub generators for training and validation. It is time consuming to call 340 generators - on my laptop (i6700HQ) it takes usually up to 4s to evaluate *[next(gen_list_TRAIN[id]) for id in range(num_classes)]* (or 0.8s using Pools) but on Kaggle server it is only around 0.6s. It is definately a part to replace/improve. However it is quite flexible in terms of removing/adding classes (separte files) to analysis and is at least good for prototyping."},{"metadata":{"_uuid":"d60c8207e0baafe4485597579bf2acd0b5326c90","trusted":true},"cell_type":"code","source":"gen_list_TRAIN = [items_gen(i, isTraining=True ) for i in range(num_classes)]\ngen_list_VALID = [items_gen(i, isTraining=False) for i in range(num_classes)]","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"7f3d6b7aaf3e78c1beb644220c631c7d5dfc7933"},"cell_type":"markdown","source":"For the sake of submission, why not to define another generator, so we won't have to repeat parts of the code."},{"metadata":{"trusted":true,"_uuid":"a45d2c29b46d3b7f755881208fcc7a7720abd1e2"},"cell_type":"code","source":"def test_gen():\n    _path = '../input/test_simplified.csv'        \n    while True:        \n        for _chunk in pd.read_csv(_path, chunksize=batch_size, usecols=['drawing','countrycode'], keep_default_na=False):\n            yield _chunk            \n\ngen_TEST = test_gen()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"bc0c14f94ef8e12d827d019aea6ab99d35e2fc15"},"cell_type":"markdown","source":"Encoders for labels and context information. Since the country/continent feature is more qualitative rather than quantitative it should be represented as one-hot vector (so it will require *n* number of additional neurons, where *n* is the number of countries/continents)."},{"metadata":{"_uuid":"5a2ae872cc242ba991e449b99c4229219fa29c03","_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"labels_encoder = LabelEncoder()\nccodes_encoder = LabelEncoder()\ncntnts_encoder = LabelEncoder()\nlabels_encoder.fit(cat_names[0:num_classes])\nccodes_encoder.fit(list(all_country_codes))\ncntnts_encoder.fit(cntntns)\n#------------------------------------------\nlabels_oneh = OneHotEncoder().fit(np.arange(num_classes).reshape(-1,1))\nccodes_oneh = OneHotEncoder().fit(np.arange(len(list(all_country_codes))).reshape(-1,1))\ncntnts_oneh = OneHotEncoder().fit(np.arange(len(cntntns)).reshape(-1,1))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"793f00d6f82bc5d6d96893633afc2882b83e4637"},"cell_type":"markdown","source":"Main data generator. Allows to be set as training/validation/testing data generator. Also yields input data with or without context, depending on the selected mode. "},{"metadata":{"_uuid":"d0379a608449d182036b8846c9cbf738be819335","trusted":true},"cell_type":"code","source":"class GeneratorMode(Enum):\n    training = 1\n    validation = 2\n    testing = 3\n    \ndef main_generator(whatGeneratorMode, whatContextMode=ContextMode.no_context):\n    while True:\n        \n        #---getting and simple filtering of data--------------------------\n        if whatGeneratorMode==GeneratorMode.training:\n            results = [next(gen_list_TRAIN[id]) for id in range(num_classes)]                \n            results = pd.concat(results)\n            results = results[results.recognized == True]\n            \n        elif whatGeneratorMode==GeneratorMode.validation:\n            results = [next(gen_list_VALID[id]) for id in range(num_classes)]                \n            results = pd.concat(results)\n            results = results[results.recognized == True]\n            \n        elif whatGeneratorMode==GeneratorMode.testing:\n            results = next(gen_TEST)                        \n                                 \n        #---shuffling and batch size setting------------------------------\n        #-(depending on the number of not recognized samples, we need to--\n        #-add or subtract samples from the concatenated dataframe)--------\n        _itms = len(results)        \n        results = results.sample(frac=1, random_state=2018).reset_index(drop=True)\n        if(_itms>batch_size):\n            results=results[0:batch_size]\n        elif(_itms<batch_size):\n            results = pd.concat([results, results[0:batch_size-_itms]])\n                        \n        #---drawing images: ref [1,2]--------------------------------------\n        results['drawing'] = results['drawing'].apply(json.loads)        \n        x = np.zeros((batch_size, size, size))\n        for i, raw_strokes in enumerate(results.drawing.values):\n            x[i] = draw_cv2(raw_strokes)\n    \n        x = x / 255.\n        x = x.reshape((batch_size, size, size, 1)).astype(np.float32)        \n        \n        if whatGeneratorMode!=GeneratorMode.testing:                            \n            #---converting labels to 1-hot---------------------------------\n            _all_labels = results['word'].values\n            _all_labels = labels_encoder.transform(_all_labels)        \n            y = labels_oneh.transform(_all_labels.reshape(-1,1)).toarray()\n                \n        #---output: - yielded 'x' depends on the selected context mode----\n        if whatContextMode==ContextMode.no_context:\n            if whatGeneratorMode==GeneratorMode.testing:                            \n                yield x\n            else:\n                yield x, y\n        elif whatContextMode==ContextMode.country_context:\n            _countries = results['countrycode'].apply(ValidateCountry).values\n            _countries = ccodes_encoder.transform(_countries)        \n            xcc = ccodes_oneh.transform(_countries.reshape(-1,1)).toarray()                        \n            \n            if whatGeneratorMode==GeneratorMode.testing:                            \n                yield [x, xcc]\n            else:\n                yield [x, xcc], y            \n        elif whatContextMode==ContextMode.continent_context:\n            _countries = results['countrycode'].apply(ValidateCountry).values\n            _continents = [ValidateContinent(_cntry) for _cntry in _countries]                    \n            _continents = cntnts_encoder.transform(_continents)        \n            xct = cntnts_oneh.transform(_continents.reshape(-1,1)).toarray()            \n            \n            if whatGeneratorMode==GeneratorMode.testing:                \n                yield [x, xct]\n            else:                \n                yield [x, xct], y   ","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"142749a67055d2d32640a04a4210e0af169e95ee"},"cell_type":"markdown","source":"### Testing generators / data insight\nLet's test the generator and take an insight into the data received:"},{"metadata":{"_uuid":"bb81230946b2b8650a0e4f2b5cfa75e876ebbb05","trusted":true},"cell_type":"code","source":"ourContextMode=ContextMode.continent_context\ntrain_gen=main_generator(whatGeneratorMode=GeneratorMode.training, whatContextMode=ourContextMode)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"83e12f01d64ac5fc960d7c01430795f83cd3f8b9","trusted":true},"cell_type":"code","source":"xx,yy=next(train_gen)\n#print('x shape: {}'.format(np.shape(xx)))\nprint('x[0] shape: {}'.format(np.shape(xx[0])))\nprint('x[1] shape: {}'.format(np.shape(xx[1])))\nprint('y shape:    {}'.format(np.shape(yy)))","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d05bbf7261814500ba22ded9ae96dfcb4382542e","trusted":true},"cell_type":"code","source":"def insight_function(xx, yy, whatContextMode, selected_word='bear'):\n    if whatContextMode==ContextMode.no_context:\n        print('No context')\n        return\n    \n    context = cntntns if whatContextMode==whatContextMode.continent_context else list(all_country_codes)\n\n    certain_image_list=[ind for ind in range(batch_size) if cat_names[np.argmax(yy[ind])] == selected_word]\n    certain_image_context=[context[np.argmax(xx[1][_ind])] for _ind in certain_image_list]\n    proper_order=sorted(range(len(certain_image_context)), key=lambda c: certain_image_context[c])\n\n    print(\"{}:\".format(selected_word))    \n    fig, axs = plt.subplots(nrows=9, ncols=7, sharex=True, sharey=True, figsize=(12, 12))\n    for i,index in enumerate(proper_order):\n        ax = axs[i//9, i%7]\n        selected_example=certain_image_list[index]\n        ax.imshow(xx[0][selected_example,:,:,0], cmap=plt.cm.gray_r)\n        ax.set_title(context[np.argmax(xx[1][selected_example])]) \n        ax.axis('off')\n        if i==len(proper_order) or i==9*7:\n            break\n    plt.tight_layout()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"b58fb7cda7f523e3748fed8f7a05a7193426a6d3"},"cell_type":"markdown","source":"This function returns us images from the received batch labeled as 'bear' by default. It presents them sorted in terms of competitor continent/country to give us tiny insight, if there are any patterns which repeat within given area. But you probably need much more images for inspection, hence playing with the code is advised."},{"metadata":{"_uuid":"ca50c9fb3dc0d36945bf60f706f8d2702809fc13","scrolled":false,"trusted":true},"cell_type":"code","source":"insight_function(xx, yy, ourContextMode)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"1b3ab34ac7e7d55da4a8d1d10b07b5f6854976f1"},"cell_type":"markdown","source":"### CNN Model \nHelper function to evaluate top3 accuracy."},{"metadata":{"_uuid":"1dd455da3f6432b93281b57378769a3de3dd46d8","trusted":true},"cell_type":"code","source":"def in_top_3(y_true, y_pred):\n    return top_k_categorical_accuracy(y_true, y_pred, k=3)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"63aa6a061351a6b133c5824e9ec55e4da340be51"},"cell_type":"markdown","source":"The create_model function with custom CNN allows to add extra input for the context. It can be concatenated with flatten result of convolutions, e.g. as in [[3]](http://vision.stanford.edu/pdf/tang2015iccv.pdf). How to do it in Keras I learned from [@Vadim Borisov](https://www.kaggle.com/hireme) in his [kernel [4]](https://www.kaggle.com/hireme/two-inputs-neural-network-using-keras)."},{"metadata":{"_uuid":"7a1db76c3c2149f9caa4530bcdc17f4aac0a0696","trusted":true},"cell_type":"code","source":"def create_model(whatContextMode=ContextMode.no_context, denseLayerNeurons=512, show=False):\n    \n    from keras.models import Model\n    from keras.layers import Conv2D, MaxPooling2D, Input, concatenate\n    from keras.layers import Dense, Dropout, Flatten, BatchNormalization\n    from keras.optimizers import Adam \n    from keras.initializers import glorot_normal\n    from keras import backend as K\n    K.clear_session()\n\n    Input_image = Input(shape=(size, size, 1))\n    \n    if whatContextMode==ContextMode.country_context:\n        Input_context = Input(shape=(len(all_country_codes),))\n    elif whatContextMode==ContextMode.continent_context:\n        Input_context = Input(shape=(len(cntntns),))\n    \n    x = Conv2D(32, kernel_size=(3, 3), activation='relu', padding='same',\\\n               kernel_initializer=glorot_normal(seed=2018))(Input_image)\n    x = MaxPooling2D(pool_size=(2, 2))(x)\n\n    x = Conv2D(64, kernel_size=(3, 3), activation='relu', padding='same',\\\n              kernel_initializer=glorot_normal(seed=2018))(x)\n    x = MaxPooling2D(pool_size=(2, 2))(x)\n\n    x = Conv2D(128, kernel_size=(3, 3), activation='relu', padding='same',\\\n              kernel_initializer=glorot_normal(seed=2018))(x)\n    x = MaxPooling2D(pool_size=(2, 2))(x)\n    \n    x = Conv2D(256, kernel_size=(3, 3), activation='relu', padding='same',\\\n              kernel_initializer=glorot_normal(seed=2018))(x)\n    x = MaxPooling2D(pool_size=(2, 2))(x)\n    \n    x = Flatten()(x)\n    \n    # if image context mode allowed, concat context with image summary from CNN\n    if whatContextMode!=ContextMode.no_context:\n        x = concatenate([x, Input_context])\n\n    x = Dense(denseLayerNeurons, activation='relu',\\\n              kernel_initializer=glorot_normal(seed=2018))(x)\n    x = BatchNormalization()(x)\n    x = Dropout(seed=2018, rate=0.5)(x)\n    x = Dense(denseLayerNeurons, activation='relu',\\\n             kernel_initializer=glorot_normal(seed=2018))(x)\n    x = BatchNormalization()(x)\n    out = Dense(num_classes, activation='softmax',\\\n               kernel_initializer=glorot_normal(seed=2018))(x)\n\n    if whatContextMode==ContextMode.no_context:\n        my_model = Model(inputs = Input_image, outputs = out)        \n    else:\n        my_model = Model(inputs = [Input_image, Input_context], outputs = out)        \n\n    my_model.compile(optimizer=Adam(), loss='categorical_crossentropy',\n              metrics=['accuracy',in_top_3])\n    \n    if show:\n        my_model.summary()\n    \n    return my_model","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"36aa0bea03d48b51df9c59f27c1047a254c54f1c"},"cell_type":"markdown","source":"Take a look how the context is included in the CNN model:"},{"metadata":{"_uuid":"8257ceebd87b4abc6416b81bfbf34eb1fe1cb210","trusted":true},"cell_type":"code","source":"create_model(whatContextMode=ourContextMode, denseLayerNeurons=1024, show=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"99bfaffd78ba1dfc84206a439cb686365a7235a1"},"cell_type":"markdown","source":"### Models evaluation"},{"metadata":{"_uuid":"6cb7fd4847f944d49d07fb012f6099fdd8fbe22a","trusted":true},"cell_type":"code","source":"from numpy.random import seed as nseed\nnseed(2018)\nfrom tensorflow import set_random_seed\nset_random_seed(2018)\n\ndef reset_seeds():    \n    nseed(2018)\n    set_random_seed(2018)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"06c730a44d7b0948f22c54401a1f6cc0a140ef22"},"cell_type":"markdown","source":"Training settings. As we have selected only 30 classes and reduced training set to 30%, even with slow generators 10 training epochs for three models can be performed."},{"metadata":{"_uuid":"e316c0fa9520328ce97368286128c36200029cf0","trusted":true},"cell_type":"code","source":"# average file length * training_ratio is around 58 000, dividing by around 70 items of class in a batch (for 30 classes and 2048 batch size) gives 830 steps/epoch\n# average validation set size is 0.05*140 000=7000, dividing by around 70 items of class in a batch (for 30 classes and 2048 batch size) gives 100 steps\ntest_epoch=10\ntest_steps=620\ntest_validation_steps=100","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"0d26a5b00ae84ec4ce961af3a15f9e5607f1bb14"},"cell_type":"markdown","source":"Function for creating proper generators, models and performing a training, according to the context mode."},{"metadata":{"_uuid":"fbf16781cda9799726d3e103a0d1c434afb2367d","trusted":true},"cell_type":"code","source":"def nice_evaluator(whatContextMode, denseLayerNeurons=512):    \n    reset_seeds()    \n    gen_list_TRAIN =[items_gen(i, True ) for i in range(num_classes)]\n    gen_list_VALID =[items_gen(i, False) for i in range(num_classes)]\n    train_gen=main_generator(whatGeneratorMode=GeneratorMode.training,  whatContextMode=whatContextMode)\n    valid_gen=main_generator(whatGeneratorMode=GeneratorMode.validation,whatContextMode=whatContextMode)\n\n    model = create_model(whatContextMode=whatContextMode, denseLayerNeurons=denseLayerNeurons)\n    history = model.fit_generator(\n            generator=train_gen, steps_per_epoch=test_steps,\n            validation_data=valid_gen, validation_steps=test_validation_steps,\n            epochs=test_epoch, verbose=2)\n    \n    return {'hist': history.history, 'model': model, 'context': whatContextMode}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"3d471110eb88db3637710ced13d6a8c48654c0af"},"cell_type":"code","source":"dict1 = nice_evaluator(whatContextMode=ContextMode.no_context,        denseLayerNeurons=256)\ndict2 = nice_evaluator(whatContextMode=ContextMode.country_context,   denseLayerNeurons=256)\ndict3 = nice_evaluator(whatContextMode=ContextMode.continent_context, denseLayerNeurons=256)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"7bdfe3a674aa6f899dd61d23e7dd2f497e504c0e","trusted":true},"cell_type":"code","source":"fig, axs = plt.subplots(nrows=2, ncols=3, figsize=(12, 8))\noptions = ['val_loss','val_acc','val_in_top_3','loss','acc','in_top_3']\nfor i, opt in enumerate(options):    \n    ax = axs[i//3, i%3]        \n    ax.plot(np.arange(test_epoch)+1, dict1['hist'][opt],marker='o', label='no context')\n    ax.plot(np.arange(test_epoch)+1, dict2['hist'][opt],marker='x', label='country context')\n    ax.plot(np.arange(test_epoch)+1, dict3['hist'][opt],marker='d', label='continent context')    \n    ax.set_xlabel('epochs')\n    ax.set_title(opt)\n    ax.legend()\n    ax.grid()\nplt.tight_layout()\n","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9ef997634003f988cc1dc5ba9b10fa70baa62043"},"cell_type":"markdown","source":"I think it is especially though to compare the performance of the model with the country context as it gives significant amount of neurons to a dense layer. Maybe it would be also interesting to check the solution of training CNN model first and applying transfer learning with convolutional layers frozen and only then add the context. Thinking of non custom CNNs like MobileNet the challange itself would be to add the context within the graph properly.\n______________________________\n\n### Predictions"},{"metadata":{"_uuid":"3c8cd3b8891052c2500f49cc903dcb7de5037915","trusted":true},"cell_type":"code","source":"model_to_test = dict3\n\ntest_gen = main_generator(whatGeneratorMode=GeneratorMode.testing, whatContextMode=model_to_test['context'])\npredictions = model_to_test['model'].predict_generator(test_gen, steps=5, verbose=1) #steps=np.ceil(112199/batch_size) # for all data","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"83468a8cfd3f09016382b70344815aa76ba97e7b"},"cell_type":"code","source":"print(np.shape(predictions))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e0d1703383e3426df2791f4b05f376d0efa3a5d9"},"cell_type":"code","source":"def top3cats(predictions):\n    return pd.DataFrame(np.argsort(-predictions, axis=1)[:, :3], columns=['word','word2','word3'] )\n\ntest_simp = pd.read_csv('../input/test_simplified.csv', nrows=np.shape(predictions)[0])\ncategories_dict = {_id: cat_name.replace(' ', '_') for _id, cat_name in enumerate(cat_names)}\n\noutput = top3cats(predictions).replace(categories_dict)\n\noutput = (output.word + ' ').str.cat([output.word2 + ' ', output.word3])\noutput.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"2dc97f3d93e3d029d3d9f7aa0d8c58b7ce046dde"},"cell_type":"code","source":"output = pd.concat([test_simp['key_id'], output], axis=1)\noutput.tail()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"f7efa6297461a6073be465c004a122efc2844e37"},"cell_type":"code","source":"output.to_csv('myResults.csv',index=False) # But note we trained only 30 classes here","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d426a827637e3fe60f23344974ca51b318a05d2e"},"cell_type":"markdown","source":"### References \n[1] [Beluga - Black&White CNN [LB=0.77], *kernel*](https://www.kaggle.com/gaborfodor/black-white-cnn-lb-0-77)  \n[2] [Beluga - Greyscale MobileNet Animals, *kernel*](https://www.kaggle.com/gaborfodor/greyscale-mobilenet-animals)  \n[3] [K. Tang et al. - Improving Image Classification with Location Context, *article*](http://vision.stanford.edu/pdf/tang2015iccv.pdf)  \n[4] [V. Borisov - Two-Inputs Neural Network using Keras, *kernel*](https://www.kaggle.com/hireme/two-inputs-neural-network-using-keras)  "}],"metadata":{"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}