diff --git a/.gitignore b/.gitignore index 6e10f0b1b8..c7eeeefd21 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ __pycache__/ *.py[cod] *$py.class +#DataSets +**DataSets/ + # C extensions *.so .idea @@ -132,4 +135,4 @@ dmypy.json .idea/ # no tracking mypy config file -mypy.ini +mypy.ini \ No newline at end of file diff --git a/recognition/ISICs_UNet/README.md b/recognition/ISICs_UNet/README.md index 788ea17b79..f2c009212e 100644 --- a/recognition/ISICs_UNet/README.md +++ b/recognition/ISICs_UNet/README.md @@ -1,101 +1,52 @@ -# Segment the ISICs data set with the U-net +# Segmenting ISICs with U-Net -## Project Overview -This project aim to solve the segmentation of skin lesian (ISIC2018 data set) using the U-net, with all labels having a minimum Dice similarity coefficient of 0.7 on the test set[Task 3]. +COMP3710 Report recognition problem 3 (Segmenting ISICs data set with U-Net) solved in TensorFlow -## ISIC2018 -![ISIC example](imgs/example.jpg) +Created by Christopher Bailey (45576430) -Skin Lesion Analysis towards Melanoma Detection +## The problem and algorithm +The problem solved by this program is binary segmentation of the ISICs skin lesion data set. Segmentation is a way to label pixels in an image according to some grouping, in this case lesion or non-lesion. This translates images of skin to masks representing areas of concern for skin lesions. -Task found in https://challenge2018.isic-archive.com/ +U-Net is a form of autoencoder where the downsampling path is expected to learn the features of the image and the upsampling path learns how to recreate the masks. Long skip connections between downpooling and upsampling layers are utilised to overcome the bottleneck in traditional autoencoders allowing feature representations to be recreated. +## How it works +A four layer padded U-Net is used, preserving skin features and mask resolution. The implementation utilises Adam as the optimizer and implements Dice distance as the loss function as this appeared to give quicker convergence than other methods (eg. binary cross-entropy). -## U-net -![UNet](imgs/uent.png) +The utilised metric is a Dice coefficient implementation. My initial implementation appeared faulty and was replaced with a 3rd party implementation which appears correct. 3 epochs was observed to be generally sufficient to observe Dice coefficients of 0.8+ on test datasets but occasional non-convergence was observed and could be curbed by increasing the number of epochs. Visualisation of predictions is also implemented and shows reasonable correspondence. Orange bandaids represent an interesting challenge for the implementation as presented. -U-net is one of the popular image segmentation architectures used mostly in biomedical purposes. The name UNet is because it’s architecture contains a compressive path and an expansive path which can be viewed as a U shape. This architecture is built in such a way that it could generate better results even for a less number of training data sets. +### Training, validation and testing split +Training, validation and testing uses a respective 60:20:20 split, a commonly assumed starting point suggested by course staff. U-Net in particular was developed to work "with very few training images" (Ronneberger et al, 2015) The input data for this problem consists of 2594 images and masks. This split appears to provide satisfactory results. -## Data Set Structure +## Using the model +### Dependencies required +* Python3 (tested with 3.8) +* TensorFlow 2.x (tested with 2.3) +* glob (used to load filenames) +* matplotlib (used for visualisations, tested with 3.3) -data set folder need to be stored in same directory with structure same as below -```bash -ISIC2018 - |_ ISIC2018_Task1-2_Training_Input_x2 - |_ ISIC_0000000 - |_ ISIC_0000001 - |_ ... - |_ ISIC2018_Task1_Training_GroundTruth_x2 - |_ ISIC_0000000_segmentation - |_ ISIC_0000001_segmentation - |_ ... -``` +### Parameter tuning +The model was developed on a GTX 1660 TI (6GB VRAM) and certain values (notably batch size and image resolution) were set lower than might otherwise be ideal on more capable hardware. This is commented in the relevant code. -## Dice Coefficient +### Running the model +The model is executed via the main.py script. -The Sørensen–Dice coefficient is a statistic used to gauge the similarity of two samples. +### Example output +Given a batch size of 1 and 3 epochs the following output was observed on a single run: +Era | Loss | Dice coefficient +--- | ---- | ---------------- +Epoch 1 | 0.7433 | 0.2567 +Epoch 2 | 0.3197 | 0.6803 +Epoch 3 | 0.2657 | 0.7343 +Testing | 0.1820 | 0.8180 -Further information in https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient -## Dependencies +### Figure 1 - example visualisation plot +Skin images in left column, true mask middle, predicted mask right column +![Visualisation of predictions](visual.png) -- python 3 -- tensorflow 2.1.0 -- pandas 1.1.4 -- numpy 1.19.2 -- matplotlib 3.3.2 -- scikit-learn 0.23.2 -- pillow 8.0.1 - - -## Usages - -- Run `train.py` for training the UNet on ISIC data. -- Run `evaluation.py` for evaluation and case present. - -## Advance - -- Modify `setting.py` for custom setting, such as different batch size. -- Modify `unet.py` for custom UNet, such as different kernel size. - -## Algorithm - -- data set: - - The data set we used is the training set of ISIC 2018 challenge data which has segmentation labels. - - Training: Validation: Test = 1660: 415: 519 = 0.64: 0.16 : 0.2 (Training: Test = 4: 1 and in Training, further split 4: 1 for Training: Validation) - - Training data augmentations: rescale, rotate, shift, zoom, grayscale -- model: - - Original UNet with padding which can keep the shape of input and output same. - - The first convolutional layers has 16 output channels. - - The activation function of all convolutional layers is ELU. - - Without batch normalization layers. - - The inputs is (384, 512, 1) - - The output is (384, 512, 1) after sigmoid activation. - - Optimizer: Adam, lr = 1e-4 - - Loss: dice coefficient loss - - Metrics: accuracy & dice coefficient - -## Results - -Evaluation dice coefficient is 0.805256724357605. - -plot of train/valid Dice coefficient: - -![img](imgs/train_and_valid_dice_coef.png) - -case present: - -![case](imgs/case%20present.png) - -## Reference -Manna, S. (2020). K-Fold Cross Validation for Deep Learning using Keras. [online] Medium. Available at: https://medium.com/the-owl/k-fold-cross-validation-in-keras-3ec4a3a00538 [Accessed 24 Nov. 2020]. - -zhixuhao (2020). zhixuhao/unet. [online] GitHub. Available at: https://github.com/zhixuhao/unet. - -GitHub. (n.d.). NifTK/NiftyNet. [online] Available at: https://github.com/NifTK/NiftyNet/blob/a383ba342e3e38a7ad7eed7538bfb34960f80c8d/niftynet/layer/loss_segmentation.py [Accessed 24 Nov. 2020]. - -Team, K. (n.d.). Keras documentation: Losses. [online] keras.io. Available at: https://keras.io/api/losses/#creating-custom-losses [Accessed 24 Nov. 2020]. - -262588213843476 (n.d.). unet.py. [online] Gist. Available at: https://gist.github.com/abhinavsagar/fe0c900133cafe93194c069fe655ef6e [Accessed 24 Nov. 2020]. - -Stack Overflow. (n.d.). python - Disable Tensorflow debugging information. [online] Available at: https://stackoverflow.com/questions/35911252/disable-tensorflow-debugging-information [Accessed 24 Nov. 2020]. +## References +Segments of code in this assignment were used from or based on the following sources: +1. COMP3710-demo-code.ipynb from Guest Lecture +1. https://www.tensorflow.org/tutorials/load_data/images +1. https://www.tensorflow.org/guide/gpu +1. Karan Jakhar (2019) https://medium.com/@karan_jakhar/100-days-of-code-day-7-84e4918cb72c diff --git a/recognition/XUE4645768/README.md b/recognition/XUE4645768/README.md index 36250adaa3..94bc1848c0 100644 --- a/recognition/XUE4645768/README.md +++ b/recognition/XUE4645768/README.md @@ -53,6 +53,52 @@ python gcn.py Warning: Please pay attention to whether the data path is correct when you run the gcn.py. +# Training + +Learning rate= 0.01 +Weight dacay =0.005 + +For 200 epoches: +```Epoch 000: Loss 0.2894, TrainAcc 0.9126, ValAcc 0.8954 +Epoch 001: Loss 0.2880, TrainAcc 0.9126, ValAcc 0.895 +Epoch 002: Loss 0.2866, TrainAcc 0.9126, ValAcc 0.8961 +Epoch 003: Loss 0.2853, TrainAcc 0.9132, ValAcc 0.8961 +Epoch 004: Loss 0.2839, TrainAcc 0.9137, ValAcc 0.8961 +Epoch 005: Loss 0.2826, TrainAcc 0.9141, ValAcc 0.8963 +Epoch 006: Loss 0.2813, TrainAcc 0.9146, ValAcc 0.8956 +Epoch 007: Loss 0.2800, TrainAcc 0.9146, ValAcc 0.8956 +Epoch 008: Loss 0.2788, TrainAcc 0.9146, ValAcc 0.8959 +Epoch 009: Loss 0.2775, TrainAcc 0.9146, ValAcc 0.8970 +Epoch 010: Loss 0.2763, TrainAcc 0.915, ValAcc 0.8974 +Epoch 011: Loss 0.2751, TrainAcc 0.915, ValAcc 0.8972 +Epoch 012: Loss 0.2739, TrainAcc 0.915, ValAcc 0.8976 +Epoch 013: Loss 0.2727, TrainAcc 0.9157, ValAcc 0.8979 +Epoch 014: Loss 0.2716, TrainAcc 0.9157, ValAcc 0.8983 +Epoch 015: Loss 0.2704, TrainAcc 0.9161, ValAcc 0.8990 +Epoch 016: Loss 0.2693, TrainAcc 0.9168, ValAcc 0.8988 +Epoch 017: Loss 0.2682, TrainAcc 0.9181, ValAcc 0.8990 +Epoch 018: Loss 0.2671, TrainAcc 0.9179, ValAcc 0.8990 +Epoch 019: Loss 0.2660, TrainAcc 0.9179, ValAcc 0.8992 +Epoch 020: Loss 0.2650, TrainAcc 0.9188, ValAcc 0.8996 +...... +Epoch 190: Loss 0.1623, TrainAcc 0.9553, ValAcc 0.9134 +Epoch 191: Loss 0.1619, TrainAcc 0.9555, ValAcc 0.9134 +Epoch 192: Loss 0.1615, TrainAcc 0.9555, ValAcc 0.9132 +Epoch 193: Loss 0.1611, TrainAcc 0.9557, ValAcc 0.9130 +Epoch 194: Loss 0.1607, TrainAcc 0.9562, ValAcc 0.9130 +Epoch 195: Loss 0.1603, TrainAcc 0.9559, ValAcc 0.9130 +Epoch 196: Loss 0.1599, TrainAcc 0.9562, ValAcc 0.9126 +Epoch 197: Loss 0.1595, TrainAcc 0.9562, ValAcc 0.9123 +Epoch 198: Loss 0.1591, TrainAcc 0.9562, ValAcc 0.9123 +Epoch 199: Loss 0.1587, TrainAcc 0.9562, ValAcc 0.9123``` + +For test accuracy:around 0.9 + +# TSNE +For the test:iteration=500, with lower dimension to 2 + + + ```python diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/Images/acc.png b/recognition/s46413587_Improved_Unet_on_ISIC/Images/acc.png new file mode 100644 index 0000000000..e5bdea7bca Binary files /dev/null and b/recognition/s46413587_Improved_Unet_on_ISIC/Images/acc.png differ diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/Images/dsc.png b/recognition/s46413587_Improved_Unet_on_ISIC/Images/dsc.png new file mode 100644 index 0000000000..8798f33f3a Binary files /dev/null and b/recognition/s46413587_Improved_Unet_on_ISIC/Images/dsc.png differ diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/Images/evaluation.png b/recognition/s46413587_Improved_Unet_on_ISIC/Images/evaluation.png new file mode 100644 index 0000000000..07088f2c67 Binary files /dev/null and b/recognition/s46413587_Improved_Unet_on_ISIC/Images/evaluation.png differ diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/Images/good.png b/recognition/s46413587_Improved_Unet_on_ISIC/Images/good.png new file mode 100644 index 0000000000..8943633b5a Binary files /dev/null and b/recognition/s46413587_Improved_Unet_on_ISIC/Images/good.png differ diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/Images/good1.png b/recognition/s46413587_Improved_Unet_on_ISIC/Images/good1.png new file mode 100644 index 0000000000..28142db929 Binary files /dev/null and b/recognition/s46413587_Improved_Unet_on_ISIC/Images/good1.png differ diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/Images/good3.png b/recognition/s46413587_Improved_Unet_on_ISIC/Images/good3.png new file mode 100644 index 0000000000..77d196d505 Binary files /dev/null and b/recognition/s46413587_Improved_Unet_on_ISIC/Images/good3.png differ diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/Images/good5.png b/recognition/s46413587_Improved_Unet_on_ISIC/Images/good5.png new file mode 100644 index 0000000000..0d5d389570 Binary files /dev/null and b/recognition/s46413587_Improved_Unet_on_ISIC/Images/good5.png differ diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/Images/good6.png b/recognition/s46413587_Improved_Unet_on_ISIC/Images/good6.png new file mode 100644 index 0000000000..484bf38606 Binary files /dev/null and b/recognition/s46413587_Improved_Unet_on_ISIC/Images/good6.png differ diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/Images/loss.png b/recognition/s46413587_Improved_Unet_on_ISIC/Images/loss.png new file mode 100644 index 0000000000..d001b2cedb Binary files /dev/null and b/recognition/s46413587_Improved_Unet_on_ISIC/Images/loss.png differ diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/Images/unet.png b/recognition/s46413587_Improved_Unet_on_ISIC/Images/unet.png new file mode 100644 index 0000000000..efbb22d907 Binary files /dev/null and b/recognition/s46413587_Improved_Unet_on_ISIC/Images/unet.png differ diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/README.md b/recognition/s46413587_Improved_Unet_on_ISIC/README.md new file mode 100644 index 0000000000..54bb2d3f57 --- /dev/null +++ b/recognition/s46413587_Improved_Unet_on_ISIC/README.md @@ -0,0 +1,57 @@ +# Improved U-Net applied to the 2017 International Skin Imaging Collaboration (ISIC) Dataset with Segmentation + +## Why use U-Net on ISIC? +The ISIC is an annual challenge that serves as a part of an international effort to use computer vision to improve melanoma diagnosis based on images of skin legions. +A U-Net is a type of convolutional neural network that has been specifically developed for biomedical image segmentation, and is a collection of modifications made to a fully convolutional network with the intention of improving segmentation accuracy on smaller training sets. +By utilizing an improved U-Net algorithm, this project works to identify regions of a given image in which there is significant discolouration, based on training with images and corresponding segmentation mask images, to analyse each pixel of an image to determine whether or not the area is a different colour to the natural skin, identifying potential melanomas. + +## How an Improved U-Net works +An Improved U-Net works in 8 main sections, each comprised of two convolutional layers. These 8 sections are split into two parts - condensing and upsampling. The condensing part has 4 of these two part layers, where in the data goes through the standard convolutional layers, but at the end of each 'part' (or, every two layers), a snapshot of the state of the network layer is taken and stored for later use. Then, the network goes through 4 corresponding 'upsampling' layers, which replace the standard pooling layers by instead upscaling the previous layer and concatenating with the appropriate 'snapshot' from the convolutional layers. +I have followed the concepts and principals as explained the paper [Brain Tumor Segmentation and Radiomics Survival Prediction: Contribution to the BRATS 2017 Challenge](https://arxiv.org/pdf/1802.10508v1.pdf). This paper also provides this diagram, which provides a clearer visualization of how these layers interact. + +![1](Images/unet.png) + +I have also taken inspiration and advice from [This Kaggel U-Net implementation](https://www.kaggle.com/code/mukulkr/camvid-segmentation-using-unet/notebook) which segments an images into 32 different possible categories. + +## Results +The U-Net I have implemented has been very successful, getting these sound results on a test set after only 15 epochs: + +![2](Images/evaluation.png) + +Which can be visualised: + +![3](Images/good.png) +![4](Images/good1.png) +![5](Images/good3.png) +![6](Images/good5.png) + + +I have also created a plot of loss, Accuracy, and Dice Similarity (for more details on this metric, please read the paper above). + +![7](Images/loss.png) +![8](Images/acc.png) +![9](Images/dsc.png) + +## Reproducibility +These results were attained by running the code on google colab, with random seed set to 909, and all packages at their most current version as at 20/10/22. Note that due to computing and time constraints, it was run using only the 'training' set from the ISIC data, and was downloaded and split with the following code: +``` +dataset_url = "https://isic-challenge-data.s3.amazonaws.com/2017/ISIC-2017_Training_Data.zip" +maskset_url = "https://isic-challenge-data.s3.amazonaws.com/2017/ISIC-2017_Training_Part1_GroundTruth.zip" +data_dir = keras.utils.get_file(origin=dataset_url, extract=True) +mask_dir = keras.utils.get_file(origin=maskset_url, extract=True) + +im_root = "/root/.keras/datasets/ISIC-2017_Training_Data" +lb_root = "/root/.keras/datasets/ISIC-2017_Training_Part1_GroundTruth" +data = Path(im_root) +mask = Path(lb_root) + +base_imgs = list((data).glob("*.jpg")) + +train_imgs, val_imgs, test_imgs = np.split(base_imgs, [int(len(base_imgs)*0.7), int(len(base_imgs)*0.9)]) + +train_pair = make_pair(train_imgs, mask) +test_pair = make_pair(test_imgs, mask) +val_pair = make_pair(val_imgs, mask) +``` + +The split of 7:2:1 was chosen in accordance with the standard split, though the testing set is somewhat smaller than standard to ensure there was sufficient data for training on the smaller set. diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/dataset.py b/recognition/s46413587_Improved_Unet_on_ISIC/dataset.py new file mode 100644 index 0000000000..68eb44b673 --- /dev/null +++ b/recognition/s46413587_Improved_Unet_on_ISIC/dataset.py @@ -0,0 +1,155 @@ +import numpy as np +import matplotlib.pyplot as plt + +from tensorflow import keras +from tensorflow.keras import layers +from tensorflow.keras.preprocessing.image import load_img, img_to_array +from tensorflow.keras.utils import to_categorical ,Sequence +from tensorflow.keras import backend as K +import tensorflow_probability as tfp +import tensorflow as tf + +import os +from PIL import Image +from glob import glob +from pathlib import Path +from random import sample, choice +import shutil + +img_h = 432 +img_w = 288 +b_size = 32 + + +im_root = Path(os.path.join(os.getcwd(), "recognition\s46413587_Improved_Unet_on_ISIC\DataSets\ISIC")) + +paths = [ + "ISIC-2017_Training_Data", + "ISIC-2017_Training_Truth", + "ISIC-2017_Test_Data", + "ISIC-2017_Test_Truth", + "ISIC-2017_Validation_Data", + "ISIC-2017_Validation_Truth" +] +#Import data into lists +train_imgs = list((im_root / "ISIC-2017_Training_Data").glob("*.jpg")) +train_labels = list((im_root / "ISIC-2017_Training_Truth").glob("*.png")) +test_imgs = list((im_root / "ISIC-2017_Test_Data").glob("*.jpg")) +test_labels = list((im_root / "ISIC-2017_Test_Truth").glob("*.png")) +val_imgs = list((im_root / "ISIC-2017_Validation_Data").glob("*.jpg")) +val_labels = list((im_root / "ISIC-2017_Validation_Truth").glob("*.png")) + +(len(train_imgs),len(train_labels)), (len(test_imgs),len(test_labels)) , (len(val_imgs),len(val_labels)) + +#Pair correct mask with correct image +def make_pair(img,label,dataset): + pairs = [] + for im in img: + pairs.append((im , dataset / label / (im.stem +"_segmentation.png"))) + + return pairs + +train_pair = make_pair(train_imgs, "ISIC-2017_Training_Truth", im_root) +test_pair = make_pair(test_imgs, "ISIC-2017_Test_Truth", im_root) +val_pair = make_pair(val_imgs, "ISIC-2017_Validation_Truth", im_root) + +#Check this works by producing a random pair +temp = choice(train_pair) +img = img_to_array(load_img(temp[0], target_size=(img_w,img_h))) +mask = img_to_array(load_img(temp[1], target_size = (img_w,img_h))) +plt.figure(figsize=(10,10)) +plt.subplot(121) +plt.imshow(img/255) +plt.subplot(122) +plt.imshow(mask/255) + +#plt.show() + +class_map = [(255),(0)] + +#Correct Mask size +def assert_map_range(mask,class_map): + mask = mask.astype("uint8") + for j in range(img_w): + for k in range(img_h): + assert mask[j][k] in class_map , tuple(mask[j][k]) + +#Create 2D vector representing the pixels of the mask for comparison +def form_2D_label(mask,class_map): + mask = mask.astype("uint8") + label = np.zeros(mask.shape[:2],dtype= np.uint8) + + for i, rgb in enumerate(class_map): + label[(mask == rgb).all(axis=2)] = i + + return label + +lab = form_2D_label(mask,class_map) +np.unique(lab,return_counts=True) + +#Keras Data generator (Very Generic for Segmentation problems) +class DataGenerator(Sequence): + 'Generates data for Keras' + + def __init__(self, pair, class_map, batch_size=16, dim=(224,224,3), shuffle=True): + 'Initialization' + self.dim = dim + self.pair = pair + self.class_map = class_map + self.batch_size = batch_size + self.shuffle = shuffle + self.on_epoch_end() + + def __len__(self): + 'Denotes the number of batches per epoch' + return int(np.floor(len(self.pair) / self.batch_size)) + + def __getitem__(self, index): + 'Generate one batch of data' + # Generate indexes of the batch + indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size] + + # Find list of IDs + list_IDs_temp = [k for k in indexes] + + # Generate data + X, Y = self.__data_generation(list_IDs_temp) + + return X, Y + + def on_epoch_end(self): + 'Updates indexes after each epoch' + self.indexes = np.arange(len(self.pair)) + if self.shuffle == True: + np.random.shuffle(self.indexes) + + def __data_generation(self, list_IDs_temp): + 'Generates data containing batch_size samples' # X : (n_samples, *dim, n_channels) + # Initialization + batch_imgs = list() + batch_labels = list() + + # Generate data + for i in list_IDs_temp: + # Store sample + img = load_img(self.pair[i][0] ,target_size=self.dim) + img = img_to_array(img)/255. + batch_imgs.append(img) + + label = load_img(self.pair[i][1],target_size=self.dim) + label = img_to_array(label) + label = form_2D_label(label,self.class_map) + label = to_categorical(label , num_classes = 2) + batch_labels.append(label) + + return np.array(batch_imgs) ,np.array(batch_labels) + +#Correctly format all the data +train_generator = DataGenerator(train_pair,class_map,b_size, dim=(img_w,img_h,3) ,shuffle=True) +train_steps = train_generator.__len__() + +test_generator = DataGenerator(test_pair,class_map,b_size, dim=(img_w,img_h,3) ,shuffle=True) +test_steps = test_generator.__len__() + +val_generator = DataGenerator(val_pair, class_map, batch_size=4, dim=(img_w,img_h,3) ,shuffle=True) +val_steps = val_generator.__len__() \ No newline at end of file diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/modules.py b/recognition/s46413587_Improved_Unet_on_ISIC/modules.py new file mode 100644 index 0000000000..e5a930ed69 --- /dev/null +++ b/recognition/s46413587_Improved_Unet_on_ISIC/modules.py @@ -0,0 +1,81 @@ +import numpy as np +import matplotlib.pyplot as plt + +from tensorflow import keras +from tensorflow.keras import layers +import tensorflow_probability as tfp +import tensorflow as tf + +from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, concatenate, Conv2DTranspose, BatchNormalization, Activation, Dropout +from tensorflow.keras.models import Model, load_model + +import dataset + +img_h = dataset.img_h +img_w = dataset.img_w +b_size = dataset.b_size + +#Convolutional 'block' with two layers - four of these pairs go in a U-Net, and they are split into these pairs so +# snapshots can be taken for upsampling +def conv_block(tensor, nfilters, size=3, padding='same', initializer="he_normal"): + block = Conv2D(filters=nfilters, kernel_size=(size, size), padding=padding, kernel_initializer=initializer)(tensor) + block = BatchNormalization()(block) + block = Activation("relu")(block) + block = Conv2D(filters=nfilters, kernel_size=(size, size), padding=padding, kernel_initializer=initializer)(block) + block = BatchNormalization()(block) + block = Activation("relu")(block) + return block + +#Upsampling blocks that replace pooling and 'undo' the above convolutions +def deconv_block(tensor, residual, nfilters, size=3, padding='same', strides=(2, 2)): + block = Conv2DTranspose(nfilters, kernel_size=(size, size), strides=strides, padding=padding)(tensor) + block = concatenate([block, residual], axis=3) + block = conv_block(block, nfilters) + return block + +#Dice similarity coefficient calculator - Metric for the model +def dice_similarity(x, y): + """ + Returns: + int: dice coefficient + """ + return 2 * (tf.keras.backend.sum(tf.keras.backend.flatten(x) * tf.keras.backend.flatten(y)) + 1) / \ + (tf.keras.backend.sum(tf.keras.backend.flatten(x) + tf.keras.backend.flatten(y)) + 1) + +#The inverse of the above function to be used as a loss function for the model +def dice_loss(x, y): + """ + Returns: + int: dice co-efficient loss + """ + return 1 - dice_similarity(x, y) + +#The overarching U-Net structure +def Unet(h, w, filters): +# down + input = Input(shape=(h, w, 3), name='image_input') + conv1_snapshot = conv_block(input, nfilters=filters) + conv1_out = MaxPooling2D(pool_size=(2, 2))(conv1_snapshot) + conv2_snapshot = conv_block(conv1_out, nfilters=filters*2) + conv2_out = MaxPooling2D(pool_size=(2, 2))(conv2_snapshot) + conv3_snapshot = conv_block(conv2_out, nfilters=filters*4) + conv3_out = MaxPooling2D(pool_size=(2, 2))(conv3_snapshot) + conv4_snapshot = conv_block(conv3_out, nfilters=filters*8) + conv4_out = MaxPooling2D(pool_size=(2, 2))(conv4_snapshot) + conv4_out = Dropout(0.5)(conv4_out) + conv5 = conv_block(conv4_out, nfilters=filters*16) + conv5 = Dropout(0.5)(conv5) +# up + deconv6 = deconv_block(conv5, residual=conv4_snapshot, nfilters=filters*8) + deconv6 = Dropout(0.5)(deconv6) + deconv7 = deconv_block(deconv6, residual=conv3_snapshot, nfilters=filters*4) + deconv7 = Dropout(0.5)(deconv7) + deconv8 = deconv_block(deconv7, residual=conv2_snapshot, nfilters=filters*2) + deconv9 = deconv_block(deconv8, residual=conv1_snapshot, nfilters=filters) + output_layer = Conv2D(filters=2, kernel_size=(1, 1), activation='softmax')(deconv9) + + model = Model(inputs=input, outputs=output_layer, name='Unet') + return model + +#Make the Model +model = Unet(img_w,img_h, 64) diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/predict.py b/recognition/s46413587_Improved_Unet_on_ISIC/predict.py new file mode 100644 index 0000000000..2f0578acd6 --- /dev/null +++ b/recognition/s46413587_Improved_Unet_on_ISIC/predict.py @@ -0,0 +1,46 @@ +import numpy as np +import matplotlib.pyplot as plt + +from tensorflow import keras +from tensorflow.keras import layers +from tensorflow.keras.preprocessing.image import load_img, img_to_array + +from random import sample, choice + + +import train +import dataset + +#Colour Map to create the predicted image from the 2D pixel vector +def form_colourmap(prediction,mapping): + h,w = prediction.shape + colour_label = np.zeros((h,w,3),dtype=np.uint8) + colour_label = mapping[prediction] + colour_label = colour_label.astype(np.uint8) + return colour_label + +#Use the model to predict the 2D pixel vector +def make_prediction(model,img_path,shape): + img= img_to_array(load_img(img_path , target_size= shape))/255. + img = np.expand_dims(img,axis=0) + labels = model.predict(img) + labels = np.argmax(labels[0],axis=2) + return labels + +#Produce 5 sample images with matching predictions and the true mask (from validation set) +for i in range(5): + img_mask = choice(dataset.val_pair) + img= img_to_array(load_img(img_mask[0] , target_size= (dataset.img_w,dataset.img_h))) + gt_img = img_to_array(load_img(img_mask[1] , target_size= (dataset.img_w,dataset.img_h))) + + pred_label = make_prediction(train.model, img_mask[0], (dataset.img_w,dataset.img_h,3)) + + pred_coloured = form_colourmap(pred_label,np.array(dataset.class_map)) + + plt.figure(figsize=(15,15)) + plt.subplot(131);plt.title('Original Image') + plt.imshow(img/255.) + plt.subplot(132);plt.title('True labels') + plt.imshow(gt_img/255.) + plt.subplot(133) + plt.imshow(pred_coloured/255., cmap='gray');plt.title('predicted labels') \ No newline at end of file diff --git a/recognition/s46413587_Improved_Unet_on_ISIC/train.py b/recognition/s46413587_Improved_Unet_on_ISIC/train.py new file mode 100644 index 0000000000..8eed063e48 --- /dev/null +++ b/recognition/s46413587_Improved_Unet_on_ISIC/train.py @@ -0,0 +1,53 @@ +import numpy as np +import matplotlib.pyplot as plt + +from tensorflow import keras +from tensorflow.keras import layers +import tensorflow_probability as tfp +import tensorflow as tf + + +import dataset +import modules + +#Make and train the model + +modules.model.compile(tf.keras.optimizers.Adam(learning_rate= 0.00003),loss=[modules.dice_loss],metrics=[modules.dice_similarity, 'accuracy']) + +history = modules.model.fit(dataset.train_generator , steps_per_epoch=dataset.train_steps ,epochs=15, + validation_data=dataset.val_generator,validation_steps=dataset.val_steps, verbose=1) + +#Plot the results + +#Accuracy +plt.plot(history.history['accuracy']) +plt.plot(history.history['val_accuracy']) +plt.title('model accuracy') +plt.ylabel('accuracy') +plt.xlabel('epoch') +plt.legend(['train', 'validation'], loc='upper left') +plt.show() +#Dice similarity +plt.plot(history.history['dice_similarity']) +plt.plot(history.history['val_dice_similarity']) +plt.title('model dice_similarity') +plt.ylabel('dice_similarity') +plt.xlabel('epoch') +plt.legend(['train', 'validation'], loc='upper left') +plt.show() +# "Loss" +plt.plot(history.history['loss']) +plt.plot(history.history['val_loss']) +plt.title('model loss') +plt.ylabel('loss') +plt.xlabel('epoch') +plt.legend(['train', 'validation'], loc='upper left') +plt.show() + +#Evaluate the model on the test set + +loss, dice_similarity, acc = modules.model.evaluate(dataset.test_generator,batch_size=dataset.b_size) + +print('Test loss:', loss) +print('Test dice_similarity:', dice_similarity) +print('Test accuracy:', acc) \ No newline at end of file