From a1ce917a7a7273d47e84e52138661ad5136bcd02 Mon Sep 17 00:00:00 2001 From: joaquintb Date: Tue, 7 May 2024 16:54:19 +0200 Subject: [PATCH] crossval + hyperparam tested on DF --- .../{train_models.py => eval_models.py} | 20 +- training_models/hyperparam_tuning.py | 182 ++++++++++++++++++ training_models/output/cross_val_res.xlsx | Bin 9853 -> 9795 bytes training_models/output/hyperparam.xlsx | Bin 0 -> 14138 bytes 4 files changed, 192 insertions(+), 10 deletions(-) rename training_models/{train_models.py => eval_models.py} (90%) create mode 100644 training_models/hyperparam_tuning.py create mode 100644 training_models/output/hyperparam.xlsx diff --git a/training_models/train_models.py b/training_models/eval_models.py similarity index 90% rename from training_models/train_models.py rename to training_models/eval_models.py index 04cf608..ad3a781 100644 --- a/training_models/train_models.py +++ b/training_models/eval_models.py @@ -79,26 +79,26 @@ if __name__ == "__main__": # -------------------------------------------------------------------------------------------------------- # 1. No class weight models_1 = {#"DT" : DecisionTreeClassifier(), - "RF" : RandomForestClassifier(), + "RF" : RandomForestClassifier(n_estimators=50), # "Bagging" : BaggingClassifier(), # "AB" : AdaBoostClassifier(), # "XGB": XGBClassifier(), - # "LR" : LogisticRegression(), - # "ElNet" : LogisticRegression(penalty='elasticnet'), - # "SVM" : SVC(), - # "MLP" : MLPClassifier(), + # "LR" : LogisticRegression(max_iter=1000), + # "ElNet" : LogisticRegression(max_iter=1000, penalty='elasticnet'), + # "SVM" : SVC(probability=True), + # "MLP" : MLPClassifier(max_iter=500), } # 2. Class weight models_2 = {#"DT" : DecisionTreeClassifier(class_weight='balanced'), - "RF" : RandomForestClassifier(class_weight='balanced'), + "RF" : RandomForestClassifier(n_estimators=50, class_weight='balanced'), # "Bagging" : BaggingClassifier(), # <- # "AB" : AdaBoostClassifier(), # <- # "XGB": XGBClassifier(), # <- - # "LR" : LogisticRegression(class_weight='balanced'), - # "ElNet" : LogisticRegression(penalty='elasticnet', class_weight='balanced'), - # "SVM" : SVC(class_weight='balanced'), - # "MLP" : MLPClassifier(), # <- + # "LR" : LogisticRegression(max_iter=1000, class_weight='balanced'), + # "ElNet" : LogisticRegression(max_iter=1000, penalty='elasticnet', class_weight='balanced'), + # "SVM" : SVC(probability=True, class_weight='balanced'), + # "MLP" : MLPClassifier(max_iter=500), # <- } # -------------------------------------------------------------------------------------------------------- diff --git a/training_models/hyperparam_tuning.py b/training_models/hyperparam_tuning.py new file mode 100644 index 0000000..05e0bcd --- /dev/null +++ b/training_models/hyperparam_tuning.py @@ -0,0 +1,182 @@ +""" + Selecting best models through cross validation and hyperparameter tunning + for each method: + 1. Original training dataset + 2. Original training dataset - Cost sensitive + 3. Oversampling + 4. Undersampling +""" + +# Libraries +# -------------------------------------------------------------------------------------------------------- +import pandas as pd +import numpy as np +from xgboost import XGBClassifier +from sklearn.metrics import confusion_matrix +from sklearn.metrics import f1_score, make_scorer, precision_score, recall_score +from sklearn.model_selection import StratifiedKFold, cross_validate +from sklearn.ensemble import RandomForestClassifier, BaggingClassifier, AdaBoostClassifier +from sklearn.neural_network import MLPClassifier +from sklearn.svm import SVC +from sklearn.linear_model import LogisticRegression +from sklearn.tree import DecisionTreeClassifier +from scipy.stats import randint, uniform +from sklearn.model_selection import RandomizedSearchCV +# -------------------------------------------------------------------------------------------------------- + +# Function to read datasets +# -------------------------------------------------------------------------------------------------------- +def read_data(): + import numpy as np + + # Load test data + X_test_pre = np.load('./gen_train_data/data/output/pre/X_test_pre.npy', allow_pickle=True) + y_test_pre = np.load('./gen_train_data/data/output/pre/y_test_pre.npy', allow_pickle=True) + X_test_post = np.load('./gen_train_data/data/output/post/X_test_post.npy', allow_pickle=True) + y_test_post = np.load('./gen_train_data/data/output/post/y_test_post.npy', allow_pickle=True) + + # Load ORIGINAL training data + X_train_pre = np.load('./gen_train_data/data/output/pre/X_train_pre.npy', allow_pickle=True) + y_train_pre = np.load('./gen_train_data/data/output/pre/y_train_pre.npy', allow_pickle=True) + X_train_post = np.load('./gen_train_data/data/output/post/X_train_post.npy', allow_pickle=True) + y_train_post = np.load('./gen_train_data/data/output/post/y_train_post.npy', allow_pickle=True) + + # Load oversampled training data + X_train_over_pre = np.load('./gen_train_data/data/output/pre/X_train_over_pre.npy', allow_pickle=True) + y_train_over_pre = np.load('./gen_train_data/data/output/pre/y_train_over_pre.npy', allow_pickle=True) + X_train_over_post = np.load('./gen_train_data/data/output/post/X_train_over_post.npy', allow_pickle=True) + y_train_over_post = np.load('./gen_train_data/data/output/post/y_train_over_post.npy', allow_pickle=True) + + # Load undersampled training data + X_train_under_pre = np.load('./gen_train_data/data/output/pre/X_train_under_pre.npy', allow_pickle=True) + y_train_under_pre = np.load('./gen_train_data/data/output/pre/y_train_under_pre.npy', allow_pickle=True) + X_train_under_post = np.load('./gen_train_data/data/output/post/X_train_under_post.npy', allow_pickle=True) + y_train_under_post = np.load('./gen_train_data/data/output/post/y_train_under_post.npy', allow_pickle=True) + + data_dic = { + "X_test_pre": X_test_pre, + "y_test_pre": y_test_pre, + "X_test_post": X_test_post, + "y_test_post": y_test_post, + "X_train_pre": X_train_pre, + "y_train_pre": y_train_pre, + "X_train_post": X_train_post, + "y_train_post": y_train_post, + "X_train_over_pre": X_train_over_pre, + "y_train_over_pre": y_train_over_pre, + "X_train_over_post": X_train_over_post, + "y_train_over_post": y_train_over_post, + "X_train_under_pre": X_train_under_pre, + "y_train_under_pre": y_train_under_pre, + "X_train_under_post": X_train_under_post, + "y_train_under_post": y_train_under_post, + } + + return data_dic +# -------------------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + + # Reading training data + data_dic = read_data() + + # Defining the models to train + # -------------------------------------------------------------------------------------------------------- + # 1. No class weight + models_1 = {"DT" : DecisionTreeClassifier(), + # "RF" : RandomForestClassifier(n_estimators=50), + # "Bagging" : BaggingClassifier(), + # "AB" : AdaBoostClassifier(), + # "XGB": XGBClassifier(), + # "LR" : LogisticRegression(max_iter=1000), + # "ElNet" : LogisticRegression(max_iter=1000, penalty='elasticnet'), + # "SVM" : SVC(probability=True), + # "MLP" : MLPClassifier(max_iter=500), + } + + # 2. Class weight + models_2 = {"DT" : DecisionTreeClassifier(class_weight='balanced'), + # "RF" : RandomForestClassifier(n_estimators=50, class_weight='balanced'), + # "Bagging" : BaggingClassifier(), # <- + # "AB" : AdaBoostClassifier(), # <- + # "XGB": XGBClassifier(), # <- + # "LR" : LogisticRegression(max_iter=1000, class_weight='balanced'), + # "ElNet" : LogisticRegression(max_iter=1000, penalty='elasticnet', class_weight='balanced'), + # "SVM" : SVC(probability=True, class_weight='balanced'), + # "MLP" : MLPClassifier(max_iter=500), # <- + } + + # Hyperparameter tuning setup + # -------------------------------------------------------------------------------------------------------- + hyperparameters = { + "DT": {'splitter': ['best', 'random'], + 'max_features': ['sqrt', 'log2'], + 'criterion': ['gini', 'entropy', 'log_loss']}, + "RF": {'n_estimators': randint(100, 250), + 'max_features': ['sqrt', 'log2'], + 'criterion': ['gini', 'entropy']}, + "Bagging": {'n_estimators': randint(10, 100), + 'max_samples': [0.8, 1.0], + 'max_features': [0.8, 1.0], + 'warm_start': [True, False]}, + "AB": {'n_estimators': randint(50, 150), + 'learning_rate': uniform(0.8, 1.2)}, + "XGB": {'n_estimators': randint(100, 1000), + 'max_depth': randint(3, 10), + 'learning_rate': uniform(0.01, 0.3)}, + "LR": {'penalty': ['l1', 'l2', None], + 'solver': ['lbfgs', 'sag', 'saga']}, + "EL": {'solver': ['lbfgs', 'sag', 'saga']}, + "SVM": {'C': uniform(0.8, 1.2), + 'kernel': ['linear', 'poly', 'rbf', 'sigmoid']}, + "MLP": {'activation': ['identity', 'logistic', 'tanh', 'relu'], + 'hidden_layer_sizes': randint(50, 150), + 'learning_rate': ['constant', 'invscaling', 'adaptive']} + } + # -------------------------------------------------------------------------------------------------------- + + # Cross-validation setup + # -------------------------------------------------------------------------------------------------------- + # Defining cross-validation protocol + cv = StratifiedKFold(n_splits=10, shuffle=True, random_state=1) + method_names = { + 0: "ORIG", + 1: "ORIG_CW", + 2: "OVER", + 3: "UNDER" + } + # -------------------------------------------------------------------------------------------------------- + + # Hyperparameter tuning loop and exporting results + # -------------------------------------------------------------------------------------------------------- + # Store each df as a sheet in an excel file + sheets_dict = {} + for i, group in enumerate(['pre', 'post']): + for j, method in enumerate(['', '', 'over_', 'under_']): + print(f"ITERATION {i+j}") + # Get dataset based on group and method + X = data_dic['X_train_' + method + group] + y = data_dic['y_train_' + method + group] + # Use group of models with class weight if needed + models = models_2 if j == 2 else models_1 + # Save results: params and best score for each of the mdodels of this method and group + hyperparam_df = pd.DataFrame(index=list(models.keys()), columns=['Parameters','Score']) + for model_name, model in models.items(): + # Find optimal hyperparams for curr model + params = hyperparameters[model_name] + search = RandomizedSearchCV(model, param_distributions=params, cv=cv, n_jobs=1, scoring='precision') + search.fit(X,y) + hyperparam_df.at[model_name,'Parameters']=search.best_params_ + hyperparam_df.at[model_name,'Score']=round(search.best_score_,4) + + # Store the DataFrame in the dictionary with a unique key for each sheet + sheet_name = f"{group}_{method_names[j]}" + sheets_dict[sheet_name] = hyperparam_df + + # Write results to Excel file + with pd.ExcelWriter('./training_models/output/hyperparam.xlsx') as writer: + for sheet_name, data in sheets_dict.items(): + data.to_excel(writer, sheet_name=sheet_name) + # -------------------------------------------------------------------------------------------------------- + + diff --git a/training_models/output/cross_val_res.xlsx b/training_models/output/cross_val_res.xlsx index 5c87162d1058c8cbef378cb3d15f167ed6c97631..f1c0c3b472619e5938ec3f287c8972f748dd36ac 100644 GIT binary patch delta 5198 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 Ab^rhX delta 5311 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 F{2z~WMzH_@ diff --git a/training_models/output/hyperparam.xlsx b/training_models/output/hyperparam.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..cf23ae22eda7fecc10b15ae0a5a978074eb30f38 GIT binary patch literal 14138 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 E1380llmGw# literal 0 HcmV?d00001 -- 2.24.1