Multi Classification - Py (For 1 Class TP, TN, FP, FN)
Multi Classification - Py (For 1 Class TP, TN, FP, FN)
import pandas as pd
import numpy as np
import random
import re
from fastapi.responses import JSONResponse
from fastapi import HTTPException
#added
from sklearn.compose import ColumnTransformer
from sklearn.discriminant_analysis import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import LabelEncoder, OneHotEncoder
from scipy.special import softmax
from sklearn.preprocessing import label_binarize
import os
import io
import logging
from logging.handlers import RotatingFileHandler
from fastapi import HTTPException, Response
from fastapi.responses import JSONResponse
from botocore.exceptions import ClientError
logging.basicConfig(level=logging.INFO)
# S3 Config
AWS_ACCESS_KEY = os.getenv('AWS_ACCESS_KEY')
AWS_SECRET_KEY = os.getenv('AWS_SECRET_KEY')
AWS_REGION = os.getenv('AWS_REGION')
S3_BUCKET_NAME = os.getenv('S3_BUCKET_NAME')
# S3 Client setup
s3 = boto3.client(
's3',
aws_access_key_id=AWS_ACCESS_KEY,
aws_secret_access_key=AWS_SECRET_KEY,
region_name=AWS_REGION
)
if file_extension == '.csv':
dataset = pd.read_csv(io.StringIO(s3_object['Body'].read().decode('utf-
8')))
elif file_extension in ['.xls', '.xlsx']:
dataset = pd.read_excel(io.BytesIO(s3_object['Body'].read()))
else:
raise ValueError(f"Unsupported file type: {file_extension}")
# dataset = pd.read_csv(s3_object['Body'])
dataset = label_encode_data(dataset,target_column)
return dataset_imputed
except ClientError as e:
error_code = e.response['Error']['Code']
if error_code == 'NoSuchKey':
raise ValueError("The specified file does not exist in the S3 bucket.")
elif error_code == 'NoSuchBucket':
raise ValueError("The specified S3 bucket does not exist.")
elif error_code == 'AccessDenied':
raise ValueError("Access denied to the S3 bucket or object.")
else:
# Log full details for unexpected ClientError scenarios
raise ValueError(f"An unexpected error occurred with S3:
{e.response['Error']['Message']}")
except ValueError as e:
# Re-raise ValueError with the same message
raise ValueError(str(e))
except Exception as e:
# Handle other unexpected errors
raise ValueError(f"An unexpected error occurred: {str(e)}")
#adding
# Label encode target column and any non-numeric features
def label_encode_data(df, targetColumn):
# label_encoders = {}
# Convert column names to lowercase for consistency
# df.columns = df.columns.str.strip().str.lower()
targetColumn = targetColumn.strip()
return df #, label_encoders
categorical_transformer = Pipeline(steps=[
('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
('encoder', OneHotEncoder(handle_unknown='ignore'))
])
# Fit on training data and transform both train and test data
X_train_processed = preprocessor.fit_transform(X_train)
X_test_processed = preprocessor.transform(X_test)
if type=="ratio":
#adding
# Determine if the target variable is binary or multi-class
unique_values = df[targetColumn].nunique()
target_type = 'binary' if unique_values == 2 else 'multi'
logging.info(f"Target variable '{targetColumn}' identified as
'{target_type}' classification "
f"with {unique_values} unique values.")
# Handle random_seed
# if randomSeed == 'True':
if randomSeed == 'true':
randomSeed = 42 # Set to 42 if True
else:
randomSeed = None # Disable randomness
random_state=randomSeed,
shuffle=shuffleData)
print("train points are",y_train.head(10))
return X_train, X_test, y_train, y_test
if parameters:
for key, value in parameters.items():
# Handle different parameter types
if isinstance(value, list):
param_dict[key] = {'list': value}
elif isinstance(value, (int, float)):
if key == 'C':
param_dict[key] = {'range': (value * 0.1, value * 10)}
else:
param_dict[key] = {'value': value}
else:
param_dict[key] = {'value': value}
if not model_match:
raise ValueError(f"Model name '{modelName}' not recognized.")
# Extract the core model name from the match (in case of a version or extra
words)
modelName_core = model_match.group(0).strip()
random_state = param_dict.get("random_state")
if isinstance(random_state, dict) and random_state.get('value') == 'null':
random_state = None
elif random_state is None:
random_state = np.random.RandomState(seed=42)
# SVM
if modelCategory == "Classification" and modelName_core == 'Svm':
# Define C_values to include a range as a tuple
C_values = param_dict.get('C', {}).get('range', (0.1, 10.0))
kernel_values = param_dict.get('kernel', {}).get('list', ['linear', 'rbf'])
hyperparams = {
'C': C,
'kernel': kernel,
'degree': degree,
'gamma': gamma,
'coef0': coef0,
'shrinking': shrinking,
'probability': probability,
'tol': tol,
'cache_size': cache_size,
'class_weight': class_weight,
'verbose': verbose,
'max_iter': max_iter,
'decision_function_shape': decision_function_shape,
'break_ties': break_ties,
'random_state': random_state
}
# Decision Tree
if modelCategory == "Classification" and modelName_core == 'Decision Tree':
# Criterion values with a default value
criterion_values = param_dict.get('criterion', {}).get('list') or ['gini',
'entropy']
# Splitter values with a default value #added
splitter_values = param_dict.get('splitter', {}).get('list') or ['best',
'random']
return hyperparams
# Predictions
y_pred = model.predict(X_test)
if objectiveMetric == 'accuracy':
score = round(accuracy_score(y_test, y_pred), 2)
elif objectiveMetric == 'f1':
score = round(f1_score(y_test, y_pred, average='weighted'), 2)
elif objectiveMetric == 'precision':
score = round(precision_score(y_test, y_pred, average='weighted'), 2)
elif objectiveMetric == 'recall':
score = round(recall_score(y_test, y_pred, average='weighted'), 2)
else:
raise ValueError(f"Unsupported objective metric: {objectiveMetric}")
return score
else:
raise ValueError(f"Unsupported algorithm category: {modelCategory}")
# return SVC(**hyperparams)
#return SVC(**(hyperparams or {})),model
def objective(trial):
# Get hyperparameters using the updated function
params = get_hyperparams(modelCategory, modelName, trial, model_params)
if objective_metric == 'accuracy':
score = round(accuracy_score(y_test, y_pred), 2)
elif objective_metric == 'f1':
score = round(f1_score(y_test, y_pred, average='weighted'), 2)
elif objective_metric == 'precision':
score = round(precision_score(y_test, y_pred, average='weighted'), 2)
elif objective_metric == 'recall':
score = round(recall_score(y_test, y_pred, average='weighted'), 2)
else:
score = round(accuracy_score(y_test, y_pred), 2) # default to accuracy
return score
return study.best_params
for i in range(num_classes):
tp = cm[i, i] # True Positives: Diagonal element for class i
fp = cm[:, i].sum() - tp # False Positives: Sum of column i - TP
fn = cm[i, :].sum() - tp # False Negatives: Sum of row i - TP
tn = cm.sum() - (tp + fp + fn) # True Negatives: Total sum - (TP + FP +
FN)
metrics[i]["TP"] = tp
metrics[i]["FP"] = fp
metrics[i]["FN"] = fn
metrics[i]["TN"] = tn
return metrics
# Training the model with the best set of parameters & calculate the evaluation
metrics
def train_and_save_metrics(X_train, y_train, X_test, y_test, modelName,
modelCategory, best_params, df, targetColumn,
modelId, hyperparameterTuning, workflowId, trainSize,
projectId, versionId, updatedAt, createdAt, keyMetrics):
# def datatype(obj):
# if isinstance(obj, (np.int64, np.int32)):
# return int(obj)
# Extract TP, TN, FP, FN for each class in the test set
num_classes = len(set(y_test)) # Number of classes in the test set
metrics_test = calculate_tp_tn_fp_fn(cm_test, num_classes)
print("----------", metrics_test)
# Store the confusion matrix and metrics for test data
metrics_test["confusionMatrix"] = cm_test.tolist()
print("----------", metrics_test["confusionMatrix"])
# Extract TP, TN, FP, FN for each class in the train set
metrics_train = calculate_tp_tn_fp_fn(cm_train, num_classes)
#import pdb;pdb.set_trace()
# Store the confusion matrix and metrics for training data
metrics_train["confusionMatrix"] = cm_train.tolist()
#12-11-2024
# Build the model with the best hyperparameters
# model = build_model(modelName, best_params)
# # added
# is_binary = len(set(y_test)) == 2
num_classes = len(set(y_train))
class_labels = [f'class{i}' for i in range(num_classes)]
#added
# Metrics for test data
metrics_test.update({
"accuracyScore": round(float(accuracy_score(y_test, y_pred_test)), 3),
"f1_per_class": [round(f1, 3) for f1 in f1_score(y_test, y_pred_test,
average=None)],
"precision_per_class": [round(prec, 3) for prec in precision_score(y_test,
y_pred_test, average=None)],
"recall_per_class": [round(rec, 3) for rec in recall_score(y_test,
y_pred_test, average=None)],
})
if is_binary:
y_prob_test_binary = y_prob_test[:, 1]
y_prob_train_binary = y_prob_train[:, 1]
fpr_test, tpr_test,thresholds_test = roc_curve(y_test,
y_prob_test_binary)
valid_thresholds = []
for threshold in thresholds_test:
if np.isfinite(threshold):
valid_thresholds.append(round(float(threshold), 2))
metrics_test["aucRocCurve"] = {
"auc": round(roc_auc_score(y_test, y_prob_test_binary), 2),
"curve": [{"fpr": round(fpr, 2),
"tpr": round(tpr, 2),
"thresholds": threshold}
for fpr, tpr,threshold in zip(fpr_test,
tpr_test,valid_thresholds)][:10]}
else:
from sklearn.preprocessing import label_binarize
# Binarize labels for multi-class
classes = np.unique(np.concatenate([y_train, y_test]))
y_test_binarized = label_binarize(y_test, classes=classes)
auc_scores = []
for i in range(y_prob_test.shape[1]):
auc_test = roc_auc_score(y_test_binarized[:, i], y_prob_test[:, i])
auc_scores.append(auc_test)
valid_thresholds = []
for threshold in thresholds_train:
if np.isfinite(threshold):
valid_thresholds.append(round(float(threshold), 2))
metrics_test["aucRocCurve"].append({
"className": f"class_{i}", # Add className
"auc": round(auc_test, 2),
"data": [ # Rename "curve" to "data"
{
"fpr": round(fpr, 2),
"tpr": round(tpr, 2),
"thresholds": threshold
}
for fpr, tpr, threshold in zip(fpr_test, tpr_test,
valid_thresholds)
][:10] # Limit to the first 10 points
})
# # Common Metrics
metrics_train["confusionMatrix"] = confusion_matrix(y_train,
model.predict(X_train)).tolist()
metrics_train["accuracyScore"] = accuracy_score(y_train,
model.predict(X_train))
metrics_train["precision_per_class"] = precision_score(y_train,
model.predict(X_train), average=None) # Per-class precision
metrics_train["f1_per_class"] = f1_score(y_train, model.predict(X_train),
average=None) # Per-class F1 score
metrics_train["recall_per_class"] = recall_score(y_train,
model.predict(X_train), average=None)
metrics_train["specificity_per_class"] = calculate_specificity(y_train,
model.predict(X_train), num_classes) # Per-class recall
# metrics_train["precision"] = precision_score(y_train, model.predict(X_train),
average="weighted")
# metrics_train["f1Score"] = f1_score(y_train, model.predict(X_train),
average="weighted")
# metrics_train["recall"] = recall_score(y_train, model.predict(X_train),
average="weighted")
metrics_test["confusionMatrix"] = confusion_matrix(y_test,
model.predict(X_test)).tolist()
metrics_test["accuracyScore"] = accuracy_score(y_test, model.predict(X_test))
metrics_test["precision_per_class"] = precision_score(y_test,
model.predict(X_test), average=None) # Per-class precision
metrics_test["f1_per_class"] = f1_score(y_test, model.predict(X_test),
average=None) # Per-class F1 score
metrics_test["recall_per_class"] = recall_score(y_test, model.predict(X_test),
average=None) # Per-class recall
metrics_test["specificity_per_class"] = calculate_specificity(y_test,
y_pred_test, num_classes)
precision_avg_test = np.mean(metrics_test["precision_per_class"])
recall_avg_test = np.mean(metrics_test["recall_per_class"])
f1_avg_test = np.mean(metrics_test["f1_per_class"])
specificity_avg_test = np.mean(metrics_test["specificity_per_class"])
metrics_train.update({
"average_precision": round(precision_avg_train, 3),
"average_recall": round(recall_avg_train, 3),
"average_f1": round(f1_avg_train, 3),
"average_specificity": round(specificity_avg_train, 3)
})
metrics_test.update({
"average_precision": round(precision_avg_test, 3),
"average_recall": round(recall_avg_test, 3),
"average_f1": round(f1_avg_test, 3),
"average_specificity": round(specificity_avg_test, 3)
})
# Update response metrics to include TP, TN, FP, FN per class for train and
test
metrics_test.update({
# "tp_tn_fp_fn_per_class": tp_tn_fp_fn_per_class
"tp_tn_fp_fn_per_class": metrics_test
})
metrics_train.update({
"tp_tn_fp_fn_per_class": metrics_train
})
# metrics_train = datatype(metrics_train)
# metrics_test = datatype(metrics_test)
# print("--------------------metrics_train--------------",
type(metrics_train["tp_tn_fp_fn_per_class"][0]['TP']))
# if isinstance(metrics_test, (np.int64, np.int32)):
# return int(metrics_test)
# if isinstance(metrics_train, (np.int64, np.int32)):
# return int(metrics_train)
# metrics_train["tp_tn_fp_fn_per_class"][0]
['TP']=int(metrics_train["tp_tn_fp_fn_per_class"][0]['TP'])
# metrics_train["tp_tn_fp_fn_per_class"][0]['TP'] =
json.dumps(metrics_train["tp_tn_fp_fn_per_class"][0]['TP'])
# metrics_train["tp_tn_fp_fn_per_class"][0]
['TN']=int(metrics_train["tp_tn_fp_fn_per_class"][0]['TN'])
# metrics_train["tp_tn_fp_fn_per_class"][0]['TN'] =
json.dumps(metrics_train["tp_tn_fp_fn_per_class"][0]['TN'])
# metrics_train["tp_tn_fp_fn_per_class"][0]
['FP']=int(metrics_train["tp_tn_fp_fn_per_class"][0]['FP'])
# metrics_train["tp_tn_fp_fn_per_class"][0]['FP'] =
json.dumps(metrics_train["tp_tn_fp_fn_per_class"][0]['FP'])
# metrics_train["tp_tn_fp_fn_per_class"][0]
['FN']=int(metrics_train["tp_tn_fp_fn_per_class"][0]['FN'])
# metrics_train["tp_tn_fp_fn_per_class"][0]['FN'] =
json.dumps(metrics_train["tp_tn_fp_fn_per_class"][0]['FN'])
# metrics_train["tp_tn_fp_fn_per_class"][0]['TP']=
int(metrics_train["tp_tn_fp_fn_per_class"][0]['TP'])
# metrics_train["tp_tn_fp_fn_per_class"][0]['TP']=
int(metrics_train["tp_tn_fp_fn_per_class"][0]['TN'])
# metrics_train["tp_tn_fp_fn_per_class"][0]['TP']=
int(metrics_train["tp_tn_fp_fn_per_class"][0]['FP'])
# metrics_train["tp_tn_fp_fn_per_class"][0]['TP']=
int(metrics_train["tp_tn_fp_fn_per_class"][0]['FN'])
# metrics_test["tp_tn_fp_fn_per_class"][0]['TP']=
int(metrics_test["tp_tn_fp_fn_per_class"][0]['TP'])
# metrics_test["tp_tn_fp_fn_per_class"][0]['TP']=
int(metrics_test["tp_tn_fp_fn_per_class"][0]['TN'])
# metrics_test["tp_tn_fp_fn_per_class"][0]['TP']=
int(metrics_test["tp_tn_fp_fn_per_class"][0]['FP'])
# metrics_test["tp_tn_fp_fn_per_class"][0]['TP']=
int(metrics_test["tp_tn_fp_fn_per_class"][0]['FN'])
# print("--------------------metrics_train--------------",
type(metrics_train["tp_tn_fp_fn_per_class"][0]['TP']))
# print("--------------------metrics_train--------------",
metrics_train["tp_tn_fp_fn_per_class"][0]['TP'])
# print("--------------------metrics_train--------------",
metrics_train["tp_tn_fp_fn_per_class"][0]['TN'])
# print("--------------------metrics_train--------------",
metrics_train["tp_tn_fp_fn_per_class"][0]['FP'])
# print("--------------------metrics_train--------------",
metrics_train["tp_tn_fp_fn_per_class"][0]['FN'])
# train_classes_metrics = {
# f"tp_tn_fp_fn_per_class{i}": {
# "TP": int(metrics_train["tp_tn_fp_fn_per_class"][i]["TP"]),
# "TN": int(metrics_train["tp_tn_fp_fn_per_class"][i]["TN"]),
# "FP": int(metrics_train["tp_tn_fp_fn_per_class"][i]["FP"]),
# "FN": int(metrics_train["tp_tn_fp_fn_per_class"][i]["FN"])
# }
# for i in range(len(metrics_train["tp_tn_fp_fn_per_class"]))
# }
# test_classes_metrics = {
# f"tp_tn_fp_fn_per_class{i}": {
# "TP": int(metrics_test["tp_tn_fp_fn_per_class"][i]["TP"]),
# "TN": int(metrics_test["tp_tn_fp_fn_per_class"][i]["TN"]),
# "FP": int(metrics_test["tp_tn_fp_fn_per_class"][i]["FP"]),
# "FN": int(metrics_test["tp_tn_fp_fn_per_class"][i]["FN"])
# }
# for i in range(len(metrics_test["tp_tn_fp_fn_per_class"]))
# }
# Response format
accuracy_result = {
"trainReport": {
"metrics": [
{
"confusionMatrix": {
"matrix": metrics_train["confusionMatrix"]
},
"tp_tn_fp_fn_per_class0": {
"TP": int(metrics_train["tp_tn_fp_fn_per_class"][0]["TP"]),
"TN": int(metrics_train["tp_tn_fp_fn_per_class"][0]["TN"]),
"FP": int(metrics_train["tp_tn_fp_fn_per_class"][0]["FP"]),
"FN": int(metrics_train["tp_tn_fp_fn_per_class"][0]["FN"])
},
"accuracyScore": metrics_train["accuracyScore"],
# "precision": metrics_train["precision"],
# "f1Score": metrics_train["f1Score"],
# "recall": metrics_train["recall"],
"precision_per_class":
metrics_train["precision_per_class"].tolist(),
"recall_per_class": metrics_train["recall_per_class"].tolist(),
"f1_per_class": metrics_train["f1_per_class"].tolist(),
"specificity_per_class": metrics_train["specificity_per_class"],
#"tp_tn_fp_fn_per_class": metrics_train["tp_tn_fp_fn_per_class"],
"average_precision": metrics_train["average_precision"],
"average_recall": metrics_train["average_recall"],
"average_f1": metrics_train["average_f1"],
"average_specificity": metrics_train["average_specificity"],
"aucRocCurve": metrics_train.get("aucRocCurve", []), #
Only the first 10 values
"bestModel": False,
"modelId": modelId,
"hyperparameterTuning": hyperparameterTuning,
"modelCategory":modelCategory,
"modelName": modelName,
"metadata": {
"note": "Train report, first model"
},
# "_id": "train_id"
}
]
},
"testReport": {
"metrics": [
{
"confusionMatrix": {
"matrix": metrics_test["confusionMatrix"],
},
"tp_tn_fp_fn_per_class0": {
"TP": int(metrics_test["tp_tn_fp_fn_per_class"][0]["TP"]),
"TN": int(metrics_test["tp_tn_fp_fn_per_class"][0]["TN"]),
"FP": int(metrics_test["tp_tn_fp_fn_per_class"][0]["FP"]),
"FN": int(metrics_test["tp_tn_fp_fn_per_class"][0]["FN"])
},
"accuracyScore": metrics_test["accuracyScore"],
# "precision": metrics_test["precision"],
# "f1Score": metrics_test["f1Score"],
# "recall": metrics_test["recall"],
"precision_per_class":
metrics_test["precision_per_class"].tolist(),
"recall_per_class": metrics_test["recall_per_class"].tolist(),
"f1_per_class": metrics_test["f1_per_class"].tolist(),
"specificity_per_class": metrics_test["specificity_per_class"],
# "tp_tn_fp_fn_per_class": metrics_test["tp_tn_fp_fn_per_class"],
"average_precision": metrics_test["average_precision"],
"average_recall": metrics_test["average_recall"],
"average_f1": metrics_test["average_f1"],
"average_specificity": metrics_test["average_specificity"],
"aucRocCurve": metrics_test.get("aucRocCurve", []),
#"average_auc":
metrics_test["aucRocCurve"].get("average_auc"),
"average_auc": average_auc_value,
"bestModel": True,
"modelId": modelId,
"hyperparameterTuning": hyperparameterTuning,
"modelCategory":modelCategory,
"modelName": modelName,
"metadata": {
"note": "Test report, best model"
},
# "_id": "test_id"
}
]
},
# "_id": "6717b977cdd6733dfbd91477",
"workflowId": workflowId,
# "trainRatio": trainRatio_per,
# "testRatio": testRatio_per,
"projectId": projectId,
# "userId": userId,
# "jobId": "650deef95c34b36d9e12d71f",
"versionId": "V1.0", # Hardcoded for now, will be made
dynamic when the need arises
# "dataSources": dataSources,
"keyMetrics": keyMetrics,
"targetColumn": targetColumn,
"createdAt": createdAt,
"updatedAt": updatedAt,
# "__v": 0
}
print("-------------------- Payload defined
Successfully-------------------------------")
## Save results to S3
save_results_to_s3(accuracy_result, modelName)
print("-------------------- 111111111111-------------------------------")
# Return metrics
return accuracy_result
# Upload to S3
s3.put_object(Bucket=S3_BUCKET_NAME, Key=object_key, Body=results_json)