Source code for syne_tune.optimizer.schedulers.transfer_learning.quantile_based.normalization_transforms
from functools import partial
from typing import Optional
from scipy import stats
import numpy as np
[docs]
class GaussianTransform:
"""
Transform data into Gaussian by applying psi = Phi^{-1} o F where F is the truncated empirical CDF.
:param y: shape (n, dim)
:param random_state: If specified, randomize the rank when consecutive values exists between extreme values.
If none use lowest rank of duplicated values.
"""
def __init__(
self, y: np.array, random_state: Optional[np.random.RandomState] = None
):
assert y.ndim == 2
self.dim = y.shape[1]
self.sorted = y.copy()
self.sorted.sort(axis=0)
self.random_state = random_state
[docs]
@staticmethod
def z_transform(
series, values_sorted, random_state: Optional[np.random.RandomState] = None
):
"""
:param series: shape (n, dim)
:param values_sorted: series sorted on the first axis
:param random_state: if not None, ranks are drawn uniformly for values with consecutive ranges
:return: data with same shape as input series where distribution is normalized on all dimensions
"""
# Cutoff ranks since ``Phi^{-1}`` is infinite at ``0`` and ``1`` with winsorized constants.
def winsorized_delta(n):
return 1.0 / (4.0 * n**0.25 * np.sqrt(np.pi * np.log(n)))
delta = winsorized_delta(len(series))
def quantile(values_sorted, values_to_insert, delta):
low = np.searchsorted(values_sorted, values_to_insert, side="left")
if random_state is not None:
# in case where multiple occurences of the same value exists in sorted array
# we return a random index in the valid range
high = np.searchsorted(values_sorted, values_to_insert, side="right")
res = random_state.randint(low, np.maximum(high, low + 1))
else:
res = low
return np.clip(res / len(values_sorted), a_min=delta, a_max=1 - delta)
quantiles = quantile(values_sorted, series, delta)
quantiles = np.clip(quantiles, a_min=delta, a_max=1 - delta)
return stats.norm.ppf(quantiles)
[docs]
def transform(self, y: np.array):
"""
:param y: shape (n, dim)
:return: shape (n, dim), distributed along a normal
"""
assert y.shape[1] == self.dim
# compute truncated quantile, apply gaussian inv cdf
return np.stack(
[
self.z_transform(y[:, i], self.sorted[:, i], self.random_state)
for i in range(self.dim)
]
).T
[docs]
class StandardTransform:
def __init__(self, y: np.array):
"""
Transformation that removes mean and divide by standard error.
:param y:
"""
assert y.ndim == 2
self.dim = y.shape[1]
self.mean = y.mean(axis=0, keepdims=True)
self.std = y.std(axis=0, keepdims=True)
[docs]
def transform(self, y: np.array):
z = (y - self.mean) / np.clip(self.std, a_min=0.001, a_max=None)
return z
[docs]
def from_string(name: str, random_state: Optional[np.random.RandomState] = None):
assert name in ["standard", "gaussian"]
mapping = {
"standard": StandardTransform,
"gaussian": partial(GaussianTransform, random_state=random_state),
}
return mapping[name]