Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Siamese model code #5

Merged
merged 35 commits into from
Dec 2, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bca8a3f
model update to original dims
emwangs Nov 5, 2023
f4efca8
merge from main
emwangs Nov 5, 2023
5c0b034
New working Siamese network
emwangs Nov 19, 2023
7a40269
Merge
emwangs Nov 19, 2023
110731b
siamese network model saver + python file
emwangs Nov 27, 2023
ddef4e9
Integrate motion detection with capture loop
chrisfxu Nov 28, 2023
19ccdf4
Update README.md
chrisfxu Nov 28, 2023
94ab1fc
Update README.md
chrisfxu Nov 28, 2023
36c9d0c
Modify simple_camera for nano
srishagaur Nov 29, 2023
841b16c
Siamese models with cleaned up files
emwangs Nov 30, 2023
e42640b
Switched to use image absolute path
srishagaur Dec 1, 2023
9c6d7bd
Merge branch 'main' into siamese-model
emwangs Dec 1, 2023
4dbed8e
merge conflicts
ryanisho Dec 1, 2023
80b7c39
Linter issues
ryanisho Dec 1, 2023
3612015
linter issues v2
ryanisho Dec 1, 2023
b609d12
linter issues v3
ryanisho Dec 1, 2023
70030e8
removed unused directories
ryanisho Dec 1, 2023
9e1fbee
removed unused utils
ryanisho Dec 1, 2023
2571ead
Change predictor file arg name
srishagaur Dec 1, 2023
49ea0cf
merge
lisarli Dec 1, 2023
afbf6bf
didn't fix anything
srishagaur Dec 1, 2023
6c9b171
fix :)
srishagaur Dec 1, 2023
c011dba
test cmake stuff
srishagaur Dec 1, 2023
00b318e
where did temp_main go???
ryanisho Dec 1, 2023
08f40ab
Merge remote-tracking branch 'origin/fixed-cpp-final' into siamese-model
lisarli Dec 1, 2023
ce58beb
Move xml file and delete bad cmake files
srishagaur Dec 1, 2023
5c5831c
Simple camera writes to file and triggers prediction
srishagaur Dec 1, 2023
6bd431f
Fixed paths to be absolute so bounding boxes work
srishagaur Dec 1, 2023
0a0a716
Threaded model similarity score calculation
srishagaur Dec 2, 2023
3429f79
Threaded model similarity score calculation
srishagaur Dec 2, 2023
ae95f6f
Merge branch 'siamese-model' of github.com:CornellDataScience/edge-ml…
srishagaur Dec 2, 2023
fa84d1a
Try to pass the linter
lisarli Dec 2, 2023
3cbd5ef
Merge branch 'siamese-model' of github.com:CornellDataScience/edge-ml…
srishagaur Dec 2, 2023
97e65c8
Remove cmake files
srishagaur Dec 2, 2023
74747b6
Remove more cmake files
srishagaur Dec 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ src/model/train/**
src/model/test/**
src/model/david/**
venv/**
src/model/keras/**
src/model/*.h5
44 changes: 41 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,42 @@

absl-py==2.0.0
astunparse==1.6.3
cachetools==5.3.2
certifi==2023.7.22
charset-normalizer==3.3.2
flatbuffers==23.5.26
gast==0.4.0
google-auth==2.23.4
google-auth-oauthlib==1.0.0
google-pasta==0.2.0
grpcio==1.59.2
h5py==3.10.0
idna==3.4
jax==0.4.20
keras==2.12.0
Pillow==10.0.1
tensorflow-macos==2.12.0
libclang==16.0.6
Markdown==3.5.1
MarkupSafe==2.1.3
ml-dtypes==0.3.1
numpy==1.23.5
oauthlib==3.2.2
opencv-python==4.8.1.78
opt-einsum==3.3.0
packaging==23.2
protobuf==4.25.0
pyasn1==0.5.0
pyasn1-modules==0.3.0
requests==2.31.0
requests-oauthlib==1.3.1
rsa==4.9
scipy==1.11.3
six==1.16.0
tensorboard==2.12.3
tensorboard-data-server==0.7.2
tensorflow-estimator==2.12.0
tensorflow-io-gcs-filesystem==0.34.0
tensorflow-macos==2.12.0
termcolor==2.3.0
typing_extensions==4.8.0
urllib3==2.0.7
Werkzeug==3.0.1
wrapt==1.14.1
1,633 changes: 1,633 additions & 0 deletions src/model/Siamese_Network.ipynb

Large diffs are not rendered by default.

232 changes: 232 additions & 0 deletions src/model/Siamese_Network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
# https://keras.io/examples/vision/siamese_network/

import matplotlib.pyplot as plt
import numpy as np
import os
import random
import tensorflow as tf
from pathlib import Path
from tensorflow.keras import applications
from tensorflow.keras import layers
from tensorflow.keras import losses
from tensorflow.keras import optimizers
from tensorflow.keras import metrics
from tensorflow.keras import Model
from tensorflow.keras.applications import resnet

target_shape = (200, 200)

cache_dir = Path().resolve() / "keras"
anchor_images_path = cache_dir / "left"
positive_images_path = cache_dir / "right"


def preprocess_image(filename):
"""
Load the specified file as a JPEG image, preprocess it and
resize it to the target shape.
"""

image_string = tf.io.read_file(filename)
image = tf.image.decode_jpeg(image_string, channels=3)
image = tf.image.convert_image_dtype(image, tf.float32)
image = tf.image.resize(image, target_shape)
return image


def preprocess_triplets(anchor, positive, negative):
"""
Given the filenames corresponding to the three images, load and
preprocess them.
"""

return (
preprocess_image(anchor),
preprocess_image(positive),
preprocess_image(negative),
)


# We need to make sure both the anchor and positive images are loaded in
# sorted order so we can match them together.
anchor_images = sorted(
[str(anchor_images_path / f) for f in os.listdir(anchor_images_path)]
)

positive_images = sorted(
[str(positive_images_path / f) for f in os.listdir(positive_images_path)]
)

all_images = anchor_images + positive_images
random.shuffle(all_images)
print(all_images)

image_count = len(anchor_images)
print(image_count)

anchor_dataset = tf.data.Dataset.from_tensor_slices(anchor_images)
positive_dataset = tf.data.Dataset.from_tensor_slices(positive_images)
negative_dataset = tf.data.Dataset.from_tensor_slices(all_images[:100])

print(len(negative_dataset))
print(len(anchor_dataset))

dataset = tf.data.Dataset.zip((anchor_dataset, positive_dataset, negative_dataset))
dataset = dataset.shuffle(buffer_size=1024)
dataset = dataset.map(preprocess_triplets)

# Let's now split our dataset in train and validation.
train_dataset = dataset.take(round(image_count * 0.8))
val_dataset = dataset.skip(round(image_count * 0.8))

batch_size = 16

train_dataset = train_dataset.batch(batch_size, drop_remainder=False)
train_dataset = train_dataset.prefetch(8)

val_dataset = val_dataset.batch(batch_size, drop_remainder=False)
val_dataset = val_dataset.prefetch(8)


def visualize(anchor, positive, negative):
"""Visualize a few triplets from the supplied batches."""

def show(ax, image):
ax.imshow(image)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)

fig = plt.figure(figsize=(9, 9))

axs = fig.subplots(3, 3)
for i in range(3):
show(axs[i, 0], anchor[i])
show(axs[i, 1], positive[i])
show(axs[i, 2], negative[i])


visualize(*list(train_dataset.take(1).as_numpy_iterator())[0])

base_cnn = resnet.ResNet50(
weights="imagenet", input_shape=target_shape + (3,), include_top=False
)

flatten = layers.Flatten()(base_cnn.output)
dense1 = layers.Dense(512, activation="relu")(flatten)
dense1 = layers.BatchNormalization()(dense1)
dense2 = layers.Dense(256, activation="relu")(dense1)
dense2 = layers.BatchNormalization()(dense2)
output = layers.Dense(256)(dense2)

embedding = Model(base_cnn.input, output, name="Embedding")

trainable = False
for layer in base_cnn.layers:
if layer.name == "conv5_block1_out":
trainable = True
layer.trainable = trainable


class DistanceLayer(layers.Layer):
"""
This layer is responsible for computing the distance between the anchor
embedding and the positive embedding, and the anchor embedding and the
negative embedding.
"""

def __init__(self, **kwargs):
super().__init__(**kwargs)

def call(self, anchor, positive, negative):
ap_distance = tf.reduce_sum(tf.square(anchor - positive), -1)
an_distance = tf.reduce_sum(tf.square(anchor - negative), -1)
return (ap_distance, an_distance)


anchor_input = layers.Input(name="anchor", shape=target_shape + (3,))
positive_input = layers.Input(name="positive", shape=target_shape + (3,))
negative_input = layers.Input(name="negative", shape=target_shape + (3,))

distances = DistanceLayer()(
embedding(resnet.preprocess_input(anchor_input)),
embedding(resnet.preprocess_input(positive_input)),
embedding(resnet.preprocess_input(negative_input)),
)

siamese_network = Model(
inputs=[anchor_input, positive_input, negative_input], outputs=distances
)


class SiameseModel(Model):
"""The Siamese Network model with a custom training and testing loops.

Computes the triplet loss using the three embeddings produced by the
Siamese Network.

The triplet loss is defined as:
L(A, P, N) = max(‖f(A) - f(P)‖² - ‖f(A) - f(N)‖² + margin, 0)
"""

def __init__(self, siamese_network, margin=0.5):
super().__init__()
self.siamese_network = siamese_network
self.margin = margin
self.loss_tracker = metrics.Mean(name="loss")

def call(self, inputs):
return self.siamese_network(inputs)

def train_step(self, data):
# GradientTape is a context manager that records every operation that
# you do inside. We are using it here to compute the loss so we can get
# the gradients and apply them using the optimizer specified in
# `compile()`.
with tf.GradientTape() as tape:
loss = self._compute_loss(data)

# Storing the gradients of the loss function with respect to the
# weights/parameters.
gradients = tape.gradient(loss, self.siamese_network.trainable_weights)

# Applying the gradients on the model using the specified optimizer
self.optimizer.apply_gradients(
zip(gradients, self.siamese_network.trainable_weights)
)

# Let's update and return the training loss metric.
self.loss_tracker.update_state(loss)
return {"loss": self.loss_tracker.result()}

def test_step(self, data):
loss = self._compute_loss(data)

# Let's update and return the loss metric.
self.loss_tracker.update_state(loss)
return {"loss": self.loss_tracker.result()}

def _compute_loss(self, data):
# The output of the network is a tuple containing the distances
# between the anchor and the positive example, and the anchor and
# the negative example.
ap_distance, an_distance = self.siamese_network(data)

# Computing the Triplet Loss by subtracting both distances and
# making sure we don't get a negative value.
loss = ap_distance - an_distance
loss = tf.maximum(loss + self.margin, 0.0)
return loss

@property
def metrics(self):
# We need to list our metrics here so the `reset_states()` can be
# called automatically.
return [self.loss_tracker]


# Training

siamese_model = SiameseModel(siamese_network)
siamese_model.compile(optimizer=optimizers.Adam(0.0001)) # 0..0001
siamese_model.fit(train_dataset, epochs=7, validation_data=val_dataset)
embedding.save("siamese_feature.h5")
48 changes: 48 additions & 0 deletions src/model/Siamese_Predictor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# from Siamese_Network import preprocess_image
import tensorflow as tf
from tensorflow.keras.applications import resnet
import argparse
from tensorflow.keras import metrics


def preprocess_image(filename):
"""
Load the specified file as a JPEG image, preprocess it and
resize it to the target shape.
"""
target_shape = (200, 200)

image_string = tf.io.read_file(filename)
image = tf.image.decode_jpeg(image_string, channels=3)
image = tf.image.convert_image_dtype(image, tf.float32)
image = tf.image.resize(image, target_shape)
return image


# Initialize parser
parser = argparse.ArgumentParser()
parser.add_argument(
"filename", type=str, help="Path to the input image file (e.g., img.jpg)"
)

args = parser.parse_args()

# load the model
embedding = tf.keras.models.load_model("siamese_feature.h5")


david_1 = preprocess_image(args.filename)
david_2 = preprocess_image("david_base.jpg")
# add a dimension to the tensor
david_1 = tf.expand_dims(david_1, axis=0)
david_2 = tf.expand_dims(david_2, axis=0)

anchor_embedding, positive_embedding = (
embedding(resnet.preprocess_input(david_1)),
embedding(resnet.preprocess_input(david_2)),
)

cosine_similarity = metrics.CosineSimilarity()

positive_similarity = cosine_similarity(anchor_embedding, positive_embedding)
print("Similarity score: ", positive_similarity.numpy())
Binary file added src/model/david_base.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 0 additions & 9 deletions src/model/example.cpp

This file was deleted.

Binary file added src/model/faces.h5
Binary file not shown.