diff --git a/builders/__init__.py b/builders/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/builders/data_training_builders.py b/builders/data_training_builders.py new file mode 100644 index 000000000..1321b4cc1 --- /dev/null +++ b/builders/data_training_builders.py @@ -0,0 +1,213 @@ +import pandas as pd +import os +import math +import numpy as np + +''' +Checks if the directory exists, if not, creates the directory +''' +def create_directory_if_does_not_exist(directory): + if not os.path.exists(directory): + os.makedirs(directory) + +''' +Organizes the list of dataset subdirectories +in ascending order (Ex: ID1,ID2,ID3 ... ID15). +''' +def sort_by_number(id): + return int(id[2:]) + +''' +Gets the path to access the sampling, acceleration + and angular acceleration files of the dataset + ''' +def get_file_path(main_directory, subdirectory, position,preprocessing = None): + subdirectory_path = os.path.join(main_directory, subdirectory) + subdirectory_path_of_subdirectory = os.path.join(subdirectory_path, position) + + file_name = f'{subdirectory}_{position}_acceleration.csv' + file_name_2 = f'{subdirectory}_{position}_sampling.csv' + file_name_3 = f'{subdirectory}_{position}_angular_speed.csv' + + sampling_file = os.path.join(subdirectory_path_of_subdirectory, file_name_2) + acc_file = os.path.join(subdirectory_path_of_subdirectory, file_name) + gyr_file = os.path.join(subdirectory_path_of_subdirectory, file_name_3) + + if preprocessing == "sim": + return acc_file,gyr_file,sampling_file,file_name,file_name_2,file_name_3 + return acc_file, gyr_file, sampling_file + + +''' +Adds the magnitude column {force resulting +from the (a,w)x, (a,w)y and (a,w)z axes} in the dataframe. +''' +def add_magnitude_column(dataframe, sensor = None): + initial_letter = None + + if sensor == "acc": + initial_letter = "a" + else: + initial_letter = "w" + + resultant_force = [] + i = 0 + + while i < len(dataframe[f'{initial_letter}x']): + resultant = math.sqrt((dataframe[f'{initial_letter}x'][i]) ** 2 + (dataframe[f'{initial_letter}y'][i]) ** 2 + (dataframe[f'{initial_letter}z'][i]) ** 2) + resultant_force.append(resultant) + i += 1 + dataframe.insert(5, "Magnitude", resultant_force, True) + +''' +Creates a data frame from each data file in the dataset. +''' +def create_dataframe(acc_file, gyr_file, sampling_file): + + acc_dataframe = pd.DataFrame(pd.read_csv(acc_file)) + gyr_dataframe= pd.DataFrame(pd.read_csv(gyr_file)) + sampling_dataframe = pd.DataFrame(pd.read_csv(sampling_file)) + + return acc_dataframe,gyr_dataframe,sampling_dataframe + +''' +Apply the fourier transform to data arrays +''' +def fourier_transform(time_series): + altered_time_series = [] + mean_time_series = np.mean(time_series) + + for i in time_series: + #Subtraction from the average to Remove the DC Component (Zero Frequency Component). + data = i - mean_time_series + altered_time_series.append(data) + altered_time_series = np.array(altered_time_series) + + return np.abs(np.fft.fft(altered_time_series)) + +''' +selects the data columns (a,w)x, (a,w)y, (a,w)z and mag +(gyr,acc) of the dataframe according to the index I of the +for loop that represents the access key for the file for +each volunteer (ID1, ID2, etc.) and according to the activity +(ADL_1, ADL_2, etc.). Removes the value referring to the +first observation of each activity, aiming to minimize +any error in data collection. +''' +def section_data_array(acc_dataframe, gyr_dataframe, i,use_in_media_generator = None): + magacc = acc_dataframe.loc[acc_dataframe["sampling"] == i, "Magnitude"] + magacc = magacc.reset_index(drop=True) + + xacc = acc_dataframe.loc[acc_dataframe["sampling"] == i, "ax"] + xacc = xacc.reset_index(drop=True) + + yacc = acc_dataframe.loc[acc_dataframe["sampling"] == i, "ay"] + yacc = yacc.reset_index(drop=True) + + zacc = acc_dataframe.loc[acc_dataframe["sampling"] == i, "az"] + zacc = zacc.reset_index(drop=True) + + maggyr = gyr_dataframe.loc[gyr_dataframe["sampling"] == i, "Magnitude"] + maggyr = maggyr.reset_index(drop=True) + + xgyr = gyr_dataframe.loc[gyr_dataframe["sampling"] == i, "wx"] + xgyr = xgyr.reset_index(drop=True) + + ygyr = gyr_dataframe.loc[gyr_dataframe["sampling"] == i, "wy"] + ygyr = ygyr.reset_index(drop=True) + + zgyr = gyr_dataframe.loc[gyr_dataframe["sampling"] == i, "wz"] + zgyr = zgyr.reset_index(drop=True) + + timestamp_acc = acc_dataframe.loc[acc_dataframe["sampling"] == i, "timestamp"] + timestamp_acc = timestamp_acc.reset_index(drop=True) + timestamp_acc = timestamp_acc.drop(0) + timestamp_acc = timestamp_acc.reset_index(drop=True) + + timestamp_gyr = gyr_dataframe.loc[gyr_dataframe["sampling"] == i, "timestamp"] + timestamp_gyr = timestamp_gyr.reset_index(drop=True) + timestamp_gyr = timestamp_gyr.drop(0) + timestamp_gyr = timestamp_gyr.reset_index(drop=True) + + if use_in_media_generator == "yes": + return timestamp_acc, timestamp_gyr, magacc, xacc, yacc, zacc, maggyr, xgyr, ygyr, zgyr + return magacc, xacc, yacc, zacc, maggyr, xgyr, ygyr, zgyr +''' +Adds data arrays formatted in the size of observations equivalent to five seconds +(480 observations for the right and left wrist and 1050 observations for the chest) +in the time domain and frequency domain lists. +''' +def add_data_arrays_to_time_and_frequency_data_lists(initial_index,final_index,array_size,data_array,data_array_list,fourier_transformed_data_array_list): + data_array = data_array[initial_index:final_index] + numpy_data_array = np.array(data_array) + numpy_data_array = np.expand_dims(numpy_data_array, axis=1) + data_array_list.append(numpy_data_array) + + transformed_data_array = fourier_transform(data_array) + transformed_data_array = transformed_data_array[:int(array_size / 2)] + numpy_transformed_data_array = np.array(transformed_data_array) + numpy_transformed_data_array = np.expand_dims(numpy_transformed_data_array, axis=1) + fourier_transformed_data_array_list.append(numpy_transformed_data_array) + + +''' +Returns the activity label for the four approaches. +''' +def create_labels(activity): + + #Multiple_classes_labels_1 represents the approach where each activity, whether performed with or without a rifle, + # receives a distinct label. With the exception of activities FALL_5 and FALL_6, which receive a single label and + # are considered only as lateral falls regardless of the fall position. The same applies to FALL_5_with_rifle and FALL_6_with_rifle. + multiple_classes_labels_1 = {"ADL_1": 0, "ADL_2": 1, "ADL_3": 2, "ADL_4": 3, "ADL_5": 4, "ADL_6": 5, "ADL_7": 6, "ADL_8": 7, + "ADL_11": 8, "ADL_12": 9, "ADL_13": 10, "ADL_14": 11, "ADL_15": 12, "OM_1": 13, "OM_2": 14, + "OM_3": 15, "OM_4": 16, "OM_5": 17, "OM_6": 18, "OM_7": 19, "OM_8": 20, "OM_9": 21, + "FALL_1": 22, "FALL_2": 23, "FALL_3": 24, "FALL_5": 25, "FALL_6": 25,"FALL_1_with_rifle": 26, + "FALL_3_with_rifle": 27, "FALL_5_with_rifle": 28, "FALL_6_with_rifle": 28,"ADL_1_with_rifle": 29, + "ADL_4_with_rifle": 30, "ADL_5_with_rifle": 31, "ADL_6_with_rifle": 32,"ADL_11_with_rifle": 33, + "ADL_12_with_rifle": 34, "ADL_13_with_rifle": 35, "ADL_14_with_rifle": 36} + + + #Multiple_classes_labels_2 represents the approach in which each activity performed, regardless of whether a rifle + # was used or not, receives a label according to the activity. For example, activity ADL_1 receives the label 0 and + # encompasses standing activities recorded with and without the use of a rifle. The exception is activities FALL_5 + # and FALL_6, which receive a single label and are considered only as a lateral fall, regardless of the fall position. + multiple_classes_labels_2 = {"ADL_1": 0, "ADL_2": 1, "ADL_3": 2, "ADL_4": 3, "ADL_5": 4, "ADL_6": 5, "ADL_7": 6, + "ADL_8": 7,"ADL_11": 8, "ADL_12": 9, "ADL_13": 10, "ADL_14": 11, "ADL_15": 12, "OM_1": 13, + "OM_2": 14,"OM_3": 15, "OM_4": 16, "OM_5": 17, "OM_6": 18, "OM_7": 19, "OM_8": 20, "OM_9": 21, + "FALL_1": 22, "FALL_2": 23, "FALL_3": 24, "FALL_5": 25, "FALL_6": 25} + + # binary_classes_labels_1 is the approach where all activities of daily living and military operations receive + # label and falls receive label 0. However, activities 0M6 to OM8 relating to the transition to the prone + # firing position are considered as falling activities. + binary_classes_labels_1 = {"ADL_1": 1, "ADL_2": 1, "ADL_3": 1, "ADL_4": 1, "ADL_5": 1, "ADL_6": 1, "ADL_7": 1, "ADL_8": 1, + "ADL_11": 1, "ADL_12": 1, "ADL_13": 1, "ADL_14": 1, "ADL_15": 1, "OM_1": 1, + "OM_2": 1, "OM_3": 1, "OM_4": 1, "OM_5": 1, "OM_6": 0, "OM_7": 0, "OM_8": 0, "OM_9": 1, + "FALL_1": 0, "FALL_2": 0, "FALL_3": 0, "FALL_5": 0, "FALL_6": 0} + + #binary_classes_labels_2 is the approach where all activities of daily living and military operations receive + # label 1 and falls receive label 0. However, activities 0M6 to OM8 relating to the transition to the prone + # shooting position are not considered as fall activities and receive label 1. + binary_classes_labels_2 = {"ADL_1": 1, "ADL_2": 1, "ADL_3": 1, "ADL_4": 1, "ADL_5": 1, "ADL_6": 1, "ADL_7": 1, "ADL_8": 1, + "ADL_11": 1, "ADL_12": 1, "ADL_13": 1, "ADL_14": 1, "ADL_15": 1, "OM_1": 1, + "OM_2": 1, "OM_3": 1, "OM_4": 1, "OM_5": 1, "OM_6": 1, "OM_7": 1, "OM_8": 1, "OM_9": 1, + "FALL_1": 0, "FALL_2": 0, "FALL_3": 0, "FALL_5": 0, "FALL_6": 0} + + activity_without_rifle = activity.split("_with_rifle")[0] + + multiple_class_label_1 = multiple_classes_labels_1.get(activity) + multiple_class_label_2 = multiple_classes_labels_2.get(activity_without_rifle) + binary_class_label_1 = binary_classes_labels_1.get(activity_without_rifle) + binary_class_label_2 = binary_classes_labels_2.get(activity_without_rifle) + + return multiple_class_label_1,multiple_class_label_2,binary_class_label_1,binary_class_label_2 + +''' +Add the labels to the accelerometer and gyroscope label lists. +''' +def add_labels(multiple_class_label_1, multiple_class_label_2, binary_class_label_1, binary_class_label_2, labels_list): + + labels_list[0].append(multiple_class_label_1) + labels_list[1].append(multiple_class_label_2) + labels_list[2].append(binary_class_label_1) + labels_list[3].append(binary_class_label_2) + diff --git a/builders/data_training_generators.py b/builders/data_training_generators.py new file mode 100644 index 000000000..321abc534 --- /dev/null +++ b/builders/data_training_generators.py @@ -0,0 +1,146 @@ +from builders.data_training_builders import (section_data_array,create_labels,add_labels,add_data_arrays_to_time_and_frequency_data_lists) + +import math + +''' +Transforms the activities "FALL_1", "FALL_2", "FALL_3", "FALL_5", "FALL_6", "ADL_7", "ADL_8" and "ADL_15" +into five-second arrays and adds them to the time domain and data domain lists. frequency. Falling +activities originally last 10 seconds, however the fall itself occurs within the first five seconds. +Activities ADL_7" and "ADL_8" last 6 seconds, so the last second is being discarded. It was found that +"ADL_15" also occurs in the first 5 seconds, so we discard observations after this time. +''' +def generate_array_of_activities_lasting_5seconds(data_array, array_size, data_array_list, fourier_transformed_data_array_list): + add_data_arrays_to_time_and_frequency_data_lists(0, array_size, array_size, data_array, data_array_list, fourier_transformed_data_array_list) + +''' +It transforms activities in which state transitions occur (for example: transition from walking to the lying +shooting position into an array of size equivalent to 5 seconds. The function maps the largest force peak that +occurs during the activity and moves the start and the end of the data array as a function of this peak. +''' +def generate_array_of_transition_activities(data_array, array_size, data_array_list, fourier_transformed_data_array_list): + maximum_value = max(data_array) + index_of_maximum_value = int(data_array.loc[data_array == maximum_value].index[0]) + initial_index = int(index_of_maximum_value - (array_size / 2)) + final_index = int(index_of_maximum_value + (array_size / 2)) + + if initial_index < 0: + initial_index = 0 + final_index = array_size + elif final_index > int(data_array.index[-1]): + final_index = int(data_array.index[-1]) + initial_index = final_index - array_size + + add_data_arrays_to_time_and_frequency_data_lists(initial_index,final_index, array_size, data_array, data_array_list,fourier_transformed_data_array_list) + +''' +Transforms other activities lasting more than 10 seconds into several data arrays with a size of 5 seconds. +For example, the ADL_3 activity that lasts 30 seconds turns into 6 arrays of 5 seconds (480 observations or +1050 observations). +''' +def generate_array_of_other_activities(data_array_acc, data_array_gyr, array_size, acc_data_array_list, gyr_data_array_list, + acc_fourier_transformed_data_array_list, gyr_fourier_transformed_data_array_list, multiple_class_label_1, + multiple_class_label_2, binary_class_label_1, binary_class_label_2, labels_list, generate_labels = None): + + size_acc_data_array = len(data_array_acc) + size_gyr_data_array = len(data_array_gyr) + + if size_acc_data_array > size_gyr_data_array: + usable_size = size_gyr_data_array + else: + usable_size = size_acc_data_array + + parts = math.floor((usable_size)/array_size) + initial_index = 0 + final_index = array_size + + for i in range(parts): + add_data_arrays_to_time_and_frequency_data_lists(initial_index,final_index, array_size, data_array_acc,acc_data_array_list,acc_fourier_transformed_data_array_list) + add_data_arrays_to_time_and_frequency_data_lists(initial_index, final_index, array_size, data_array_gyr,gyr_data_array_list, gyr_fourier_transformed_data_array_list) + if generate_labels == "yes": + add_labels(multiple_class_label_1, multiple_class_label_2, binary_class_label_1, binary_class_label_2, labels_list) + + initial_index += array_size + final_index += array_size + +""" +Populates the lists with data arrays and labels for each activity for all +collected data files. Used inside a "for" loop in the "generate_activities" function. +""" +def create_data_sets_for_training(position, activity, magacc, xacc, yacc, zacc, maggyr, xgyr, ygyr, zgyr, + list_of_data_arrays_in_the_time_domain, list_of_data_arrays_in_the_frequency_domain, + labels_list): + + five_second_activity_list = ["FALL_1", "FALL_2","FALL_3","FALL_5","FALL_6","ADL_5","ADL_6","ADL_7","ADL_8","ADL_15"] + transition_activities_list = ["OM_3", "OM_4", "OM_5","OM_6", "OM_7", "OM_8"] + + multiple_class_label_1,multiple_class_label_2,binary_class_label_1,binary_class_label_2 = create_labels(activity) + + array_size = 1020 if position == "CHEST" else 450 + + + if len(xgyr) >= array_size and len(xacc) >= array_size: + if activity in five_second_activity_list: + + generate_array_of_activities_lasting_5seconds(magacc, array_size, list_of_data_arrays_in_the_time_domain[0],list_of_data_arrays_in_the_frequency_domain[0]) + generate_array_of_activities_lasting_5seconds(xacc, array_size, list_of_data_arrays_in_the_time_domain[1],list_of_data_arrays_in_the_frequency_domain[1]) + generate_array_of_activities_lasting_5seconds(yacc, array_size, list_of_data_arrays_in_the_time_domain[2],list_of_data_arrays_in_the_frequency_domain[2]) + generate_array_of_activities_lasting_5seconds(zacc, array_size, list_of_data_arrays_in_the_time_domain[3],list_of_data_arrays_in_the_frequency_domain[3]) + generate_array_of_activities_lasting_5seconds(maggyr, array_size, list_of_data_arrays_in_the_time_domain[4], list_of_data_arrays_in_the_frequency_domain[4]) + generate_array_of_activities_lasting_5seconds(xgyr, array_size, list_of_data_arrays_in_the_time_domain[5], list_of_data_arrays_in_the_frequency_domain[5]) + generate_array_of_activities_lasting_5seconds(ygyr, array_size, list_of_data_arrays_in_the_time_domain[6], list_of_data_arrays_in_the_frequency_domain[6]) + generate_array_of_activities_lasting_5seconds(zgyr, array_size, list_of_data_arrays_in_the_time_domain[7], list_of_data_arrays_in_the_frequency_domain[7]) + + add_labels(multiple_class_label_1, multiple_class_label_2, binary_class_label_1, binary_class_label_2, labels_list) + + elif activity in transition_activities_list: + generate_array_of_transition_activities(magacc, array_size, list_of_data_arrays_in_the_time_domain[0],list_of_data_arrays_in_the_frequency_domain[0]) + generate_array_of_transition_activities(xacc, array_size, list_of_data_arrays_in_the_time_domain[1],list_of_data_arrays_in_the_frequency_domain[1]) + generate_array_of_transition_activities(yacc, array_size, list_of_data_arrays_in_the_time_domain[2],list_of_data_arrays_in_the_frequency_domain[2]) + generate_array_of_transition_activities(zacc, array_size, list_of_data_arrays_in_the_time_domain[3],list_of_data_arrays_in_the_frequency_domain[3]) + generate_array_of_transition_activities(maggyr, array_size, list_of_data_arrays_in_the_time_domain[4], list_of_data_arrays_in_the_frequency_domain[4]) + generate_array_of_transition_activities(xgyr, array_size, list_of_data_arrays_in_the_time_domain[5], list_of_data_arrays_in_the_frequency_domain[5]) + generate_array_of_transition_activities(ygyr, array_size, list_of_data_arrays_in_the_time_domain[6], list_of_data_arrays_in_the_frequency_domain[6]) + generate_array_of_transition_activities(zgyr, array_size, list_of_data_arrays_in_the_time_domain[7], list_of_data_arrays_in_the_frequency_domain[7]) + + add_labels(multiple_class_label_1, multiple_class_label_2, binary_class_label_1, binary_class_label_2, labels_list) + else: + generate_array_of_other_activities(magacc, maggyr, array_size, list_of_data_arrays_in_the_time_domain[0], + list_of_data_arrays_in_the_time_domain[4], list_of_data_arrays_in_the_frequency_domain[0], + list_of_data_arrays_in_the_frequency_domain[4], multiple_class_label_1, + multiple_class_label_2, binary_class_label_1, binary_class_label_2, labels_list, "yes") + + generate_array_of_other_activities(xacc, xgyr, array_size, list_of_data_arrays_in_the_time_domain[1], + list_of_data_arrays_in_the_time_domain[5], + list_of_data_arrays_in_the_frequency_domain[1], + list_of_data_arrays_in_the_frequency_domain[5], multiple_class_label_1, + multiple_class_label_2, binary_class_label_1, binary_class_label_2, labels_list) + + generate_array_of_other_activities(yacc, ygyr, array_size, list_of_data_arrays_in_the_time_domain[2], + list_of_data_arrays_in_the_time_domain[6], + list_of_data_arrays_in_the_frequency_domain[2], + list_of_data_arrays_in_the_frequency_domain[6], multiple_class_label_1, + multiple_class_label_2, binary_class_label_1, binary_class_label_2, labels_list) + + generate_array_of_other_activities(zacc, zgyr, array_size, list_of_data_arrays_in_the_time_domain[3], + list_of_data_arrays_in_the_time_domain[7], + list_of_data_arrays_in_the_frequency_domain[3], + list_of_data_arrays_in_the_frequency_domain[7], multiple_class_label_1, + multiple_class_label_2, binary_class_label_1, binary_class_label_2, labels_list) + +''' +Populates the lists with data arrays and labels for each activity +''' +def generate_activities(acc_dataframe, gyr_dataframe, sampling_dataframe, position, list_of_data_arrays_in_the_time_domain, + list_of_data_arrays_in_the_frequency_domain,labels_list): + + for i in (sampling_dataframe["id"]): + + activity = (sampling_dataframe.loc[sampling_dataframe["id"] == i, "exercise"].iloc[0]) + if (sampling_dataframe.loc[sampling_dataframe["id"] == i, "withRifle"].iloc[0]) == 1 and activity[:2] != "OM": + activity = f'{activity}_with_rifle' + + magacc,xacc,yacc,zacc,maggyr,xgyr,ygyr,zgyr = section_data_array(acc_dataframe, gyr_dataframe, i) + + create_data_sets_for_training(position, activity, magacc, xacc, yacc, zacc, maggyr, xgyr, ygyr, zgyr, + list_of_data_arrays_in_the_time_domain, list_of_data_arrays_in_the_frequency_domain, + labels_list) diff --git a/builders/model_builders.py b/builders/model_builders.py new file mode 100644 index 000000000..9ed35fcb5 --- /dev/null +++ b/builders/model_builders.py @@ -0,0 +1,317 @@ +import os +import numpy as np +from matplotlib import pyplot as plt +from sklearn.model_selection import train_test_split +from sklearn.metrics import matthews_corrcoef, confusion_matrix, roc_auc_score, roc_curve, classification_report +import keras +from keras.utils import to_categorical +from keras.optimizers import SGD + + + +from keras.models import Sequential +from keras.layers import Dense, Dropout, Conv1D, Flatten, MaxPooling1D +import optuna +import csv +import itertools +from sklearn.metrics import confusion_matrix + +"Plot the training and validation accuracy graphs." +def plot_training_and_validation_accuracy_graphs(historic,output_dir,i,neural_network_type): + + acc = 'accuracy' if neural_network_type == "CNN1D" else "acc" + val_acc = 'val_accuracy' if neural_network_type == "CNN1D" else 'val_acc' + + training_accuracy = historic.history[acc] + validation_accuracy = historic.history[val_acc] + + epochs = range(1, len(training_accuracy) + 1) + + plt.plot(epochs, training_accuracy, "-g", label="Training Data Accuracy") + plt.plot(epochs, validation_accuracy, "-b", label="Validation Data Accuracy") + plt.legend() + plt.xlabel('Epochs') + plt.ylabel('Accuracy') + plt.savefig(os.path.join(output_dir, f"accuracy_plot_model_{i}.png")) + plt.close() + +'''Use the predict function to predict the classes corresponding to each array of data representing an activity.''' +def return_ypredicted_and_ytrue(model,X_test,y_test,decision_threshold): + y_predicted_probabilities = model.predict(X_test) + y_predicted = (y_predicted_probabilities[:, 1] >= decision_threshold).astype(int) + y_true = np.argmax(y_test, axis=1) + + return y_predicted,y_true,y_predicted_probabilities + +'''Create the structure of the confusion matrix.''' +def create_confusion_matrix(y_true,y_predicted): + cm = confusion_matrix(y_true, y_predicted) + + tn, fp, fn, tp = cm.ravel() + + return cm,tp, tn, fp, fn + +'''Use the confusion matrix created by create_confusion_matrix and plot it as a graph.''' +def plot_confusion_matrix(cm,number_of_labels,output_dir,i): + + plt.imshow(cm, cmap=plt.cm.Blues) + plt.title('Confusion Matrix') + plt.colorbar() + tick_marks = np.arange(0, number_of_labels) + plt.xticks(tick_marks, rotation=90) + plt.yticks(tick_marks) + + thresh = cm.max() / 2. + for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])): + plt.text(j, i, format(cm[i, j], 'd'), + horizontalalignment="center", + color="white" if cm[i, j] > thresh else "black") + + plt.tight_layout() + plt.ylabel('Real Label') + plt.xlabel('Predicted Label') + plt.savefig(os.path.join(output_dir, f"confusion_matrix_model_{i}.png")) + plt.close() + +'''Create the the classification report as text file and save in the directory''' +def save_classification_report(y_predicted, y_true, number_of_labels, output_dir, i): + target_names = np.arange(0, number_of_labels).astype(str) + report = classification_report(y_true, y_predicted, target_names=target_names) + with open(os.path.join(output_dir, f"classification_report_model_{i}.txt"), "w") as report_file: + report_file.write(report) + +'''Plot the ROC curve and save it in the output directory''' +def plot_roc_curve(y_predicted,y_true,output_dir, i): + roc_auc = roc_auc_score(y_true, y_predicted) + fpr, tpr, thresholds = roc_curve(y_true, y_predicted) + plt.figure(figsize=(8, 6)) + plt.plot(fpr, tpr, color='blue', lw=2, label='Curva ROC (área = %0.2f)' % roc_auc) + plt.plot([0, 1], [0, 1], color='gray', linestyle='--', lw=2) # Diagonal + plt.xlim([0.0, 1.0]) + plt.ylim([0.0, 1.05]) + plt.xlabel('Taxa de Falso Positivo') + plt.ylabel('Taxa de Verdadeiro Positivo') + plt.title('ROC') + plt.legend(loc="lower right") + plt.grid(True) + plt.savefig(os.path.join(output_dir, f"roc_curve_model_{i}.png")) + plt.close() + +'''Calculate the metrics that will be used to measure the model's effectiveness.''' +def calculate_metrics(tp, tn, fp, fn,y_test, y_predicted): + + mcc = matthews_corrcoef(y_test, y_predicted) + sensitivity = tp / (tp + fn) + specificity = tn / (tn + fp) + precision = tp / (tp + fp) + accuracy = (tp + tn) / (tp + tn + fp + fn) + + metrics = { + "MCC": mcc, + 'Sensitivity': sensitivity, + 'Specificity': specificity, + 'Precision': precision, + 'Accuracy': accuracy + } + return metrics + +'''Save the calculated metrics in a CSV file and store it in the output directory.''' +def record_the_metrics_in_the_table(metrics,tp, tn, fp, fn,i,output_dir): + metrics["tp"] = tp + metrics["tn"] = tn + metrics["fp"] = fp + metrics["fn"] = fn + + file_path = os.path.join(output_dir, f'metrics_model_{i}.csv') + with open(file_path, "a", newline="") as csvfile: + writer = csv.DictWriter(csvfile, + fieldnames=["Model", "MCC", "Sensitivity", "Specificity", "Precision", "Accuracy", + "tp", "tn", "fp", "fn"]) + if csvfile.tell() == 0: # Verifica se o arquivo está vazio para escrever o cabeçalho + writer.writeheader() + writer.writerow({"Model": i, + "MCC": metrics["MCC"], + "Sensitivity": metrics["Sensitivity"], + "Specificity": metrics["Specificity"], + "Precision": metrics["Precision"], + "Accuracy": metrics["Accuracy"], + "tp": metrics["tp"], + "tn": metrics["tn"], + "fp": metrics["fp"], + "fn": metrics["fn"]}) + +'''Save a text file with the best trial among the executed trials in the output directory.''' +def save_best_trial_to_csv(best_trial, best_params, file_path): + with open(file_path, mode='w', newline='') as file: + writer = csv.writer(file) + writer.writerow(["Trial Number", best_trial.number]) + writer.writerow(["Value", best_trial.value]) + writer.writerow(["Parameters"]) + for key, value in best_params.items(): + writer.writerow([key, value]) + +'''Run all files related to metrics and graphs.''' +def save_results(model, historic, X_test, y_test,number_of_labels,i,decision_threshold,output_dir,neural_network_type): + + model.save(os.path.join(output_dir, f"model_{i}.keras")) + plot_training_and_validation_accuracy_graphs(historic,output_dir,i,neural_network_type) + y_predicted,y_true,y_predicted_probabilities = return_ypredicted_and_ytrue(model, X_test, y_test,decision_threshold) + cm,tp, tn, fp, fn = create_confusion_matrix(y_true,y_predicted) + plot_confusion_matrix(cm, number_of_labels,output_dir,i) + save_classification_report(y_predicted, y_true, number_of_labels, output_dir, i) + plot_roc_curve(y_predicted, y_true,output_dir,i) + metrics = calculate_metrics(tp, tn, fp, fn, y_true, y_predicted) + record_the_metrics_in_the_table(metrics,tp, tn, fp, fn,i,output_dir) + +'''Create the structure of the CNN 1D network to be optimized.''' +def cnn1d_architecture(input_shape,X_train,y_train,X_val,y_val,filter_size,kernel_size,num_layers,num_dense_layers,dense_neurons,dropout,learning_rate,number_of_labels): + max_pool = 2 + model = Sequential() + for i in range(num_layers): + if i == 0: + model.add(Conv1D(filters=filter_size, kernel_size=kernel_size, activation="relu", input_shape= input_shape)) + else: + if filter_size < kernel_size: + filter_size = kernel_size + filter_size *= 2 + model.add(Conv1D(filters=filter_size, kernel_size=kernel_size, activation="relu")) + model.add(MaxPooling1D(pool_size=max_pool)) + model.add(Dropout(dropout)) + + model.add(Flatten()) + for i in range(num_dense_layers): + model.add(Dense(dense_neurons, activation='relu')) + model.add(Dense(number_of_labels, activation='softmax')) + + optimizer = keras.optimizers.Adam(learning_rate=learning_rate) + model.compile(optimizer=optimizer, loss='categorical_crossentropy', metrics=['accuracy']) + + historic = model.fit(X_train, y_train, batch_size=32, epochs=25, validation_data=(X_val, y_val), verbose=1) + + return model,historic + +'''Create the structure of the MLP network to be optimized.''' +def mlp_architecture(input_dim,X_train,y_train,X_val,y_val,num_layers,dense_neurons,dropout,learning_rate,number_of_labels): + model = Sequential() + + batch = int(len(y_train) / 30) + + for i in range(num_layers): + if i == 0: + model.add(Dense(dense_neurons, input_dim=input_dim, kernel_initializer='normal', activation='relu')) + model.add(Dropout(dropout)) + else: + model.add(Dense(dense_neurons, kernel_initializer='normal', activation='relu')) + model.add(Dropout(dropout)) + + model.add(Dense(number_of_labels, kernel_initializer='normal', activation='softmax')) + + optimizer = SGD(learning_rate=learning_rate) + model.compile(loss="categorical_crossentropy", optimizer=optimizer, metrics=["acc"]) + historic = model.fit(X_train, y_train, epochs=300, batch_size=batch, validation_data=(X_val, y_val), verbose=1) + + return model, historic + +'''Split the dataset into training, validation, and test sets.''' +def generate_training_testing_and_validation_sets(data=None, label=None): + + X = np.load(data) + y = np.load(label) + + X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4, random_state=42) + X_test, X_val, y_test, y_val = train_test_split(X_test, y_test, test_size=0.5, random_state=42) + + y_train = to_categorical(y_train) + y_test = to_categorical(y_test) + y_val = to_categorical(y_val) + + return X_train, X_test, y_train, y_test, X_val, y_val + +'''Define the search space and the parameters to be optimized.''' +def objective(trial,input_shape,X_train,y_train,X_val,y_val,neural_network_type,output_dir): + + mcc = None + + if neural_network_type == "CNN1D": + # Definindo o espaço de busca dos hiperparâmetros + filter_size = trial.suggest_int('filter_size', 8, 600, log=True) + kernel_size = trial.suggest_int('kernel_size', 2, 6) + num_layers = trial.suggest_int('num_layers', 2, 4) + num_dense_layers = trial.suggest_int('num_dense_layers', 1, 3) + dense_neurons = trial.suggest_int('dense_neurons', 60, 320, log=True) + dropout = trial.suggest_float('dropout', 0.5, 0.9, step=0.1) + learning_rate = trial.suggest_categorical('learning_rate', [0.0001, 0.0003, 0.0006, 0.001, 0.003, 0.006, 0.01]) + decision_threshold = trial.suggest_float('decision_threshold', 0.5, 0.9,step=0.1) + + model,historic = cnn1d_architecture(input_shape,X_train,y_train,X_val,y_val,filter_size,kernel_size, + num_layers,num_dense_layers,dense_neurons,dropout,learning_rate) + + y_pred_prob = model.predict(X_val) + y_pred = (y_pred_prob[:, 1] >= decision_threshold).astype(int) + + mcc = matthews_corrcoef(y_val.argmax(axis=1), y_pred) + + optimized_params = { + "filter_size": filter_size, + "kernel_size": kernel_size, + "num_layers": num_layers, + "num_dense_layers": num_dense_layers, + "dense_neurons": dense_neurons, + "dropout": dropout, + "learning_rate": learning_rate, + "decision_threshold": decision_threshold + } + + elif neural_network_type == "MLP": + + num_layers = trial.suggest_int('num_layers', 1, 5) + dense_neurons = trial.suggest_int('dense_neurons', 20, 4000, log=True) + dropout = trial.suggest_float('dropout', 0.5, 0.9, step=0.1) + learning_rate = trial.suggest_categorical('learning_rate', [0.001, 0.003, 0.005, 0.007, 0.01, 0.03, 0.05, 0.07]) + decision_threshold = trial.suggest_float('decision_threshold', 0.5, 0.9,step=0.1) + + model, historic = mlp_architecture(input_shape,X_train,y_train,X_val,y_val,num_layers,dense_neurons,dropout,learning_rate) + + y_pred_prob = model.predict(X_val) + y_pred = (y_pred_prob[:, 1] >= decision_threshold).astype(int) + + mcc = matthews_corrcoef(X_val.argmax(axis=1), y_pred) + + optimized_params = { + "num_layers": num_layers, + "dense_neurons": dense_neurons, + "dropout": dropout, + "learning_rate": learning_rate, + "decision_threshold": decision_threshold + } + + file_path = os.path.join(output_dir, 'optimization_results.csv') + file_exists = os.path.isfile(file_path) + + with open(file_path, "a", newline='') as csvfile: + fieldnames = ["Trial", "MCC"] + list(optimized_params.keys()) + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + + if not file_exists: + writer.writeheader() + + row = {"Trial": trial.number, "MCC": mcc} + row.update(optimized_params) + writer.writerow(row) + + return mcc + +'''Creates an Optuna study object that defines the maximization direction to optimize the objective function.''' +def create_study_object(objective, input_shape, X_train, y_train, X_val, y_val, neural_network_type,neural_network_results_dir): + study = optuna.create_study(direction="maximize") + + study.optimize(lambda trial: objective(trial, input_shape, X_train, y_train, X_val, y_val, neural_network_type,neural_network_results_dir), n_trials=100) + + best_trial = study.best_trial + best_params = best_trial.params + + return best_trial,best_params + +if __name__ == "__main__": + pass +