Techno Blender
Digitally Yours.

Siamese Neural Networks with TensorFlow Functional API | by Tan Pengshi Alvin | May, 2023

0 20


2.1 Exploring the CIFAR-10 Data Set

# import necessary libraries
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

# set random seed
np.random.seed(42)

# load CIFAR-10 data
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.cifar10.load_data()

# check data size
assert X_train.shape == (50000, 32, 32, 3)
assert X_test.shape == (10000, 32, 32, 3)
assert y_train.shape == (50000, 1)
assert y_test.shape == (10000, 1)

# combine data first - we will generate test set later.
X = np.concatenate([X_train,X_test],axis=0)
y = np.concatenate([y_train,y_test],axis=0)
y = np.squeeze(y)

assert X.shape == (60000, 32, 32, 3)
assert y.shape == (60000,)

# check number of data in each class
unique, counts = np.unique(y,return_counts=True)
np.asarray([unique,counts]).T

# Plot Class N (0-9)

TARGET = # Class index here
NUM_ARRAYS = 10

arrays = X[np.where(y==TARGET)]
random_arrays_indices = np.random.choice(len(arrays),NUM_ARRAYS)
random_arrays = arrays[random_arrays_indices]

fig = plt.figure(figsize=[NUM_ARRAYS,4])
plt.title('Class 0: Plane',fontsize = 15)
plt.axis('off')

for index in range(NUM_ARRAYS):
fig.add_subplot(2, int(NUM_ARRAYS/2), index+1)
plt.imshow(random_arrays[index])

2.2 Generating Triplets

# initialize triplets array
triplets = np.empty((0,3,32,32,3),dtype=np.uint8)

# get triplets for each class
for target in range(10):

locals()['arrays_'+str(target)] = X[np.where(y==target)].reshape(3000,2,32,32,3)
locals()['arrays_not_'+str(target)] = X[np.where(y!=target)]

random_indices = np.random.choice(len(locals()['arrays_not_'+str(target)]),3000)
locals()['arrays_not_'+str(target)] = locals()['arrays_not_'+str(target)][random_indices]

locals()['arrays_'+str(target)] = np.concatenate(
[
locals()['arrays_'+str(target)],
locals()['arrays_not_'+str(target)].reshape(3000,1,32,32,3)
],
axis = 1
)

triplets = np.concatenate([triplets,locals()['arrays_'+str(target)]],axis=0)

# check triplets size
assert triplets.shape == (30000,3,32,32,3)

# plot triplets array to visualize
TEST_SIZE = 5
random_indices = np.random.choice(len(triplets),TEST_SIZE)

fig = plt.figure(figsize=[5,2*TEST_SIZE])
plt.title('ANCHOR | POSITIVE | NEGATIVE',fontsize = 15)
plt.axis('off')

for row,i in enumerate(range(0,TEST_SIZE*3,3)):
for j in range(1,4):
fig.add_subplot(TEST_SIZE, 3, i+j)
random_index = random_indices[row]
plt.imshow(triplets[random_index,j-1])

# save triplet array
np.save('triplets_array.npy',triplets)

2.3 Preparing for Model Training/Evaluation

# Import all libraries

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras import Input, optimizers, Model
from tensorflow.keras.layers import Layer, Lambda
from tensorflow.keras.optimizers import Adam
from tensorflow.keras import backend as K
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.utils import plot_model

from sklearn.metrics import precision_recall_curve, roc_curve, roc_auc_score
from sklearn.model_selection import train_test_split

from scipy import spatial

triplets = np.load('triplets_array.npy')

triplets = triplets/255 #normalize by 255
labels = np.ones(len(triplets)) #create a fixed label

assert triplets.shape == (30000,3,32,32,3)

# Split data into our train and test set

X_train, X_test, y_train, y_test = train_test_split(
triplets,
labels,
test_size=0.05,
random_state=42
)

# Load pretrained model for transfer learning

pretrained_model = MobileNetV2(
weights='imagenet',
include_top=False,
input_shape=(32,32,3)
)

for layer in pretrained_model.layers:
layer.trainable = True

2.4 Model Training

# Initialize functions for Lambda Layer

def cosine_distance(x,y):
x = K.l2_normalize(x, axis=-1)
y = K.l2_normalize(y, axis=-1)
distance = 1 - K.batch_dot(x, y, axes=-1)
return distance

def triplet_loss(templates, margin=0.4):

anchor,positive,negative = templates

positive_distance = cosine_distance(anchor,positive)
negative_distance = cosine_distance(anchor,negative)

basic_loss = positive_distance-negative_distance+margin
loss = K.maximum(basic_loss,0.0)

return loss

# Adopting the TensorFlow Functional API

anchor = Input(shape=(32, 32,3), name='anchor_input')
A = pretrained_model(anchor)

positive = Input(shape=(32, 32,3), name='positive_input')
P = pretrained_model(positive)

negative = Input(shape=(32, 32,3), name='negative_input')
N = pretrained_model(negative)

loss = Lambda(triplet_loss)([A, P, N])

model = Model(inputs=[anchor,positive,negative],outputs=loss)

# Create a custom loss function since there are no ground truths label

def identity_loss(y_true, y_pred):
return K.mean(y_pred)

model.compile(loss=identity_loss, optimizer=Adam(learning_rate=1e-4))

callbacks=[EarlyStopping(
patience=2,
verbose=1,
restore_best_weights=True,
monitor='val_loss'
)]

# view model
plot_model(model, show_shapes=True, show_layer_names=True, to_file='siamese_triplet_loss_model.png')

# Start training - y_train and y_test are dummy

model.fit(
[X_train[:,0],X_train[:,1],X_train[:,2]],
y_train,
epochs=50,
batch_size=64,
validation_data=([X_test[:,0],X_test[:,1],X_test[:,2]],y_test),
callbacks=callbacks
)

2.5 Model Evaluation

X_test_anchor = X_test[:,0]
X_test_positive = X_test[:,1]
X_test_negative = X_test[:,2]

# extract the CNN model for inference
siamese_model = model.layers[3]

X_test_anchor_template = np.squeeze(siamese_model.predict(X_test_anchor))
X_test_positive_template = np.squeeze(siamese_model.predict(X_test_positive))
X_test_negative_template = np.squeeze(siamese_model.predict(X_test_negative))

y_test_targets = np.concatenate([np.ones((len(X_test),)),np.zeros((len(X_test),))])

# Get predictions in angular similarity scores

def angular_similarity(template1,template2):

score = np.float32(1-np.arccos(1-spatial.distance.cosine(template1,template2))/np.pi)

return score

y_predict_targets = []

for index in range(len(X_test)):
similarity = angular_similarity(X_test_anchor_template[index],X_test_positive_template[index])
y_predict_targets.append(similarity)

for index in range(len(X_test)):
similarity = angular_similarity(X_test_anchor_template[index],X_test_negative_template[index])
y_predict_targets.append(similarity)

# Get prediction results with ROC Curve and AUC scores

fpr, tpr, thresholds = roc_curve(y_test_targets, y_predict_targets)

fig = plt.figure(figsize=[10,7])
plt.plot(fpr, tpr,lw=2,label='UnoFace_v2 (AUC={:.3f})'.format(roc_auc_score(y_test_targets, y_predict_targets)))
plt.plot([0,1],[0,1],c='violet',ls='--')
plt.xlim([-0.05,1.05])
plt.ylim([-0.05,1.05])
plt.legend(loc="lower right",fontsize=15)

plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('Receiver Operating Characteristic (ROC) Curve',weight='bold',fontsize=15)

# Getting Test Pairs and their Corresponding Predictions

positive_comparisons = X_test[:,[0,1]]
negative_comparisons = X_test[:,[0,2]]

positive_predict_targets = np.array(y_predict_targets)[:1500]
negative_predict_targets = np.array(y_predict_targets)[1500:]

assert positive_comparisons.shape == (1500,2,32,32,3)
assert negative_comparisons.shape == (1500,2,32,32,3)

assert positive_predict_targets.shape == (1500,)
assert negative_predict_targets.shape == (1500,)

np.random.seed(21)
NUM_EXAMPLES = 5
random_index = np.random.choice(range(len(positive_comparisons)),NUM_EXAMPLES)

# Plotting Similarity Scores for Positive Comparisons 
# (Switch values and input to plot for Negative Comparisons)

plt.figure(figsize=(10,4))
plt.title('Positive Comparisons and Their Similarity Scores')
plt.ylabel('Anchors')
plt.yticks([])
plt.xticks([32*x+16 for x in range(NUM_EXAMPLES)], ['.' for x in range(NUM_EXAMPLES)])
for i,t in enumerate(plt.gca().xaxis.get_ticklabels()):
t.set_color('green')
plt.grid(None)
anchor = np.swapaxes(positive_comparisons[:,0][random_index],0,1)
anchor = np.reshape(anchor,[32,NUM_EXAMPLES*32,3])
plt.imshow(anchor)

plt.figure(figsize=(10,4))
plt.ylabel('Positives')
plt.yticks([])
plt.xticks([32*x+16 for x in range(NUM_EXAMPLES)], positive_predict_targets[random_index])
for i,t in enumerate(plt.gca().xaxis.get_ticklabels()):
t.set_color('green')
plt.grid(None)
positive = np.swapaxes(positive_comparisons[:,1][random_index],0,1)
positive = np.reshape(positive,[32,NUM_EXAMPLES*32,3])
plt.imshow(positive)

Congratulations on completing the theory and code-along! I hope this tutorial has provided an all-around useful introduction to Siamese Networks and their application to object similarity.

Before we end, I should also add that how the object similarity scores are processed depends on the problem statement.

If we are doing a 1:1 object comparison during production (whether 2 objects are similar or different), then usually a similarity threshold must be set based on the level of False Match Rate (FMR) at test time. On the other hand, if we are doing 1:N object matching, then usually the objects with the highest similarity scores are returned and ranked.

Note: For the complete codes, check out my GitHub.

With that, I thank you for your time and hope you enjoyed this tutorial. I would also like to end off by introducing you to an extremely important topic of data-centric machine learning that is elaborated on in this article:

Thanks for reading! If you have enjoyed the content, pop by my other articles on Medium and follow me on LinkedIn.

Support me! — If you are not subscribed to Medium, and like my content, do consider supporting me by joining Medium via my referral link.


2.1 Exploring the CIFAR-10 Data Set

# import necessary libraries
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

# set random seed
np.random.seed(42)

# load CIFAR-10 data
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.cifar10.load_data()

# check data size
assert X_train.shape == (50000, 32, 32, 3)
assert X_test.shape == (10000, 32, 32, 3)
assert y_train.shape == (50000, 1)
assert y_test.shape == (10000, 1)

# combine data first - we will generate test set later.
X = np.concatenate([X_train,X_test],axis=0)
y = np.concatenate([y_train,y_test],axis=0)
y = np.squeeze(y)

assert X.shape == (60000, 32, 32, 3)
assert y.shape == (60000,)

# check number of data in each class
unique, counts = np.unique(y,return_counts=True)
np.asarray([unique,counts]).T

# Plot Class N (0-9)

TARGET = # Class index here
NUM_ARRAYS = 10

arrays = X[np.where(y==TARGET)]
random_arrays_indices = np.random.choice(len(arrays),NUM_ARRAYS)
random_arrays = arrays[random_arrays_indices]

fig = plt.figure(figsize=[NUM_ARRAYS,4])
plt.title('Class 0: Plane',fontsize = 15)
plt.axis('off')

for index in range(NUM_ARRAYS):
fig.add_subplot(2, int(NUM_ARRAYS/2), index+1)
plt.imshow(random_arrays[index])

2.2 Generating Triplets

# initialize triplets array
triplets = np.empty((0,3,32,32,3),dtype=np.uint8)

# get triplets for each class
for target in range(10):

locals()['arrays_'+str(target)] = X[np.where(y==target)].reshape(3000,2,32,32,3)
locals()['arrays_not_'+str(target)] = X[np.where(y!=target)]

random_indices = np.random.choice(len(locals()['arrays_not_'+str(target)]),3000)
locals()['arrays_not_'+str(target)] = locals()['arrays_not_'+str(target)][random_indices]

locals()['arrays_'+str(target)] = np.concatenate(
[
locals()['arrays_'+str(target)],
locals()['arrays_not_'+str(target)].reshape(3000,1,32,32,3)
],
axis = 1
)

triplets = np.concatenate([triplets,locals()['arrays_'+str(target)]],axis=0)

# check triplets size
assert triplets.shape == (30000,3,32,32,3)

# plot triplets array to visualize
TEST_SIZE = 5
random_indices = np.random.choice(len(triplets),TEST_SIZE)

fig = plt.figure(figsize=[5,2*TEST_SIZE])
plt.title('ANCHOR | POSITIVE | NEGATIVE',fontsize = 15)
plt.axis('off')

for row,i in enumerate(range(0,TEST_SIZE*3,3)):
for j in range(1,4):
fig.add_subplot(TEST_SIZE, 3, i+j)
random_index = random_indices[row]
plt.imshow(triplets[random_index,j-1])

# save triplet array
np.save('triplets_array.npy',triplets)

2.3 Preparing for Model Training/Evaluation

# Import all libraries

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt

from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras import Input, optimizers, Model
from tensorflow.keras.layers import Layer, Lambda
from tensorflow.keras.optimizers import Adam
from tensorflow.keras import backend as K
from tensorflow.keras.callbacks import EarlyStopping
from tensorflow.keras.utils import plot_model

from sklearn.metrics import precision_recall_curve, roc_curve, roc_auc_score
from sklearn.model_selection import train_test_split

from scipy import spatial

triplets = np.load('triplets_array.npy')

triplets = triplets/255 #normalize by 255
labels = np.ones(len(triplets)) #create a fixed label

assert triplets.shape == (30000,3,32,32,3)

# Split data into our train and test set

X_train, X_test, y_train, y_test = train_test_split(
triplets,
labels,
test_size=0.05,
random_state=42
)

# Load pretrained model for transfer learning

pretrained_model = MobileNetV2(
weights='imagenet',
include_top=False,
input_shape=(32,32,3)
)

for layer in pretrained_model.layers:
layer.trainable = True

2.4 Model Training

# Initialize functions for Lambda Layer

def cosine_distance(x,y):
x = K.l2_normalize(x, axis=-1)
y = K.l2_normalize(y, axis=-1)
distance = 1 - K.batch_dot(x, y, axes=-1)
return distance

def triplet_loss(templates, margin=0.4):

anchor,positive,negative = templates

positive_distance = cosine_distance(anchor,positive)
negative_distance = cosine_distance(anchor,negative)

basic_loss = positive_distance-negative_distance+margin
loss = K.maximum(basic_loss,0.0)

return loss

# Adopting the TensorFlow Functional API

anchor = Input(shape=(32, 32,3), name='anchor_input')
A = pretrained_model(anchor)

positive = Input(shape=(32, 32,3), name='positive_input')
P = pretrained_model(positive)

negative = Input(shape=(32, 32,3), name='negative_input')
N = pretrained_model(negative)

loss = Lambda(triplet_loss)([A, P, N])

model = Model(inputs=[anchor,positive,negative],outputs=loss)

# Create a custom loss function since there are no ground truths label

def identity_loss(y_true, y_pred):
return K.mean(y_pred)

model.compile(loss=identity_loss, optimizer=Adam(learning_rate=1e-4))

callbacks=[EarlyStopping(
patience=2,
verbose=1,
restore_best_weights=True,
monitor='val_loss'
)]

# view model
plot_model(model, show_shapes=True, show_layer_names=True, to_file='siamese_triplet_loss_model.png')

# Start training - y_train and y_test are dummy

model.fit(
[X_train[:,0],X_train[:,1],X_train[:,2]],
y_train,
epochs=50,
batch_size=64,
validation_data=([X_test[:,0],X_test[:,1],X_test[:,2]],y_test),
callbacks=callbacks
)

2.5 Model Evaluation

X_test_anchor = X_test[:,0]
X_test_positive = X_test[:,1]
X_test_negative = X_test[:,2]

# extract the CNN model for inference
siamese_model = model.layers[3]

X_test_anchor_template = np.squeeze(siamese_model.predict(X_test_anchor))
X_test_positive_template = np.squeeze(siamese_model.predict(X_test_positive))
X_test_negative_template = np.squeeze(siamese_model.predict(X_test_negative))

y_test_targets = np.concatenate([np.ones((len(X_test),)),np.zeros((len(X_test),))])

# Get predictions in angular similarity scores

def angular_similarity(template1,template2):

score = np.float32(1-np.arccos(1-spatial.distance.cosine(template1,template2))/np.pi)

return score

y_predict_targets = []

for index in range(len(X_test)):
similarity = angular_similarity(X_test_anchor_template[index],X_test_positive_template[index])
y_predict_targets.append(similarity)

for index in range(len(X_test)):
similarity = angular_similarity(X_test_anchor_template[index],X_test_negative_template[index])
y_predict_targets.append(similarity)

# Get prediction results with ROC Curve and AUC scores

fpr, tpr, thresholds = roc_curve(y_test_targets, y_predict_targets)

fig = plt.figure(figsize=[10,7])
plt.plot(fpr, tpr,lw=2,label='UnoFace_v2 (AUC={:.3f})'.format(roc_auc_score(y_test_targets, y_predict_targets)))
plt.plot([0,1],[0,1],c='violet',ls='--')
plt.xlim([-0.05,1.05])
plt.ylim([-0.05,1.05])
plt.legend(loc="lower right",fontsize=15)

plt.xlabel('False positive rate')
plt.ylabel('True positive rate')
plt.title('Receiver Operating Characteristic (ROC) Curve',weight='bold',fontsize=15)

# Getting Test Pairs and their Corresponding Predictions

positive_comparisons = X_test[:,[0,1]]
negative_comparisons = X_test[:,[0,2]]

positive_predict_targets = np.array(y_predict_targets)[:1500]
negative_predict_targets = np.array(y_predict_targets)[1500:]

assert positive_comparisons.shape == (1500,2,32,32,3)
assert negative_comparisons.shape == (1500,2,32,32,3)

assert positive_predict_targets.shape == (1500,)
assert negative_predict_targets.shape == (1500,)

np.random.seed(21)
NUM_EXAMPLES = 5
random_index = np.random.choice(range(len(positive_comparisons)),NUM_EXAMPLES)

# Plotting Similarity Scores for Positive Comparisons 
# (Switch values and input to plot for Negative Comparisons)

plt.figure(figsize=(10,4))
plt.title('Positive Comparisons and Their Similarity Scores')
plt.ylabel('Anchors')
plt.yticks([])
plt.xticks([32*x+16 for x in range(NUM_EXAMPLES)], ['.' for x in range(NUM_EXAMPLES)])
for i,t in enumerate(plt.gca().xaxis.get_ticklabels()):
t.set_color('green')
plt.grid(None)
anchor = np.swapaxes(positive_comparisons[:,0][random_index],0,1)
anchor = np.reshape(anchor,[32,NUM_EXAMPLES*32,3])
plt.imshow(anchor)

plt.figure(figsize=(10,4))
plt.ylabel('Positives')
plt.yticks([])
plt.xticks([32*x+16 for x in range(NUM_EXAMPLES)], positive_predict_targets[random_index])
for i,t in enumerate(plt.gca().xaxis.get_ticklabels()):
t.set_color('green')
plt.grid(None)
positive = np.swapaxes(positive_comparisons[:,1][random_index],0,1)
positive = np.reshape(positive,[32,NUM_EXAMPLES*32,3])
plt.imshow(positive)

Congratulations on completing the theory and code-along! I hope this tutorial has provided an all-around useful introduction to Siamese Networks and their application to object similarity.

Before we end, I should also add that how the object similarity scores are processed depends on the problem statement.

If we are doing a 1:1 object comparison during production (whether 2 objects are similar or different), then usually a similarity threshold must be set based on the level of False Match Rate (FMR) at test time. On the other hand, if we are doing 1:N object matching, then usually the objects with the highest similarity scores are returned and ranked.

Note: For the complete codes, check out my GitHub.

With that, I thank you for your time and hope you enjoyed this tutorial. I would also like to end off by introducing you to an extremely important topic of data-centric machine learning that is elaborated on in this article:

Thanks for reading! If you have enjoyed the content, pop by my other articles on Medium and follow me on LinkedIn.

Support me! — If you are not subscribed to Medium, and like my content, do consider supporting me by joining Medium via my referral link.

FOLLOW US ON GOOGLE NEWS

Read original article here

Denial of responsibility! Techno Blender is an automatic aggregator of the all world’s media. In each content, the hyperlink to the primary source is specified. All trademarks belong to their rightful owners, all materials to their authors. If you are the owner of the content and do not want us to publish your materials, please contact us by email – [email protected]. The content will be deleted within 24 hours.
Leave a comment