Source code for metaperceptron.helpers.scaler_util
#!/usr/bin/env python
# Created by "Thieu" at 12:36, 17/09/2023 ----------%
# Email: nguyenthieu2102@gmail.com %
# Github: https://github.com/thieu1995 %
# --------------------------------------------------%
import numpy as np
from scipy.stats import boxcox, yeojohnson
from scipy.special import inv_boxcox
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.preprocessing import StandardScaler, MinMaxScaler, MaxAbsScaler, RobustScaler
[docs]class ObjectiveScaler:
"""
For label scaler in classification (binary and multiple classification)
"""
def __init__(self, obj_name="sigmoid", ohe_scaler=None):
"""
ohe_scaler: Need to be an instance of One-Hot-Encoder for softmax scaler (multiple classification problem)
"""
self.obj_name = obj_name
self.ohe_scaler = ohe_scaler
[docs] def transform(self, data):
if self.obj_name == "sigmoid" or self.obj_name == "self":
return data
elif self.obj_name == "hinge":
data = np.squeeze(np.array(data))
data[np.where(data == 0)] = -1
return data
elif self.obj_name == "softmax":
data = self.ohe_scaler.transform(np.reshape(data, (-1, 1)))
return data
[docs] def inverse_transform(self, data):
if self.obj_name == "sigmoid":
data = np.squeeze(np.array(data))
data = np.rint(data).astype(int)
elif self.obj_name == "hinge":
data = np.squeeze(np.array(data))
data = np.ceil(data).astype(int)
data[np.where(data == -1)] = 0
elif self.obj_name == "softmax":
data = np.squeeze(np.array(data))
data = np.argmax(data, axis=1)
return data
[docs]class Log1pScaler(BaseEstimator, TransformerMixin):
[docs] def fit(self, X, y=None):
# LogETransformer doesn't require fitting, so we simply return self.
return self
[docs] def transform(self, X):
# Apply the natural logarithm to each element of the input data
return np.log1p(X)
[docs] def inverse_transform(self, X):
# Apply the exponential function to reverse the logarithmic transformation
return np.expm1(X)
[docs]class LogeScaler(BaseEstimator, TransformerMixin):
[docs] def fit(self, X, y=None):
# LogETransformer doesn't require fitting, so we simply return self.
return self
[docs] def transform(self, X):
# Apply the natural logarithm (base e) to each element of the input data
return np.log(X)
[docs] def inverse_transform(self, X):
# Apply the exponential function to reverse the logarithmic transformation
return np.exp(X)
[docs]class SqrtScaler(BaseEstimator, TransformerMixin):
[docs] def fit(self, X, y=None):
# SqrtScaler doesn't require fitting, so we simply return self.
return self
[docs] def transform(self, X):
# Apply the square root transformation to each element of the input data
return np.sqrt(X)
[docs] def inverse_transform(self, X):
# Apply the square of each element to reverse the square root transformation
return X ** 2
[docs]class BoxCoxScaler(BaseEstimator, TransformerMixin):
def __init__(self, lmbda=None):
self.lmbda = lmbda
[docs] def fit(self, X, y=None):
# Estimate the lambda parameter from the data if not provided
if self.lmbda is None:
_, self.lmbda = boxcox(X.flatten())
return self
[docs] def transform(self, X):
# Apply the Box-Cox transformation to the data
X_new = boxcox(X.flatten(), lmbda=self.lmbda)
return X_new.reshape(X.shape)
[docs] def inverse_transform(self, X):
# Inverse transform using the original lambda parameter
return inv_boxcox(X, self.lmbda)
[docs]class YeoJohnsonScaler(BaseEstimator, TransformerMixin):
def __init__(self, lmbda=None):
self.lmbda = lmbda
[docs] def fit(self, X, y=None):
# Estimate the lambda parameter from the data if not provided
if self.lmbda is None:
_, self.lmbda = yeojohnson(X.flatten())
return self
[docs] def transform(self, X):
# Apply the Yeo-Johnson transformation to the data
X_new = boxcox(X.flatten(), lmbda=self.lmbda)
return X_new.reshape(X.shape)
[docs] def inverse_transform(self, X):
# Inverse transform using the original lambda parameter
return inv_boxcox(X, self.lmbda)
[docs]class SinhArcSinhScaler(BaseEstimator, TransformerMixin):
# https://stats.stackexchange.com/questions/43482/transformation-to-increase-kurtosis-and-skewness-of-normal-r-v
def __init__(self, epsilon=0.1, delta=1.0):
self.epsilon = epsilon
self.delta = delta
[docs]class DataTransformer(BaseEstimator, TransformerMixin):
SUPPORTED_SCALERS = {"standard": StandardScaler, "minmax": MinMaxScaler, "max-abs": MaxAbsScaler,
"log1p": Log1pScaler, "loge": LogeScaler, "sqrt": SqrtScaler,
"sinh-arc-sinh": SinhArcSinhScaler, "robust": RobustScaler,
"box-cox": BoxCoxScaler, "yeo-johnson": YeoJohnsonScaler}
def __init__(self, scaling_methods=('standard', ), list_dict_paras=None):
if type(scaling_methods) is str:
if list_dict_paras is None:
self.list_dict_paras = [{}]
elif type(list_dict_paras) is dict:
self.list_dict_paras = [list_dict_paras]
else:
raise TypeError(f"You use only 1 scaling method, the list_dict_paras should be dict of parameter for that scaler.")
self.scaling_methods = [scaling_methods]
elif type(scaling_methods) in (tuple, list, np.ndarray):
if list_dict_paras is None:
self.list_dict_paras = [{}, ]*len(scaling_methods)
elif type(list_dict_paras) in (tuple, list, np.ndarray):
self.list_dict_paras = list(list_dict_paras)
else:
raise TypeError(f"Invalid type of list_dict_paras. Supported type are: tuple, list, or np.ndarray of parameter dict")
self.scaling_methods = list(scaling_methods)
else:
raise TypeError(f"Invalid type of scaling_methods. Supported type are: str, tuple, list, or np.ndarray")
self.scalers = [self._get_scaler(technique, paras) for (technique, paras) in zip(self.scaling_methods, self.list_dict_paras)]
def _get_scaler(self, technique, paras):
if technique in self.SUPPORTED_SCALERS.keys():
if type(paras) is not dict:
paras = {}
return self.SUPPORTED_SCALERS[technique](**paras)
else:
raise ValueError(f"Invalid scaling technique. Supported techniques are {self.SUPPORTED_SCALERS.keys()}")
[docs] def fit(self, X, y=None):
for idx, _ in enumerate(self.scalers):
X = self.scalers[idx].fit_transform(X)
return self
[docs] def inverse_transform(self, X):
for scaler in reversed(self.scalers):
X = scaler.inverse_transform(X)
return X