#!/usr/bin/env python
# Created by "Thieu" at 16:22, 14/05/2025 ----------%
# Email: nguyenthieu2102@gmail.com %
# Github: https://github.com/thieu1995 %
# --------------------------------------------------%
import inspect
import pickle
import pprint
from pathlib import Path
import numpy as np
import pandas as pd
from sklearn.base import BaseEstimator
from permetrics import RegressionMetric, ClassificationMetric
[docs]class BaseModel(BaseEstimator):
"""
Base class for all models
"""
def __init__(self):
self.loss_train = None
def __repr__(self, **kwargs):
"""Pretty-print parameters like scikit-learn's Estimator."""
param_order = list(inspect.signature(self.__init__).parameters.keys())
param_dict = {k: getattr(self, k) for k in param_order}
param_str = ", ".join(f"{k}={repr(v)}" for k, v in param_dict.items())
if len(param_str) <= 80:
return f"{self.__class__.__name__}({param_str})"
else:
formatted_params = ",\n ".join(f"{k}={pprint.pformat(v)}" for k, v in param_dict.items())
return f"{self.__class__.__name__}(\n {formatted_params}\n)"
[docs] def fit(self, X, y):
"""
Fit the model to the training data.
"""
raise NotImplementedError("fit method not implemented")
[docs] def predict(self, X):
"""
Predict using the trained model.
"""
raise NotImplementedError("predict method not implemented")
[docs] def score(self, X, y):
"""
Evaluate the model's performance on the given data.
"""
raise NotImplementedError("score method not implemented")
def _evaluate_reg(self, y_true, y_pred, list_metrics=("MSE", "MAE")):
"""
Evaluate regression performance metrics.
Parameters
----------
y_true : array-like
True target values.
y_pred : array-like
Predicted values.
list_metrics : tuple of str, list of str
List of metrics for evaluation (e.g., "MSE" and "MAE").
Returns
-------
dict
Dictionary of calculated metric values.
"""
rm = RegressionMetric(y_true=y_true, y_pred=y_pred)
return rm.get_metrics_by_list_names(list_metrics)
def _evaluate_cls(self, y_true, y_pred, list_metrics=("AS", "RS")):
"""
Evaluate classification performance metrics.
Parameters
----------
y_true : array-like
True target values.
y_pred : array-like
Predicted labels.
list_metrics : tuple of str, list of str
List of metrics for evaluation (e.g., "AS" and "RS").
Returns
-------
dict
Dictionary of calculated metric values.
"""
cm = ClassificationMetric(y_true, y_pred)
return cm.get_metrics_by_list_names(list_metrics)
[docs] def evaluate(self, y_true, y_pred, list_metrics=None):
"""
Evaluate the model using specified metrics.
Parameters
----------
y_true : array-like
True target values.
y_pred : array-like
Model's predicted values.
list_metrics : list of str, optional
Names of metrics for evaluation (e.g., "MSE", "MAE").
Returns
-------
dict
Evaluation metrics and their values.
"""
pass
[docs] def save_training_loss(self, save_path="history", filename="loss.csv"):
"""
Save training loss history to a CSV file.
Parameters
----------
save_path : str, optional
Path to save the file (default: "history").
filename : str, optional
Filename for saving loss history (default: "loss.csv").
"""
Path(save_path).mkdir(parents=True, exist_ok=True)
if self.loss_train is None:
print(f"{self.__class__.__name__} model doesn't have training loss!")
else:
data = {"epoch": list(range(1, len(self.loss_train) + 1)), "loss": self.loss_train}
pd.DataFrame(data).to_csv(f"{save_path}/{filename}", index=False)
[docs] def save_evaluation_metrics(self, y_true, y_pred, list_metrics=("RMSE", "MAE"),
save_path="history", filename="metrics.csv"):
"""
Save evaluation metrics to a CSV file.
Parameters
----------
y_true : array-like
Ground truth values.
y_pred : array-like
Model predictions.
list_metrics : list of str, optional
Metrics for evaluation (default: ("RMSE", "MAE")).
save_path : str, optional
Path to save the file (default: "history").
filename : str, optional
Filename for saving metrics (default: "metrics.csv").
"""
Path(save_path).mkdir(parents=True, exist_ok=True)
results = self.evaluate(y_true, y_pred, list_metrics)
df = pd.DataFrame.from_dict(results, orient='index').T
df.to_csv(f"{save_path}/{filename}", index=False)
[docs] def save_y_predicted(self, X, y_true, save_path="history", filename="y_predicted.csv"):
"""
Save true and predicted values to a CSV file.
Parameters
----------
X : array-like or torch.Tensor
Input features.
y_true : array-like
True values.
save_path : str, optional
Path to save the file (default: "history").
filename : str, optional
Filename for saving predicted values (default: "y_predicted.csv").
"""
Path(save_path).mkdir(parents=True, exist_ok=True)
y_pred = self.predict(X)
data = {"y_true": np.squeeze(np.asarray(y_true)), "y_pred": np.squeeze(np.asarray(y_pred))}
pd.DataFrame(data).to_csv(f"{save_path}/{filename}", index=False)
[docs] def save_model(self, save_path="history", filename="model.pkl"):
"""
Save the trained model to a pickle file.
Parameters
----------
save_path : str, optional
Path to save the model (default: "history").
filename : str, optional
Filename for saving model, with ".pkl" extension (default: "model.pkl").
"""
Path(save_path).mkdir(parents=True, exist_ok=True)
if filename[-4:] != ".pkl":
filename += ".pkl"
pickle.dump(self, open(f"{save_path}/{filename}", 'wb'))
[docs] @staticmethod
def load_model(load_path="history", filename="model.pkl"):
"""
Load a model from a pickle file.
Parameters
----------
load_path : str, optional
Path to load the model from (default: "history").
filename : str, optional
Filename of the saved model (default: "model.pkl").
Returns
-------
BaseMlp
The loaded model.
"""
if filename[-4:] != ".pkl":
filename += ".pkl"
return pickle.load(open(f"{load_path}/{filename}", 'rb'))