{"cells":[{"metadata":{},"cell_type":"markdown","source":"## imports"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\nimport re\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\n\nimport cv2\n\nimport numpy as np\nimport pandas as pd\n\nfrom keras.models import *\nfrom keras.layers import *\nfrom keras.optimizers import *\nfrom keras.utils import *\nfrom keras.callbacks import *\n\nfrom keras import backend as K\nfrom keras.applications.densenet import DenseNet121, preprocess_input\nfrom keras.preprocessing.image import ImageDataGenerator\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import fbeta_score\n\nfrom tqdm import tqdm","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## load data"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_images = os.listdir(\"../input/imet-2019-fgvc6/train/\")\ntest_images = os.listdir(\"../input/imet-2019-fgvc6/test/\")\n\nprint(\"number of train images: \", len(train_images))\nprint(\"number of test  images: \", len(test_images))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train = pd.read_csv(\"../input/imet-2019-fgvc6/train.csv\")\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"labels = pd.read_csv(\"../input/imet-2019-fgvc6/labels.csv\")\nlabels.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"labels_map = {v:i for i, v in zip(labels.attribute_id.values, labels.attribute_name.values)}\nlabels_map_rev = {i:v for i, v in zip(labels.attribute_id.values, labels.attribute_name.values)}\n\nnum_classes = len(labels_map)\nprint(\"{} categories\".format(num_classes))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission = pd.read_csv(\"../input/imet-2019-fgvc6/sample_submission.csv\")\nsubmission.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## EDA"},{"metadata":{"trusted":true},"cell_type":"code","source":"def ids_to_lables(attribute_id):\n    return \"\\n\".join([labels_map_rev[int(i)] for i in attribute_id.split(\" \")])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train[\"labels\"] = train.attribute_ids.apply(lambda x: ids_to_lables(x))\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train[\"n_cate\"] = train.attribute_ids.apply(lambda x: len(x.split(\" \")))\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# TODO: maybe multi cultures here.\n\ndef get_culture(x):\n    try: \n        return re.search(r\"culture::(\\w+)\", x).group(1)\n    except:\n        return \"none\"\n\ntrain[\"culture\"] = train.labels.apply(lambda x: get_culture(x))\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_num_tag(x):\n    return len(re.findall(r\"tag::(\\w+)\", x))\n\ntrain[\"n_tag\"] = train.labels.apply(lambda x: get_num_tag(x))\ntrain.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"num_not_culture = train[train.culture == \"none\"].shape[0]\n\nprint(\"{} ({:.2f}%) not have a culture categroy\".format(num_not_culture, \n                                                        num_not_culture *100 / train.shape[0]))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"num_not_tag = train[train.n_tag == 0].shape[0]\n\nprint(\"{} ({:.2f}%) not have a tag categroy\".format(num_not_tag, \n                                                    num_not_tag *100 / train.shape[0]))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"_ = train.n_cate.value_counts().sort_index().plot.bar()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"_ = train.n_tag.value_counts().sort_index().plot.bar()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"_ = train.culture.value_counts()[:10].sort_index().plot.bar()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def show_images(n_to_show, is_train=True):\n    img_dir = \"../input/imet-2019-fgvc6/train/\" if is_train else \"../input/imet-2019-fgvc6/test/\"\n    plt.figure(figsize=(16,16))\n    images = os.listdir(img_dir)[:n_to_show]\n    for i in range(n_to_show):\n        img = mpimg.imread(img_dir + images[i])\n        plt.subplot(n_to_show/2+1, 2, i+1)\n        if is_train:\n            plt.title(train[train.id == images[i].split(\".\")[0]].labels.values[0])\n        plt.imshow(img)\n        plt.axis('off')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"show_images(6)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"show_images(6, is_train=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## prepare X and y"},{"metadata":{"trusted":true},"cell_type":"code","source":"def obtain_y(ids):\n    y = np.zeros(num_classes)\n    for idx in ids.split(\" \"):\n        y[int(idx)] = 1\n    return y","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"paths = [\"../input/imet-2019-fgvc6/train/{}.png\".format(x) for x in train.id.values]\ntargets = np.array([obtain_y(y) for y in train.attribute_ids.values])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### image generator"},{"metadata":{"trusted":true},"cell_type":"code","source":"class ImageGenerator(Sequence):\n    \n    def __init__(self, paths, targets, batch_size, shape, augment=False):\n        self.paths = paths\n        self.targets = targets\n        self.batch_size = batch_size\n        self.shape = shape\n        self.augment = augment\n        \n    def __len__(self):\n        return int(np.ceil(len(self.paths) / float(self.batch_size)))\n    \n    def __getitem__(self, idx):\n        batch_paths = self.paths[idx * self.batch_size : (idx + 1) * self.batch_size]\n        x = np.zeros((len(batch_paths), self.shape[0], self.shape[1], self.shape[2]), dtype=np.float32)\n        y = np.zeros((self.batch_size, num_classes, 1))\n        for i, path in enumerate(batch_paths):\n            x[i] = self.__load_image(path)\n        y = self.targets[idx * self.batch_size : (idx + 1) * self.batch_size]\n        return x, y\n    \n    def __iter__(self):\n        for item in (self[i] for i in range(len(self))):\n            yield item\n            \n    def __load_image(self, path):\n        image = cv2.imread(path)\n        image = cv2.resize(image, (self.shape[0], self.shape[1]))\n        image = preprocess_input(image)\n        if self.augment:\n            seq = iaa.Sequential([\n                iaa.OneOf([\n                    iaa.Fliplr(0.5),\n                    iaa.Flipud(0.5),\n                    iaa.CropAndPad(percent=(-0.25, 0.25)),\n                    iaa.Crop(percent=(0, 0.1)),\n                    iaa.Sometimes(0.5,\n                        iaa.GaussianBlur(sigma=(0, 0.5))\n                    ),\n                    iaa.Affine(\n                        scale={\"x\": (0.8, 1.2), \"y\": (0.8, 1.2)},\n                        translate_percent={\"x\": (-0.2, 0.2), \"y\": (-0.2, 0.2)},\n                        rotate=(-180, 180),\n                        shear=(-8, 8)\n                    )\n                ])\n            ], random_order=True)\n            image = seq.augment_image(image)\n        return image","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### train test split"},{"metadata":{"trusted":true},"cell_type":"code","source":"batch_size = 64\n\ntrain_paths, val_paths, train_targets, val_targets = train_test_split(paths, \n                                                                      targets,\n                                                                      test_size=0.1, \n                                                                      random_state=1029)\n\ntrain_gen = ImageGenerator(train_paths, train_targets, batch_size=batch_size, shape=(224,224,3), augment=False)\nval_gen = ImageGenerator(val_paths, val_targets, batch_size=batch_size, shape=(224,224,3), augment=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## build model"},{"metadata":{"trusted":true},"cell_type":"code","source":"inp = Input((224, 224, 3))\nbackbone = DenseNet121(input_tensor=inp,\n                       weights=\"../input/densenet-keras/DenseNet-BC-121-32-no-top.h5\",\n                       include_top=False)\nx = backbone.output\nx = GlobalAveragePooling2D()(x)\nx = Dense(2048, activation=\"relu\")(x)\nx = Dropout(0.5)(x)\noutp = Dense(num_classes, activation=\"sigmoid\")(x)\n\nmodel = Model(inp, outp)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### f_score for Keras"},{"metadata":{"trusted":true},"cell_type":"code","source":"def f_score(y_true, y_pred, threshold=0.1, beta=2):\n    tp = tp_score(y_true, y_pred, threshold)\n    fp = fp_score(y_true, y_pred, threshold)\n    fn = fn_score(y_true, y_pred, threshold)\n    precision = tp / (tp + fp)\n    recall = tp / (tp + fn)\n    return (1+beta**2) * ((precision * recall) / ((beta**2)*precision + recall))\n\n\ndef tp_score(y_true, y_pred, threshold=0.1):\n    tp_3d = K.concatenate(\n        [\n            K.cast(K.expand_dims(K.flatten(y_true)), 'bool'),\n            K.cast(K.expand_dims(K.flatten(K.greater(y_pred, K.constant(threshold)))), 'bool'),\n            K.cast(K.ones_like(K.expand_dims(K.flatten(y_pred))), 'bool')\n        ], axis=1\n    )\n    tp = K.sum(K.cast(K.all(tp_3d, axis=1), 'int32'))\n    return tp\n\n\ndef fp_score(y_true, y_pred, threshold=0.1):\n    fp_3d = K.concatenate(\n        [\n            K.cast(K.expand_dims(K.flatten(K.abs(y_true - K.ones_like(y_true)))), 'bool'),\n            K.cast(K.expand_dims(K.flatten(K.greater(y_pred, K.constant(threshold)))), 'bool'),\n            K.cast(K.ones_like(K.expand_dims(K.flatten(y_pred))), 'bool')\n        ], axis=-1\n    )\n    fp = K.sum(K.cast(K.all(fp_3d, axis=1), 'int32'))\n    return fp\n\n\ndef fn_score(y_true, y_pred, threshold=0.1):\n    fn_3d = K.concatenate(\n        [\n            K.cast(K.expand_dims(K.flatten(y_true)), 'bool'),\n            K.cast(K.expand_dims(K.flatten(K.abs(K.cast(K.greater(y_pred, K.constant(threshold)), 'float') - K.ones_like(y_pred)))), 'bool'),\n            K.cast(K.ones_like(K.expand_dims(K.flatten(y_pred))), 'bool')\n        ], axis=1\n    )\n    fn = K.sum(K.cast(K.all(fn_3d, axis=1), 'int32'))\n    return fn\n\n\ndef precision_score(y_true, y_pred, threshold=0.1):\n    tp = tp_score(y_true, y_pred, threshold)\n    fp = fp_score(y_true, y_pred, threshold)\n    return tp / (tp + fp)\n\n\ndef recall_score(y_true, y_pred, threshold=0.1):\n    tp = tp_score(y_true, y_pred, threshold)\n    fn = fn_score(y_true, y_pred, threshold)\n    return tp / (tp + fn)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"checkpoint = ModelCheckpoint('model.h5', \n                             monitor='val_f_score', \n                             verbose=1, \n                             save_best_only=True, \n                             mode='max', \n                             save_weights_only=True)\n\nreduce_lr = ReduceLROnPlateau(monitor='val_f_score', factor=0.2,\n                              patience=1, verbose=1, mode='max',\n                              min_delta=0.0001, cooldown=2, min_lr=1e-7)\n\nearly_stop = EarlyStopping(monitor=\"val_f_score\", mode=\"max\", patience=5)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.compile(\n    loss='binary_crossentropy',\n    optimizer=Adam(1e-03),\n    metrics=['acc', f_score])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"history = model.fit_generator(generator=train_gen, \n                              steps_per_epoch=len(train_gen), \n                              validation_data=val_gen, \n                              validation_steps=len(val_gen),\n                              epochs=15,\n                              callbacks=[checkpoint, reduce_lr, early_stop])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.rcParams['figure.figsize'] = (6,6)\n\nfscore = history.history['f_score']\nval_fscore = history.history['val_f_score']\nepochs = range(1, len(fscore) + 1)\n\nplt.title('Training and validation accuracy')\nplt.plot(epochs, fscore, 'red', label='Training f_score')\nplt.plot(epochs, val_fscore, 'blue', label='Validation f_score')\nplt.legend()\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.load_weights(\"./model.h5\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## prediction"},{"metadata":{},"cell_type":"markdown","source":"### test image generator"},{"metadata":{"trusted":true},"cell_type":"code","source":"class TestImageGenerator(Sequence):\n    \n    def __init__(self, paths, batch_size, shape):\n        self.paths = paths\n        self.targets = targets\n        self.batch_size = batch_size\n        self.shape = shape\n        \n    def __len__(self):\n        return int(np.ceil(len(self.paths) / float(self.batch_size)))\n    \n    def __getitem__(self, idx):\n        batch_paths = self.paths[idx * self.batch_size : (idx + 1) * self.batch_size]\n        x = np.zeros((len(batch_paths), self.shape[0], self.shape[1], self.shape[2]), dtype=np.float32)\n        for i, path in enumerate(batch_paths):\n            x[i] = self.__load_image(path)\n        return x\n    \n    def __iter__(self):\n        for item in (self[i] for i in range(len(self))):\n            yield item\n            \n    def __load_image(self, path):\n        image = cv2.imread(path)\n        image = cv2.resize(image, (self.shape[0], self.shape[1]))\n        image = preprocess_input(image)\n        return image","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### do prediction"},{"metadata":{"trusted":true},"cell_type":"code","source":"test_paths = [\"../input/imet-2019-fgvc6/test/{}.png\".format(x) for x in submission.id.values]\ntest_gen = TestImageGenerator(test_paths, batch_size=batch_size, shape=(224,224,3))\n\npredicts = model.predict_generator(test_gen, verbose=1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### check our prediction"},{"metadata":{"trusted":true},"cell_type":"code","source":"n = 6\nthreshold = 0.15\n\nimg = cv2.imread(test_paths[n])\nplt.imshow(img)\n\na = np.array(predicts[n]>threshold, dtype=np.int8)\nb = np.where(a==1)[0]\nfor idx in b.tolist():\n    print(labels_map_rev[idx])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train.n_tag.describe()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def classifier(probs):\n    \n    culture = None\n    tags = None\n    arr = probs.argsort()\n    \n    culture_threshold = 0.1\n    tag_max_threshold = 0.55\n    \n    n_min_tag = 1\n    n_max_tag = 3\n    \n    # first: find culture category by sorting probs\n    \n    for idx in arr[::-1]:\n        if labels_map_rev[idx].startswith(\"culture\") and probs[idx] > culture_threshold:\n            culture = str(idx)\n            break           # TODO: maybe multi culture here.\n    \n    # second: find tags by different threshold\n    for threshold in np.arange(0.05, tag_max_threshold, 0.05):\n        n = 0                # stores len(tags)\n        tags_list = list()   # stores tags\n        \n        a = np.array(probs > threshold, dtype=np.int8)\n        b = np.where(a == 1)[0]\n        for idx in b.tolist():\n            if labels_map_rev[idx].startswith(\"tag\"):\n                n += 1\n                tags_list.append(str(idx))\n        if n >= n_min_tag and n <= n_max_tag:\n            tags = tags_list\n            break\n    \n    # finally packs our answer\n    answer = list()\n    if culture:\n        answer.append(culture)\n    if tags:\n        for t in tags:\n            answer.append(t)\n            \n    return \" \".join(answer)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"predictions = list()\n\nfor probs in tqdm(predicts):\n    predictions.append(classifier(probs))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission[\"attribute_ids\"] = np.array(predictions)\nsubmission.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission_df = submission.copy()\nsubmission_df.n_cate = submission.attribute_ids.apply(lambda x: len(x.split(\" \")))\n_ = submission_df.n_cate.value_counts().sort_index().plot.bar()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### submission"},{"metadata":{"trusted":true},"cell_type":"code","source":"submission.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!head submission.csv","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.4","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}