{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Siamese Network","metadata":{}},{"cell_type":"code","source":"import random\nimport numpy as np\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nimport matplotlib.pyplot as plt","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:15:23.238868Z","iopub.execute_input":"2023-03-24T09:15:23.240102Z","iopub.status.idle":"2023-03-24T09:15:34.22483Z","shell.execute_reply.started":"2023-03-24T09:15:23.240057Z","shell.execute_reply":"2023-03-24T09:15:34.222511Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"epochs = 10\nbatch_size = 16\nmargin = 1  # Margin for constrastive loss.","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:15:34.227415Z","iopub.execute_input":"2023-03-24T09:15:34.228721Z","iopub.status.idle":"2023-03-24T09:15:34.237437Z","shell.execute_reply.started":"2023-03-24T09:15:34.228666Z","shell.execute_reply":"2023-03-24T09:15:34.235296Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"(x_train_val, y_train_val), (x_test, y_test)= keras.datasets.mnist.load_data()\nx_train_val= x_train_val.astype(\"float32\")\nx_test = x_test.astype(\"float32\")","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:15:34.239869Z","iopub.execute_input":"2023-03-24T09:15:34.241056Z","iopub.status.idle":"2023-03-24T09:15:35.667431Z","shell.execute_reply.started":"2023-03-24T09:15:34.240984Z","shell.execute_reply":"2023-03-24T09:15:35.665995Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Combine train and validation data\nx_train_val = np.concatenate((x_train, x_val), axis=0)\ny_train_val = np.concatenate((y_train, y_val), axis=0)\n\n# Keep 50% of train_val in validation set\ntrain_val_size = len(x_train_val)\nval_size = int(train_val_size * 0.5)\nx_train, x_val = x_train_val[:train_val_size - val_size], x_train_val[train_val_size - val_size:]\ny_train, y_val = y_train_val[:train_val_size - val_size], y_train_val[train_val_size - val_size:]","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:18:50.412292Z","iopub.execute_input":"2023-03-24T09:18:50.412718Z","iopub.status.idle":"2023-03-24T09:18:50.522274Z","shell.execute_reply.started":"2023-03-24T09:18:50.412681Z","shell.execute_reply":"2023-03-24T09:18:50.520858Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Make couple pictures","metadata":{}},{"cell_type":"code","source":"def make_pairs(x, y):\n    \"\"\"Creates a tuple containing image pairs with corresponding label.\n\n    Arguments:\n        x: List containing images, each index in this list corresponds to one image.\n        y: List containing labels, each label with datatype of `int`.\n\n    Returns:\n        Tuple containing two numpy arrays as (pairs_of_samples, labels),\n        where pairs_of_samples' shape is (2len(x), 2,n_features_dims) and\n        labels are a binary array of shape (2len(x)).\n    \"\"\"\n\n    num_classes = max(y) + 1\n    digit_indices = [np.where(y == i)[0] for i in range(num_classes)]\n\n    pairs = []\n    labels = []\n\n    for idx1 in range(len(x)):\n        # add a matching example\n        x1 = x[idx1]\n        label1 = y[idx1]\n        idx2 = random.choice(digit_indices[label1])\n        x2 = x[idx2]\n\n        pairs += [[x1, x2]]\n        labels += [0]\n\n        # add a non-matching example\n        label2 = random.randint(0, num_classes - 1)\n        while label2 == label1:\n            label2 = random.randint(0, num_classes - 1)\n\n        idx2 = random.choice(digit_indices[label2])\n        x2 = x[idx2]\n\n        pairs += [[x1, x2]]\n        labels += [1]\n\n    return np.array(pairs), np.array(labels).astype(\"float32\")\n\n\n# make train pairs\npairs_train, labels_train = make_pairs(x_train, y_train)\n\n# make validation pairs\npairs_val, labels_val = make_pairs(x_val, y_val)\n\n# make test pairs\npairs_test, labels_test = make_pairs(x_test, y_test)","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:18:55.61342Z","iopub.execute_input":"2023-03-24T09:18:55.613871Z","iopub.status.idle":"2023-03-24T09:18:57.16576Z","shell.execute_reply.started":"2023-03-24T09:18:55.613831Z","shell.execute_reply":"2023-03-24T09:18:57.164438Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Split the training pairs","metadata":{}},{"cell_type":"code","source":"x_train_1 = pairs_train[:, 0]  # x_train_1.shape is (60000, 28, 28)\nx_train_2 = pairs_train[:, 1]","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:19:07.304089Z","iopub.execute_input":"2023-03-24T09:19:07.304526Z","iopub.status.idle":"2023-03-24T09:19:07.310797Z","shell.execute_reply.started":"2023-03-24T09:19:07.304477Z","shell.execute_reply":"2023-03-24T09:19:07.309334Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Split the validation pairs","metadata":{}},{"cell_type":"code","source":"x_val_1 = pairs_val[:, 0]  # x_val_1.shape = (60000, 28, 28)\nx_val_2 = pairs_val[:, 1]","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:19:11.145676Z","iopub.execute_input":"2023-03-24T09:19:11.146101Z","iopub.status.idle":"2023-03-24T09:19:11.152149Z","shell.execute_reply.started":"2023-03-24T09:19:11.146066Z","shell.execute_reply":"2023-03-24T09:19:11.150689Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Split the test pairs","metadata":{}},{"cell_type":"code","source":"x_test_1 = pairs_test[:, 0]  # x_test_1.shape = (20000, 28, 28)\nx_test_2 = pairs_test[:, 1]\n","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:19:21.397878Z","iopub.execute_input":"2023-03-24T09:19:21.398347Z","iopub.status.idle":"2023-03-24T09:19:21.404644Z","shell.execute_reply.started":"2023-03-24T09:19:21.398286Z","shell.execute_reply":"2023-03-24T09:19:21.40341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualize pairs and their labels","metadata":{}},{"cell_type":"code","source":"def visualize(pairs, labels, to_show=6, num_col=3, predictions=None, test=False):\n    \"\"\"Creates a plot of pairs and labels, and prediction if it's test dataset.\n\n    Arguments:\n        pairs: Numpy Array, of pairs to visualize, having shape\n               (Number of pairs, 2, 28, 28).\n        to_show: Int, number of examples to visualize (default is 6)\n                `to_show` must be an integral multiple of `num_col`.\n                 Otherwise it will be trimmed if it is greater than num_col,\n                 and incremented if if it is less then num_col.\n        num_col: Int, number of images in one row - (default is 3)\n                 For test and train respectively, it should not exceed 3 and 7.\n        predictions: Numpy Array of predictions with shape (to_show, 1) -\n                     (default is None)\n                     Must be passed when test=True.\n        test: Boolean telling whether the dataset being visualized is\n              train dataset or test dataset - (default False).\n\n    Returns:\n        None.\n    \"\"\"\n\n\n    num_row = to_show // num_col if to_show // num_col != 0 else 1\n\n    to_show = num_row * num_col\n\n    # Plot the images\n    fig, axes = plt.subplots(num_row, num_col, figsize=(5, 5))\n    for i in range(to_show):\n\n        # If the number of rows is 1, the axes array is one-dimensional\n        if num_row == 1:\n            ax = axes[i % num_col]\n        else:\n            ax = axes[i // num_col, i % num_col]\n\n        ax.imshow(tf.concat([pairs[i][0], pairs[i][1]], axis=1), cmap=\"gray\")\n        ax.set_axis_off()\n        if test:\n            ax.set_title(\"True: {} | Pred: {:.5f}\".format(labels[i], predictions[i][0]))\n        else:\n            ax.set_title(\"Label: {}\".format(labels[i]))\n    if test:\n        plt.tight_layout(rect=(0, 0, 1.9, 1.9), w_pad=0.0)\n    else:\n        plt.tight_layout(rect=(0, 0, 1.5, 1.5))\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:19:28.817675Z","iopub.execute_input":"2023-03-24T09:19:28.818265Z","iopub.status.idle":"2023-03-24T09:19:28.831006Z","shell.execute_reply.started":"2023-03-24T09:19:28.818215Z","shell.execute_reply":"2023-03-24T09:19:28.829353Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inspect training pairs","metadata":{}},{"cell_type":"code","source":"visualize(pairs_train[:-1], labels_train[:-1], to_show=4, num_col=4)","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:19:32.053608Z","iopub.execute_input":"2023-03-24T09:19:32.054952Z","iopub.status.idle":"2023-03-24T09:19:32.31486Z","shell.execute_reply.started":"2023-03-24T09:19:32.054889Z","shell.execute_reply":"2023-03-24T09:19:32.313457Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inspect validation pairs","metadata":{}},{"cell_type":"code","source":"visualize(pairs_val[:-1], labels_val[:-1], to_show=4, num_col=4)","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:19:35.533884Z","iopub.execute_input":"2023-03-24T09:19:35.53505Z","iopub.status.idle":"2023-03-24T09:19:35.899849Z","shell.execute_reply.started":"2023-03-24T09:19:35.534998Z","shell.execute_reply":"2023-03-24T09:19:35.898391Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Inspect test pairs","metadata":{}},{"cell_type":"code","source":"visualize(pairs_test[:-1], labels_test[:-1], to_show=4, num_col=4)","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:19:42.439871Z","iopub.execute_input":"2023-03-24T09:19:42.440295Z","iopub.status.idle":"2023-03-24T09:19:42.598627Z","shell.execute_reply.started":"2023-03-24T09:19:42.440258Z","shell.execute_reply":"2023-03-24T09:19:42.596529Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Pada model ini terdapat dua layer input","metadata":{}},{"cell_type":"code","source":"# Provided two tensors t1 and t2\n# Euclidean distance = sqrt(sum(square(t1-t2)))\ndef euclidean_distance(vects):\n    \"\"\"Find the Euclidean distance between two vectors.\n\n    Arguments:\n        vects: List containing two tensors of same length.\n\n    Returns:\n        Tensor containing euclidean distance\n        (as floating point value) between vectors.\n    \"\"\"\n\n    x, y = vects\n    sum_square = tf.math.reduce_sum(tf.math.square(x - y), axis=1, keepdims=True)\n    return tf.math.sqrt(tf.math.maximum(sum_square, tf.keras.backend.epsilon()))\n\n\ninput = layers.Input((28, 28, 1))\nx = tf.keras.layers.BatchNormalization()(input)\nx = layers.Conv2D(4, (5, 5), activation=\"tanh\")(x)\nx = layers.AveragePooling2D(pool_size=(2, 2))(x)\nx = layers.Conv2D(16, (5, 5), activation=\"tanh\")(x)\nx = layers.AveragePooling2D(pool_size=(2, 2))(x)\nx = layers.Flatten()(x)\n\nx = tf.keras.layers.BatchNormalization()(x)\nx = layers.Dense(10, activation=\"tanh\")(x)\nembedding_network = keras.Model(input, x)\n\n\ninput_1 = layers.Input((28, 28, 1))\ninput_2 = layers.Input((28, 28, 1))\n\n# As mentioned above, Siamese Network share weights between\n# tower networks (sister networks). To allow this, we will use\n# same embedding network for both tower networks.\ntower_1 = embedding_network(input_1)\ntower_2 = embedding_network(input_2)\n\nmerge_layer = layers.Lambda(euclidean_distance)([tower_1, tower_2])\nnormal_layer = tf.keras.layers.BatchNormalization()(merge_layer)\noutput_layer = layers.Dense(1, activation=\"sigmoid\")(normal_layer)\nsiamese = keras.Model(inputs=[input_1, input_2], outputs=output_layer)\n","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:19:47.286794Z","iopub.execute_input":"2023-03-24T09:19:47.287216Z","iopub.status.idle":"2023-03-24T09:19:47.512655Z","shell.execute_reply.started":"2023-03-24T09:19:47.287179Z","shell.execute_reply":"2023-03-24T09:19:47.511314Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Define the constrastive Loss","metadata":{}},{"cell_type":"code","source":"\ndef loss(margin=1):\n    \"\"\"Provides 'constrastive_loss' an enclosing scope with variable 'margin'.\n\n    Arguments:\n        margin: Integer, defines the baseline for distance for which pairs\n                should be classified as dissimilar. - (default is 1).\n\n    Returns:\n        'constrastive_loss' function with data ('margin') attached.\n    \"\"\"\n\n    # Contrastive loss = mean( (1-true_value) * square(prediction) +\n    #                         true_value * square( max(margin-prediction, 0) ))\n    def contrastive_loss(y_true, y_pred):\n        \"\"\"Calculates the constrastive loss.\n\n        Arguments:\n            y_true: List of labels, each label is of type float32.\n            y_pred: List of predictions of same length as of y_true,\n                    each label is of type float32.\n\n        Returns:\n            A tensor containing constrastive loss as floating point value.\n        \"\"\"\n\n        square_pred = tf.math.square(y_pred)\n        margin_square = tf.math.square(tf.math.maximum(margin - (y_pred), 0))\n        return tf.math.reduce_mean(\n            (1 - y_true) * square_pred + (y_true) * margin_square\n        )\n\n    return contrastive_loss\n","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:19:50.815778Z","iopub.execute_input":"2023-03-24T09:19:50.816198Z","iopub.status.idle":"2023-03-24T09:19:50.825217Z","shell.execute_reply.started":"2023-03-24T09:19:50.816163Z","shell.execute_reply":"2023-03-24T09:19:50.823357Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Compile the model with the contrastive loss","metadata":{}},{"cell_type":"code","source":"siamese.compile(loss=loss(margin=margin), optimizer=\"RMSprop\", metrics=[\"acuracy\"])\nsiamese.summary()","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:19:53.783261Z","iopub.execute_input":"2023-03-24T09:19:53.78374Z","iopub.status.idle":"2023-03-24T09:19:53.828375Z","shell.execute_reply.started":"2023-03-24T09:19:53.783695Z","shell.execute_reply":"2023-03-24T09:19:53.82713Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train the model","metadata":{}},{"cell_type":"code","source":"from keras.optimizers import Adam\n\noptimizer = Adam(learning_rate=0.001)\n\nsiamese.compile(loss=\"binary_crossentropy\", optimizer=optimizer, metrics=[\"accuracy\"])\n\nhistory = siamese.fit(\n    [x_train_1, x_train_2], \n    labels_train,\n    validation_data=([x_val_1, x_val_2], labels_val),\n    batch_size=batch_size,\n    epochs=epochs,\n    )","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:20:02.607159Z","iopub.execute_input":"2023-03-24T09:20:02.608077Z","iopub.status.idle":"2023-03-24T09:28:26.853608Z","shell.execute_reply.started":"2023-03-24T09:20:02.608026Z","shell.execute_reply":"2023-03-24T09:28:26.851946Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualize results","metadata":{}},{"cell_type":"code","source":"\ndef plt_metric(history, metric, title, has_valid=True):\n    \"\"\"Plots the given 'metric' from 'history'.\n\n    Arguments:\n        history: history attribute of History object returned from Model.fit.\n        metric: Metric to plot, a string value present as key in 'history'.\n        title: A string to be used as title of plot.\n        has_valid: Boolean, true if valid data was passed to Model.fit else false.\n\n    Returns:\n        None.\n    \"\"\"\n    plt.plot(history[metric])\n    if has_valid:\n        plt.plot(history[\"val_\" + metric])\n        plt.legend([\"train\", \"validation\"], loc=\"upper left\")\n    plt.title(title)\n    plt.ylabel(metric)\n    plt.xlabel(\"epoch\")\n    plt.show()\n\n\n# Plot the accuracy\nplt_metric(history=history.history, metric=\"accuracy\", title=\"Model accuracy\")\n\n# Plot the constrastive loss\nplt_metric(history=history.history, metric=\"loss\", title=\"Constrastive Loss\")","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:28:26.855606Z","iopub.execute_input":"2023-03-24T09:28:26.855972Z","iopub.status.idle":"2023-03-24T09:28:27.353517Z","shell.execute_reply.started":"2023-03-24T09:28:26.855937Z","shell.execute_reply":"2023-03-24T09:28:27.352174Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Evaluate the model","metadata":{}},{"cell_type":"code","source":"results = siamese.evaluate([x_test_1, x_test_2], labels_test)\nprint(\"test loss, test acc:\", results)","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:28:36.247641Z","iopub.execute_input":"2023-03-24T09:28:36.248087Z","iopub.status.idle":"2023-03-24T09:28:40.86821Z","shell.execute_reply.started":"2023-03-24T09:28:36.248045Z","shell.execute_reply":"2023-03-24T09:28:40.866971Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualize the predictions","metadata":{}},{"cell_type":"code","source":"# prediksi dan visualisasi hasil prediksi\npredictions = siamese.predict([x_test_1, x_test_2])\nvisualize(pairs_test, labels_test, to_show=3)\nprint(\"Predictions:\", predictions)","metadata":{"execution":{"iopub.status.busy":"2023-03-24T09:28:43.688698Z","iopub.execute_input":"2023-03-24T09:28:43.689141Z","iopub.status.idle":"2023-03-24T09:28:49.460045Z","shell.execute_reply.started":"2023-03-24T09:28:43.689099Z","shell.execute_reply":"2023-03-24T09:28:49.458264Z"},"trusted":true},"execution_count":null,"outputs":[]}]}