From 203b75e891d05ca77a3e1174c045792a46e4e327 Mon Sep 17 00:00:00 2001 From: Joaquin Torres Bravo Date: Wed, 22 May 2024 12:13:29 +0200 Subject: [PATCH] Found optimal hyperparamers and exported, more analysis to be done in different files --- model_selection/hyperparam_tuning.py | 52 ++++++------------ .../output_hyperparam/hyperparamers.xlsx | Bin 8635 -> 8924 bytes 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/model_selection/hyperparam_tuning.py b/model_selection/hyperparam_tuning.py index 6d8af49..4f7734d 100644 --- a/model_selection/hyperparam_tuning.py +++ b/model_selection/hyperparam_tuning.py @@ -1,6 +1,6 @@ """ - Selecting best models through cross validation and hyperparameter tunning - for each method: + Finding optimal hyperparameters through RandomSearchCV for each group (1. pre - 2. post) + and method: 1. Original training dataset 2. Original training dataset - Cost sensitive 3. Oversampling @@ -21,7 +21,6 @@ from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from scipy.stats import randint, uniform from sklearn.model_selection import RandomizedSearchCV -import os # -------------------------------------------------------------------------------------------------------- # Function to read training datasets @@ -73,26 +72,22 @@ if __name__ == "__main__": # -------------------------------------------------------------------------------------------------------- # 1. No class weight models_simple = {"DT" : DecisionTreeClassifier(), - "RF" : RandomForestClassifier(), - "Bagging" : BaggingClassifier(), - "AB" : AdaBoostClassifier(algorithm='SAMME'), - "XGB": XGBClassifier(), - "LR" : LogisticRegression(max_iter=1000), - "SVM" : SVC(probability=True, max_iter=1000), - "MLP" : MLPClassifier(max_iter=500) - # "ElNet" : LogisticRegression(max_iter=1000, penalty='elasticnet') + # "RF" : RandomForestClassifier(), + # "Bagging" : BaggingClassifier(), + # "AB" : AdaBoostClassifier(algorithm='SAMME'), + # "XGB": XGBClassifier(), + # "LR" : LogisticRegression(max_iter=1000), + # "SVM" : SVC(probability=True, max_iter=1000), + # "MLP" : MLPClassifier(max_iter=500) } # 2. Class weight: cost-sensitive learning models_CS = {"DT" : DecisionTreeClassifier(class_weight='balanced'), - "RF" : RandomForestClassifier(class_weight='balanced'), - "Bagging" : BaggingClassifier(estimator= DecisionTreeClassifier(class_weight='balanced')), - "AB" : AdaBoostClassifier(estimator= DecisionTreeClassifier(class_weight='balanced'), algorithm='SAMME'), - "LR" : LogisticRegression(max_iter=1000, class_weight='balanced'), - "SVM" : SVC(probability=True, max_iter = 1000, class_weight='balanced'), - # "ElNet" : LogisticRegression(max_iter=1000, penalty='elasticnet', class_weight='balanced'), - # "XGB": XGBClassifier(), # <- - # "MLP" : MLPClassifier(max_iter=500) # <- + # "RF" : RandomForestClassifier(class_weight='balanced'), + # "Bagging" : BaggingClassifier(estimator= DecisionTreeClassifier(class_weight='balanced')), + # "AB" : AdaBoostClassifier(estimator= DecisionTreeClassifier(class_weight='balanced'), algorithm='SAMME'), + # "LR" : LogisticRegression(max_iter=1000, class_weight='balanced'), + # "SVM" : SVC(probability=True, max_iter = 1000, class_weight='balanced'), } # -------------------------------------------------------------------------------------------------------- @@ -121,7 +116,6 @@ if __name__ == "__main__": "MLP": {'activation': ['identity', 'logistic', 'tanh', 'relu'], 'hidden_layer_sizes': randint(50, 150), 'learning_rate': ['constant', 'invscaling', 'adaptive']} - # "ElNet": {'solver': ['lbfgs', 'sag', 'saga']}, } # -------------------------------------------------------------------------------------------------------- @@ -148,25 +142,17 @@ if __name__ == "__main__": y = data_dic['y_train_' + method + group] # Use group of models with class weight if needed models = models_CS if j == 1 else models_simple - # Save results: set of optimal hyperpameters -> mean precision and sd for those parameters across folds - hyperparam_df = pd.DataFrame(index=list(models.keys()), columns=['Best Parameters','Mean Precision', 'SD']) + # Save optimal hyperparameters for each of the models -> metrics will be computed in a different file + hyperparam_df = pd.DataFrame(index=list(models.keys()), columns=['Model Name', 'Best Parameters']) for model_name, model in models.items(): print(f"{group}-{method_names[j]}-{model_name}") # Find optimal hyperparams for curr model params = hyperparameters[model_name] search = RandomizedSearchCV(model, param_distributions=params, cv=cv, n_jobs=10, scoring='precision') search.fit(X,y) - # Access the results - results = search.cv_results_ - best_index = search.best_index_ - # Get sd and mean across folds for best set of hyperpameters + # Keep optimal parameters best_params = search.best_params_ - mean_precision_best = results['mean_test_score'][best_index] - std_precision_best = results['std_test_score'][best_index] - # Storing these values hyperparam_df.at[model_name, 'Best Parameters'] = best_params - hyperparam_df.at[model_name, 'Mean Precision'] = round(mean_precision_best, 4) - hyperparam_df.at[model_name, 'SD'] = round(std_precision_best, 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 @@ -177,6 +163,4 @@ if __name__ == "__main__": data.to_excel(writer, sheet_name=sheet_name) print("Successful tuning") - # -------------------------------------------------------------------------------------------------------- - - + # -------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/model_selection/output_hyperparam/hyperparamers.xlsx b/model_selection/output_hyperparam/hyperparamers.xlsx index 0f4ba7ea4cbbfa8585dcc2d47f8faffc61ec8ab8..96eff997c434ccdd40d61a42fe25cf01d2f6fdd3 100644 GIT binary patch literal 8924 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 -- 2.24.1