Commit 72f55184 authored by Oleh Astappiev's avatar Oleh Astappiev
Browse files

fix: refactor siamese model

parent 60016a16
...@@ -16,15 +16,15 @@ model = AlexNetModel() ...@@ -16,15 +16,15 @@ model = AlexNetModel()
model.compile() model.compile()
load_weights_of(model, dataset) load_weights_of(model, dataset)
emb_vectors, emb_labels = get_embeddings_of(model.get_embedding_model(), dataset) emb_model = model.get_embedding_model()
emb_vectors, emb_labels = get_embeddings_of(emb_model, dataset)
emb_ds = SiameseModel.prepare_dataset(emb_vectors, emb_labels) emb_ds = SiameseModel.prepare_dataset(emb_vectors, emb_labels)
MARGIN = 0.5 siamese = SiameseModel(emb_model, image_vector_dimensions=512, loss_margin=1, fit_epochs=5)
siamese = SiameseModel(embedding_model=model.get_embedding_model(), image_vector_dimensions=512) siamese.compile()
siamese.compile(loss_margin=MARGIN)
siamese.fit(emb_ds, num_classes=dataset.num_classes) siamese.fit(emb_ds, num_classes=dataset.num_classes)
projection_vectors = siamese.projection_model.predict(emb_vectors) projection_vectors = siamese.projection_model.predict(emb_vectors)
# save_vectors(projection_vectors, emb_labels, dataset.name + '_' + siamese.name + '_vectors') save_vectors(projection_vectors, emb_labels, dataset.name + '_' + siamese.name + '_vectors')
project_embeddings(projection_vectors, emb_labels, str(MARGIN) + '_' + siamese.name + '_' + dataset.name) project_embeddings(projection_vectors, emb_labels, siamese.name + '_' + dataset.name)
print('Done!') print('Done!')
...@@ -12,7 +12,7 @@ DEFAULT_MARGIN = 0.5 ...@@ -12,7 +12,7 @@ DEFAULT_MARGIN = 0.5
NUM_EPOCHS = 3 NUM_EPOCHS = 3
TRAIN_BATCH_SIZE = 128 TRAIN_BATCH_SIZE = 128
STEPS_PER_EPOCH = 100 # TODO: try restore 1000 STEPS_PER_EPOCH = 100
class SiameseModel(Model): class SiameseModel(Model):
...@@ -25,26 +25,21 @@ class SiameseModel(Model): ...@@ -25,26 +25,21 @@ class SiameseModel(Model):
calculates the euclidean distance between the two generated image vectors and calculates the contrastive loss. calculates the euclidean distance between the two generated image vectors and calculates the contrastive loss.
""" """
def __init__(self, embedding_model, image_vector_dimensions=IMAGE_VECTOR_DIMENSIONS): def __init__(self, embedding_model, image_vector_dimensions=IMAGE_VECTOR_DIMENSIONS, loss_margin=DEFAULT_MARGIN, fit_epochs=NUM_EPOCHS):
super().__init__() embedding_vector_dimension = embedding_model.output_shape[1]
emb_input_1 = layers.Input(embedding_vector_dimension)
self.embedding_model = embedding_model emb_input_2 = layers.Input(embedding_vector_dimension)
self.embedding_vector_dimension = embedding_model.output_shape[1]
self.image_vector_dimensions = image_vector_dimensions
emb_input_1 = layers.Input(self.embedding_vector_dimension)
emb_input_2 = layers.Input(self.embedding_vector_dimension)
# projection model is the one to use for queries (put in a sequence after the embedding-generator model above) # projection model is the one to use for queries (put in a sequence after the embedding-generator model above)
self.projection_model = tf.keras.models.Sequential([ projection_model = tf.keras.models.Sequential([
# layers.Dense(image_vector_dimensions, activation=ACTIVATION_FN, input_shape=(embedding_vector_dimension,)) # layers.Dense(image_vector_dimensions, activation=ACTIVATION_FN, input_shape=(embedding_vector_dimension,))
layers.Dense(128, activation='relu', input_shape=(self.embedding_vector_dimension,)), layers.Dense(128, activation='relu', input_shape=(embedding_vector_dimension,)),
layers.Dense(self.image_vector_dimensions, activation=None), layers.Dense(image_vector_dimensions, activation=None),
layers.Lambda(lambda x: tf.keras.backend.l2_normalize(x, axis=1)), layers.Lambda(lambda x: tf.keras.backend.l2_normalize(x, axis=1)),
], name='siamese_projection') ], name='siamese_projection')
v1 = self.projection_model(emb_input_1) v1 = projection_model(emb_input_1)
v2 = self.projection_model(emb_input_2) v2 = projection_model(emb_input_2)
# As a note, [here](https://towardsdatascience.com/contrastive-loss-explaned-159f2d4a87ec) they mention that # As a note, [here](https://towardsdatascience.com/contrastive-loss-explaned-159f2d4a87ec) they mention that
# cosine distance is preferable to euclidean distance: in a large dimensional space, all points tend to be far # cosine distance is preferable to euclidean distance: in a large dimensional space, all points tend to be far
...@@ -54,20 +49,32 @@ class SiameseModel(Model): ...@@ -54,20 +49,32 @@ class SiameseModel(Model):
# computed_distance = layers.Lambda(euclidean_distance)([v1, v2]) # computed_distance = layers.Lambda(euclidean_distance)([v1, v2])
""" Inference model is a model from image to image vector """ """ Inference model is a model from image to image vector """
im_input = self.embedding_model.input im_input = embedding_model.input
embedding = self.embedding_model(im_input) embedding = embedding_model(im_input)
image_vector = self.projection_model(embedding) image_vector = projection_model(embedding)
self.inference_model = Model(inputs=im_input, outputs=image_vector, name='siamese_inference') inference_model = Model(inputs=im_input, outputs=image_vector, name='siamese_inference')
super(SiameseModel, self).__init__( super(SiameseModel, self).__init__(
inputs=[emb_input_1, emb_input_2], outputs=computed_distance, inputs=[emb_input_1, emb_input_2], outputs=computed_distance,
name=embedding_model.name + '_siamese_' + str(self.image_vector_dimensions) name=embedding_model.name + '_siamese_d' + str(image_vector_dimensions) + '_m' + str(loss_margin) + '_s' + str(fit_epochs * STEPS_PER_EPOCH)
) )
def compile(self, optimizer=tf.keras.optimizers.RMSprop(), loss_margin=DEFAULT_MARGIN, **kwargs): self.loss_margin = loss_margin
self.fit_epochs = fit_epochs
self.projection_model = projection_model
self.inference_model = inference_model
def compile(self, optimizer=tf.keras.optimizers.RMSprop(), loss_margin=None, **kwargs):
if loss_margin is None:
loss_margin = self.loss_margin
super().compile(optimizer=optimizer, loss=tfa.losses.ContrastiveLoss(margin=loss_margin), **kwargs) super().compile(optimizer=optimizer, loss=tfa.losses.ContrastiveLoss(margin=loss_margin), **kwargs)
def fit(self, x=None, y=None, epochs=NUM_EPOCHS, steps_per_epoch=STEPS_PER_EPOCH, num_classes=None, callbacks=[tensorboard_cb], **kwargs): def fit(self, x=None, y=None, epochs=None, steps_per_epoch=STEPS_PER_EPOCH, num_classes=None, callbacks=[tensorboard_cb], **kwargs):
if epochs is None:
epochs = self.fit_epochs
if num_classes is not None and 'class_weight' not in kwargs: if num_classes is not None and 'class_weight' not in kwargs:
kwargs = dict(kwargs, class_weight={0: 1 / num_classes, 1: (num_classes - 1) / num_classes}) kwargs = dict(kwargs, class_weight={0: 1 / num_classes, 1: (num_classes - 1) / num_classes})
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment