User-defined Functions
HyperTS supports several user-defined extension functions in addition to the built-in algorithms.
User-defined Evaluation Metric
When creating an experiment, the evaluation criterion could be set by the argument reward_metric
. See example below:
from hyperts import make_experiment
experiment = make_experiment(train_data,
task='forecast',
timestamp='TimeStamp',
reward_metric='mae',
...)
Except these built-in criterions, users could define their own criterion. Two approaches are introduced below:
Approach one:
import numpy as np
from sklearn.metrics import mean_squared_error
def custom_metric(y_true, y_pred, epsihon=1e-06):
if (y_true < 0).any():
y_true = np.clip(y_true, a_min=epsihon, a_max=abs(y_true))
if (y_pred < 0).any():
y_pred = np.clip(y_pred, a_min=epsihon, a_max=abs(y_pred))
return mean_squared_error(np.log1p(y_true), np.log1p(y_pred))
experiment = make_experiment(train_data,
task='forecast',
timestamp='TimeStamp',
reward_metric=custom_metric,
optimize_direction='min',
...)
Note
In this case, the optimize_direction
is required.
Approach two:
import numpy as np
from sklearn.metrics import mean_squared_error, make_scorer
def custom_metric(y_true, y_pred):
if (y_true < 0).any():
y_true = np.clip(y_true, a_min=epsihon, a_max=abs(y_true))
if (y_pred < 0).any():
y_pred = np.clip(y_pred, a_min=epsihon, a_max=abs(y_pred))
return mean_squared_error(np.log1p(y_true), np.log1p(y_pred))
custom_scorer = make_scorer(custom_metric, greater_is_better=True, needs_proba=False)
experiment = make_experiment(train_data,
task='forecast',
timestamp='TimeStamp',
reward_metric=custom_metric,
scorer=custom_scorer,
...)
Note
In this case, the scorer
is required.
User-defined Search Space
HyperTS provides various algorithms with default search space for every mode. Most of them are listed below:
‘StatsForecastSearchSpace’ includes the search space for statistical models (Prophet, ARIMA, VAR);
‘StatsClassificationSearchSpace’ includes the search space for statistical models (TSForest, k-NNs);
‘DLForecastSearchSpace’ includes the search space for deep learning models (DeepAR, RNN, GPU, LSTM, LSNet);
‘DLClassificationSearchSpace’ includes the search space for deep learning models (RNN, GPU, LSTM, LSNet).
By setting the argument search_space
, users could define their own search space. The instructions and an example are given below to modify the StatsForecastSearchSpace
.
Set the argument as false to disable a certain algorithm. For instance,
enable_arima=False
;Change the initial parameters of a certain algorithm by function
prophet_init_kwargs={xxx:xxx, ...}
;Import the argument
Choice
,Int
Real
fromhypernets.core.search_space
could define the parameters with specific options. For instance,Choice
supports the boolean data type.Real
supports the floating data type.For more information, please refer to Search Space.
Code example:
from hypernets.core.search_space import Choice, Int, Real
from hyperts.framework.search_space.macro_search_space import StatsForecastSearchSpace
custom_search_space = StatsForecastSearchSpace(enable_arima=False,
prophet_init_kwargs={
'seasonality_mode': 'multiplicative',
'daily_seasonality': Choice([True, False]),
'n_changepoints': Int(10, 50, step=10),
'interval_width': Real(0.1, 0.5, step=0.1)}
)
experiment = make_experiment(train_data,
task='univariate-forecast',
timestamp='TimeStamp',
covariables=['HourSin', 'WeekCos', 'CBWD'],
search_space=custom_search_space,
...)
User Defined Modeling Algorithm
In addition to the built-in modeling algorithms as mentioned above, users could also define new algorithms. The instructions and an example are given below to build a modified neural network model ‘Transformer’ inside ‘DLForecastSearchSpace’:
Package the user-modified algorithm as a subclass of
HyperEstimator
;Add the subclass to the specific search space and define the search parameters;
Assign the search space to the argument of function
make_experiment
.
Code example
1. Build the Model Structure
The example is to build a Transformer Encoder based on tensorflow. See Keras tutorial.
from tensorflow.keras import layers
def transformer_encoder(inputs, head_size, num_heads, ff_dim, dropout=0.):
x = layers.MultiHeadAttention(
key_dim=head_size, num_heads=num_heads, dropout=dropout
)(inputs, inputs)
x = layers.Dropout(dropout)(x)
x = layers.LayerNormalization(epsilon=1e-6)(x)
res = x + inputs
x = layers.Conv1D(filters=ff_dim, kernel_size=1, activation="relu")(res)
x = layers.Dropout(dropout)(x)
x = layers.Conv1D(filters=inputs.shape[-1], kernel_size=1)(x)
x = layers.LayerNormalization(epsilon=1e-6)(x)
return x + res
2. Build the Algorithm
To make it sample, this example uses a template of an existing algorithm in HyperTS. Only a small part of _init_
and _build_estimator
are modified.
import tensorflow as tf
import tensorflow.keras.backend as K
from hyperts.framework.dl import layers
from hyperts.framework.dl.models import HybridRNN
class Transformer(HybridRNN):
def __init__(self,
task,
timestamp=None,
window=7,
horizon=1,
forecast_length=1,
head_size=10,
num_heads=6,
ff_dim=10,
transformer_blocks=1,
drop_rate=0.,
metrics='auto',
monitor_metric='val_loss',
optimizer='auto',
learning_rate=0.001,
loss='auto',
out_activation='linear',
reducelr_patience=5,
earlystop_patience=10,
embedding_output_dim=4,
**kwargs):
super(Transformer, self).__init__(task=task,
timestamp=timestamp,
window=window,
horizon=horizon,
forecast_length=forecast_length,
drop_rate=drop_rate,
metrics=metrics,
monitor_metric=monitor_metric,
optimizer=optimizer,
learning_rate=learning_rate,
loss=loss,
out_activation=out_activation,
reducelr_patience=reducelr_patience,
earlystop_patience=earlystop_patience,
embedding_output_dim=embedding_output_dim,
**kwargs)
self.head_size = head_size
self.num_heads = num_heads
self.ff_dim = ff_dim
self.transformer_blocks = transformer_blocks
def _build_estimator(self, **kwargs):
K.clear_session()
continuous_inputs, categorical_inputs = layers.build_input_head(self.window, self.continuous_columns, self.categorical_columns)
denses = layers.build_denses(self.continuous_columns, continuous_inputs)
embeddings = layers.build_embeddings(self.categorical_columns, categorical_inputs)
if embeddings is not None:
x = layers.Concatenate(axis=-1, name='concat_embeddings_dense_inputs')([denses, embeddings])
else:
x = denses
############################################ backbone ############################################
for _ in range(self.transformer_blocks):
x = transformer_encoder(x, self.head_size, self.num_heads, self.ff_dim, self.drop_rate)
x = layers.GlobalAveragePooling1D(data_format="channels_first")(x)
##################################################################################################
outputs = layers.build_output_tail(x, self.task, nb_outputs=self.meta.classes_, nb_steps=self.forecast_length)
outputs = layers.Activation(self.out_activation, name=f'output_activation_{self.out_activation}')(outputs)
all_inputs = list(continuous_inputs.values()) + list(categorical_inputs.values())
model = tf.keras.models.Model(inputs=all_inputs, outputs=[outputs], name=f'Transformer')
model.summary()
return model
1. Build the Estimator
Estimator connectes the algorithm model and search space. It defines the hyperparameters for optimization.
from hyperts.utils import consts
from hyperts.framework.wrappers.dl_wrappers import HybridRNNWrapper
from hyperts.framework.estimators import HyperEstimator
class TransformerWrapper(HybridRNNWrapper):
def __init__(self, fit_kwargs, **kwargs):
super(TransformerWrapper, self).__init__(fit_kwargs, **kwargs)
self.update_dl_kwargs()
self.model = Transformer(**self.init_kwargs)
class TransfomerEstimator(HyperEstimator):
def __init__(self, fit_kwargs=None, timestamp=None, task='univariate-forecast', window=7,
head_size=10, num_heads=6, ff_dim=10, transformer_blocks=1, drop_rate=0.,
metrics='auto', optimizer='auto', out_activation='linear',
learning_rate=0.001, batch_size=None, epochs=1, verbose=1,
space=None, name=None, **kwargs):
if task in consts.TASK_LIST_FORECAST and timestamp is None:
raise ValueError('Timestamp need to be given for forecast task.')
else:
kwargs['timestamp'] = timestamp
if task is not None:
kwargs['task'] = task
if window is not None and window != 7:
kwargs['window'] = window
if head_size is not None and head_size != 10:
kwargs['head_size'] = head_size
if num_heads is not None and num_heads != 6:
kwargs['num_heads'] = num_heads
if ff_dim is not None and ff_dim != 10:
kwargs['ff_dim'] = ff_dim
if transformer_blocks is not None and transformer_blocks != 1:
kwargs['transformer_blocks'] = transformer_blocks
if drop_rate is not None and drop_rate != 0.:
kwargs['drop_rate'] = drop_rate
if metrics is not None and metrics != 'auto':
kwargs['metrics'] = metrics
if optimizer is not None and optimizer != 'auto':
kwargs['optimizer'] = optimizer
if out_activation is not None and out_activation != 'linear':
kwargs['out_activation'] = out_activation
if learning_rate is not None and learning_rate != 0.001:
kwargs['learning_rate'] = learning_rate
if batch_size is not None:
kwargs['batch_size'] = batch_size
if epochs is not None and epochs != 1:
kwargs['epochs'] = epochs
if verbose is not None and verbose != 1:
kwargs['verbose'] = verbose
HyperEstimator.__init__(self, fit_kwargs, space, name, **kwargs)
def _build_estimator(self, task, fit_kwargs, kwargs):
if task in consts.TASK_LIST_FORECAST + consts.TASK_LIST_CLASSIFICATION:
transformer = TransformerWrapper(fit_kwargs, **kwargs)
else:
raise ValueError('Check whether the task type meets specifications.')
return transformer
4. Build the Search Space
Add the estimator to the search space, in which the hyperparameters also could be defined properly to ensure the performance.
from hypernets.core.search_space import Choice, Real
from hyperts.framework.macro_search_space import DLForecastSearchSpace
class DLForecastSearchSpacePlusTransformer(DLForecastSearchSpace):
def __init__(self, task, timestamp=None, metrics=None, window=None, enable_transformer=True, **kwargs):
super().__init__(task=task, timestamp=timestamp, metrics=metrics, window=window, **kwargs)
self.enable_transformer = enable_transformer
@property
def default_transformer_init_kwargs(self):
return {
'timestamp': self.timestamp,
'task': self.task,
'metrics': self.metrics,
'head_size': Choice([8, 16, 24, 32]),
'num_heads': Choice([2, 4, 6]),
'ff_dim': Choice([8, 16, 24, 32]),
'drop_rate': Real(0., 0.5, 0.1),
'transformer_blocks': Choice([1, 2, 3]),
'window': self.window if self.window is not None else Choice([12, 24, 48]),
'y_log': Choice(['logx', 'log-none']),
'y_scale': Choice(['min_max', 'max_abs'])
}
@property
def default_transformer_fit_kwargs(self):
return {
'epochs': 60,
'batch_size': None,
'verbose': 1,
}
@property
def estimators(self):
r = super().estimators
if self.enable_transformer:
r['transformer'] = (TransfomerEstimator, self.default_transformer_init_kwargs, self.default_transformer_fit_kwargs)
return r
5. Execute the Experiment with Custom Search Space
from hyperts import make_experiment
from hyperts.datasets import load_network_traffic
from sklearn.model_selection import train_test_split
df = load_network_traffic(univariate=True)
train_data, test_data = train_test_split(df, test_size=168, shuffle=False)
custom_search_space = DLForecastSearchSpacePlusTransformer()
experiment = make_experiment(train_data,
task='univariate-forecast',
mode='dl',
timestamp='TimeStamp',
covariables=['HourSin', 'WeekCos', 'CBWD'],
search_space=custom_search_space,
reward_metric='mape',
...)
model = experiment.run()