+
```python
diff --git a/recognition/s4640439_siamese_network/README.MD b/recognition/s4640439_siamese_network/README.MD
new file mode 100644
index 0000000000..30404ce4c5
--- /dev/null
+++ b/recognition/s4640439_siamese_network/README.MD
@@ -0,0 +1 @@
+TODO
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/dataset.py b/recognition/s4640439_siamese_network/dataset.py
new file mode 100644
index 0000000000..f87f5c14cb
--- /dev/null
+++ b/recognition/s4640439_siamese_network/dataset.py
@@ -0,0 +1 @@
+# TODO
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/modules.py b/recognition/s4640439_siamese_network/modules.py
new file mode 100644
index 0000000000..f87f5c14cb
--- /dev/null
+++ b/recognition/s4640439_siamese_network/modules.py
@@ -0,0 +1 @@
+# TODO
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/predict.py b/recognition/s4640439_siamese_network/predict.py
new file mode 100644
index 0000000000..f87f5c14cb
--- /dev/null
+++ b/recognition/s4640439_siamese_network/predict.py
@@ -0,0 +1 @@
+# TODO
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/train.py b/recognition/s4640439_siamese_network/train.py
new file mode 100644
index 0000000000..f87f5c14cb
--- /dev/null
+++ b/recognition/s4640439_siamese_network/train.py
@@ -0,0 +1 @@
+# TODO
\ No newline at end of file
From abfbd5d589bc240f8d3a9c1363b9c0f225fd2c80 Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Thu, 13 Oct 2022 11:36:30 +1000
Subject: [PATCH 02/16] Created image pre-processing
Created a function to load the images from a directory and convert to a numpy array
---
.../s4640439_siamese_network/dataset.py | 56 ++++++++++++++++++-
.../s4640439_siamese_network/modules.py | 4 +-
2 files changed, 58 insertions(+), 2 deletions(-)
diff --git a/recognition/s4640439_siamese_network/dataset.py b/recognition/s4640439_siamese_network/dataset.py
index f87f5c14cb..719c62868c 100644
--- a/recognition/s4640439_siamese_network/dataset.py
+++ b/recognition/s4640439_siamese_network/dataset.py
@@ -1 +1,55 @@
-# TODO
\ No newline at end of file
+import numpy as np
+from PIL import Image
+import os
+import time
+
+# Data has already been separated into training and test data
+AD_TEST_PATH = "E:/ADNI/AD_NC/test/AD/"
+AD_TRAIN_PATH = "E:/ADNI/AD_NC/train/AD/"
+NC_TEST_PATH = "E:/ADNI/AD_NC/test/NC/"
+NC_TRAIN_PATH = "E:/ADNI/AD_NC/train/NC/"
+
+# image constants
+WIDTH = 256
+HEIGHT = 240
+CHANNELS = 1
+
+PRE_PROC_DATA_SAVE_LOC = "E:/ADNI/Processed"
+
+def load_data(directory_path, prefix):
+ save_path = os.path.join(PRE_PROC_DATA_SAVE_LOC, f"{prefix}_preprocessed.npy")
+
+ if not os.path.isfile(save_path):
+ start = time.time()
+ print("Processing data for file", save_path)
+
+ data = []
+
+ for filename in os.listdir(directory_path):
+ path = os.path.join(directory_path, filename)
+
+ img = Image.open(path)
+ img_arr = np.asarray(img).astype(np.float32)
+
+ # normalise
+ img_arr = img_arr / 127.5 - 1
+ data.append(img_arr)
+
+ data = np.reshape(data, (-1, HEIGHT, WIDTH, CHANNELS))
+
+ print("Saving data")
+ np.save(save_path, data)
+
+ elapsed = time.time() - start
+ print (f'Image preprocess time: {elapsed}')
+
+ else:
+ print("Loading preprocessed data")
+ data = np.load(save_path)
+
+ return data
+
+#training_data_positive = load_data(AD_TRAIN_PATH, "ad_train")
+#training_data_negative = load_data(NC_TRAIN_PATH, "nc_train")
+#testing_data_positive = load_data(AD_TEST_PATH, "ad_test")
+#testing_data_negative = load_data(NC_TEST_PATH, "nc_test")
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/modules.py b/recognition/s4640439_siamese_network/modules.py
index f87f5c14cb..935117c883 100644
--- a/recognition/s4640439_siamese_network/modules.py
+++ b/recognition/s4640439_siamese_network/modules.py
@@ -1 +1,3 @@
-# TODO
\ No newline at end of file
+# Generate Siamese model
+
+# Generate binary classifier
\ No newline at end of file
From e0769e3f551daf7ee52e5ae4ab154b914464633c Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Tue, 18 Oct 2022 09:30:43 +1000
Subject: [PATCH 03/16] Defined model generator functions
- Created the functions necessary for building the NNs that will be used for training and classification.
- Siamese model needs to be tweaked for performance and output size
- Binary classifier needs to be built as it only has the barebones
---
.../s4640439_siamese_network/modules.py | 48 ++++++++++++++++++-
1 file changed, 46 insertions(+), 2 deletions(-)
diff --git a/recognition/s4640439_siamese_network/modules.py b/recognition/s4640439_siamese_network/modules.py
index 935117c883..83bb26dde8 100644
--- a/recognition/s4640439_siamese_network/modules.py
+++ b/recognition/s4640439_siamese_network/modules.py
@@ -1,3 +1,47 @@
-# Generate Siamese model
+import tensorflow as tf
+from tensorflow.keras.models import Sequential
+from tensorflow.keras.layers import Conv2D, LeakyReLU, Flatten, MaxPool2d
-# Generate binary classifier
\ No newline at end of file
+IMAGE_SIZE = (240,256,1)
+ALPHA = 0.2
+
+def build_siamese():
+ """
+ Generate Siamese model
+ This model needs to be a CNN that reduces an image to a vector
+ """
+ model = Sequential()
+
+ model.add(Conv2D(32, kernel_size=3, input_shape=IMAGE_SIZE))
+ model.add(LeakyReLU(alpha=ALPHA))
+
+ model.add(MaxPool2d(pool_size=(2,2), strides=(1, 1)))
+
+ model.add(Conv2D(64, kernel_size=3))
+ model.add(LeakyReLU(alpha=ALPHA))
+
+ model.add(MaxPool2d(pool_size=(2,2), strides=(1, 1)))
+
+ model.add(Conv2D(128, kernel_size=3))
+ model.add(LeakyReLU(alpha=ALPHA))
+
+ model.add(MaxPool2d(pool_size=(2,2), strides=(1, 1)))
+
+ model.add(Flatten())
+
+ return model
+
+
+
+def build_binary():
+ """
+ Generate binary classifier
+ This model needs to be a binary classifier that takes an output vector from
+ siamese model and converts it into a single value in the range [0,1]
+ """
+
+ # TODO: define layers of model
+
+ model = Sequential()
+
+ return model
\ No newline at end of file
From 31f021541b21323bbbb49ae3753443f34a6b96aa Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Tue, 18 Oct 2022 09:44:42 +1000
Subject: [PATCH 04/16] Added docstrings to load_data function
Added documentation to load_data function in dataset.py
---
.../s4640439_siamese_network/dataset.py | 18 +++++++++++++++++-
1 file changed, 17 insertions(+), 1 deletion(-)
diff --git a/recognition/s4640439_siamese_network/dataset.py b/recognition/s4640439_siamese_network/dataset.py
index 719c62868c..f6ee3190ec 100644
--- a/recognition/s4640439_siamese_network/dataset.py
+++ b/recognition/s4640439_siamese_network/dataset.py
@@ -16,15 +16,30 @@
PRE_PROC_DATA_SAVE_LOC = "E:/ADNI/Processed"
-def load_data(directory_path, prefix):
+def load_data(directory_path: str, prefix: str) -> np.ndarray:
+ """
+ Processes and saves image data as a numpy array.
+
+ Attempts to find pre-processed data and load it from a save.
+ If a save cannot be found, processes the data.
+
+ Parameters:
+ - directory_path: Path to folder containing images to process
+ - prefix: String representing data type. Used for save filename
+
+ Returns:
+ - processed image dataset as numpy array.
+ """
save_path = os.path.join(PRE_PROC_DATA_SAVE_LOC, f"{prefix}_preprocessed.npy")
if not os.path.isfile(save_path):
+ # save cannot be found
start = time.time()
print("Processing data for file", save_path)
data = []
+ # loop through and process images
for filename in os.listdir(directory_path):
path = os.path.join(directory_path, filename)
@@ -44,6 +59,7 @@ def load_data(directory_path, prefix):
print (f'Image preprocess time: {elapsed}')
else:
+ # save found
print("Loading preprocessed data")
data = np.load(save_path)
From 176701884cf46d43bd7d7d94c4701079ea1cc0f2 Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Tue, 18 Oct 2022 18:27:13 +1000
Subject: [PATCH 05/16] Began implementing training code
Created train_step function
Various other documentation updates
---
.../s4640439_siamese_network/README.MD | 11 ++++-
.../s4640439_siamese_network/dataset.py | 4 ++
.../s4640439_siamese_network/modules.py | 24 ++++++++++
.../s4640439_siamese_network/predict.py | 15 ++++++-
recognition/s4640439_siamese_network/train.py | 44 ++++++++++++++++++-
5 files changed, 95 insertions(+), 3 deletions(-)
diff --git a/recognition/s4640439_siamese_network/README.MD b/recognition/s4640439_siamese_network/README.MD
index 30404ce4c5..e156db5911 100644
--- a/recognition/s4640439_siamese_network/README.MD
+++ b/recognition/s4640439_siamese_network/README.MD
@@ -1 +1,10 @@
-TODO
\ No newline at end of file
+Requirements
+1. The readme file should contain a title, a description of the algorithm and the problem that it solves
+(approximately a paragraph), how it works in a paragraph and a figure/visualisation.
+2. It should also list any dependencies required, including versions and address reproduciblility of results,
+if applicable.
+3. provide example inputs, outputs and plots of your algorithm
+4. The read me file should be properly formatted using GitHub markdown
+5. Describe any specific pre-processing you have used with references if any. Justify your training, validation
+and testing splits of the data.
+
diff --git a/recognition/s4640439_siamese_network/dataset.py b/recognition/s4640439_siamese_network/dataset.py
index f6ee3190ec..8fbf82c5df 100644
--- a/recognition/s4640439_siamese_network/dataset.py
+++ b/recognition/s4640439_siamese_network/dataset.py
@@ -3,6 +3,10 @@
import os
import time
+"""
+Containing the data loader for loading and preprocessing your data.
+"""
+
# Data has already been separated into training and test data
AD_TEST_PATH = "E:/ADNI/AD_NC/test/AD/"
AD_TRAIN_PATH = "E:/ADNI/AD_NC/train/AD/"
diff --git a/recognition/s4640439_siamese_network/modules.py b/recognition/s4640439_siamese_network/modules.py
index 83bb26dde8..992fbbddb4 100644
--- a/recognition/s4640439_siamese_network/modules.py
+++ b/recognition/s4640439_siamese_network/modules.py
@@ -5,6 +5,11 @@
IMAGE_SIZE = (240,256,1)
ALPHA = 0.2
+"""
+Containing the source code of the components of your model.
+Each component must be implementated as a class or a function.
+"""
+
def build_siamese():
"""
Generate Siamese model
@@ -31,6 +36,25 @@ def build_siamese():
return model
+def siamese_loss(x0, x1, y: int) -> float:
+ """
+ Custom loss function for siamese network.
+
+ Takes two vectors, then calculates their distance.
+
+ Vectors of the same class are rewarded for being close and punished for being far away.
+ Vectors of different classes are punished for being close and rewarded for being far away.
+
+ Parameters:
+ - x0 -- first vector
+ - x1 -- second vector
+ - y -- integer representing whether or not the two vectors are from the same class
+
+ Returns:
+ - loss value
+ """
+ # TODO
+ return 0
def build_binary():
diff --git a/recognition/s4640439_siamese_network/predict.py b/recognition/s4640439_siamese_network/predict.py
index f87f5c14cb..318940a32f 100644
--- a/recognition/s4640439_siamese_network/predict.py
+++ b/recognition/s4640439_siamese_network/predict.py
@@ -1 +1,14 @@
-# TODO
\ No newline at end of file
+"""
+Showing example usage of your trained model
+Print out any results and / or provide visualisations where applicable
+"""
+
+from train import *
+from dataset import *
+
+"""
+TODO:
+Load saved binary classification model from train.py
+Load test data using dataset.py
+Test the data and print/plot results
+"""
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/train.py b/recognition/s4640439_siamese_network/train.py
index f87f5c14cb..2a870ed7f6 100644
--- a/recognition/s4640439_siamese_network/train.py
+++ b/recognition/s4640439_siamese_network/train.py
@@ -1 +1,43 @@
-# TODO
\ No newline at end of file
+import tensorflow as tf
+
+from modules import *
+from dataset import *
+
+"""
+Containing the source code for training, validating, testing and saving your model.
+The model should be imported from “modules.py” and the data loader should be imported from “dataset.py”
+Make sure to plot the losses and metrics during training.
+
+"""
+
+@tf.function
+def train_step(siamese, siamese_optimiser, images1, images2, same_class: bool):
+ """
+ Executes one step of training the siamese model.
+ Backpropogates to update weightings.
+
+ Parameters:
+ - siamese -- the siamese network
+ - siamese_optimiser -- the optimiser which will be used for backprop
+ - images1, images2 -- batch of image data which is either positive or negative
+ - same_class -- flag representing whether the two sets of images are of the same class
+
+ Returns:
+ - loss value from this the training step
+
+ """
+ with tf.GradientTape() as siamese_tape:
+
+ x0 = siamese(images1, training=True)
+ x1 = siamese(images2, training=True)
+ y = int(same_class)
+
+ loss = siamese_loss(x0, x1, y)
+
+ siamese_gradients = siamese_tape.gradient(\
+ loss, siamese.trainable_variables)
+
+ siamese_optimiser.apply_gradients(zip(
+ siamese_gradients, siamese.trainable_variables))
+
+ return loss
\ No newline at end of file
From c422b405cc054b2a7658956a18ab6995d4e15e5a Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Tue, 18 Oct 2022 18:49:42 +1000
Subject: [PATCH 06/16] Created main method in train.py
Further built out functionality of train.py
Laid groundwork for README
---
.../s4640439_siamese_network/README.MD | 14 +++++++++++
recognition/s4640439_siamese_network/train.py | 24 ++++++++++++++++++-
2 files changed, 37 insertions(+), 1 deletion(-)
diff --git a/recognition/s4640439_siamese_network/README.MD b/recognition/s4640439_siamese_network/README.MD
index e156db5911..be89b9d102 100644
--- a/recognition/s4640439_siamese_network/README.MD
+++ b/recognition/s4640439_siamese_network/README.MD
@@ -8,3 +8,17 @@ if applicable.
5. Describe any specific pre-processing you have used with references if any. Justify your training, validation
and testing splits of the data.
+# Siamese Networks for Alzheimer's Disease Classification Using MRI Images
+
+## Description and Problem
+
+## How the Algorithm Works
+
+## Results
+
+## Running the Code
+### Dependencies
+
+### Dataset and Pre-processing
+
+### Example Usage
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/train.py b/recognition/s4640439_siamese_network/train.py
index 2a870ed7f6..8b4949bac4 100644
--- a/recognition/s4640439_siamese_network/train.py
+++ b/recognition/s4640439_siamese_network/train.py
@@ -10,6 +10,10 @@
"""
+EPOCHS = 100
+BATCH_SIZE = 32
+BUFFER_SIZE = 20000
+
@tf.function
def train_step(siamese, siamese_optimiser, images1, images2, same_class: bool):
"""
@@ -40,4 +44,22 @@ def train_step(siamese, siamese_optimiser, images1, images2, same_class: bool):
siamese_optimiser.apply_gradients(zip(
siamese_gradients, siamese.trainable_variables))
- return loss
\ No newline at end of file
+ return loss
+
+def main():
+ # get training data
+ training_data_positive = load_data(AD_TRAIN_PATH, "ad_train")
+ training_data_negative = load_data(NC_TRAIN_PATH, "nc_train")
+
+ # convert to tensors
+ train_data_pos = tf.data.Dataset.from_tensor_slices(training_data_positive
+ ).shuffle(BUFFER_SIZE, reshuffle_each_iteration=True).batch(BATCH_SIZE)
+ train_data_neg = tf.data.Dataset.from_tensor_slices(training_data_negative
+ ).shuffle(BUFFER_SIZE, reshuffle_each_iteration=True).batch(BATCH_SIZE)
+
+ # build models
+ siamese_model = build_siamese()
+ binary_classifier = build_binary()
+
+if __name__ == "__main__":
+ train()
\ No newline at end of file
From 0a0fa3753387db38ed5e83d05c199f1434f6309e Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Tue, 18 Oct 2022 20:02:12 +1000
Subject: [PATCH 07/16] Further implementation in training pipeline
- Moved loss function from modules to train
- Defined training function for siamese
- Fleshed out main function
---
recognition/s4640439_siamese_network/train.py | 76 ++++++++++++++++++-
1 file changed, 74 insertions(+), 2 deletions(-)
diff --git a/recognition/s4640439_siamese_network/train.py b/recognition/s4640439_siamese_network/train.py
index 8b4949bac4..6ddc38a4ca 100644
--- a/recognition/s4640439_siamese_network/train.py
+++ b/recognition/s4640439_siamese_network/train.py
@@ -2,6 +2,8 @@
from modules import *
from dataset import *
+import time
+import os
"""
Containing the source code for training, validating, testing and saving your model.
@@ -9,11 +11,32 @@
Make sure to plot the losses and metrics during training.
"""
-
EPOCHS = 100
BATCH_SIZE = 32
BUFFER_SIZE = 20000
+MODEL_SAVE_DIR = "E:/ADNI/models"
+
+def siamese_loss(x0, x1, y: int) -> float:
+ """
+ Custom loss function for siamese network.
+
+ Takes two vectors, then calculates their distance.
+
+ Vectors of the same class are rewarded for being close and punished for being far away.
+ Vectors of different classes are punished for being close and rewarded for being far away.
+
+ Parameters:
+ - x0 -- first vector
+ - x1 -- second vector
+ - y -- integer representing whether or not the two vectors are from the same class
+
+ Returns:
+ - loss value
+ """
+ # TODO
+ return 0
+
@tf.function
def train_step(siamese, siamese_optimiser, images1, images2, same_class: bool):
"""
@@ -46,6 +69,44 @@ def train_step(siamese, siamese_optimiser, images1, images2, same_class: bool):
return loss
+def train_siamese_model(model, optimiser, pos_dataset, neg_dataset, epochs):
+ start = time.time()
+ print("Beginning Siamese Network Training")
+
+ for epoch in range(epochs):
+ epoch_start = time.time()
+
+ i = 0
+ for pos_batch, neg_batch in zip(pos_dataset, neg_dataset):
+ # alternate between same-same training and same-diff training
+ if i % 2 == 0:
+ # same training
+ same_class = True
+
+ #split batches
+ pos1, pos2 = tf.split(pos_batch, num_or_size_splits=2)
+ neg1, neg2 = tf.split(neg_batch, num_or_size_splits=2)
+
+ pos_loss = train_step(model, optimiser, pos1, pos2 , same_class)
+ neg_loss = train_step(model, optimiser, neg1, neg2 , same_class)
+
+ else:
+ # diff training
+ same_class = False
+ diff_loss = train_step(model, optimiser, pos_batch, neg_batch, same_class)
+
+ i += 1
+
+ epoch_elapsed = time.time() - epoch_start
+ print(f"Epoch {i} - training time: {epoch_elapsed}")
+
+ elapsed = time.time() - start
+ print(f"Siamese Network Training Completed in {elapsed}")
+
+
+def train_binary_classifier(model, optimiser, siamese_model, pos_dataset, neg_dataset, epochs):
+ pass
+
def main():
# get training data
training_data_positive = load_data(AD_TRAIN_PATH, "ad_train")
@@ -60,6 +121,17 @@ def main():
# build models
siamese_model = build_siamese()
binary_classifier = build_binary()
+
+ siamese_optimiser = tf.keras.optimizers.Adam(1.5e-4,0.5)
+ classifier_optimiser = tf.keras.optimizers.Adam(1.5e-4,0.5)
+
+ train_siamese_model(siamese_model, siamese_optimiser, train_data_pos, train_data_neg, EPOCHS)
+ train_binary_classifier(binary_classifier, classifier_optimiser, siamese_model,
+ train_data_pos, train_data_neg, EPOCHS)
+
+ siamese_model.save(os.path.join(MODEL_SAVE_DIR, "siamese_model.h5"))
+ binary_classifier.save(os.path.join(MODEL_SAVE_DIR, "binary_model.h5"))
+
if __name__ == "__main__":
- train()
\ No newline at end of file
+ main()
\ No newline at end of file
From cc3adf7cb2a0bca9ce4369f2c86afc1170c9a679 Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Thu, 20 Oct 2022 09:53:40 +1000
Subject: [PATCH 08/16] Implemented Binary Classifier
- Created binary classifier function in modules.py
- Created train_binary_classifier function in train.py
---
.../s4640439_siamese_network/modules.py | 31 ++++++-------------
recognition/s4640439_siamese_network/train.py | 29 ++++++++++++++---
2 files changed, 33 insertions(+), 27 deletions(-)
diff --git a/recognition/s4640439_siamese_network/modules.py b/recognition/s4640439_siamese_network/modules.py
index 992fbbddb4..4b81419c8f 100644
--- a/recognition/s4640439_siamese_network/modules.py
+++ b/recognition/s4640439_siamese_network/modules.py
@@ -1,10 +1,12 @@
import tensorflow as tf
from tensorflow.keras.models import Sequential
-from tensorflow.keras.layers import Conv2D, LeakyReLU, Flatten, MaxPool2d
+from tensorflow.keras.layers import Conv2D, LeakyReLU, Flatten, MaxPool2d, Dense
IMAGE_SIZE = (240,256,1)
ALPHA = 0.2
+SIAMESE_OUTPUT_SHAPE = (512,)
+
"""
Containing the source code of the components of your model.
Each component must be implementated as a class or a function.
@@ -36,27 +38,6 @@ def build_siamese():
return model
-def siamese_loss(x0, x1, y: int) -> float:
- """
- Custom loss function for siamese network.
-
- Takes two vectors, then calculates their distance.
-
- Vectors of the same class are rewarded for being close and punished for being far away.
- Vectors of different classes are punished for being close and rewarded for being far away.
-
- Parameters:
- - x0 -- first vector
- - x1 -- second vector
- - y -- integer representing whether or not the two vectors are from the same class
-
- Returns:
- - loss value
- """
- # TODO
- return 0
-
-
def build_binary():
"""
Generate binary classifier
@@ -68,4 +49,10 @@ def build_binary():
model = Sequential()
+ model.add(Dense(32, input_shape=SIAMESE_OUTPUT_SHAPE, activation="relu"))
+ model.add(Dense(8, activation="relu"))
+ model.add(Dense(1, activation="sigmoid"))
+
+ model.compile(loss="binary_crossentropy", optimiser="adam", metrics=["accuracy"])
+
return model
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/train.py b/recognition/s4640439_siamese_network/train.py
index 6ddc38a4ca..6e63b665e0 100644
--- a/recognition/s4640439_siamese_network/train.py
+++ b/recognition/s4640439_siamese_network/train.py
@@ -21,6 +21,8 @@ def siamese_loss(x0, x1, y: int) -> float:
"""
Custom loss function for siamese network.
+ Based on contrastive loss.
+
Takes two vectors, then calculates their distance.
Vectors of the same class are rewarded for being close and punished for being far away.
@@ -103,9 +105,28 @@ def train_siamese_model(model, optimiser, pos_dataset, neg_dataset, epochs):
elapsed = time.time() - start
print(f"Siamese Network Training Completed in {elapsed}")
+def train_binary_classifier(model, siamese_model, pos_dataset, neg_dataset, epochs):
+ start = time.time()
+ print("Beginning Binary Classifier Training")
+
+ for epoch in range(epochs):
+ epoch_start = time.time()
+
+ for pos_batch, neg_batch in zip(pos_dataset, neg_dataset):
+ transformed_pos = siamese_model(pos_batch, training=False)
+ transformed_neg = siamese_model(neg_batch, training=False)
+
+ pos_labels = tf.ones_like(transformed_pos)
+ neg_labels = tf.zeros_like(transformed_neg)
+
+ model.fit(transformed_pos, pos_labels)
+ model.fit(transformed_neg, neg_labels)
-def train_binary_classifier(model, optimiser, siamese_model, pos_dataset, neg_dataset, epochs):
- pass
+ epoch_elapsed = time.time() - epoch_start
+ print(f"Epoch {i} - training time: {epoch_elapsed}")
+
+ elapsed = time.time() - start
+ print(f"Binary Classifier Training Completed in {elapsed}")
def main():
# get training data
@@ -123,11 +144,9 @@ def main():
binary_classifier = build_binary()
siamese_optimiser = tf.keras.optimizers.Adam(1.5e-4,0.5)
- classifier_optimiser = tf.keras.optimizers.Adam(1.5e-4,0.5)
train_siamese_model(siamese_model, siamese_optimiser, train_data_pos, train_data_neg, EPOCHS)
- train_binary_classifier(binary_classifier, classifier_optimiser, siamese_model,
- train_data_pos, train_data_neg, EPOCHS)
+ train_binary_classifier(binary_classifier, siamese_model, train_data_pos, train_data_neg, EPOCHS)
siamese_model.save(os.path.join(MODEL_SAVE_DIR, "siamese_model.h5"))
binary_classifier.save(os.path.join(MODEL_SAVE_DIR, "binary_model.h5"))
From b650559195dd8569b6890faad72f3603005d83fd Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Thu, 20 Oct 2022 12:55:34 +1000
Subject: [PATCH 09/16] Defined siamese loss function
- Defined siamese loss function
- Other minor changes
---
.../s4640439_siamese_network/modules.py | 5 +---
recognition/s4640439_siamese_network/train.py | 28 +++++++++++--------
2 files changed, 18 insertions(+), 15 deletions(-)
diff --git a/recognition/s4640439_siamese_network/modules.py b/recognition/s4640439_siamese_network/modules.py
index 4b81419c8f..863271e0a5 100644
--- a/recognition/s4640439_siamese_network/modules.py
+++ b/recognition/s4640439_siamese_network/modules.py
@@ -44,15 +44,12 @@ def build_binary():
This model needs to be a binary classifier that takes an output vector from
siamese model and converts it into a single value in the range [0,1]
"""
-
- # TODO: define layers of model
-
model = Sequential()
model.add(Dense(32, input_shape=SIAMESE_OUTPUT_SHAPE, activation="relu"))
model.add(Dense(8, activation="relu"))
model.add(Dense(1, activation="sigmoid"))
- model.compile(loss="binary_crossentropy", optimiser="adam", metrics=["accuracy"])
+ model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"])
return model
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/train.py b/recognition/s4640439_siamese_network/train.py
index 6e63b665e0..06032546fc 100644
--- a/recognition/s4640439_siamese_network/train.py
+++ b/recognition/s4640439_siamese_network/train.py
@@ -11,13 +11,14 @@
Make sure to plot the losses and metrics during training.
"""
-EPOCHS = 100
+EPOCHS = 40
BATCH_SIZE = 32
BUFFER_SIZE = 20000
+MARGIN = 0.2
MODEL_SAVE_DIR = "E:/ADNI/models"
-def siamese_loss(x0, x1, y: int) -> float:
+def siamese_loss(x0, x1, label: int, margin: float) -> float:
"""
Custom loss function for siamese network.
@@ -29,15 +30,20 @@ def siamese_loss(x0, x1, y: int) -> float:
Vectors of different classes are punished for being close and rewarded for being far away.
Parameters:
- - x0 -- first vector
- - x1 -- second vector
- - y -- integer representing whether or not the two vectors are from the same class
+ - x0 -- batch of vectors
+ - x1 -- batch of vectors
+ - label -- whether or not the two vectors are from the same class. 1 = yes, 0 = no
Returns:
- loss value
"""
- # TODO
- return 0
+ dist = tf.reduce_sum(tf.square(x0 - x1), 1)
+ dist_sqrt = tf.sqrt(dist)
+
+ loss = label * tf.square(tf.maximum(0., margin - dist_sqrt)) + (1 - label) * dist
+ loss = 0.5 * tf.reduce_mean(loss)
+
+ return loss
@tf.function
def train_step(siamese, siamese_optimiser, images1, images2, same_class: bool):
@@ -59,9 +65,9 @@ def train_step(siamese, siamese_optimiser, images1, images2, same_class: bool):
x0 = siamese(images1, training=True)
x1 = siamese(images2, training=True)
- y = int(same_class)
+ label = int(same_class)
- loss = siamese_loss(x0, x1, y)
+ loss = siamese_loss(x0, x1, label, MARGIN)
siamese_gradients = siamese_tape.gradient(\
loss, siamese.trainable_variables)
@@ -100,7 +106,7 @@ def train_siamese_model(model, optimiser, pos_dataset, neg_dataset, epochs):
i += 1
epoch_elapsed = time.time() - epoch_start
- print(f"Epoch {i} - training time: {epoch_elapsed}")
+ print(f"Epoch {epoch} - training time: {epoch_elapsed}")
elapsed = time.time() - start
print(f"Siamese Network Training Completed in {elapsed}")
@@ -123,7 +129,7 @@ def train_binary_classifier(model, siamese_model, pos_dataset, neg_dataset, epoc
model.fit(transformed_neg, neg_labels)
epoch_elapsed = time.time() - epoch_start
- print(f"Epoch {i} - training time: {epoch_elapsed}")
+ print(f"Epoch {epoch} - training time: {epoch_elapsed}")
elapsed = time.time() - start
print(f"Binary Classifier Training Completed in {elapsed}")
From c9530afae75f32c3dad91c236741e069644b6e97 Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Thu, 20 Oct 2022 17:10:58 +1000
Subject: [PATCH 10/16] Finished first pass of training
- Got training working for both models
- Model tuning still required
- Starting to fill out predict.py
---
.../s4640439_siamese_network/predict.py | 16 ++++++++++++-
recognition/s4640439_siamese_network/train.py | 23 +++++++++++--------
2 files changed, 29 insertions(+), 10 deletions(-)
diff --git a/recognition/s4640439_siamese_network/predict.py b/recognition/s4640439_siamese_network/predict.py
index 318940a32f..d472818752 100644
--- a/recognition/s4640439_siamese_network/predict.py
+++ b/recognition/s4640439_siamese_network/predict.py
@@ -11,4 +11,18 @@
Load saved binary classification model from train.py
Load test data using dataset.py
Test the data and print/plot results
-"""
\ No newline at end of file
+"""
+def load_test_data():
+ pass
+
+def load_classifier():
+ pass
+
+def load_siamese():
+ pass
+
+def main():
+ pass
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/train.py b/recognition/s4640439_siamese_network/train.py
index 06032546fc..f118d90169 100644
--- a/recognition/s4640439_siamese_network/train.py
+++ b/recognition/s4640439_siamese_network/train.py
@@ -12,7 +12,7 @@
"""
EPOCHS = 40
-BATCH_SIZE = 32
+BATCH_SIZE = 128
BUFFER_SIZE = 20000
MARGIN = 0.2
@@ -75,7 +75,7 @@ def train_step(siamese, siamese_optimiser, images1, images2, same_class: bool):
siamese_optimiser.apply_gradients(zip(
siamese_gradients, siamese.trainable_variables))
- return loss
+ return loss
def train_siamese_model(model, optimiser, pos_dataset, neg_dataset, epochs):
start = time.time()
@@ -84,8 +84,14 @@ def train_siamese_model(model, optimiser, pos_dataset, neg_dataset, epochs):
for epoch in range(epochs):
epoch_start = time.time()
- i = 0
+ i = 1
for pos_batch, neg_batch in zip(pos_dataset, neg_dataset):
+ if i % 20 == 0:
+ print("-----------------------")
+ print("Batch number", i, "complete")
+ print(f"{i} batches completed in {time.time() - epoch_start}")
+ print(f"Avg batch time: {(time.time() - epoch_start) / i}")
+
# alternate between same-same training and same-diff training
if i % 2 == 0:
# same training
@@ -122,8 +128,8 @@ def train_binary_classifier(model, siamese_model, pos_dataset, neg_dataset, epoc
transformed_pos = siamese_model(pos_batch, training=False)
transformed_neg = siamese_model(neg_batch, training=False)
- pos_labels = tf.ones_like(transformed_pos)
- neg_labels = tf.zeros_like(transformed_neg)
+ pos_labels = tf.ones_like(transformed_pos[1])
+ neg_labels = tf.zeros_like(transformed_neg[1])
model.fit(transformed_pos, pos_labels)
model.fit(transformed_neg, neg_labels)
@@ -141,15 +147,15 @@ def main():
# convert to tensors
train_data_pos = tf.data.Dataset.from_tensor_slices(training_data_positive
- ).shuffle(BUFFER_SIZE, reshuffle_each_iteration=True).batch(BATCH_SIZE)
+ ).shuffle(BUFFER_SIZE, reshuffle_each_iteration=True).batch(BATCH_SIZE, drop_remainder=True)
train_data_neg = tf.data.Dataset.from_tensor_slices(training_data_negative
- ).shuffle(BUFFER_SIZE, reshuffle_each_iteration=True).batch(BATCH_SIZE)
+ ).shuffle(BUFFER_SIZE, reshuffle_each_iteration=True).batch(BATCH_SIZE, drop_remainder=True)
# build models
siamese_model = build_siamese()
binary_classifier = build_binary()
- siamese_optimiser = tf.keras.optimizers.Adam(1.5e-4,0.5)
+ siamese_optimiser = tf.keras.optimizers.Adam(0.05)
train_siamese_model(siamese_model, siamese_optimiser, train_data_pos, train_data_neg, EPOCHS)
train_binary_classifier(binary_classifier, siamese_model, train_data_pos, train_data_neg, EPOCHS)
@@ -157,6 +163,5 @@ def main():
siamese_model.save(os.path.join(MODEL_SAVE_DIR, "siamese_model.h5"))
binary_classifier.save(os.path.join(MODEL_SAVE_DIR, "binary_model.h5"))
-
if __name__ == "__main__":
main()
\ No newline at end of file
From c043eb9d7fabcb10368c7985c88fdf78c86ae008 Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Fri, 21 Oct 2022 09:55:51 +1000
Subject: [PATCH 11/16] Edited Binary Classifier
- changed structure of binary NN
- changed binary classifier training function to feed in entire dataset rather than pre-batched
---
.../s4640439_siamese_network/modules.py | 5 ++--
recognition/s4640439_siamese_network/train.py | 23 ++++++++-----------
2 files changed, 12 insertions(+), 16 deletions(-)
diff --git a/recognition/s4640439_siamese_network/modules.py b/recognition/s4640439_siamese_network/modules.py
index 863271e0a5..26320feed2 100644
--- a/recognition/s4640439_siamese_network/modules.py
+++ b/recognition/s4640439_siamese_network/modules.py
@@ -46,8 +46,9 @@ def build_binary():
"""
model = Sequential()
- model.add(Dense(32, input_shape=SIAMESE_OUTPUT_SHAPE, activation="relu"))
- model.add(Dense(8, activation="relu"))
+ model.add(Dense(64, input_shape=SIAMESE_OUTPUT_SHAPE, activation="relu"))
+ model.add(Dense(16, activation="relu"))
+ model.add(Dense(4, activation="relu"))
model.add(Dense(1, activation="sigmoid"))
model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"])
diff --git a/recognition/s4640439_siamese_network/train.py b/recognition/s4640439_siamese_network/train.py
index f118d90169..7a5cec9f2d 100644
--- a/recognition/s4640439_siamese_network/train.py
+++ b/recognition/s4640439_siamese_network/train.py
@@ -117,25 +117,20 @@ def train_siamese_model(model, optimiser, pos_dataset, neg_dataset, epochs):
elapsed = time.time() - start
print(f"Siamese Network Training Completed in {elapsed}")
-def train_binary_classifier(model, siamese_model, pos_dataset, neg_dataset, epochs):
+def train_binary_classifier(model, siamese_model, training_data_positive, training_data_negative):
start = time.time()
print("Beginning Binary Classifier Training")
- for epoch in range(epochs):
- epoch_start = time.time()
+ pos_labels = np.ones(training_data_positive.shape[0])
+ neg_labels = np.zeros(training_data_negative.shape[0])
- for pos_batch, neg_batch in zip(pos_dataset, neg_dataset):
- transformed_pos = siamese_model(pos_batch, training=False)
- transformed_neg = siamese_model(neg_batch, training=False)
+ pos_embeddings = siamese_model.predict(training_data_positive)
+ neg_embeddings = siamese_model.predict(training_data_negative)
- pos_labels = tf.ones_like(transformed_pos[1])
- neg_labels = tf.zeros_like(transformed_neg[1])
+ embeddings = np.concatenate((pos_embeddings, neg_embeddings))
+ labels = np.concatenate((pos_labels, neg_labels))
- model.fit(transformed_pos, pos_labels)
- model.fit(transformed_neg, neg_labels)
-
- epoch_elapsed = time.time() - epoch_start
- print(f"Epoch {epoch} - training time: {epoch_elapsed}")
+ model.fit(embeddings, labels, epochs=EPOCHS, batch_size=BATCH_SIZE)
elapsed = time.time() - start
print(f"Binary Classifier Training Completed in {elapsed}")
@@ -158,7 +153,7 @@ def main():
siamese_optimiser = tf.keras.optimizers.Adam(0.05)
train_siamese_model(siamese_model, siamese_optimiser, train_data_pos, train_data_neg, EPOCHS)
- train_binary_classifier(binary_classifier, siamese_model, train_data_pos, train_data_neg, EPOCHS)
+ train_binary_classifier(binary_classifier, siamese_model, training_data_positive, training_data_negative)
siamese_model.save(os.path.join(MODEL_SAVE_DIR, "siamese_model.h5"))
binary_classifier.save(os.path.join(MODEL_SAVE_DIR, "binary_model.h5"))
From f770e8b7d0c53618bb636cdc0da73aa2a8336a9c Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Fri, 21 Oct 2022 19:01:11 +1000
Subject: [PATCH 12/16] Minor tweaks and documentation additions
---
.../s4640439_siamese_network/dataset.py | 11 ++--
.../s4640439_siamese_network/modules.py | 45 +++++++++++-----
.../s4640439_siamese_network/predict.py | 23 ++++----
recognition/s4640439_siamese_network/train.py | 54 ++++++++++++++++---
4 files changed, 96 insertions(+), 37 deletions(-)
diff --git a/recognition/s4640439_siamese_network/dataset.py b/recognition/s4640439_siamese_network/dataset.py
index 8fbf82c5df..03e3412b7f 100644
--- a/recognition/s4640439_siamese_network/dataset.py
+++ b/recognition/s4640439_siamese_network/dataset.py
@@ -13,13 +13,13 @@
NC_TEST_PATH = "E:/ADNI/AD_NC/test/NC/"
NC_TRAIN_PATH = "E:/ADNI/AD_NC/train/NC/"
+PRE_PROC_DATA_SAVE_LOC = "E:/ADNI/Processed"
+
# image constants
WIDTH = 256
HEIGHT = 240
CHANNELS = 1
-PRE_PROC_DATA_SAVE_LOC = "E:/ADNI/Processed"
-
def load_data(directory_path: str, prefix: str) -> np.ndarray:
"""
Processes and saves image data as a numpy array.
@@ -67,9 +67,4 @@ def load_data(directory_path: str, prefix: str) -> np.ndarray:
print("Loading preprocessed data")
data = np.load(save_path)
- return data
-
-#training_data_positive = load_data(AD_TRAIN_PATH, "ad_train")
-#training_data_negative = load_data(NC_TRAIN_PATH, "nc_train")
-#testing_data_positive = load_data(AD_TEST_PATH, "ad_test")
-#testing_data_negative = load_data(NC_TEST_PATH, "nc_test")
\ No newline at end of file
+ return data
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/modules.py b/recognition/s4640439_siamese_network/modules.py
index 26320feed2..0582f6c37c 100644
--- a/recognition/s4640439_siamese_network/modules.py
+++ b/recognition/s4640439_siamese_network/modules.py
@@ -1,6 +1,7 @@
import tensorflow as tf
from tensorflow.keras.models import Sequential
-from tensorflow.keras.layers import Conv2D, LeakyReLU, Flatten, MaxPool2d, Dense
+from tensorflow.keras.layers import Conv2D, LeakyReLU, Flatten, MaxPooling2D
+from tensorflow.keras.layers import BatchNormalization, Dropout, Dense
IMAGE_SIZE = (240,256,1)
ALPHA = 0.2
@@ -19,30 +20,50 @@ def build_siamese():
"""
model = Sequential()
- model.add(Conv2D(32, kernel_size=3, input_shape=IMAGE_SIZE))
- model.add(LeakyReLU(alpha=ALPHA))
+ model.add(Conv2D(32, kernel_size=3, strides=2, input_shape=IMAGE_SIZE,
+ padding="same"))
+ model.add(LeakyReLU(alpha=0.2))
- model.add(MaxPool2d(pool_size=(2,2), strides=(1, 1)))
+ model.add(Dropout(0.25))
+ model.add(Conv2D(32, kernel_size=3, strides=2, padding="same"))
+ model.add(BatchNormalization(momentum=0.8))
+ model.add(LeakyReLU(alpha=0.2))
- model.add(Conv2D(64, kernel_size=3))
- model.add(LeakyReLU(alpha=ALPHA))
+ model.add(MaxPooling2D((2, 2)))
- model.add(MaxPool2d(pool_size=(2,2), strides=(1, 1)))
+ model.add(Dropout(0.25))
+ model.add(Conv2D(64, kernel_size=3, strides=2, padding="same"))
+ model.add(BatchNormalization(momentum=0.8))
+ model.add(LeakyReLU(alpha=0.2))
- model.add(Conv2D(128, kernel_size=3))
- model.add(LeakyReLU(alpha=ALPHA))
+ model.add(Dropout(0.25))
+ model.add(Conv2D(64, kernel_size=3, strides=2, padding="same"))
+ model.add(BatchNormalization(momentum=0.8))
+ model.add(LeakyReLU(alpha=0.2))
- model.add(MaxPool2d(pool_size=(2,2), strides=(1, 1)))
+ model.add(MaxPooling2D((2, 2)))
+ model.add(Dropout(0.25))
+ model.add(Conv2D(128, kernel_size=3, strides=2, padding="same"))
+ model.add(BatchNormalization(momentum=0.8))
+ model.add(LeakyReLU(alpha=0.2))
+
+ model.add(Dropout(0.25))
+ model.add(Conv2D(128, kernel_size=3, strides=2, padding="same"))
+ model.add(BatchNormalization(momentum=0.8))
+ model.add(LeakyReLU(alpha=0.2))
+
+ model.add(Dropout(0.25))
model.add(Flatten())
+ model.add(LeakyReLU(alpha=0.2))
return model
def build_binary():
"""
Generate binary classifier
- This model needs to be a binary classifier that takes an output vector from
- siamese model and converts it into a single value in the range [0,1]
+ This model needs to be a binary classifier that takes an output embedding from
+ siamese model and converts it into a single value in the range [0,1] for classification
"""
model = Sequential()
diff --git a/recognition/s4640439_siamese_network/predict.py b/recognition/s4640439_siamese_network/predict.py
index d472818752..4747e323b8 100644
--- a/recognition/s4640439_siamese_network/predict.py
+++ b/recognition/s4640439_siamese_network/predict.py
@@ -2,7 +2,7 @@
Showing example usage of your trained model
Print out any results and / or provide visualisations where applicable
"""
-
+import tensorflow as tf
from train import *
from dataset import *
@@ -12,17 +12,22 @@
Load test data using dataset.py
Test the data and print/plot results
"""
-def load_test_data():
- pass
-
-def load_classifier():
- pass
-def load_siamese():
- pass
+TEST_DATA_POSITIVE_LOC = "ad_test"
+TEST_DATA_NEGATIVE_LOC = "nc_test"
def main():
- pass
+ # load testing data
+ test_data_positive = load_data(AD_TRAIN_PATH, TEST_DATA_POSITIVE_LOC)
+ test_data_negative = load_data(NC_TRAIN_PATH, TEST_DATA_NEGATIVE_LOC)
+
+ # load models
+ siamese_model = tf.keras.models.load_model(os.path.join(MODEL_SAVE_DIR, "siamese_model.h5"))
+ binary_model = tf.keras.models.load_model(os.path.join(MODEL_SAVE_DIR, "binary_model.h5"))
+
+
+
+ results = binary_model.evaluate()
if __name__ == "__main__":
main()
\ No newline at end of file
diff --git a/recognition/s4640439_siamese_network/train.py b/recognition/s4640439_siamese_network/train.py
index 7a5cec9f2d..2b60be658c 100644
--- a/recognition/s4640439_siamese_network/train.py
+++ b/recognition/s4640439_siamese_network/train.py
@@ -11,8 +11,8 @@
Make sure to plot the losses and metrics during training.
"""
-EPOCHS = 40
-BATCH_SIZE = 128
+EPOCHS = 100
+BATCH_SIZE = 64
BUFFER_SIZE = 20000
MARGIN = 0.2
@@ -30,8 +30,7 @@ def siamese_loss(x0, x1, label: int, margin: float) -> float:
Vectors of different classes are punished for being close and rewarded for being far away.
Parameters:
- - x0 -- batch of vectors
- - x1 -- batch of vectors
+ - x0, x1 -- batch of vectors. Shape: (batch size, embedding size)
- label -- whether or not the two vectors are from the same class. 1 = yes, 0 = no
Returns:
@@ -55,7 +54,8 @@ def train_step(siamese, siamese_optimiser, images1, images2, same_class: bool):
- siamese -- the siamese network
- siamese_optimiser -- the optimiser which will be used for backprop
- images1, images2 -- batch of image data which is either positive or negative
- - same_class -- flag representing whether the two sets of images are of the same class
+ shape: (batch size, width, height, number of channels)
+ - same_class -- bool flag representing whether the two sets of images are of the same class
Returns:
- loss value from this the training step
@@ -63,6 +63,7 @@ def train_step(siamese, siamese_optimiser, images1, images2, same_class: bool):
"""
with tf.GradientTape() as siamese_tape:
+ # convert images to embeddings
x0 = siamese(images1, training=True)
x1 = siamese(images2, training=True)
label = int(same_class)
@@ -77,7 +78,19 @@ def train_step(siamese, siamese_optimiser, images1, images2, same_class: bool):
return loss
-def train_siamese_model(model, optimiser, pos_dataset, neg_dataset, epochs):
+def train_siamese_model(model, optimiser, pos_dataset, neg_dataset, epochs) -> None:
+ """
+ Trains the siamese model.
+
+ Alternates between training images of the same class then images of different classes.
+
+ Parameters:
+ - model -- the siamese model to train
+ - optimiser -- the optimiser used for back propogation
+ - pos_dataset, neg_dataset -- pre-batched tensorflow dataset
+ - epochs -- number of epochs to train for
+ """
+
start = time.time()
print("Beginning Siamese Network Training")
@@ -117,16 +130,29 @@ def train_siamese_model(model, optimiser, pos_dataset, neg_dataset, epochs):
elapsed = time.time() - start
print(f"Siamese Network Training Completed in {elapsed}")
-def train_binary_classifier(model, siamese_model, training_data_positive, training_data_negative):
+def train_binary_classifier(model, siamese_model, training_data_positive, training_data_negative) -> None:
+ """
+ Trains the binary classifier used to classify the images into one of the two classes.
+
+ Converts raw data to embeddings then fits the model.
+
+ Parameters:
+ - model -- the binary classification model to train
+ - siamese_model -- the pre-trained siamese model used to generate embeddings
+ - training_data_positive, training_data_negative -- raw image data
+ """
start = time.time()
print("Beginning Binary Classifier Training")
+ # generate labels - 1: positive, 0: negative
pos_labels = np.ones(training_data_positive.shape[0])
neg_labels = np.zeros(training_data_negative.shape[0])
+ # convert image data to embeddings
pos_embeddings = siamese_model.predict(training_data_positive)
neg_embeddings = siamese_model.predict(training_data_negative)
+ # merge positive and negative datasets
embeddings = np.concatenate((pos_embeddings, neg_embeddings))
labels = np.concatenate((pos_labels, neg_labels))
@@ -136,11 +162,20 @@ def train_binary_classifier(model, siamese_model, training_data_positive, traini
print(f"Binary Classifier Training Completed in {elapsed}")
def main():
+ """
+ Trains the models
+
+ Loads training data using dataset.py
+ Generates the models using modules.py
+ Uses functions defined above to train the models
+ Saves the models for later prediction
+ """
+
# get training data
training_data_positive = load_data(AD_TRAIN_PATH, "ad_train")
training_data_negative = load_data(NC_TRAIN_PATH, "nc_train")
- # convert to tensors
+ # convert to tensors for siamese training
train_data_pos = tf.data.Dataset.from_tensor_slices(training_data_positive
).shuffle(BUFFER_SIZE, reshuffle_each_iteration=True).batch(BATCH_SIZE, drop_remainder=True)
train_data_neg = tf.data.Dataset.from_tensor_slices(training_data_negative
@@ -150,11 +185,14 @@ def main():
siamese_model = build_siamese()
binary_classifier = build_binary()
+ # create optimiser for siamese model
siamese_optimiser = tf.keras.optimizers.Adam(0.05)
+ # train the models
train_siamese_model(siamese_model, siamese_optimiser, train_data_pos, train_data_neg, EPOCHS)
train_binary_classifier(binary_classifier, siamese_model, training_data_positive, training_data_negative)
+ # save the models
siamese_model.save(os.path.join(MODEL_SAVE_DIR, "siamese_model.h5"))
binary_classifier.save(os.path.join(MODEL_SAVE_DIR, "binary_model.h5"))
From fe0d94ed66b9915527670f35a2288e2b92bbe3c3 Mon Sep 17 00:00:00 2001
From: Matt McDonnell <85379302+bushlemon@users.noreply.github.com>
Date: Fri, 21 Oct 2022 19:50:51 +1000
Subject: [PATCH 13/16] Various readme and documentation updates
- Updated documentation
- Added PCA plot
---
.../s4640439_siamese_network/README.MD | 30 +++++++++++++++++-
.../s4640439_siamese_network/images/PCA.png | Bin 0 -> 31589 bytes
2 files changed, 29 insertions(+), 1 deletion(-)
create mode 100644 recognition/s4640439_siamese_network/images/PCA.png
diff --git a/recognition/s4640439_siamese_network/README.MD b/recognition/s4640439_siamese_network/README.MD
index be89b9d102..600038b505 100644
--- a/recognition/s4640439_siamese_network/README.MD
+++ b/recognition/s4640439_siamese_network/README.MD
@@ -12,13 +12,41 @@ and testing splits of the data.
## Description and Problem
+
## How the Algorithm Works
## Results
+Unfortunately, as of the current version, this implementation has failed to construct a suitable classifier.
+
+All attempts at training a Binary Classifier using my Siamese Model to generate embeddings led to the Classifier getting stuck at a 51% accuracy (the ratio of negative to positive samples).
+
+I tried many different model structures, as well as tweaked various hyperparameters, but was not able to get the Siamese Model to generate satisfactory embeddings with which to classify with.
+
+This leads to the Binary Classifier quickly getting stuck in a local minimum, unable to differentiate between the classes just by the embeddings.
+
+Running principal component analysis revealed that the issue was with the siamese model. Taking the two principal components with the highest variance and plotting them for each embedding resulted in the following scatter plot:
+
+
+As can be seen, jsut considering the two components with the highest variance, there is a major overlap between the two classes. Thus, it is no wonder that the binary classifier was unable to assess the data sufficiently.
+
+I attempted many different tweaks to my Siamese Model in order to try to improve the embeddings.
+
+Techniques attempted include:
+* Trying various batch sizes in range (32, 128)
+* Trying various epochs num in range (30, 100)
+* Changing structure of model - size of convolutions, number of convolutions, strides, max padding
+* Changing the margin in the loss function in the range (0.1, 0.5)
+
+If more time were available, I would try to train the Siamese Model using various other loss functions instead, for example Triplet Loss.
## Running the Code
### Dependencies
+Requires Python version 3.9 or above (for type hinting)
+Requires tensorflow version 2.8.2 or above
+Requires numpy version 1.21.3 or above
### Dataset and Pre-processing
+Original dataset sourced from: [ADNI dataset for Alzheimer's disease](http://adni.loni.usc.edu/)
+Pre-processed dataset (used in this project) available from: [UQ Blackboard](https://cloudstor.aarnet.edu.au/plus/s/L6bbssKhUoUdTSI)
-### Example Usage
\ No newline at end of file
+### Example Usage
diff --git a/recognition/s4640439_siamese_network/images/PCA.png b/recognition/s4640439_siamese_network/images/PCA.png
new file mode 100644
index 0000000000000000000000000000000000000000..aa33ac7a66db67c2ee6026790b208646688b65ee
GIT binary patch
literal 31589
zcma&N1yodF+b%qGcQ+ykNC`uy$PkiBmw?0|(hOZgC>;_4LyAZ@NQ#t{!~h~CHHa`s
zmvo$szTbDw`oI6I|934H%rLW`XW!3V*L7bz`iZtG2_YjP2m~T|tOn5qfiNjRAdGW-
zT;QFSKdeaL2R7`{V|{$!Um(716!4nBP0a)b0ufQ({>S*X5PS)|De0+f?5XDp_4Ik}
zVFz-4?&;>_>gnWQ&F*dI0dsJ50Sk%xu`zj+s+;lp5Nm~l(iCGn<8y%ILeKPMM}nGTMX{rqbT1Ox#VGDSCj
zlbDg%5fgk_5wZUBMYNf*?BCRs!Wpop43u<_2VX;&=qJ1~H!@4AON(tB@J2{R>|SoK
z|81c)!_2?X76|M