From b1ba31e9a57beed1a490d7ab52eb37406c39016c Mon Sep 17 00:00:00 2001 From: ChrisKre Date: Wed, 6 Feb 2019 14:30:43 +0100 Subject: [PATCH 1/4] Adjust code --- models/OurModel.py | 82 ++++---- models/optimizer/LR_SGD.py | 72 ------- models/optimizer/cb.py | 75 ++++--- trainers/Train.py | 75 +++---- trainers/Trainer.py | 190 ++++++++---------- trainers/parser/parse_age.py | 17 ++ .../{parseRes.py => parser/parse_gender.py} | 0 7 files changed, 207 insertions(+), 304 deletions(-) delete mode 100644 models/optimizer/LR_SGD.py create mode 100644 trainers/parser/parse_age.py rename trainers/{parseRes.py => parser/parse_gender.py} (100%) diff --git a/models/OurModel.py b/models/OurModel.py index cce6b32..5a724d9 100644 --- a/models/OurModel.py +++ b/models/OurModel.py @@ -1,67 +1,57 @@ -import sys, os -parent_dir = os.getcwd() -sys.path.append("/home/ip/IPNeuronaleNetze") -sys.path.append("/home/ip/IPNeuronaleNetze/trainers") +import sys +sys.path.append("/IPNeuronaleNetze") +sys.path.append("/IPNeuronaleNetze/trainers") import tensorflow as tf - -from tensorflow.python.keras.models import Sequential from tensorflow.python.keras.applications.vgg16 import VGG16 from tensorflow.python.keras.models import Model -from tensorflow.python.keras import models -from tensorflow.python.keras.layers import Flatten, Dense, Dropout, GlobalAveragePooling2D, Input +from tensorflow.python.keras.layers import Flatten, Dense, GlobalAveragePooling2D, Input from tensorflow.python.keras.optimizers import SGD -import numpy as np -from models.optimizer.LR_SGD import LR_SGD -from keras.utils import plot_model + class OurModel: def __init__(self, identifier): - self.model = self.buildModel(identifier) - + self.model = self.build_model(identifier) + + def build_model(self, identifier): + # Load VGG16 + base_model = VGG16(weights='imagenet', include_top=False) + fyipg = base_model.output - def buildModel(self, identifier): - # LOAD VGG16 - base_model = VGG16(weights='imagenet', include_top=False) - fyipg = base_model.output - - # add a global spatial average pooling layer - fyipg = GlobalAveragePooling2D()(fyipg) - # let's add a fully-connected layer - fyipg = Flatten(name ='Flatten1')(fyipg) + # Add a global spatial average pooling layer + fyipg = GlobalAveragePooling2D()(fyipg) + # Let's add a fully-connected layer + fyipg = Flatten(name ='Flatten1')(fyipg) fyipg = Dense(4096, activation='relu', name='AdditianlLayer1')(fyipg) fyipg = Dense(4096, activation='relu', name='AdditianlLayer2')(fyipg) if identifier == 1: fyipg = Dense(1, activation='sigmoid', name='Predictions')(fyipg) else: fyipg = Dense(101, activation='softmax', name='Predictions')(fyipg) - - # this is the model we will train - model = Model(inputs = base_model.input , outputs = fyipg) - - # Setting the Learning rate multipliers - LR_mult_dict = {} - LR_mult_dict['Flatten1'] = 100 - LR_mult_dict['AdditianLayer1'] = 100 - LR_mult_dict['AdditianLayer2'] = 100 - LR_mult_dict['Predictions'] = 100 - - # Setting optimizer for model - optimizer = LR_SGD(lr=0.0001, momentum=0.9, decay=0.0005, nesterov=True, multipliers = LR_mult_dict) + # This is the model we will train + model = Model(inputs = base_model.input , outputs = fyipg) + # Setting optimizer for model + optimizer =tf.train.GradientDescentOptimizer(learning_rate = 0.0001) # Optimize model for gender- and agemodel if identifier == 1: model.compile(optimizer=optimizer, - loss='binary_crossentropy', metrics=['mae']) + loss='binary_crossentropy', metrics=['mae','acc']) else: model.compile(optimizer= optimizer, - loss='categorical_crossentropy', metrics=['mae']) - - return model - - - def loadModel(self, filepath): - self.model = tf.contrib.saved_model.load_keras_model(filepath) - return self.model - - + loss='categorical_crossentropy', metrics=['mae', 'acc']) + return model + + def load_model(self, filepath, identifier): + # Load the save_model file + self.model = tf.contrib.saved_model.load_keras_model(filepath) + # Setting optimizer for model + optimizer =tf.train.GradientDescentOptimizer(learning_rate = 0.0001) + # Optimize model for gender- and agemodel + if identifier == 1: + self.model.compile(optimizer=optimizer, + loss='binary_crossentropy', metrics=['mae','acc']) + else: + self.model.compile(optimizer= optimizer, + loss='categorical_crossentropy', metrics=['mae', 'acc']) + return self.model \ No newline at end of file diff --git a/models/optimizer/LR_SGD.py b/models/optimizer/LR_SGD.py deleted file mode 100644 index e034e34..0000000 --- a/models/optimizer/LR_SGD.py +++ /dev/null @@ -1,72 +0,0 @@ -from tensorflow.python.keras import backend as K -from tensorflow.python.keras.optimizers import Optimizer -from tensorflow.python.util.tf_export import tf_export - -@tf_export('keras.optimizers.SGD') -class LR_SGD(Optimizer): - """Stochastic gradient descent optimizer. - - Includes support for momentum, - learning rate decay, and Nesterov momentum. - - # Arguments - lr: float >= 0. Learning rate. - momentum: float >= 0. Parameter updates momentum. - decay: float >= 0. Learning rate decay over each update. - nesterov: boolean. Whether to apply Nesterov momentum. - """ - - def __init__(self, lr=0.0001, momentum=0., decay=0., - nesterov=False,multipliers=None,**kwargs): - super(LR_SGD, self).__init__(**kwargs) - with K.name_scope(self.__class__.__name__): - self.iterations = K.variable(0, dtype='int64', name='iterations') - self.lr = K.variable(lr, name='lr') - self.momentum = K.variable(momentum, name='momentum') - self.decay = K.variable(decay, name='decay') - self.initial_decay = decay - self.nesterov = nesterov - self.lr_multipliers = multipliers - - def get_updates(self, loss, params): - grads = self.get_gradients(loss, params) - self.updates = [K.update_add(self.iterations, 1)] - - lr = self.lr - if self.initial_decay > 0: - lr =lr * (1. / (1. + self.decay * K.cast(self.iterations, - K.dtype(self.decay)))) - # momentum - shapes = [K.int_shape(p) for p in params] - moments = [K.zeros(shape) for shape in shapes] - self.weights = [self.iterations] + moments - for p, g, m in zip(params, grads, moments): - - matched_layer = [x for x in self.lr_multipliers.keys() if x in p.name] - if matched_layer: - new_lr = lr * self.lr_multipliers[matched_layer[0]] - else: - new_lr = lr - - v = self.momentum * m - new_lr * g # velocity - self.updates.append(K.update(m, v)) - - if self.nesterov: - new_p = p + self.momentum * v - new_lr * g - else: - new_p = p + v - - # Apply constraints. - if getattr(p, 'constraint', None) is not None: - new_p = p.constraint(new_p) - - self.updates.append(K.update(p, new_p)) - return self.updates - - def get_config(self): - config = {'lr': float(K.get_value(self.lr)), - 'momentum': float(K.get_value(self.momentum)), - 'decay': float(K.get_value(self.decay)), - 'nesterov': self.nesterov} - base_config = super(LR_SGD, self).get_config() - return dict(list(base_config.items()) + list(config.items())) \ No newline at end of file diff --git a/models/optimizer/cb.py b/models/optimizer/cb.py index 748c261..4425fa2 100644 --- a/models/optimizer/cb.py +++ b/models/optimizer/cb.py @@ -1,48 +1,47 @@ -import sys, os -parent_dir = os.getcwd() -sys.path.append(parent_dir) +import sys +sys.path.append("/home/ip/IPNeuronaleNetze") +from time import time +import tensorflow as tf +import tensorflow.python.keras from tensorflow.python.keras import callbacks import json class Cback: - - def __init__(self): - self.batchsize = [] - def makeCb(self): - - #images_validation, labels_validation = self.loadData(self.validation_dataset_filepath) - # Print the batch number at the beginning of every batch. - trainedbatch = [] - batch_print_callback = callbacks.LambdaCallback( - on_batch_begin=lambda batch,logs: trainedbatch.append(batch)) - - # Stream the epoch loss to a file in JSON format. The file content - # is not well-formed JSON but rather has a JSON object per line. - - json_log = open('loss_log.json', mode='wt', buffering=1) - json_logging_callback = callbacks.LambdaCallback( - on_epoch_end=lambda epoch, logs: json_log.write( - json.dumps({'epoch': epoch, 'loss': logs['loss']}) + '\n'), - on_train_end=lambda logs: json_log.close() - ) - - # Terminate some processes after having finished model training. - processes = [] - cleanup_callback = callbacks.LambdaCallback( - on_train_end=lambda logs: [ - p.terminate() for p in processes if p.is_alive()]) - + def __init__(self): + self.batchsize = [] - callback =[ - callbacks.EarlyStopping( - monitor='mean_absolute_error', min_delta=0, - patience=8, verbose=0, - mode='auto', baseline=None), - batch_print_callback, json_logging_callback, - cleanup_callback] - + def makeCb(self): + #images_validation, labels_validation = self.loadData(self.validation_dataset_filepath) + # Print the batch number at the beginning of every batch. + trainedbatch = [] + batch_print_callback = callbacks.LambdaCallback( + on_batch_begin=lambda batch,logs: trainedbatch.append(batch)) + + # Stream the epoch loss to a file in JSON format. The file content + # is not well-formed JSON but rather has a JSON object per line. + + json_log = open('loss_log.json', mode='wt', buffering=1) + json_logging_callback = callbacks.LambdaCallback( + on_epoch_end=lambda epoch, logs: json_log.write( + json.dumps({'epoch': epoch, 'loss': logs['loss'], + 'mae': logs['mean_absolute_error'], 'val_loss':logs['val_loss'], + 'val_mae':logs['val_mean_absolute_error']}) + '\n'), + on_train_end=lambda logs: json_log.close() + ) + + # Terminate some processes after having finished model training. + processes = [] + cleanup_callback = callbacks.LambdaCallback( + on_train_end=lambda logs: [ + p.terminate() for p in processes if p.is_alive()]) + + tensorboard = callbacks.TensorBoard(log_dir="logs/{}".format(time())) + + callback =[callbacks.EarlyStopping(monitor='val_loss', patience=4, mode = 'auto'), batch_print_callback, + json_logging_callback, cleanup_callback] + return callback diff --git a/trainers/Train.py b/trainers/Train.py index 6cc9056..c24adec 100644 --- a/trainers/Train.py +++ b/trainers/Train.py @@ -1,56 +1,37 @@ -import sys, os -parent_dir = os.getcwd() -sys.path.append("/home/ip/IPNeuronaleNetze") -sys.path.append("/home/ip/IPNeuronaleNetze/trainers") +import sys +sys.path.append("/IPNeuronaleNetze") +sys.path.append("/IPNeuronaleNetze/trainers") import tensorflow as tf - -from models.optimizer.LR_SGD import LR_SGD from models.OurModel import OurModel -from models.dataloaders.DataLoader import DataLoader from Trainer import Trainer -#filepathGender = "/home/ip/IPNeuronaleNetze/data/gender.tfrecords" -#filepathGendervalidation = "/home/ip/IPNeuronaleNetze/data/validationgender.tfrecords" - -#filepathAge = "/home/ip/IPNeuronaleNetze/data/lap_train.tfrecords" -#filepathAgevalidation = "/home/ip/IPNeuronaleNetze/data/lap_valid.tfrecords" - -filepathAge = "/home/ip/IPNeuronaleNetze/data/LAP/Train" -filepathAgevalidation = "/home/ip/IPNeuronaleNetze/data/LAP/Valid" -filepathAgetest = "/home/ip/IPNeuronaleNetze/data/LAP/Test" - -imdb_age_test = "/home/ip/IPNeuronaleNetze/data/IMDB/age/test" -imdb_age_train= "/home/ip/IPNeuronaleNetze/data/IMDB/age/train" -imdb_age_val = "/home/ip/IPNeuronaleNetze/data/IMDB/age/val" - -imdb_gender_test = "/home/ip/IPNeuronaleNetze/data/IMDB/gender/test" -imdb_gender_train= "/home/ip/IPNeuronaleNetze/data/IMDB/gender/train" -imdb_gender_val = "/home/ip/IPNeuronaleNetze/data/IMDB/gender/val" - - - +# Please enter the filepath to your dataset. +filepath_age = "/IPNeuronaleNetze/data/" +filepath_gender = "/IPNeuronaleNetze/data/" def main(): - - #IMDB Dataset - #------------------------------------------------------------------------------------------- + # This main will start the training for both, age as well as gender estimation + # with the default amount of epochs with the value 312 and an early stopping callback with the patience of 4. + # You can change the amount of epochs by typing "epochs = X" into the Trainer constructor. + # If you want to train a model by loading the weights of an allready trained model, + # use the load_model method from the OurModel class. + + # After the training is done, the models will be saved and evulation files (.csv) will be created. + # Use the parsers to interpret the .csv files + + # Start training for age estimation + AgeModel = OurModel(0) + AgeModel = Trainer(AgeModel.model, filepath_age + "Train", + filepath_age + "Valid", filepath_age + "Test", + identifier = 0) + AgeModel.train() + + # Start training for gender estimation GenderModel = OurModel(1) - GenderModel = Trainer(GenderModel.model, imdb_gender_train, imdb_gender_val, imdb_gender_test, 1) - - #AgeModel = OurModel(0) - #AgeModel = Trainer(AgeModel.model, imdb_age_train, imdb_age_val, imdb_age_test,0) - - - - #LAP Dataset - #------------------------------------------------------------------------------------------- - #AgeModel = OurModel(0) - #AgeModel = Trainer(AgeModel.model, filepathAge, filepathAgevalidation, filepathAgetest, 0) - - - - - + GenderModel = Trainer(GenderModel.model, filepath_gender + "Train", + filepath_gender + "Valid", filepath_gender + "Test", + identifier = 1) + GenderModel.train() if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/trainers/Trainer.py b/trainers/Trainer.py index e6789a4..5223243 100644 --- a/trainers/Trainer.py +++ b/trainers/Trainer.py @@ -1,120 +1,108 @@ -import sys, os -parent_dir = os.getcwd() -sys.path.append("/home/ip/IPNeuronaleNetze") +import sys +sys.path.append("/IPNeuronaleNetze") import tensorflow as tf - +from tensorflow.python.keras import callbacks from models.optimizer.cb import Cback from keras.preprocessing.image import ImageDataGenerator from tensorflow.python.keras.applications.vgg16 import preprocess_input import numpy as np import pandas as pd - - class Trainer: - - def __init__(self, model, filepath, validation_filepath, test_filepath, identifier, epochs = 312): - self.filepath = filepath - self.model = model - self.saved_model_path = "" - self.identifier = identifier - self.validation_filepath = validation_filepath - self.test_filepath = test_filepath - self.epochs = epochs - self.Trainer = self.train() + def __init__(self, model, filepath, validation_filepath, test_filepath, identifier, epochs = 312, save_model = True): + self.filepath = filepath + self.model = model + self.saved_model_path = "" + self.identifier = identifier + self.validation_filepath = validation_filepath + self.test_filepath = test_filepath + self.epochs = epochs + self.save_model = save_model + + def train(self): + # Set batchsize, the higher, the faster the training + # Steps per epoch will later then be calculated automatically by diving samples/batch_size + batch_size = 42 - def train(self): - - # Set batch_size for training - batch_size = 12 + # Set filepath where our model will be saved. + # Please add directory to this project in the beginning of the string + if self.identifier == 1: + self.saved_model_path = "/IPNeuronaleNetze/models/GenderWeights" + class_mode ='binary' + else: + self.saved_model_path = "/IPNeuronaleNetze/models/AgeWeights" + class_mode = 'categorical' - # Set settings for saving the model weights after training - # and the class_mode depending on gender or age estimation - if self.identifier == 1: - self.saved_model_path = os.getcwd()+ "/models/GenderWeights" - weights_name = "model_gender.h5" - class_mode ='binary' - else: - self.saved_model_path = os.getcwd() + "/models/AgeWeights" - weights_name = "model_age.h5" - class_mode = 'categorical' - - # Setting up the the generators for training, using the keras preprocessing_function - datagen = ImageDataGenerator(preprocessing_function = preprocess_input) - - train_generator = datagen.flow_from_directory( - self.filepath, target_size = (224,224), - batch_size=batch_size, class_mode = class_mode, - shuffle = True) - - validation_generator = datagen.flow_from_directory( - self.validation_filepath, target_size =(224,224), - batch_size = batch_size, class_mode = class_mode, - shuffle = True) - - test_generator = datagen.flow_from_directory( - self.test_filepath, target_size =(224,224), - batch_size = 1, class_mode = class_mode, - shuffle = False) + # Load the datasets via ImageDataGenerator + datagen = ImageDataGenerator(preprocessing_function = preprocess_input) - # Loading the callbacks defined in the cb.py - callbacks = Cback() - callbacks = callbacks.makeCb() - - # Start the training - history = self.model.fit_generator( - train_generator, steps_per_epoch = train_generator.samples/train_generator.batch_size, - validation_data=validation_generator, validation_steps = validation_generator.samples/validation_generator.batch_size, - epochs = self.epochs , callbacks = callbacks) + train_generator = datagen.flow_from_directory( + self.filepath, target_size = (224,224), + batch_size=batch_size, class_mode = class_mode, + shuffle = True) - # Save weights in h5 format - self.model.save_weights(weights_name) + validation_generator = datagen.flow_from_directory( + self.validation_filepath, target_size =(224,224), + batch_size = batch_size, class_mode = class_mode, + shuffle = True) - # Save weights in saved_model format - self.saved_model_path = tf.contrib.saved_model.save_keras_model( - self.model, self.saved_model_path, - custom_objects=None, as_text=None) - - # Evaluate the trained model and print results - scores = self.model.evaluate_generator( - test_generator,test_generator.samples) + # Load the callbacks, which are separated in a different class + callbacks = Cback() + callbacks = callbacks.makeCb() - print( - "Evaluation Loss: ", scores[0], - "Evaluation MAE: ", scores[1]) + # Start the training wit the fit_generator + history = self.model.fit_generator( + train_generator, steps_per_epoch = train_generator.samples/train_generator.batch_size, + validation_data=validation_generator, validation_steps = validation_generator.samples/validation_generator.batch_size, + epochs = self.epochs ,callbacks = callbacks) - # Reset the test_generator for predictions - test_generator.reset() - # Make predictions on test_generator - pred = self.model.predict_generator( - test_generator, verbose=1) + # Save the model if demanded + if self.save_model == True: + self.saved_model_path = tf.contrib.saved_model.save_keras_model( + self.model, self.saved_model_path, + custom_objects=None, as_text=None) - # Write prediction results in separate .csv file - self.write_results( - pred, test_generator) - - # Return the trained model - return self.model + # Evaluate the trained model + self.evaluate(self.test_filepath) - def write_results(self, predictions, test_generator): - # Set settings for either age or gender estimation - # Gender: get rounded value: 0 or 1 - if self.identifier == 1: - predicted_class_indices = np.around(predictions) - results_name = "results_imdb_gender.csv" - # Age: get index with max value - else: - predicted_class_indices = np.argmax( - predictions, axis=1) - results_name = "results_imdb_age.csv" + return self.model + + + def evaluate(self, test_filepath, name = "evaluation"): + # Set the right parameters for evaluation + if self.identifier == 1: + class_mode ='binary' + results_name = "_gender.csv" + else: + class_mode = 'categorical' + results_name = "_age.csv" + # Load the test dataset via ImageDataGenerator + datagen = ImageDataGenerator(preprocessing_function = preprocess_input) + test_generator = datagen.flow_from_directory( + test_filepath, target_size =(224,224), + batch_size = 1, class_mode = class_mode, + shuffle = False) - # Get the matching labels from test_generator to write them into .csv file - labels = (test_generator.class_indices) - labels = dict((v,k) for k,v in labels.items()) - predictions = [labels[k] for k in predicted_class_indices[:,0]] - filenames = test_generator.filenames - results = pd.DataFrame ({ - "Filename":filenames, - "Predictions":predictions}) - results.to_csv(results_name, index = False) + # Start the prediction walk through + pred = self.model.predict_generator(test_generator, verbose=1) + # Depending on gender or age estimation we need our class_indices in a different structure + # for the predictions + if self.identifier == 1: + predicted_class_indices = np.around(pred) + else: + predicted_class_indices = np.argmax(pred, axis=1) + + labels = (test_generator.class_indices) + labels = dict((v,k) for k,v in labels.items()) + + if self.identifier == 1: + predictions = [labels[k] for k in predicted_class_indices[:,0]] + else: + predictions = [labels[k] for k in predicted_class_indices] + + filenames = test_generator.filenames + # Create the .csv file for the evaluation + results = pd.DataFrame ({"Filename":filenames, + "Predictions":predictions}) + results.to_csv(name+results_name, index = False) \ No newline at end of file diff --git a/trainers/parser/parse_age.py b/trainers/parser/parse_age.py new file mode 100644 index 0000000..3af4d6e --- /dev/null +++ b/trainers/parser/parse_age.py @@ -0,0 +1,17 @@ +import sys +program_name = sys.argv[0] +arguments = sys.argv[1:] + +lines = [line.rstrip('\n') for line in open(arguments[0])] +del lines[0] +total = 0 +correct = 0 +for l in lines: + total += 1 + p = l.split(",") + a = p[0].split("/")[0] + b = p[1] + correct += abs(int(a)-int(b)) + +print(correct / total) +~ \ No newline at end of file diff --git a/trainers/parseRes.py b/trainers/parser/parse_gender.py similarity index 100% rename from trainers/parseRes.py rename to trainers/parser/parse_gender.py From 70a3e255e97bb70c45fc011b35c9d4f0cd1c2cea Mon Sep 17 00:00:00 2001 From: ChrisKre Date: Wed, 6 Feb 2019 17:22:11 +0100 Subject: [PATCH 2/4] Rework Unittest --- models/tests/test.py | 145 +++++++++++++++++++++---------------------- trainers/Trainer.py | 75 +++++++++++----------- 2 files changed, 108 insertions(+), 112 deletions(-) diff --git a/models/tests/test.py b/models/tests/test.py index 466b239..34dc907 100644 --- a/models/tests/test.py +++ b/models/tests/test.py @@ -1,52 +1,61 @@ import sys, os -parent_dir = os.getcwd() -sys.path.append("/Users/ronnyaretz/IPNeuronaleNetze") -sys.path.append("/Users/ronnyaretz/IPNeuronaleNetze/trainers") -import tensorflow as tf -import numpy as np -from models.optimizer.LR_SGD import LR_SGD +sys.path.append("/home/ip/IPNeuronaleNetze") +sys.path.append("/home/ip/IPNeuronaleNetze/trainers") from models.OurModel import OurModel -from models.dataloaders.DataLoader import DataLoader from Trainer import Trainer +import tensorflow as tf -filepathGender = "IPNeuronaleNetze/data/gender.tfrecords" -filepathGendervalidation = "IPNeuronaleNetze/data/validationgender.tfrecords" - -filepathAge = "IPNeuronaleNetze/data/age.tfrecords" -filepathAgevalidation = "IPNeuronaleNetze/data/validationage.tfrecords" +# Please enter filepath to the dataset for unittests +# Take in mind to choose a small one. Due to tf.test.TestCase class the single +# trainingsteps will take longer than normal +filepath = "/home/ip/IPNeuronaleNetze/data/LAP/" class modelTest(tf.test.TestCase): - # Test whether weights files get created after training - def test_weights_get_saved(self): + # Test whether saved model file will be created after training + def test_model_get_saved(self): with self.test_session(): - stored = True - GenderModelWeights = "IPNeuronaleNetze/models/weights/GenderModel_weights.h5" - GenderModel = OurModel(1, filepathGender) - GenderModel = Trainer(GenderModel.model, filepathGender, 1) - with open(GenderModelWeights) as weightsfile: - first = weightsfile.read(1) - if not first: - stored = False + model = OurModel(0) + trainer = Trainer(model.model,filepath + "Train", + filepath + "Valid", filepath + "Test", + identifier = 0, epochs = 1, + save_model = True) + trainer.train() + self.assertEqual(True,os.path.exists(trainer.saved_model_path)) - AgeModelWeights = "IPNeuronaleNetze/models/weights/AgeModel_weights.h5" - AgeModel = OurModel(0, filepathAge) - AgeModel = Trainer(AgeModel.model, filepathAge, 0) - with open(AgeModelWeights) as weightsfile: - first = weightsfile.read(1) - if not first: - stored = False - - self.assertEqual(True, stored) + # Test whether model will be saved and loaded properly + def test_model_get_saved_and_loaded_correctly(self): + with self.test_session(): + model = OurModel(0) + trainer = Trainer(model.model,filepath + "Valid", + filepath + "Valid", filepath + "Valid", + identifier = 0, epochs = 1, + save_model = True) + trainer.train() + model_load = OurModel(0) + model_load.load_model(trainer.saved_model_path, 0) + a = trainer.model.get_weights() + b = model_load.model.get_weights() + equal = True + while len(a) != 0: + c = a.pop() + d = b.pop() + if(c != d).any(): + equal = False + self.assertEqual(True,equal) - # Test whether the Model weights are changed after training with one step + # Test whether the weigts are changed after one step of training def test_one_training_step(self): - with self.test_session(): - model = OurModel(1, filepathGender) - a = model.model.get_weights() - trainer = Trainer(model.model,filepathGender , 1) - b = trainer.model.get_weights() - same = True + with self.test_session(): + model = OurModel(0) + a = model.model.get_weights() + trainer = Trainer(model.model,filepath + "Train", + filepath + "Valid", filepath + "Test", + identifier = 0, epochs = 1, + save_model = False) + trainer = trainer.train() + b = trainer.get_weights() + same = True while len(a) != 0: c = a.pop() d = b.pop() @@ -55,51 +64,37 @@ def test_one_training_step(self): self.assertEqual(False, same) # Test whether all of the layers of a model are trainable, except the input layers - def test_Model_layers_allTrainable(self): + def test_model_layers_allTrainable(self): with self.test_session(): - GenderModel = OurModel(1, filepathGender) + GenderModel = OurModel(1) trainable = True - for layer in GenderModel.model.layers: - if layer.trainable != True and layer.name != 'input_1': + for layer in GenderModel.model.layers: + if layer.trainable != True: trainable = False - AgeModel = OurModel(0, filepathAge) + AgeModel = OurModel(0) for layer in AgeModel.model.layers: - if layer.trainable != True and layer.name != 'input_3': + if layer.trainable != True: trainable = False - self.assertEqual(trainable, True) + self.assertEqual(trainable, True) - # Test wether the models layer length are in expected manner - def test_GenderModel_layerLength(self): - with self.test_session(): - GenderModel = OurModel(1, filepathGender) - length=0 - for layer in GenderModel.model.layers: - length = length+1 - self.assertEqual(length,23) + # Test whether Gendermodel is in expected shape + def test_Gendermodel_layerLength(self): + with self.test_session(): + GenderModel = OurModel(1) + GenderModel.model.summary() + length=0 + for layer in GenderModel.model.layers: + length = length+1 + self.assertEqual(length,24) - # Test wether the models layer length are in expected manner - def test_AgeModel_layerLength(self): - with self.test_session(): - AgeModel = OurModel(0, filepathAge) + # Test whether Gendermodel is in expected shape + def test_Agemodel_layerLength(self): + with self.test_session(): + AgeModel = OurModel(0) length = 0 - for layer in AgeModel.model.layers: - length = length+1 - self.assertEqual(length,23) - - # Test wether the loss of the models is not equal 0 - def test_loss_AgeModel(self): - with self.test_session(): - GenderModel = OurModel(1, filepathGender) - GenderModel = Trainer(GenderModel.model, filepathGender, 1) - self.assertNotEqual(GenderModel.model.loss,0) - - # Test wether the loss of the models is not equal 0 - def test_loss_GenderModel(self): - with self.test_session(): - AgeModel = OurModel(0, filepathAge) - AgeModel = Trainer(AgeModel.model, filepathAge, 0) - self.assertNotEqual(AgeModel.model.loss,0) - + for layer in AgeModel.model.layers: + length = length+1 + self.assertEqual(length,24) if __name__ == '__main__': tf.test.main() \ No newline at end of file diff --git a/trainers/Trainer.py b/trainers/Trainer.py index 5223243..abbba4d 100644 --- a/trainers/Trainer.py +++ b/trainers/Trainer.py @@ -18,54 +18,55 @@ def __init__(self, model, filepath, validation_filepath, test_filepath, identifi self.test_filepath = test_filepath self.epochs = epochs self.save_model = save_model + def train(self): - # Set batchsize, the higher, the faster the training - # Steps per epoch will later then be calculated automatically by diving samples/batch_size - batch_size = 42 + # Set batchsize, the higher, the faster the training + # Steps per epoch will later then be calculated automatically by diving samples/batch_size + batch_size = 42 - # Set filepath where our model will be saved. - # Please add directory to this project in the beginning of the string - if self.identifier == 1: - self.saved_model_path = "/IPNeuronaleNetze/models/GenderWeights" - class_mode ='binary' - else: - self.saved_model_path = "/IPNeuronaleNetze/models/AgeWeights" - class_mode = 'categorical' + # Set filepath where our model will be saved. + # Please add directory to this project in the beginning of the string + if self.identifier == 1: + self.saved_model_path = "/IPNeuronaleNetze/models/GenderWeights" + class_mode ='binary' + else: + self.saved_model_path = "/IPNeuronaleNetze/models/AgeWeights" + class_mode = 'categorical' - # Load the datasets via ImageDataGenerator - datagen = ImageDataGenerator(preprocessing_function = preprocess_input) + # Load the datasets via ImageDataGenerator + datagen = ImageDataGenerator(preprocessing_function = preprocess_input) - train_generator = datagen.flow_from_directory( - self.filepath, target_size = (224,224), - batch_size=batch_size, class_mode = class_mode, - shuffle = True) + train_generator = datagen.flow_from_directory( + self.filepath, target_size = (224,224), + batch_size=batch_size, class_mode = class_mode, + shuffle = True) - validation_generator = datagen.flow_from_directory( - self.validation_filepath, target_size =(224,224), - batch_size = batch_size, class_mode = class_mode, - shuffle = True) + validation_generator = datagen.flow_from_directory( + self.validation_filepath, target_size =(224,224), + batch_size = batch_size, class_mode = class_mode, + shuffle = True) - # Load the callbacks, which are separated in a different class - callbacks = Cback() - callbacks = callbacks.makeCb() + # Load the callbacks, which are separated in a different class + callbacks = Cback() + callbacks = callbacks.makeCb() - # Start the training wit the fit_generator - history = self.model.fit_generator( - train_generator, steps_per_epoch = train_generator.samples/train_generator.batch_size, - validation_data=validation_generator, validation_steps = validation_generator.samples/validation_generator.batch_size, - epochs = self.epochs ,callbacks = callbacks) + # Start the training wit the fit_generator + history = self.model.fit_generator( + train_generator, steps_per_epoch = train_generator.samples/train_generator.batch_size, + validation_data=validation_generator, validation_steps = validation_generator.samples/validation_generator.batch_size, + epochs = self.epochs ,callbacks = callbacks) - # Save the model if demanded - if self.save_model == True: - self.saved_model_path = tf.contrib.saved_model.save_keras_model( - self.model, self.saved_model_path, - custom_objects=None, as_text=None) + # Save the model if demanded + if self.save_model == True: + self.saved_model_path = tf.contrib.saved_model.save_keras_model( + self.model, self.saved_model_path, + custom_objects=None, as_text=None) - # Evaluate the trained model - self.evaluate(self.test_filepath) + # Evaluate the trained model + self.evaluate(self.test_filepath) - return self.model + return self.model def evaluate(self, test_filepath, name = "evaluation"): From 47cbdfd9a2ca03fae50670cfb59108c61b4a6642 Mon Sep 17 00:00:00 2001 From: ChrisKre Date: Wed, 6 Feb 2019 18:40:33 +0100 Subject: [PATCH 3/4] Write docs --- docs/OurModel.md | 34 +++++++++++++++++++++++++ docs/Train.md | 12 +++++++++ docs/Trainer.md | 27 ++++++++++++++++++++ docs/parsers.md | 11 +++++++++ docs/tests.md | 37 ++++++++++++++++++++++++++++ models/dataloaders/image.py | 49 ------------------------------------- 6 files changed, 121 insertions(+), 49 deletions(-) create mode 100644 docs/OurModel.md create mode 100644 docs/Train.md create mode 100644 docs/Trainer.md create mode 100644 docs/parsers.md create mode 100644 docs/tests.md delete mode 100644 models/dataloaders/image.py diff --git a/docs/OurModel.md b/docs/OurModel.md new file mode 100644 index 0000000..b90c1a3 --- /dev/null +++ b/docs/OurModel.md @@ -0,0 +1,34 @@ +The OurModel class builds and compiles the convolutional networks for gender or age estimation. +Depending on the identifier in the construcor either a gender or a age estimation network will be build. + +The model from the OurModel class consists of a vgg16 base model with the pre-trained weights from 'imagenet' and 5 additional layers. + +So the structure is as follows: + + 16 Layer from the vgg16 base model + 1 GlobalAveragePooling2D layer + 2 Dense layer + + 1 Dense layer with sigmoid for age estimation + or + 1 Dense layer with softmax for gender estimation + +The structure of this model is inspired by the following paper. + https://www.vision.ee.ethz.ch/en/publications/papers/proceedings/eth_biwi_01229.pdf + +As optimizer the GradientDescentOptimizer from tf.train is use. +This optimizer makes it possible to load and save the trained model as saved_model file effortlessly. + https://www.tensorflow.org/api_docs/python/tf/train/GradientDescentOptimizer + + +The load_model method offers the possibility to load a saved_model file and compile it allready. +So after the model is loaded with this method, it's ready for training. + + + + + + + + + diff --git a/docs/Train.md b/docs/Train.md new file mode 100644 index 0000000..33735cd --- /dev/null +++ b/docs/Train.md @@ -0,0 +1,12 @@ +This is the main method of the training. Here the training will be started, models can be loaded and/or evaluated. + +Requirement for training: + Make sure to have the dataset be available in subfolders sorted by their use. So the structure is as follows: + ../dataset/Train + ../dataset/Valid + ../dataset/Test + + https://medium.com/@vijayabhaskar96/tutorial-image-classification-with-keras-flow-from-directory-and-generators-95f75ebe5720 + + + diff --git a/docs/Trainer.md b/docs/Trainer.md new file mode 100644 index 0000000..6b05baf --- /dev/null +++ b/docs/Trainer.md @@ -0,0 +1,27 @@ +The Trainer class does the training for gender or age estimation depending on the identifier given in the constructor of this class. + +Loading and preprocessing of the dataset will be done by the ImageDataGenerator from keras. +All the preprocessing parameters are adjusted by the preprocess_input from keras. These are the parameters which were used for the imagent weights. + +The Trainer class takes use of the Cback class, which consits of the callbacks for the training. +These callbacks are in use: + EarlyStopping + batch_print_callback + json_logging_callback + cleanup_callback + +For further reading: +https://keras.io/callbacks/ + +The train method returns the trained model and saves the model in the Trainer instance. +The trained model will be saved per default. This will be default directory for the saved model: + + /IPNeuronaleNetze/models/GenderWeights for gender + or + /IPNeuronaleNetze/models/AgeWeights" for age + +A .csv file will be created by the evaluate method. Use this .csv file in combination with the parser scripts to interpret the results of the training. + + + + diff --git a/docs/parsers.md b/docs/parsers.md new file mode 100644 index 0000000..57bb274 --- /dev/null +++ b/docs/parsers.md @@ -0,0 +1,11 @@ +The files parse_age.py and parse_gender.py are for evualtion of the .csv file created in training. + parse_age: returns the average difference in years from test dataset + parse_gender: returns the correct predictions in percent from test dataset + +Requirement: + python 3 + +How to: + python3 parse_age csv.file_name + python3 parse_gender csv.file_name + diff --git a/docs/tests.md b/docs/tests.md new file mode 100644 index 0000000..dbcac44 --- /dev/null +++ b/docs/tests.md @@ -0,0 +1,37 @@ +The modelTest class offers a few methods to confirm the proper functionality of the convolutional network you want to train. +It is advisable to take a small dataset with just a couple of hundreds of pictures. Due to the test environment each single step +of training will take more ressources of your computer as well as time than it would normally do. + +This testclass tests the correct structure of the OurModel class, which will be used for training. +Also it tests the training itself, to confirm the right functionality within the training. + +Test cases: + 1. Test whether saved model file will be created after training + test_model_get_saved + + 2. Test whether model will be saved and loaded properly + test_model_get_saved_and_loaded_correctly + + 3. Test whether the weigts are changed after one step of training + test_one_training_step + + 4. Test whether all of the layers of a model are trainable, except the input layers + test_model_layers_allTrainable + + 5. Test whether Gendermodel is in expected shape + test_Gendermodel_layerLength + + 6. Test whether Gendermodel is in expected shape + test_Agemodel_layerLength + +Recommendation: + Use the test.py before the actual training. + +Note: + Test case 1 and 2 will create a folder with a saved_model file. + You might want to delete these folders before the actual training. + +Inspiration for the test cases: + https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765 + + \ No newline at end of file diff --git a/models/dataloaders/image.py b/models/dataloaders/image.py deleted file mode 100644 index 86676c7..0000000 --- a/models/dataloaders/image.py +++ /dev/null @@ -1,49 +0,0 @@ -import numpy as np -from matplotlib import pyplot as plt -import cv2 as cv - - -img_1d = np.load('test2.out.npy') - -img_mat = img_1d.reshape(-224,224,3) - -print(img_mat.shape) -print(img_mat.dtype) -tmp = img_mat.astype(int) - -print(tmp.dtype) -print(tmp.shape) - -#print(tmp[:,:,0]) -#print(tmp[:,:,1]) -#print(tmp[:,:,2]) - - -print(img_mat[:,:,0]) -print(img_mat[:,:,1]) -print(img_mat[:,:,2]) - - -#img = cv.cvtColor(tmp,cv.COLOR_BGR2RGB) - -#cv.imwrite('./farbschema2.jpg',img_mat) - -#(B, G, R) = cv.split(img_mat) -#merged = cv.merge([R,G,B]) - -#cv.imshow("image",img_mat) - -plt.imshow(img_mat) -plt.show() - - -reloaded_img = cv.imread('farbschema2.jpg') -print(reloaded_img.shape) - -print("____________________________________________________________") -print(reloaded_img[:,:0]) -print(reloaded_img[:,:,1]) -print(reloaded_img[:,:,2]) - -plt.imshow(reloaded_img) -plt.show() From 57e72b8ad1bc0b7898119a0c09f2b1199544d243 Mon Sep 17 00:00:00 2001 From: ChrisKre <34210692+ChrisKre@users.noreply.github.com> Date: Thu, 7 Feb 2019 15:52:02 +0100 Subject: [PATCH 4/4] Update tests.md --- docs/tests.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tests.md b/docs/tests.md index dbcac44..c258138 100644 --- a/docs/tests.md +++ b/docs/tests.md @@ -21,7 +21,7 @@ Test cases: 5. Test whether Gendermodel is in expected shape test_Gendermodel_layerLength - 6. Test whether Gendermodel is in expected shape + 6. Test whether Agemodel is in expected shape test_Agemodel_layerLength Recommendation: @@ -34,4 +34,4 @@ Note: Inspiration for the test cases: https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765 - \ No newline at end of file +