{"cells":[{"metadata":{"_uuid":"0797e7059df956203a4a67d4dfa8d1db7f52fea8"},"cell_type":"markdown","source":"# 1 Setup"},{"metadata":{"_uuid":"ee155567cbc86d7acebb08c28f6236504a7a7b71"},"cell_type":"markdown","source":"## 1.1 Imports"},{"metadata":{"trusted":true,"_uuid":"6b0f11a247e113a58d1f7b20e4a5e3038f8dd203"},"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom os.path import isfile\nimport pandas as pd\nimport numpy as np\nimport warnings\nimport sklearn\nfrom tqdm._tqdm_notebook import tqdm_notebook\nfrom keras import backend as K\nfrom keras.layers import Input, Dense, Dropout, Conv2D, GlobalMaxPooling2D, BatchNormalization, MaxPool2D, concatenate, merge, Lambda\nfrom keras.models import Model, Sequential\nfrom keras.callbacks import ModelCheckpoint, Callback, EarlyStopping, ReduceLROnPlateau, LearningRateScheduler\nfrom keras.optimizers import Adam\nimport tensorflow as tf\nfrom keras.preprocessing.image import ImageDataGenerator\nfrom sklearn import preprocessing\nfrom keras.applications.resnet50 import ResNet50, preprocess_input\n\n# Image cropping\nimport os\nimport PIL\nfrom PIL import Image\nfrom PIL.ImageDraw import Draw\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom keras.models import load_model\nfrom keras.preprocessing import image\n\n'''\n# Image processing\nimport imageio\nimport skimage\nimport skimage.io\nimport skimage.transform\nfrom skimage import color\n'''\n\nfrom sklearn.model_selection import train_test_split\nfrom collections import defaultdict\nimport glob\nfrom os.path import join\n\nimport seaborn as sns\nplt.style.use('fivethirtyeight')\n%matplotlib inline","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"022101db3e3c526188d44991529f45168faca09c"},"cell_type":"markdown","source":"## 1.2 Functions"},{"metadata":{"trusted":true,"_uuid":"0b5252d13066322ed8fea8fef8c4d2b5e3112bf7"},"cell_type":"code","source":"img_width = 128\nimg_height = 128\nimg_channels = 1\nimg_to_load = 25000\nnew_whale_distance = 0.1\n\nbatch_size = 200\nepochs = 300\npatience = 10\n\ncrop_margin = 0.2\nembedding_size = 256","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a56a9cd518d211e335a7888a25b01918f3b8a4f4"},"cell_type":"code","source":"MODEL_BASE = '../input/bbox-model-whale-recognition'\nBOXES = '../input/generating-whale-bounding-boxes/bounding_boxes.csv'\nmodel = load_model(os.path.join(MODEL_BASE, 'cropping.model'))\nImage.MAX_IMAGE_PIXELS = 3360368385340928\n\nboxes = pd.read_csv(BOXES).set_index('Image')\n    \ndef crop_image(rb_img_arr, img_width = img_width, img_height = img_height, draw = False):\n    bbox  = model.predict(np.expand_dims(rb_img_arr, axis=0))[0]\n    \n    if bbox[0] >= bbox[2] or bbox[1] >= bbox[3]:\n        bbox = [0,0,128,128]\n        \n    if (draw):\n        # draw rectangle\n        draw = Draw(rimg)\n        draw.rectangle(bbox, outline='red')\n    \n    rb_img = image.array_to_img(rb_img_arr)\n    try:\n        img_crop = rb_img.crop(tuple(bbox))\n    except MemoryError:\n        img_crop = rb_img\n    \n    img_crop = img_crop.convert('L')\n    img_crop = img_crop.resize((img_width, img_height), PIL.Image.ANTIALIAS) \n    img_crop_arr = image.img_to_array(img_crop)    \n    \n    return img_crop_arr / 255.\n\ndef load_single_image(img_path, img_width, img_height):\n    main_img = image.load_img(img_path)\n    r_img = main_img.resize((128, 128), PIL.Image.ANTIALIAS)    \n    r_img = r_img.convert('L')\n    \n    return image.img_to_array(r_img) / 255.\n    \ndef make_bbox_image(img_name, img_width, img_height, draw = False):\n    \"\"\"\n    :param img: path to image\n    \"\"\"\n    main_img = image.load_img(expand_path(img_name))\n    x0, y0, x1, y1 = tuple(boxes.loc[img_name,['x0','y0','x1','y1']].tolist())\n    width, height = main_img.size\n    dx            = x1 - x0\n    dy            = y1 - y0\n    x0           -= dx*crop_margin\n    x1           += dx*crop_margin + 1\n    y0           -= dy*crop_margin\n    y1           += dy*crop_margin + 1\n    \n    if (x0 < 0     ): x0 = 0\n    if (x1 > width): x1 = width\n    if (y0 < 0     ): y0 = 0\n    if (y1 > height): y1 = height\n        \n    try:\n        img_crop = main_img.crop((x0, y0, x1, y1))\n    except MemoryError:\n        img_crop = main_img\n    \n    img_crop = img_crop.convert('L')\n    img_crop = img_crop.resize((img_width, img_height), PIL.Image.ANTIALIAS)\n    img_crop_arr = image.img_to_array(img_crop)    \n    return img_crop_arr / 255.","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"2dfa72762ab63f5beff5ce0d981f83e3f9d38cd4"},"cell_type":"code","source":"def expand_path(image_name):\n    if isfile('../input/humpback-whale-identification/train/' + image_name): return '../input/humpback-whale-identification/train/' + image_name\n    if isfile('../input/humpback-whale-identification/test/' + image_name): return '../input/humpback-whale-identification/test/' + image_name\n    return image_name\n\ndef read_data(path):\n    whales = pd.read_csv(path, index_col=False)\n    return whales\n\ndef read_train_data():\n    return read_data('../input/humpback-whale-identification/train.csv')\n\ndef read_test_files():\n    return [image.split('/')[4] for image in glob.glob(join('../input/humpback-whale-identification/test', '*.jpg'))]\n\ndef filter_whales(whales):\n    not_new_whale = (whales.Id != \"new_whale\")\n    return whales[not_new_whale]\n\ndef read_img(file, img_width, img_height):\n    \"\"\"\n    Read and resize img, adjust channels. \n    @param file: file name without full path\n    \"\"\"\n    return load_single_image(expand_path(file), img_width, img_height)\n    #return make_bbox_image(file, img_width, img_height)\n    '''\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\")\n        img = skimage.io.imread(expand_path(file))\n        img = skimage.transform.resize(img, (img_width, img_height), mode='reflect', )\n        img = color.rgb2gray(img)\n        img = np.reshape(img, (img_width, img_height, 1))\n    return img    \n    '''\n\ndef load_images(files, img_width, img_height):\n    \"\"\"\n    Load images for features, drop other columns\n    One hot encode for label, drop other columns\n    @return: train images, validation images, test images, train labels, validation labels, test labels\n    \"\"\"\n    # Bees already splitted to train, validation and test\n    # Load and transform images to have equal width/height/channels. \n    # Use np.stack to get NumPy array for CNN input\n\n    # Train data\n    tqdm_notebook.pandas()\n    \n    return {image_name:read_img(image_name, img_width, img_height) for image_name in tqdm_notebook(files)}\n    \ndef load_images_and_target(train_whales, img_width, img_height):\n    \"\"\"\n    Load images for features, drop other columns\n    One hot encode for label, drop other columns\n    @return: train images, validation images, test images, train labels, validation labels, test labels\n    \"\"\"\n    # Bees already splitted to train, validation and test\n    # Load and transform images to have equal width/height/channels. \n    # Use np.stack to get NumPy array for CNN input\n\n    return load_images(train_whales['Image'], img_width, img_height)\n    \ndef show_triplets(triplets, batch = 5):\n    _, ax = plt.subplots(nrows = batch, ncols = 3, figsize = (100, 100))\n    for i, (anchor, positive, negative) in enumerate(zip(triplets['anchor_input'], triplets['positive_input'], triplets['negative_input'])):\n        ax[i][0].set_title(\"anchor\", fontsize = 60)\n        ax[i][0].imshow(anchor.squeeze(), cmap='gray')\n        ax[i][1].set_title(\"positive\", fontsize = 60)\n        ax[i][1].imshow(positive.squeeze(), cmap='gray')\n        ax[i][2].set_title(\"negative\", fontsize = 60)\n        ax[i][2].imshow(negative.squeeze(), cmap='gray')\n        if (i >= batch - 1): break\n    plt.tight_layout()\n    plt.show()\n    \ndef show_predictions(predictions, batch = 5):\n    _, ax = plt.subplots(nrows = batch, ncols = 2, figsize = (100, 100))\n    for i, prediction in enumerate(predictions):\n        ax[i][1].set_title(\"test\", fontsize = 60)\n        ax[i][1].imshow(prediction['test_image'].squeeze(), cmap='gray')\n        ax[i][0].set_title(\"predicted\", fontsize = 60)\n        ax[i][0].imshow(prediction['best_predicted_image'].squeeze(), cmap='gray')\n        if (i >= batch - 1): break\n    plt.tight_layout()\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"52cd9e2637c181cc6996361b666641137865dfb1"},"cell_type":"markdown","source":"# 2 Carga de datos"},{"metadata":{"trusted":true,"_uuid":"56b781c045c181e122825e663d154903f16e6811"},"cell_type":"code","source":"all_train_whales = read_train_data()\nall_train_whales.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b4a3bb7a1a4ca43e60f24d7c3efcec7965c3074c"},"cell_type":"code","source":"all_train_whales.shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"f45cc9707254dc29e98fac1580d24e66c5d30165"},"cell_type":"code","source":"np.unique(all_train_whales['Id']).shape","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d4f833855c052f29e01fb4a0909ea31107e74b3b"},"cell_type":"code","source":"train_whales = filter_whales(all_train_whales)[:img_to_load]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e432eb286fa565d7415607e0820afcc8d0f9aeaf","scrolled":true},"cell_type":"code","source":"file_mapping_image = load_images_and_target(train_whales, \n                                  img_width, \n                                  img_height \n                                  )","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"38b3b45b461476a5cec5bad650dbbcd97b790ac9","scrolled":true},"cell_type":"code","source":"#img = make_bbox_image(train_whales['Image'][1], img_width, img_height)\n#img.shape\nimg = file_mapping_image[train_whales['Image'][2]]\nplt.imshow(img.squeeze(), cmap = 'gray')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d32bd79f66d28cf449dd20a1ad6788f9276a48d4"},"cell_type":"code","source":"#plt.imshow(np.uint8(image.img_to_array(img)))\n#plt.show()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d3b4592de517d5655fd59f91781050b0557abea6"},"cell_type":"markdown","source":"# 3 Analisis exploratorio"},{"metadata":{"trusted":true,"_uuid":"1a9c57c6fb5820f99ad840cdd0b3634ba1e3f0fa","scrolled":true},"cell_type":"code","source":"labels_count = train_whales.Id.value_counts()\n\nplt.figure(figsize=(18, 4))\nplt.subplot(121)\nplt.hist(labels_count.values)\nplt.ylabel(\"frequency\")\nplt.xlabel(\"class size\")\n\nplt.title('class distribution')\nlabels_count.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"bef2ff7b1c71b894b5174c3650f7c1cfa11822aa"},"cell_type":"markdown","source":"# 4 Siamese neural network"},{"metadata":{"_uuid":"da5eb73cbb92df8d1218000a4f09ae0227aad744"},"cell_type":"markdown","source":"## 4.1 Building CNN model"},{"metadata":{"trusted":true,"_uuid":"76dcd77b732ad35cdec7c70ce6e75feffe99910f"},"cell_type":"code","source":"def triplet_loss(y_true, y_pred, margin=3):\n    anchor_embedding = y_pred[:,:embedding_size]\n    positive_embedding = y_pred[:,embedding_size:embedding_size*2]\n    negative_embedding = y_pred[:,embedding_size*2:]\n    positive_distance = K.square(anchor_embedding - positive_embedding)\n    negative_distance = K.square(anchor_embedding - negative_embedding) \n    positive_distance = K.mean(positive_distance, axis=-1, keepdims=True)\n    negative_distance = K.mean(negative_distance, axis=-1, keepdims=True)\n    loss = K.maximum(0.0, margin + positive_distance - negative_distance)\n    return K.mean(loss)\n    \ndef add_convolutional_layer(model, filter_size, conv_layer_number = 2, batch_normalization = False, dropout = False):\n    for _ in range(conv_layer_number):\n        model.add(Conv2D(filters = filter_size, kernel_size = (3,3), padding='Same', activation ='relu', input_shape = (img_height, img_width, img_channels)))\n        model.add(BatchNormalization()) if (batch_normalization) else False\n        model.add(Dropout(0.5)) if (dropout) else False\n    \n    model.add(MaxPool2D(pool_size=(2,2), strides=(2,2)))\n    \ndef add_convolutional_base(model, conv_layer_number = 2, batch_normalization = False):\n    add_convolutional_layer(model, 32, conv_layer_number, batch_normalization, True)\n    add_convolutional_layer(model, 64, conv_layer_number, batch_normalization)\n    add_convolutional_layer(model, 128, conv_layer_number, batch_normalization, True)\n    add_convolutional_layer(model, 256, conv_layer_number, batch_normalization)\n    add_convolutional_layer(model, 512, conv_layer_number, batch_normalization, True)\n    add_convolutional_layer(model, 1024, conv_layer_number, batch_normalization)\n    model.add(GlobalMaxPooling2D())\n    \ndef add_embeddings(model, embeddings_number = 128, kernel_regularizer = None):\n    model.add(Dense(1024, activation='relu', kernel_regularizer = kernel_regularizer, name='dense'))\n    model.add(Dropout(0.5))\n    model.add(Dense(embeddings_number, activation='relu', kernel_regularizer = kernel_regularizer, name='embeddings'))\n    \ndef create_embedding_network_with_custom_model():\n    model = Sequential()\n    add_convolutional_base(model, 2, True)\n    add_embeddings(model, embedding_size, None)\n    \n    return model\n\ndef add_resnet50(model):\n    resnet_model = ResNet50(weights='imagenet', include_top=False, pooling='max')    \n    for layer in resnet_model.layers:\n        layer.trainable = False\n    \n    model.add(resnet_model)\n    \ndef create_embedding_network_with_resnet():\n    model = Sequential()\n    add_resnet50(model)\n    add_embeddings(model, embedding_size, None)\n    \n    return model\n\ndef build_model():\n    positive_input = Input(shape=(img_height, img_width, img_channels), name = 'positive_input')\n    negative_input = Input(shape=(img_height, img_width, img_channels), name = 'negative_input')\n    anchor_input = Input(shape=(img_height, img_width, img_channels), name = 'anchor_input')\n\n    # Create Common network to share the weights along different examples (+/-/Anchor)\n    embedding_network = create_embedding_network_with_custom_model()\n\n    positive_embedding = embedding_network(positive_input)\n    negative_embedding = embedding_network(negative_input)\n    anchor_embedding = embedding_network(anchor_input)\n    \n    output_vector = concatenate([anchor_embedding, positive_embedding, negative_embedding], name=\"positive_labels\")\n        \n    model = Model(inputs=[anchor_input, positive_input, negative_input], outputs=output_vector)\n    #model.add_loss(triplet_loss(output_vector))\n\n    model.compile(optimizer=Adam(lr=1e-4), loss=triplet_loss, metrics=[triplet_loss])\n    return model, embedding_network\n\ndef train_model(model, gen_train, gen_test):\n    earlystopper = EarlyStopping(monitor='loss', patience=patience, verbose=1,restore_best_weights=True)\n    return model.fit_generator(gen_train, \n                              validation_data=gen_test,\n                              callbacks=[earlystopper],\n                              epochs=epochs, \n                              verbose=0,\n                              #workers=4,\n                              #use_multiprocessing=True,\n                              steps_per_epoch=steps_per_epoch,\n                              validation_steps=validation_steps)\n\ndef eval_plot(History, epoch):\n    epochs = len(History.history['loss'])\n    plt.figure(figsize=(20,10))\n    plt.figure(figsize=(20,10))\n    sns.lineplot(range(1, epochs + 1), History.history['loss'], label='Train loss')\n    sns.lineplot(range(1, epochs + 1), History.history['val_loss'], label='Test loss')\n    plt.legend(['train', 'validation'], loc='upper left')\n    plt.ylabel('loss')\n    plt.xlabel('epoch')\n    plt.title(\"Loss Graph\")\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"9e01e7582bd93f00098b4393461e6bdeb77b62d0"},"cell_type":"code","source":"class sample_gen(object):\n    def __init__(self, image_name_mapping_whale_id, other_class = \"new_whale\"):\n        self.whale_id_with_images = dict()\n        \n        for image_name in image_name_mapping_whale_id:\n            whale_id = image_name_mapping_whale_id[image_name]\n            if whale_id in self.whale_id_with_images:\n                self.whale_id_with_images[whale_id].append(image_name)\n            else:\n                self.whale_id_with_images[whale_id] = [image_name]\n                \n    def get_random_whale_id(self, exclude_whale_id, more_than_one_image):\n        whale_ids = list(self.whale_id_with_images.keys())\n        filtered_whale_ids = list(filter(lambda whale_id: whale_id != exclude_whale_id, whale_ids))\n        # Get 0 probability for ids with just one image\n        images_to_remove_count = 1 if more_than_one_image else 0\n        filtered_whale_ids_probabilities = [len(self.whale_id_with_images[whale_id]) - images_to_remove_count for whale_id in filtered_whale_ids]\n        filtered_whale_ids_probabilities = filtered_whale_ids_probabilities / np.sum(filtered_whale_ids_probabilities)\n        return np.random.choice(filtered_whale_ids, 1, p = filtered_whale_ids_probabilities)[0]\n    \n    def get_sample(self):\n        anchor_whale_id = self.get_random_whale_id('new_whale', True)\n        negative_whale_id = self.get_random_whale_id(anchor_whale_id, False)\n        \n        positive_images = np.random.choice(self.whale_id_with_images[anchor_whale_id], 2, replace=False)\n        negative_image = np.random.choice(self.whale_id_with_images[negative_whale_id], 1)\n\n        return ((positive_images[0], positive_images[1], negative_image[0]), \n                (anchor_whale_id, negative_whale_id))\n\nimage_gen = ImageDataGenerator(rescale=(1/255),\n                            rotation_range=5,\n                           width_shift_range=0.0025,\n                           height_shift_range=0.0025,\n                           shear_range=0.001,\n                           zoom_range=[0.95, 1.05],\n                           horizontal_flip=True,\n                           vertical_flip=False,\n                           fill_mode='nearest',\n                           data_format='channels_last',\n                           #preprocessing_function=crop_image,\n                           brightness_range=[0.9, 1.1])\n\nimage_gen.fit(list(file_mapping_image.values()))\n\nle = preprocessing.LabelEncoder()\nle.fit(np.array(train_whales.Id.values))\n\ndef create_aug_gen(in_gen, batch_size=25):\n    for in_x, label in in_gen:\n        anchor_input = image_gen.flow(in_x['anchor_input'], shuffle=False, batch_size=batch_size)\n        positive_input = image_gen.flow(in_x['positive_input'], shuffle=False, batch_size=batch_size)\n        negative_input = image_gen.flow(in_x['negative_input'], shuffle=False, batch_size=batch_size)\n        \n        yield ({'anchor_input': next(anchor_input), 'positive_input': next(positive_input), 'negative_input': next(negative_input)}, label)\n        \ndef gen(triplet_gen, batch_size=25):\n    while True:\n        list_positive_examples_1 = []\n        list_negative_examples = []\n        list_positive_examples_2 = []\n        positive_labels = []\n        negative_labels = []\n\n        for i in range(batch_size):\n            ((positive_example_1, positive_example_2, negative_example),(positive_label, negative_label)) = triplet_gen.get_sample()\n            \n            list_positive_examples_1.append(file_mapping_image[positive_example_1])\n            list_negative_examples.append(file_mapping_image[negative_example])\n            list_positive_examples_2.append(file_mapping_image[positive_example_2])\n            positive_labels.append(positive_label)\n            negative_labels.append(negative_label)\n\n        A = np.array(list_positive_examples_1)\n        B = np.array(list_positive_examples_2)\n        C = np.array(list_negative_examples)\n         \n        label = None\n        \n        yield ({'anchor_input': A, 'positive_input': B, 'negative_input': C}, {'positive_labels': le.transform(np.array(positive_labels)), 'negative_labels': le.transform(np.array(negative_labels))})","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"5c3e07b88da0fc11224393cea5ffaff16c395959"},"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport matplotlib.patheffects as PathEffects\nimport seaborn as sns\nsns.set_style('darkgrid')\nsns.set_palette('muted')\nsns.set_context('notebook', font_scale=1.5,\n                rc={\"lines.linewidth\": 2.5})\n\nfrom sklearn.manifold import TSNE\n\ndef scatter(x, labels, subtitle=None):\n    scatter_le = preprocessing.LabelEncoder()\n    labels = scatter_le.fit_transform(np.array(labels))\n    clusters = max(labels) + 1\n    # We choose a color palette with seaborn.\n    palette = np.array(sns.color_palette(\"hls\", clusters))\n\n    # We create a scatter plot.\n    f = plt.figure(figsize=(8, 8))\n    ax = plt.subplot(aspect='equal')\n    sc = ax.scatter(x[:,0], x[:,1], lw=0, s=40,\n                    c=palette[labels.astype(np.int)])\n    plt.xlim(-25, 25)\n    plt.ylim(-25, 25)\n    ax.axis('off')\n    ax.axis('tight')\n\n    # We add the labels for each digit.\n    txts = []\n    for i in range(clusters):\n        # Position of each label.\n        xtext, ytext = np.median(x[labels == i, :], axis=0)\n        txt = ax.text(xtext, ytext, str(i), fontsize=24)\n        txt.set_path_effects([\n            PathEffects.Stroke(linewidth=5, foreground=\"w\"),\n            PathEffects.Normal()])\n        txts.append(txt)\n        \n    if subtitle != None:\n        plt.suptitle(subtitle)\n        \n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"9a19fc4210a1bf3065d778eb18283695a37a58b8","scrolled":false},"cell_type":"code","source":"train, test = train_test_split(train_whales, train_size=0.7, test_size = 0.3, random_state=1337)\nvalidation_steps = int(len(test) / batch_size) + 1\nsteps_per_epoch = int(len(train) / batch_size) + 1\nprint(\"Validation steps: \" + str(validation_steps))\nprint(\"Steps per epoch: \" + str(steps_per_epoch))\nfile_id_mapping_train = {k: v for k, v in zip(train.Image.values, train.Id.values)}\nfile_id_mapping_test = {k: v for k, v in zip(test.Image.values, test.Id.values)}\ngen_train = create_aug_gen(gen(sample_gen(file_id_mapping_train),batch_size), batch_size)\n#gen_train = gen(sample_gen(file_id_mapping_train),batch_size)\ngen_test = gen(sample_gen(file_id_mapping_test),batch_size)\none_batch = next(gen_train)\nshow_triplets(one_batch[0], min(batch_size, 5))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4ef4e0d1ec9f7916b1c76b4cbb47e660e1519342"},"cell_type":"code","source":"def get_images_from_triplets(triplet_images, triplet_labels):\n    images = []\n    labels = []\n    images.extend(triplet_images['anchor_input'])\n    labels.extend(triplet_labels['positive_labels'])\n    images.extend(triplet_images['positive_input'])\n    labels.extend(triplet_labels['positive_labels'])\n    images.extend(triplet_images['negative_input'])\n    labels.extend(triplet_labels['negative_labels'])\n\n    return np.array(images), np.array(labels)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"scrolled":false,"_uuid":"bfd35d670de3ee116181e98ead459cb013a26c3f"},"cell_type":"code","source":"tsne = TSNE()\nimages, labels = next(gen_train)\nimages, labels = get_images_from_triplets(images, labels)\ntrain_tsne_embeds = tsne.fit_transform(images.reshape(-1, img_width*img_height*img_channels))\nscatter(train_tsne_embeds, labels, \"Samples from Training Data\")\n\nimages, labels = next(gen_test)\nimages, labels = get_images_from_triplets(images, labels)\ntrain_tsne_embeds = tsne.fit_transform(images.reshape(-1, img_width*img_height*img_channels))\nscatter(train_tsne_embeds, labels, \"Samples from Test Data\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d0bb5f8c5485ae736a7f98d953e048f53cd7d69f","scrolled":true},"cell_type":"code","source":"triplet_model, embedding_model = build_model()\nhistory = train_model(triplet_model, gen_train, gen_test)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4192be85b4bba9576c75eb5ee090a46ec0225ca6"},"cell_type":"code","source":"eval_plot(history, epochs)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d9cc646a89a74b09aba7fba991d322982540ecc8","scrolled":true},"cell_type":"code","source":"tsne = TSNE()\nimages, labels = next(gen_train)\nimages, labels = get_images_from_triplets(images, labels)\ntrain_tsne_embeds = tsne.fit_transform(embedding_model.predict(images))\nscatter(train_tsne_embeds, labels, \"Samples from Training Data\")\n\nimages, labels = next(gen_test)\nimages, labels = get_images_from_triplets(images, labels)\ntrain_tsne_embeds = tsne.fit_transform(embedding_model.predict(images))\nscatter(train_tsne_embeds, labels, \"Samples from Test Data\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"9914a1696795a3250e42ad4c77682e4f0aec23e9"},"cell_type":"markdown","source":"# Predicción"},{"metadata":{"trusted":true,"_uuid":"bfe8655ffe55b6cae8e2694ed23c39d2bb5a8c2c"},"cell_type":"code","source":"from sklearn.neighbors import NearestNeighbors\nfrom __future__ import division\n\ndef data_generator(file_mapping_whale_id, batch = 25):\n    i = 0\n    for file, whale_id in file_mapping_whale_id.items():\n        if i == 0:\n            whale_ids = []\n            whale_images = []\n        whale_image = file_mapping_image[file]\n        whale_images.append(whale_image)\n        whale_ids.append(whale_id)\n        i += 1\n        if i == batch:\n            i = 0\n            yield whale_ids, np.array(whale_images)\n    \n    if i != 0:\n        yield whale_ids, np.array(whale_images)\n        \n    raise StopIteration()\n            \ndef predict_embeddings(file_mapping_whale_id):\n    embedding_mapping_whale_id  = dict()\n    embedding_mapping_image = dict()\n    for whale_ids, whale_imgs in tqdm_notebook(data_generator(file_mapping_whale_id, batch=32)):\n        predict_embeddings = embedding_model.predict(whale_imgs)\n        for i, predict_embedding in enumerate(predict_embeddings):\n            embedding_mapping_whale_id[tuple(predict_embedding)] = whale_ids[i]\n            embedding_mapping_image[tuple(predict_embedding)] = whale_imgs[i]\n    \n    return embedding_mapping_whale_id, embedding_mapping_image\n\ndef remove_duplicates(predicted_whales):\n    unique_whales_id = []\n    no_duplicated_whales = []\n    for predicted_whale in predicted_whales:\n        if (predicted_whale['whale_id'] not in unique_whales_id):\n            no_duplicated_whales.append(predicted_whale)\n            unique_whales_id.append(predicted_whale['whale_id'])\n    \n    return no_duplicated_whales\n    \ndef predict_nearest_neighbors(train_whales, test_whales):\n    file_mapping_whale_id = {k: v for k, v in zip(train_whales.Image.values, train_whales.Id.values)}                      \n    embedding_mapping_whale_id, embedding_mapping_image = predict_embeddings(file_mapping_whale_id)\n    train_embeddings = list(embedding_mapping_whale_id.keys())\n    neigh = NearestNeighbors(n_neighbors=20)\n    neigh.fit(train_embeddings)\n    \n    test_whales_list = list(test_whales.items())\n    test_images = np.array([image for file, image in test_whales_list])\n    test_embeddings = embedding_model.predict(test_images)\n    distances_test, neighbors_test = neigh.kneighbors(test_embeddings)\n    predictions = []\n    for i, (distance_test, neighbor_test) in enumerate(zip(distances_test, neighbors_test)):\n        nearest_embedding = train_embeddings[neighbor_test[0]]\n        \n        predicted_whales = [{ \n            'whale_id': embedding_mapping_whale_id[train_embeddings[neighbor]],\n            'distance': distance\n        } for distance, neighbor in zip(distance_test, neighbor_test)]\n        if \"new_whale\" not in [prediction['whale_id'] for prediction in predicted_whales]:\n            predicted_whales.append({\n                'whale_id': \"new_whale\",\n                'distance': new_whale_distance\n            })\n        predicted_whales.sort(key=lambda prediction: prediction['distance'])\n        \n        predictions.append({ \n            'predicted_whale_id': remove_duplicates(predicted_whales)[:5],\n            'best_predicted_image': embedding_mapping_image[nearest_embedding], \n            'test_image': test_whales_list[i][1],\n            'test_file': test_whales_list[i][0]\n        })\n        \n    return predictions\n\ndef calculate_prediction_accuracy(train_whales, predictions):\n    file_mapping_whale_id = {k: v for k, v in zip(train_whales.Image.values, train_whales.Id.values)}\n    accuracy = 0\n    for prediction in predictions:\n        accuracy += 1 if file_mapping_whale_id[prediction['test_file']] in [predicted_whale_id['whale_id'] for predicted_whale_id in prediction['predicted_whale_id']] else 0\n        \n    return accuracy / len(predictions)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"ed13162436fb1803fe40ddfa773d8577ec38eb93","scrolled":false},"cell_type":"code","source":"train, test = train_test_split(train_whales, train_size=0.7, test_size = 0.3, random_state=1337)\ntest_whales = dict([(whale['Image'], file_mapping_image[whale['Image']]) for i, whale in test.iterrows()])\npredictions = predict_nearest_neighbors(train, test_whales)\nshow_predictions(predictions)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"2369ee0256f27cb3b78dabc29f8510ced88ddcc8"},"cell_type":"code","source":"calculate_prediction_accuracy(train_whales, predictions)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"scrolled":false,"_uuid":"7fdbe4ae09434e252038787d078f48da6147d812"},"cell_type":"code","source":"test_whales = load_images(read_test_files(), img_height, img_width)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"da2abb2ec542c65e70da3e963724ff1f327d281c"},"cell_type":"code","source":"predictions = predict_nearest_neighbors(train_whales, test_whales)\nshow_predictions(predictions)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"206becff8b741b85b94d51beb94e95bc4f3095e8"},"cell_type":"code","source":"def create_results(predictions):\n    results = {\n        'Id' : [],\n        'Image': []\n    }\n    for prediction in predictions:\n        results['Id'].append(\" \".join([prediction['whale_id'] for prediction in prediction['predicted_whale_id']]))\n        results['Image'].append(prediction['test_file'])\n        \n    return results","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"fdfff3e4a88f814a8d4d7ba623f2314368c34248"},"cell_type":"code","source":"results = create_results(predictions)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"d6debeb42ca96018f605bdbbf4c044d464892650"},"cell_type":"code","source":"df = pd.DataFrame(data=results)\ndf.to_csv(\"sub_humpback.csv\", index=False)\ndf.head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6db470be7c02a47f1aa883b6141c9a37bf20a1a6"},"cell_type":"markdown","source":"# Links\n\n* [Visualizing t-SNE embeddings on raw data.](https://www.kaggle.com/guichristmann/training-a-triplet-loss-model-on-mnist)\n* [Lossless Triplet loss](https://towardsdatascience.com/lossless-triplet-loss-7e932f990b24)\n* https://www.kaggle.com/ashishpatel26/triplet-loss-network-for-humpback-whale-prediction\n* https://github.com/gujiuxiang/humpback_whale_identification/blob/master/train.py\n* https://www.kaggle.com/iafoss/similarity-resnext50-0-740-lb-kernel-time-limit/notebook"},{"metadata":{"_uuid":"53eccd8501342bb417b1b60b17e7078446135b31"},"cell_type":"markdown","source":""}],"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}