1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-18 09:54:33 +08:00

Format TFT

This commit is contained in:
Wendi Li
2020-11-23 16:09:03 +08:00
committed by you-n-g
parent 93323ed6b3
commit c2c96a817f
15 changed files with 3821 additions and 3971 deletions

View File

@@ -12,4 +12,3 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

View File

@@ -35,6 +35,7 @@ import enum
# Type defintions
class DataTypes(enum.IntEnum):
"""Defines numerical types of each column."""
REAL_VALUED = 0
CATEGORICAL = 1
DATE = 2
@@ -42,6 +43,7 @@ class DataTypes(enum.IntEnum):
class InputTypes(enum.IntEnum):
"""Defines input types of each column."""
TARGET = 0
OBSERVED_INPUT = 1
KNOWN_INPUT = 2
@@ -141,8 +143,7 @@ class GenericDataFormatter(abc.ABC):
length = len([tup for tup in column_definition if tup[2] == input_type])
if length != 1:
raise ValueError('Illegal number of inputs ({}) of type {}'.format(
length, input_type))
raise ValueError("Illegal number of inputs ({}) of type {}".format(length, input_type))
_check_single_column(InputTypes.ID)
_check_single_column(InputTypes.TIME)
@@ -150,65 +151,50 @@ class GenericDataFormatter(abc.ABC):
identifier = [tup for tup in column_definition if tup[2] == InputTypes.ID]
time = [tup for tup in column_definition if tup[2] == InputTypes.TIME]
real_inputs = [
tup for tup in column_definition if tup[1] == DataTypes.REAL_VALUED and
tup[2] not in {InputTypes.ID, InputTypes.TIME}
tup
for tup in column_definition
if tup[1] == DataTypes.REAL_VALUED and tup[2] not in {InputTypes.ID, InputTypes.TIME}
]
categorical_inputs = [
tup for tup in column_definition if tup[1] == DataTypes.CATEGORICAL and
tup[2] not in {InputTypes.ID, InputTypes.TIME}
tup
for tup in column_definition
if tup[1] == DataTypes.CATEGORICAL and tup[2] not in {InputTypes.ID, InputTypes.TIME}
]
return identifier + time + real_inputs + categorical_inputs
def _get_input_columns(self):
"""Returns names of all input columns."""
return [
tup[0]
for tup in self.get_column_definition()
if tup[2] not in {InputTypes.ID, InputTypes.TIME}
]
return [tup[0] for tup in self.get_column_definition() if tup[2] not in {InputTypes.ID, InputTypes.TIME}]
def _get_tft_input_indices(self):
"""Returns the relevant indexes and input sizes required by TFT."""
# Functions
def _extract_tuples_from_data_type(data_type, defn):
return [
tup for tup in defn if tup[1] == data_type and
tup[2] not in {InputTypes.ID, InputTypes.TIME}
]
return [tup for tup in defn if tup[1] == data_type and tup[2] not in {InputTypes.ID, InputTypes.TIME}]
def _get_locations(input_types, defn):
return [i for i, tup in enumerate(defn) if tup[2] in input_types]
# Start extraction
column_definition = [
tup for tup in self.get_column_definition()
if tup[2] not in {InputTypes.ID, InputTypes.TIME}
tup for tup in self.get_column_definition() if tup[2] not in {InputTypes.ID, InputTypes.TIME}
]
categorical_inputs = _extract_tuples_from_data_type(DataTypes.CATEGORICAL,
column_definition)
real_inputs = _extract_tuples_from_data_type(DataTypes.REAL_VALUED,
column_definition)
categorical_inputs = _extract_tuples_from_data_type(DataTypes.CATEGORICAL, column_definition)
real_inputs = _extract_tuples_from_data_type(DataTypes.REAL_VALUED, column_definition)
locations = {
'input_size':
len(self._get_input_columns()),
'output_size':
len(_get_locations({InputTypes.TARGET}, column_definition)),
'category_counts':
self.num_classes_per_cat_input,
'input_obs_loc':
_get_locations({InputTypes.TARGET}, column_definition),
'static_input_loc':
_get_locations({InputTypes.STATIC_INPUT}, column_definition),
'known_regular_inputs':
_get_locations({InputTypes.STATIC_INPUT, InputTypes.KNOWN_INPUT},
real_inputs),
'known_categorical_inputs':
_get_locations({InputTypes.STATIC_INPUT, InputTypes.KNOWN_INPUT},
categorical_inputs),
"input_size": len(self._get_input_columns()),
"output_size": len(_get_locations({InputTypes.TARGET}, column_definition)),
"category_counts": self.num_classes_per_cat_input,
"input_obs_loc": _get_locations({InputTypes.TARGET}, column_definition),
"static_input_loc": _get_locations({InputTypes.STATIC_INPUT}, column_definition),
"known_regular_inputs": _get_locations({InputTypes.STATIC_INPUT, InputTypes.KNOWN_INPUT}, real_inputs),
"known_categorical_inputs": _get_locations(
{InputTypes.STATIC_INPUT, InputTypes.KNOWN_INPUT}, categorical_inputs
),
}
return locations
@@ -217,18 +203,20 @@ class GenericDataFormatter(abc.ABC):
"""Returns fixed model parameters for experiments."""
required_keys = [
'total_time_steps', 'num_encoder_steps', 'num_epochs',
'early_stopping_patience', 'multiprocessing_workers'
"total_time_steps",
"num_encoder_steps",
"num_epochs",
"early_stopping_patience",
"multiprocessing_workers",
]
fixed_params = self.get_fixed_params()
for k in required_keys:
if k not in fixed_params:
raise ValueError('Field {}'.format(k) +
' missing from fixed parameter definitions!')
raise ValueError("Field {}".format(k) + " missing from fixed parameter definitions!")
fixed_params['column_definition'] = self.get_column_definition()
fixed_params["column_definition"] = self.get_column_definition()
fixed_params.update(self._get_tft_input_indices())

View File

@@ -43,13 +43,13 @@ class ElectricityFormatter(GenericDataFormatter):
"""
_column_definition = [
('id', DataTypes.REAL_VALUED, InputTypes.ID),
('hours_from_start', DataTypes.REAL_VALUED, InputTypes.TIME),
('power_usage', DataTypes.REAL_VALUED, InputTypes.TARGET),
('hour', DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
('day_of_week', DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
('hours_from_start', DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
('categorical_id', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("id", DataTypes.REAL_VALUED, InputTypes.ID),
("hours_from_start", DataTypes.REAL_VALUED, InputTypes.TIME),
("power_usage", DataTypes.REAL_VALUED, InputTypes.TARGET),
("hour", DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
("day_of_week", DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
("hours_from_start", DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
("categorical_id", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
]
def __init__(self):
@@ -60,7 +60,7 @@ class ElectricityFormatter(GenericDataFormatter):
self._cat_scalers = None
self._target_scaler = None
self._num_classes_per_cat_input = None
self._time_steps = self.get_fixed_params()['total_time_steps']
self._time_steps = self.get_fixed_params()["total_time_steps"]
def split_data(self, df, valid_boundary=1315, test_boundary=1339):
"""Splits data frame into training-validation-test data frames.
@@ -76,9 +76,9 @@ class ElectricityFormatter(GenericDataFormatter):
Tuple of transformed (train, valid, test) data.
"""
print('Formatting train-valid-test splits.')
print("Formatting train-valid-test splits.")
index = df['days_from_start']
index = df["days_from_start"]
train = df.loc[index < valid_boundary]
valid = df.loc[(index >= valid_boundary - 7) & (index < test_boundary)]
test = df.loc[index >= test_boundary - 7]
@@ -93,18 +93,16 @@ class ElectricityFormatter(GenericDataFormatter):
Args:
df: Data to use to calibrate scalers.
"""
print('Setting scalers with training data...')
print("Setting scalers with training data...")
column_definitions = self.get_column_definition()
id_column = utils.get_single_col_by_input_type(InputTypes.ID,
column_definitions)
target_column = utils.get_single_col_by_input_type(InputTypes.TARGET,
column_definitions)
id_column = utils.get_single_col_by_input_type(InputTypes.ID, column_definitions)
target_column = utils.get_single_col_by_input_type(InputTypes.TARGET, column_definitions)
# Format real scalers
real_inputs = utils.extract_cols_from_data_type(
DataTypes.REAL_VALUED, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.REAL_VALUED, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
# Initialise scaler caches
self._real_scalers = {}
@@ -116,25 +114,22 @@ class ElectricityFormatter(GenericDataFormatter):
data = sliced[real_inputs].values
targets = sliced[[target_column]].values
self._real_scalers[identifier] \
= sklearn.preprocessing.StandardScaler().fit(data)
self._real_scalers[identifier] = sklearn.preprocessing.StandardScaler().fit(data)
self._target_scaler[identifier] \
= sklearn.preprocessing.StandardScaler().fit(targets)
self._target_scaler[identifier] = sklearn.preprocessing.StandardScaler().fit(targets)
identifiers.append(identifier)
# Format categorical scalers
categorical_inputs = utils.extract_cols_from_data_type(
DataTypes.CATEGORICAL, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.CATEGORICAL, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
categorical_scalers = {}
num_classes = []
for col in categorical_inputs:
# Set all to str so that we don't have mixed integer/string columns
srs = df[col].apply(str)
categorical_scalers[col] = sklearn.preprocessing.LabelEncoder().fit(
srs.values)
categorical_scalers[col] = sklearn.preprocessing.LabelEncoder().fit(srs.values)
num_classes.append(srs.nunique())
# Set categorical scaler outputs
@@ -158,18 +153,17 @@ class ElectricityFormatter(GenericDataFormatter):
"""
if self._real_scalers is None and self._cat_scalers is None:
raise ValueError('Scalers have not been set!')
raise ValueError("Scalers have not been set!")
# Extract relevant columns
column_definitions = self.get_column_definition()
id_col = utils.get_single_col_by_input_type(InputTypes.ID,
column_definitions)
id_col = utils.get_single_col_by_input_type(InputTypes.ID, column_definitions)
real_inputs = utils.extract_cols_from_data_type(
DataTypes.REAL_VALUED, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.REAL_VALUED, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
categorical_inputs = utils.extract_cols_from_data_type(
DataTypes.CATEGORICAL, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.CATEGORICAL, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
# Transform real inputs per entity
df_list = []
@@ -178,8 +172,7 @@ class ElectricityFormatter(GenericDataFormatter):
# Filter out any trajectories that are too short
if len(sliced) >= self._time_steps:
sliced_copy = sliced.copy()
sliced_copy[real_inputs] = self._real_scalers[identifier].transform(
sliced_copy[real_inputs].values)
sliced_copy[real_inputs] = self._real_scalers[identifier].transform(sliced_copy[real_inputs].values)
df_list.append(sliced_copy)
output = pd.concat(df_list, axis=0)
@@ -202,17 +195,17 @@ class ElectricityFormatter(GenericDataFormatter):
"""
if self._target_scaler is None:
raise ValueError('Scalers have not been set!')
raise ValueError("Scalers have not been set!")
column_names = predictions.columns
df_list = []
for identifier, sliced in predictions.groupby('identifier'):
for identifier, sliced in predictions.groupby("identifier"):
sliced_copy = sliced.copy()
target_scaler = self._target_scaler[identifier]
for col in column_names:
if col not in {'forecast_time', 'identifier'}:
if col not in {"forecast_time", "identifier"}:
sliced_copy[col] = target_scaler.inverse_transform(sliced_copy[col])
df_list.append(sliced_copy)
@@ -225,11 +218,11 @@ class ElectricityFormatter(GenericDataFormatter):
"""Returns fixed model parameters for experiments."""
fixed_params = {
'total_time_steps': 8 * 24,
'num_encoder_steps': 7 * 24,
'num_epochs': 100,
'early_stopping_patience': 5,
'multiprocessing_workers': 5
"total_time_steps": 8 * 24,
"num_encoder_steps": 7 * 24,
"num_epochs": 100,
"early_stopping_patience": 5,
"multiprocessing_workers": 5,
}
return fixed_params
@@ -238,13 +231,13 @@ class ElectricityFormatter(GenericDataFormatter):
"""Returns default optimised model parameters."""
model_params = {
'dropout_rate': 0.1,
'hidden_layer_size': 160,
'learning_rate': 0.001,
'minibatch_size': 64,
'max_gradient_norm': 0.01,
'num_heads': 4,
'stack_size': 1
"dropout_rate": 0.1,
"hidden_layer_size": 160,
"learning_rate": 0.001,
"minibatch_size": 64,
"max_gradient_norm": 0.01,
"num_heads": 4,
"stack_size": 1,
}
return model_params

View File

@@ -38,28 +38,28 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
"""
_column_definition = [
('traj_id', DataTypes.REAL_VALUED, InputTypes.ID),
('date', DataTypes.DATE, InputTypes.TIME),
('log_sales', DataTypes.REAL_VALUED, InputTypes.TARGET),
('onpromotion', DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
('transactions', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('oil', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('day_of_week', DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
('day_of_month', DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
('month', DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
('national_hol', DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
('regional_hol', DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
('local_hol', DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
('open', DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
('item_nbr', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
('store_nbr', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
('city', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
('state', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
('type', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
('cluster', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
('family', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
('class', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
('perishable', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT)
("traj_id", DataTypes.REAL_VALUED, InputTypes.ID),
("date", DataTypes.DATE, InputTypes.TIME),
("log_sales", DataTypes.REAL_VALUED, InputTypes.TARGET),
("onpromotion", DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
("transactions", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("oil", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("day_of_week", DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
("day_of_month", DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
("month", DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
("national_hol", DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
("regional_hol", DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
("local_hol", DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
("open", DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
("item_nbr", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("store_nbr", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("city", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("state", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("type", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("cluster", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("family", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("class", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("perishable", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
]
def __init__(self):
@@ -85,27 +85,27 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
Tuple of transformed (train, valid, test) data.
"""
print('Formatting train-valid-test splits.')
print("Formatting train-valid-test splits.")
if valid_boundary is None:
valid_boundary = pd.datetime(2015, 12, 1)
fixed_params = self.get_fixed_params()
time_steps = fixed_params['total_time_steps']
lookback = fixed_params['num_encoder_steps']
time_steps = fixed_params["total_time_steps"]
lookback = fixed_params["num_encoder_steps"]
forecast_horizon = time_steps - lookback
df['date'] = pd.to_datetime(df['date'])
df_lists = {'train': [], 'valid': [], 'test': []}
for _, sliced in df.groupby('traj_id'):
index = sliced['date']
df["date"] = pd.to_datetime(df["date"])
df_lists = {"train": [], "valid": [], "test": []}
for _, sliced in df.groupby("traj_id"):
index = sliced["date"]
train = sliced.loc[index < valid_boundary]
train_len = len(train)
valid_len = train_len + forecast_horizon
valid = sliced.iloc[train_len - lookback : valid_len, :]
test = sliced.iloc[valid_len - lookback : valid_len + forecast_horizon, :]
sliced_map = {'train': train, 'valid': valid, 'test': test}
sliced_map = {"train": train, "valid": valid, "test": test}
for k in sliced_map:
item = sliced_map[k]
@@ -115,7 +115,7 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
dfs = {k: pd.concat(df_lists[k], axis=0) for k in df_lists}
train = dfs['train']
train = dfs["train"]
self.set_scalers(train, set_real=True)
# Use all data for label encoding to handle labels not present in training.
@@ -124,11 +124,11 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
# Filter out identifiers not present in training (i.e. cold-started items).
def filter_ids(frame):
identifiers = set(self.identifiers)
index = frame['traj_id']
index = frame["traj_id"]
return frame.loc[index.apply(lambda x: x in identifiers)]
valid = filter_ids(dfs['valid'])
test = filter_ids(dfs['test'])
valid = filter_ids(dfs["valid"])
test = filter_ids(dfs["test"])
return (self.transform_inputs(data) for data in [train, valid, test])
@@ -142,13 +142,11 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
df: Data to use to calibrate scalers.
set_real: Whether to fit set real-valued or categorical scalers
"""
print('Setting scalers with training data...')
print("Setting scalers with training data...")
column_definitions = self.get_column_definition()
id_column = utils.get_single_col_by_input_type(InputTypes.ID,
column_definitions)
target_column = utils.get_single_col_by_input_type(InputTypes.TARGET,
column_definitions)
id_column = utils.get_single_col_by_input_type(InputTypes.ID, column_definitions)
target_column = utils.get_single_col_by_input_type(InputTypes.TARGET, column_definitions)
if set_real:
@@ -157,7 +155,7 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
# Format real scalers
self._real_scalers = {}
for col in ['oil', 'transactions', 'log_sales']:
for col in ["oil", "transactions", "log_sales"]:
self._real_scalers[col] = (df[col].mean(), df[col].std())
self._target_scaler = (df[target_column].mean(), df[target_column].std())
@@ -165,20 +163,19 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
else:
# Format categorical scalers
categorical_inputs = utils.extract_cols_from_data_type(
DataTypes.CATEGORICAL, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.CATEGORICAL, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
categorical_scalers = {}
num_classes = []
if self.identifiers is None:
raise ValueError('Scale real-valued inputs first!')
raise ValueError("Scale real-valued inputs first!")
id_set = set(self.identifiers)
valid_idx = df['traj_id'].apply(lambda x: x in id_set)
valid_idx = df["traj_id"].apply(lambda x: x in id_set)
for col in categorical_inputs:
# Set all to str so that we don't have mixed integer/string columns
srs = df[col].apply(str).loc[valid_idx]
categorical_scalers[col] = sklearn.preprocessing.LabelEncoder().fit(
srs.values)
categorical_scalers[col] = sklearn.preprocessing.LabelEncoder().fit(srs.values)
num_classes.append(srs.nunique())
@@ -201,21 +198,21 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
output = df.copy()
if self._real_scalers is None and self._cat_scalers is None:
raise ValueError('Scalers have not been set!')
raise ValueError("Scalers have not been set!")
column_definitions = self.get_column_definition()
categorical_inputs = utils.extract_cols_from_data_type(
DataTypes.CATEGORICAL, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.CATEGORICAL, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
# Format real inputs
for col in ['log_sales', 'oil', 'transactions']:
for col in ["log_sales", "oil", "transactions"]:
mean, std = self._real_scalers[col]
output[col] = (df[col] - mean) / std
if col == 'log_sales':
output[col] = output[col].fillna(0.) # mean imputation
if col == "log_sales":
output[col] = output[col].fillna(0.0) # mean imputation
# Format categorical inputs
for col in categorical_inputs:
@@ -238,7 +235,7 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
column_names = predictions.columns
mean, std = self._target_scaler
for col in column_names:
if col not in {'forecast_time', 'identifier'}:
if col not in {"forecast_time", "identifier"}:
output[col] = (predictions[col] * std) + mean
return output
@@ -248,11 +245,11 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
"""Returns fixed model parameters for experiments."""
fixed_params = {
'total_time_steps': 120,
'num_encoder_steps': 90,
'num_epochs': 100,
'early_stopping_patience': 5,
'multiprocessing_workers': 5
"total_time_steps": 120,
"num_encoder_steps": 90,
"num_epochs": 100,
"early_stopping_patience": 5,
"multiprocessing_workers": 5,
}
return fixed_params
@@ -261,13 +258,13 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
"""Returns default optimised model parameters."""
model_params = {
'dropout_rate': 0.1,
'hidden_layer_size': 240,
'learning_rate': 0.001,
'minibatch_size': 128,
'max_gradient_norm': 100.,
'num_heads': 4,
'stack_size': 1
"dropout_rate": 0.1,
"hidden_layer_size": 240,
"learning_rate": 0.001,
"minibatch_size": 128,
"max_gradient_norm": 100.0,
"num_heads": 4,
"stack_size": 1,
}
return model_params
@@ -301,8 +298,7 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
length = len([tup for tup in column_definition if tup[2] == input_type])
if length != 1:
raise ValueError('Illegal number of inputs ({}) of type {}'.format(
length, input_type))
raise ValueError("Illegal number of inputs ({}) of type {}".format(length, input_type))
_check_single_column(InputTypes.ID)
_check_single_column(InputTypes.TIME)
@@ -310,18 +306,28 @@ class FavoritaFormatter(data_formatters.base.GenericDataFormatter):
identifier = [tup for tup in column_definition if tup[2] == InputTypes.ID]
time = [tup for tup in column_definition if tup[2] == InputTypes.TIME]
real_inputs = [
tup for tup in column_definition if tup[1] == DataTypes.REAL_VALUED and
tup[2] not in {InputTypes.ID, InputTypes.TIME}
tup
for tup in column_definition
if tup[1] == DataTypes.REAL_VALUED and tup[2] not in {InputTypes.ID, InputTypes.TIME}
]
col_definition_map = {tup[0]: tup for tup in column_definition}
col_order = [
'item_nbr', 'store_nbr', 'city', 'state', 'type', 'cluster', 'family',
'class', 'perishable', 'onpromotion', 'day_of_week', 'national_hol',
'regional_hol', 'local_hol'
]
categorical_inputs = [
col_definition_map[k] for k in col_order if k in col_definition_map
"item_nbr",
"store_nbr",
"city",
"state",
"type",
"cluster",
"family",
"class",
"perishable",
"onpromotion",
"day_of_week",
"national_hol",
"regional_hol",
"local_hol",
]
categorical_inputs = [col_definition_map[k] for k in col_order if k in col_definition_map]
return identifier + time + real_inputs + categorical_inputs

View File

@@ -27,6 +27,7 @@ GenericDataFormatter = data_formatters.base.GenericDataFormatter
DataTypes = data_formatters.base.DataTypes
InputTypes = data_formatters.base.InputTypes
class Alpha158Formatter(GenericDataFormatter):
"""Defines and formats data for the Alpha158 dataset.
@@ -37,23 +38,23 @@ class Alpha158Formatter(GenericDataFormatter):
"""
_column_definition = [
('instrument', DataTypes.CATEGORICAL, InputTypes.ID),
('LABEL0', DataTypes.REAL_VALUED, InputTypes.TARGET),
('date', DataTypes.DATE, InputTypes.TIME),
('month', DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
('day_of_week', DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
("instrument", DataTypes.CATEGORICAL, InputTypes.ID),
("LABEL0", DataTypes.REAL_VALUED, InputTypes.TARGET),
("date", DataTypes.DATE, InputTypes.TIME),
("month", DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
("day_of_week", DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
# Selected 10 features
('RESI5', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('WVMA5', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('RSQR5', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('KLEN', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('RSQR10', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('CORR5', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('CORD5', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('CORR10', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('ROC60', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('RESI10', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('const', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("RESI5", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("WVMA5", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("RSQR5", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("KLEN", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("RSQR10", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("CORR5", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("CORD5", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("CORR10", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("ROC60", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("RESI10", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("const", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
]
def __init__(self):
@@ -79,9 +80,9 @@ class Alpha158Formatter(GenericDataFormatter):
Tuple of transformed (train, valid, test) data.
"""
print('Formatting train-valid-test splits.')
print("Formatting train-valid-test splits.")
index = df['year']
index = df["year"]
train = df.loc[index < valid_boundary]
valid = df.loc[(index >= valid_boundary) & (index < test_boundary)]
test = df.loc[index >= test_boundary]
@@ -96,39 +97,37 @@ class Alpha158Formatter(GenericDataFormatter):
Args:
df: Data to use to calibrate scalers.
"""
print('Setting scalers with training data...')
print("Setting scalers with training data...")
column_definitions = self.get_column_definition()
id_column = utils.get_single_col_by_input_type(InputTypes.ID,
column_definitions)
target_column = utils.get_single_col_by_input_type(InputTypes.TARGET,
column_definitions)
id_column = utils.get_single_col_by_input_type(InputTypes.ID, column_definitions)
target_column = utils.get_single_col_by_input_type(InputTypes.TARGET, column_definitions)
# Extract identifiers in case required
self.identifiers = list(df[id_column].unique())
# Format real scalers
real_inputs = utils.extract_cols_from_data_type(
DataTypes.REAL_VALUED, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.REAL_VALUED, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
data = df[real_inputs].values
self._real_scalers = sklearn.preprocessing.StandardScaler().fit(data)
self._target_scaler = sklearn.preprocessing.StandardScaler().fit(
df[[target_column]].values) # used for predictions
df[[target_column]].values
) # used for predictions
# Format categorical scalers
categorical_inputs = utils.extract_cols_from_data_type(
DataTypes.CATEGORICAL, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.CATEGORICAL, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
categorical_scalers = {}
num_classes = []
for col in categorical_inputs:
# Set all to str so that we don't have mixed integer/string columns
srs = df[col].apply(str)
categorical_scalers[col] = sklearn.preprocessing.LabelEncoder().fit(
srs.values)
categorical_scalers[col] = sklearn.preprocessing.LabelEncoder().fit(srs.values)
num_classes.append(srs.nunique())
# Set categorical scaler outputs
@@ -150,16 +149,16 @@ class Alpha158Formatter(GenericDataFormatter):
output = df.copy()
if self._real_scalers is None and self._cat_scalers is None:
raise ValueError('Scalers have not been set!')
raise ValueError("Scalers have not been set!")
column_definitions = self.get_column_definition()
real_inputs = utils.extract_cols_from_data_type(
DataTypes.REAL_VALUED, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.REAL_VALUED, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
categorical_inputs = utils.extract_cols_from_data_type(
DataTypes.CATEGORICAL, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.CATEGORICAL, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
# Format real inputs
output[real_inputs] = self._real_scalers.transform(df[real_inputs].values)
@@ -185,7 +184,7 @@ class Alpha158Formatter(GenericDataFormatter):
column_names = predictions.columns
for col in column_names:
if col not in {'forecast_time', 'identifier'}:
if col not in {"forecast_time", "identifier"}:
output[col] = self._target_scaler.inverse_transform(predictions[col])
return output
@@ -195,11 +194,11 @@ class Alpha158Formatter(GenericDataFormatter):
"""Returns fixed model parameters for experiments."""
fixed_params = {
'total_time_steps': 16 + 6,
'num_encoder_steps': 16,
'num_epochs': 100,
'early_stopping_patience': 5,
'multiprocessing_workers': 5,
"total_time_steps": 16 + 6,
"num_encoder_steps": 16,
"num_epochs": 100,
"early_stopping_patience": 5,
"multiprocessing_workers": 5,
}
return fixed_params
@@ -208,13 +207,13 @@ class Alpha158Formatter(GenericDataFormatter):
"""Returns default optimised model parameters."""
model_params = {
'dropout_rate': 0.3,
'hidden_layer_size': 160,
'learning_rate': 0.01,
'minibatch_size': 64,
'max_gradient_norm': 0.01,
'num_heads': 1,
'stack_size': 1
"dropout_rate": 0.3,
"hidden_layer_size": 160,
"learning_rate": 0.01,
"minibatch_size": 64,
"max_gradient_norm": 0.01,
"num_heads": 1,
"stack_size": 1,
}
return model_params

View File

@@ -42,13 +42,13 @@ class TrafficFormatter(VolatilityFormatter):
"""
_column_definition = [
('id', DataTypes.REAL_VALUED, InputTypes.ID),
('hours_from_start', DataTypes.REAL_VALUED, InputTypes.TIME),
('values', DataTypes.REAL_VALUED, InputTypes.TARGET),
('time_on_day', DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
('day_of_week', DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
('hours_from_start', DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
('categorical_id', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("id", DataTypes.REAL_VALUED, InputTypes.ID),
("hours_from_start", DataTypes.REAL_VALUED, InputTypes.TIME),
("values", DataTypes.REAL_VALUED, InputTypes.TARGET),
("time_on_day", DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
("day_of_week", DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
("hours_from_start", DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
("categorical_id", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
]
def split_data(self, df, valid_boundary=151, test_boundary=166):
@@ -65,9 +65,9 @@ class TrafficFormatter(VolatilityFormatter):
Tuple of transformed (train, valid, test) data.
"""
print('Formatting train-valid-test splits.')
print("Formatting train-valid-test splits.")
index = df['sensor_day']
index = df["sensor_day"]
train = df.loc[index < valid_boundary]
valid = df.loc[(index >= valid_boundary - 7) & (index < test_boundary)]
test = df.loc[index >= test_boundary - 7]
@@ -81,11 +81,11 @@ class TrafficFormatter(VolatilityFormatter):
"""Returns fixed model parameters for experiments."""
fixed_params = {
'total_time_steps': 8 * 24,
'num_encoder_steps': 7 * 24,
'num_epochs': 100,
'early_stopping_patience': 5,
'multiprocessing_workers': 5
"total_time_steps": 8 * 24,
"num_encoder_steps": 7 * 24,
"num_epochs": 100,
"early_stopping_patience": 5,
"multiprocessing_workers": 5,
}
return fixed_params
@@ -94,13 +94,13 @@ class TrafficFormatter(VolatilityFormatter):
"""Returns default optimised model parameters."""
model_params = {
'dropout_rate': 0.3,
'hidden_layer_size': 320,
'learning_rate': 0.001,
'minibatch_size': 128,
'max_gradient_norm': 100.,
'num_heads': 4,
'stack_size': 1
"dropout_rate": 0.3,
"hidden_layer_size": 320,
"learning_rate": 0.001,
"minibatch_size": 128,
"max_gradient_norm": 100.0,
"num_heads": 4,
"stack_size": 1,
}
return model_params

View File

@@ -38,16 +38,16 @@ class VolatilityFormatter(GenericDataFormatter):
"""
_column_definition = [
('Symbol', DataTypes.CATEGORICAL, InputTypes.ID),
('date', DataTypes.DATE, InputTypes.TIME),
('log_vol', DataTypes.REAL_VALUED, InputTypes.TARGET),
('open_to_close', DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
('days_from_start', DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
('day_of_week', DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
('day_of_month', DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
('week_of_year', DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
('month', DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
('Region', DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
("Symbol", DataTypes.CATEGORICAL, InputTypes.ID),
("date", DataTypes.DATE, InputTypes.TIME),
("log_vol", DataTypes.REAL_VALUED, InputTypes.TARGET),
("open_to_close", DataTypes.REAL_VALUED, InputTypes.OBSERVED_INPUT),
("days_from_start", DataTypes.REAL_VALUED, InputTypes.KNOWN_INPUT),
("day_of_week", DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
("day_of_month", DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
("week_of_year", DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
("month", DataTypes.CATEGORICAL, InputTypes.KNOWN_INPUT),
("Region", DataTypes.CATEGORICAL, InputTypes.STATIC_INPUT),
]
def __init__(self):
@@ -73,9 +73,9 @@ class VolatilityFormatter(GenericDataFormatter):
Tuple of transformed (train, valid, test) data.
"""
print('Formatting train-valid-test splits.')
print("Formatting train-valid-test splits.")
index = df['year']
index = df["year"]
train = df.loc[index < valid_boundary]
valid = df.loc[(index >= valid_boundary) & (index < test_boundary)]
test = df.loc[index >= test_boundary]
@@ -90,39 +90,37 @@ class VolatilityFormatter(GenericDataFormatter):
Args:
df: Data to use to calibrate scalers.
"""
print('Setting scalers with training data...')
print("Setting scalers with training data...")
column_definitions = self.get_column_definition()
id_column = utils.get_single_col_by_input_type(InputTypes.ID,
column_definitions)
target_column = utils.get_single_col_by_input_type(InputTypes.TARGET,
column_definitions)
id_column = utils.get_single_col_by_input_type(InputTypes.ID, column_definitions)
target_column = utils.get_single_col_by_input_type(InputTypes.TARGET, column_definitions)
# Extract identifiers in case required
self.identifiers = list(df[id_column].unique())
# Format real scalers
real_inputs = utils.extract_cols_from_data_type(
DataTypes.REAL_VALUED, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.REAL_VALUED, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
data = df[real_inputs].values
self._real_scalers = sklearn.preprocessing.StandardScaler().fit(data)
self._target_scaler = sklearn.preprocessing.StandardScaler().fit(
df[[target_column]].values) # used for predictions
df[[target_column]].values
) # used for predictions
# Format categorical scalers
categorical_inputs = utils.extract_cols_from_data_type(
DataTypes.CATEGORICAL, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.CATEGORICAL, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
categorical_scalers = {}
num_classes = []
for col in categorical_inputs:
# Set all to str so that we don't have mixed integer/string columns
srs = df[col].apply(str)
categorical_scalers[col] = sklearn.preprocessing.LabelEncoder().fit(
srs.values)
categorical_scalers[col] = sklearn.preprocessing.LabelEncoder().fit(srs.values)
num_classes.append(srs.nunique())
# Set categorical scaler outputs
@@ -144,16 +142,16 @@ class VolatilityFormatter(GenericDataFormatter):
output = df.copy()
if self._real_scalers is None and self._cat_scalers is None:
raise ValueError('Scalers have not been set!')
raise ValueError("Scalers have not been set!")
column_definitions = self.get_column_definition()
real_inputs = utils.extract_cols_from_data_type(
DataTypes.REAL_VALUED, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.REAL_VALUED, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
categorical_inputs = utils.extract_cols_from_data_type(
DataTypes.CATEGORICAL, column_definitions,
{InputTypes.ID, InputTypes.TIME})
DataTypes.CATEGORICAL, column_definitions, {InputTypes.ID, InputTypes.TIME}
)
# Format real inputs
output[real_inputs] = self._real_scalers.transform(df[real_inputs].values)
@@ -179,7 +177,7 @@ class VolatilityFormatter(GenericDataFormatter):
column_names = predictions.columns
for col in column_names:
if col not in {'forecast_time', 'identifier'}:
if col not in {"forecast_time", "identifier"}:
output[col] = self._target_scaler.inverse_transform(predictions[col])
return output
@@ -189,11 +187,11 @@ class VolatilityFormatter(GenericDataFormatter):
"""Returns fixed model parameters for experiments."""
fixed_params = {
'total_time_steps': 252 + 5,
'num_encoder_steps': 252,
'num_epochs': 100,
'early_stopping_patience': 5,
'multiprocessing_workers': 5,
"total_time_steps": 252 + 5,
"num_encoder_steps": 252,
"num_epochs": 100,
"early_stopping_patience": 5,
"multiprocessing_workers": 5,
}
return fixed_params
@@ -202,13 +200,13 @@ class VolatilityFormatter(GenericDataFormatter):
"""Returns default optimised model parameters."""
model_params = {
'dropout_rate': 0.3,
'hidden_layer_size': 160,
'learning_rate': 0.01,
'minibatch_size': 64,
'max_gradient_norm': 0.01,
'num_heads': 1,
'stack_size': 1
"dropout_rate": 0.3,
"hidden_layer_size": 160,
"learning_rate": 0.01,
"minibatch_size": 64,
"max_gradient_norm": 0.01,
"num_heads": 1,
"stack_size": 1,
}
return model_params

View File

@@ -12,4 +12,3 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

View File

@@ -43,9 +43,9 @@ class ExperimentConfig(object):
experiment.
"""
default_experiments = ['volatility', 'electricity', 'traffic', 'favorita', 'Alpha158']
default_experiments = ["volatility", "electricity", "traffic", "favorita", "Alpha158"]
def __init__(self, experiment='volatility', root_folder=None):
def __init__(self, experiment="volatility", root_folder=None):
"""Creates configs based on default experiment chosen.
Args:
@@ -54,36 +54,32 @@ class ExperimentConfig(object):
"""
if experiment not in self.default_experiments:
raise ValueError('Unrecognised experiment={}'.format(experiment))
raise ValueError("Unrecognised experiment={}".format(experiment))
# Defines all relevant paths
if root_folder is None:
root_folder = os.path.join(
os.path.dirname(os.path.realpath(__file__)), '..', 'outputs')
print('Using root folder {}'.format(root_folder))
root_folder = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "outputs")
print("Using root folder {}".format(root_folder))
self.root_folder = root_folder
self.experiment = experiment
self.data_folder = os.path.join(root_folder, 'data', experiment)
self.model_folder = os.path.join(root_folder, 'saved_models', experiment)
self.results_folder = os.path.join(root_folder, 'results', experiment)
self.data_folder = os.path.join(root_folder, "data", experiment)
self.model_folder = os.path.join(root_folder, "saved_models", experiment)
self.results_folder = os.path.join(root_folder, "results", experiment)
# Creates folders if they don't exist
for relevant_directory in [
self.root_folder, self.data_folder, self.model_folder,
self.results_folder
]:
for relevant_directory in [self.root_folder, self.data_folder, self.model_folder, self.results_folder]:
if not os.path.exists(relevant_directory):
os.makedirs(relevant_directory)
@property
def data_csv_path(self):
csv_map = {
'volatility': 'formatted_omi_vol.csv',
'electricity': 'hourly_electricity.csv',
'traffic': 'hourly_data.csv',
'favorita': 'favorita_consolidated.csv',
'Alpha158': 'Alpha158.csv',
"volatility": "formatted_omi_vol.csv",
"electricity": "hourly_electricity.csv",
"traffic": "hourly_data.csv",
"favorita": "favorita_consolidated.csv",
"Alpha158": "Alpha158.csv",
}
return os.path.join(self.data_folder, csv_map[self.experiment])
@@ -91,7 +87,7 @@ class ExperimentConfig(object):
@property
def hyperparam_iterations(self):
return 240 if self.experiment == 'volatility' else 60
return 240 if self.experiment == "volatility" else 60
def make_data_formatter(self):
"""Gets a data formatter object for experiment.
@@ -101,11 +97,11 @@ class ExperimentConfig(object):
"""
data_formatter_class = {
'volatility': data_formatters.volatility.VolatilityFormatter,
'electricity': data_formatters.electricity.ElectricityFormatter,
'traffic': data_formatters.traffic.TrafficFormatter,
'favorita': data_formatters.favorita.FavoritaFormatter,
'Alpha158': data_formatters.qlib_Alpha158.Alpha158Formatter,
"volatility": data_formatters.volatility.VolatilityFormatter,
"electricity": data_formatters.electricity.ElectricityFormatter,
"traffic": data_formatters.traffic.TrafficFormatter,
"favorita": data_formatters.favorita.FavoritaFormatter,
"Alpha158": data_formatters.qlib_Alpha158.Alpha158Formatter,
}
return data_formatter_class[self.experiment]()

View File

@@ -12,4 +12,3 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

View File

@@ -48,11 +48,7 @@ class HyperparamOptManager:
hyperparam_folder: Where to save optimisation outputs.
"""
def __init__(self,
param_ranges,
fixed_params,
model_folder,
override_w_fixed_params=True):
def __init__(self, param_ranges, fixed_params, model_folder, override_w_fixed_params=True):
"""Instantiates model.
Args:
@@ -136,17 +132,14 @@ class HyperparamOptManager:
def _check_params(self, params):
"""Checks that parameter map is properly defined."""
valid_fields = list(self.param_ranges.keys()) + list(
self.fixed_params.keys())
valid_fields = list(self.param_ranges.keys()) + list(self.fixed_params.keys())
invalid_fields = [k for k in params if k not in valid_fields]
missing_fields = [k for k in valid_fields if k not in params]
if invalid_fields:
raise ValueError("Invalid Fields Found {} - Valid ones are {}".format(
invalid_fields, valid_fields))
raise ValueError("Invalid Fields Found {} - Valid ones are {}".format(invalid_fields, valid_fields))
if missing_fields:
raise ValueError("Missing Fields Found {} - Valid ones are {}".format(
missing_fields, valid_fields))
raise ValueError("Missing Fields Found {} - Valid ones are {}".format(missing_fields, valid_fields))
def _get_name(self, params):
"""Returns a unique key for the supplied set of params."""
@@ -176,9 +169,7 @@ class HyperparamOptManager:
def _get_next():
"""Returns next hyperparameter set per try."""
parameters = {
k: np.random.choice(self.param_ranges[k]) for k in param_range_keys
}
parameters = {k: np.random.choice(self.param_ranges[k]) for k in param_range_keys}
# Adds fixed params
for k in self.fixed_params:
@@ -240,14 +231,16 @@ class HyperparamOptManager:
class DistributedHyperparamOptManager(HyperparamOptManager):
"""Manages distributed hyperparameter optimisation across many gpus."""
def __init__(self,
def __init__(
self,
param_ranges,
fixed_params,
root_model_folder,
worker_number,
search_iterations=1000,
num_iterations_per_worker=5,
clear_serialised_params=False):
clear_serialised_params=False,
):
"""Instantiates optimisation manager.
This hyperparameter optimisation pre-generates #search_iterations
@@ -274,22 +267,19 @@ class DistributedHyperparamOptManager(HyperparamOptManager):
# Sanity checks
if worker_number > max_workers:
raise ValueError(
"Worker number ({}) cannot be larger than the total number of workers!"
.format(max_workers))
"Worker number ({}) cannot be larger than the total number of workers!".format(max_workers)
)
if worker_number > search_iterations:
raise ValueError(
"Worker number ({}) cannot be larger than the max search iterations ({})!"
.format(worker_number, search_iterations))
"Worker number ({}) cannot be larger than the max search iterations ({})!".format(
worker_number, search_iterations
)
)
print("*** Creating hyperparameter manager for worker {} ***".format(
worker_number))
print("*** Creating hyperparameter manager for worker {} ***".format(worker_number))
hyperparam_folder = os.path.join(root_model_folder, str(worker_number))
super().__init__(
param_ranges,
fixed_params,
hyperparam_folder,
override_w_fixed_params=True)
super().__init__(param_ranges, fixed_params, hyperparam_folder, override_w_fixed_params=True)
serialised_ranges_folder = os.path.join(root_model_folder, "hyperparams")
if clear_serialised_params:
@@ -299,8 +289,7 @@ class DistributedHyperparamOptManager(HyperparamOptManager):
utils.create_folder_if_not_exist(serialised_ranges_folder)
self.serialised_ranges_path = os.path.join(
serialised_ranges_folder, "ranges_{}.csv".format(search_iterations))
self.serialised_ranges_path = os.path.join(serialised_ranges_folder, "ranges_{}.csv".format(search_iterations))
self.hyperparam_folder = hyperparam_folder # override
self.worker_num = worker_number
self.total_search_iterations = search_iterations
@@ -332,8 +321,11 @@ class DistributedHyperparamOptManager(HyperparamOptManager):
Returns:
DataFrame containing hyperparameter combinations.
"""
print("Loading params for {} search iterations form {}".format(
self.total_search_iterations, self.serialised_ranges_path))
print(
"Loading params for {} search iterations form {}".format(
self.total_search_iterations, self.serialised_ranges_path
)
)
if os.path.exists(self.serialised_ranges_folder):
df = pd.read_csv(self.serialised_ranges_path, index_col=0)
@@ -351,8 +343,11 @@ class DistributedHyperparamOptManager(HyperparamOptManager):
"""
search_df = self._generate_full_hyperparam_df()
print("Serialising params for {} search iterations to {}".format(
self.total_search_iterations, self.serialised_ranges_path))
print(
"Serialising params for {} search iterations to {}".format(
self.total_search_iterations, self.serialised_ranges_path
)
)
search_df.to_csv(self.serialised_ranges_path)
@@ -428,10 +423,7 @@ class DistributedHyperparamOptManager(HyperparamOptManager):
max_worker_num = int(np.ceil(n / batch_size))
worker_idx = np.concatenate([
np.tile(i + 1, self.num_iterations_per_worker)
for i in range(max_worker_num)
])
worker_idx = np.concatenate([np.tile(i + 1, self.num_iterations_per_worker) for i in range(max_worker_num)])
output["worker"] = worker_idx[: len(output)]

File diff suppressed because it is too large Load Diff

View File

@@ -36,13 +36,12 @@ def get_single_col_by_input_type(input_type, column_definition):
l = [tup[0] for tup in column_definition if tup[2] == input_type]
if len(l) != 1:
raise ValueError('Invalid number of columns for {}'.format(input_type))
raise ValueError("Invalid number of columns for {}".format(input_type))
return l[0]
def extract_cols_from_data_type(data_type, column_definition,
excluded_input_types):
def extract_cols_from_data_type(data_type, column_definition, excluded_input_types):
"""Extracts the names of columns that correspond to a define data_type.
Args:
@@ -53,11 +52,7 @@ def extract_cols_from_data_type(data_type, column_definition,
Returns:
List of names for columns with data type specified.
"""
return [
tup[0]
for tup in column_definition
if tup[1] == data_type and tup[2] not in excluded_input_types
]
return [tup[0] for tup in column_definition if tup[1] == data_type and tup[2] not in excluded_input_types]
# Loss functions.
@@ -78,13 +73,12 @@ def tensorflow_quantile_loss(y, y_pred, quantile):
# Checks quantile
if quantile < 0 or quantile > 1:
raise ValueError(
'Illegal quantile value={}! Values should be between 0 and 1.'.format(
quantile))
raise ValueError("Illegal quantile value={}! Values should be between 0 and 1.".format(quantile))
prediction_underflow = y - y_pred
q_loss = quantile * tf.maximum(prediction_underflow, 0.) + (
1. - quantile) * tf.maximum(-prediction_underflow, 0.)
q_loss = quantile * tf.maximum(prediction_underflow, 0.0) + (1.0 - quantile) * tf.maximum(
-prediction_underflow, 0.0
)
return tf.reduce_sum(q_loss, axis=-1)
@@ -104,8 +98,9 @@ def numpy_normalised_quantile_loss(y, y_pred, quantile):
Float for normalised quantile loss.
"""
prediction_underflow = y - y_pred
weighted_errors = quantile * np.maximum(prediction_underflow, 0.) \
+ (1. - quantile) * np.maximum(-prediction_underflow, 0.)
weighted_errors = quantile * np.maximum(prediction_underflow, 0.0) + (1.0 - quantile) * np.maximum(
-prediction_underflow, 0.0
)
quantile_loss = weighted_errors.mean()
normaliser = y.abs().mean()
@@ -125,7 +120,7 @@ def create_folder_if_not_exist(directory):
# Tensorflow related functions.
def get_default_tensorflow_config(tf_device='gpu', gpu_id=0):
def get_default_tensorflow_config(tf_device="gpu", gpu_id=0):
"""Creates tensorflow config for graphs to run on CPU or GPU.
Specifies whether to run graph on gpu or cpu and which GPU ID to use for multi
@@ -139,16 +134,15 @@ def get_default_tensorflow_config(tf_device='gpu', gpu_id=0):
Tensorflow config.
"""
if tf_device == 'cpu':
os.environ['CUDA_VISIBLE_DEVICES'] = '-1' # for training on cpu
tf_config = tf.ConfigProto(
log_device_placement=False, device_count={'GPU': 0})
if tf_device == "cpu":
os.environ["CUDA_VISIBLE_DEVICES"] = "-1" # for training on cpu
tf_config = tf.ConfigProto(log_device_placement=False, device_count={"GPU": 0})
else:
os.environ['CUDA_DEVICE_ORDER'] = 'PCI_BUS_ID'
os.environ['CUDA_VISIBLE_DEVICES'] = str(gpu_id)
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = str(gpu_id)
print('Selecting GPU ID={}'.format(gpu_id))
print("Selecting GPU ID={}".format(gpu_id))
tf_config = tf.ConfigProto(log_device_placement=False)
tf_config.gpu_options.allow_growth = True
@@ -174,9 +168,8 @@ def save(tf_session, model_folder, cp_name, scope=None):
var_list = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, scope=scope)
saver = tf.train.Saver(var_list=var_list, max_to_keep=100000)
save_path = saver.save(tf_session,
os.path.join(model_folder, '{0}.ckpt'.format(cp_name)))
print('Model saved to: {0}'.format(save_path))
save_path = saver.save(tf_session, os.path.join(model_folder, "{0}.ckpt".format(cp_name)))
print("Model saved to: {0}".format(save_path))
def load(tf_session, model_folder, cp_name, scope=None, verbose=False):
@@ -190,14 +183,13 @@ def load(tf_session, model_folder, cp_name, scope=None, verbose=False):
verbose: Whether to print additional debugging information.
"""
# Load model proper
load_path = os.path.join(model_folder, '{0}.ckpt'.format(cp_name))
load_path = os.path.join(model_folder, "{0}.ckpt".format(cp_name))
print('Loading model from {0}'.format(load_path))
print("Loading model from {0}".format(load_path))
print_weights_in_checkpoint(model_folder, cp_name)
initial_vars = set(
[v.name for v in tf.get_default_graph().as_graph_def().node])
initial_vars = set([v.name for v in tf.get_default_graph().as_graph_def().node])
# Saver
if scope is None:
@@ -210,11 +202,11 @@ def load(tf_session, model_folder, cp_name, scope=None, verbose=False):
all_vars = set([v.name for v in tf.get_default_graph().as_graph_def().node])
if verbose:
print('Restored {0}'.format(','.join(initial_vars.difference(all_vars))))
print('Existing {0}'.format(','.join(all_vars.difference(initial_vars))))
print('All {0}'.format(','.join(all_vars)))
print("Restored {0}".format(",".join(initial_vars.difference(all_vars))))
print("Existing {0}".format(",".join(all_vars.difference(initial_vars))))
print("All {0}".format(",".join(all_vars)))
print('Done.')
print("Done.")
def print_weights_in_checkpoint(model_folder, cp_name):
@@ -227,10 +219,6 @@ def print_weights_in_checkpoint(model_folder, cp_name):
Returns:
"""
load_path = os.path.join(model_folder, '{0}.ckpt'.format(cp_name))
load_path = os.path.join(model_folder, "{0}.ckpt".format(cp_name))
print_tensors_in_checkpoint_file(
file_name=load_path,
tensor_name='',
all_tensors=True,
all_tensor_names=True)
print_tensors_in_checkpoint_file(file_name=load_path, tensor_name="", all_tensors=True, all_tensor_names=True)

View File

@@ -18,27 +18,29 @@ from qlib.data.dataset import DatasetH
from qlib.data.dataset.handler import DataHandlerLP
# To register new datasets, please add them here.
ALLOW_DATASET = ['Alpha158']
ALLOW_DATASET = ["Alpha158"]
DATASET_SETTING = {
'Alpha158': {
'feature_col': ['RESI5', 'WVMA5', 'RSQR5', 'KLEN', 'RSQR10', 'CORR5', 'CORD5', 'CORR10', 'ROC60', 'RESI10'],
'label_col': ['LABEL0'],
"Alpha158": {
"feature_col": ["RESI5", "WVMA5", "RSQR5", "KLEN", "RSQR10", "CORR5", "CORD5", "CORR10", "ROC60", "RESI10"],
"label_col": ["LABEL0"],
},
}
# To register new datasets, please add their configurations here.
def get_shifted_label(data_df, shifts=5, col_shift='LABEL0'):
return data_df[[col_shift]].groupby('instrument').apply(lambda df: df.shift(shifts))
def get_shifted_label(data_df, shifts=5, col_shift="LABEL0"):
return data_df[[col_shift]].groupby("instrument").apply(lambda df: df.shift(shifts))
def fill_test_na(test_df):
test_df_res = test_df.copy()
feature_cols = ~test_df_res.columns.str.contains('label', case=False)
test_feature_fna = test_df_res.loc[:, feature_cols].groupby('datetime').apply(lambda df: df.fillna(df.mean()))
feature_cols = ~test_df_res.columns.str.contains("label", case=False)
test_feature_fna = test_df_res.loc[:, feature_cols].groupby("datetime").apply(lambda df: df.fillna(df.mean()))
test_df_res.loc[:, feature_cols] = test_feature_fna
return test_df_res
def process_qlib_data(df, dataset, fillna=False):
"""Prepare data to fit the TFT model.
@@ -51,8 +53,8 @@ def process_qlib_data(df, dataset, fillna=False):
"""
# Several features selected manually
feature_col = DATASET_SETTING[dataset]['feature_col']
label_col = DATASET_SETTING[dataset]['label_col']
feature_col = DATASET_SETTING[dataset]["feature_col"]
label_col = DATASET_SETTING[dataset]["label_col"]
temp_df = df.loc[:, feature_col + label_col]
if fillna:
temp_df = fill_test_na(temp_df)
@@ -60,13 +62,14 @@ def process_qlib_data(df, dataset, fillna=False):
temp_df = temp_df.sort_index()
temp_df = temp_df.reset_index(level=0)
dates = pd.to_datetime(temp_df.index)
temp_df['date'] = dates
temp_df['day_of_week'] = dates.dayofweek
temp_df['month'] = dates.month
temp_df['year'] = dates.year
temp_df['const'] = 1.0
temp_df["date"] = dates
temp_df["day_of_week"] = dates.dayofweek
temp_df["month"] = dates.month
temp_df["year"] = dates.year
temp_df["const"] = 1.0
return temp_df
def process_predicted(df, col_name):
"""Transform the TFT predicted data into Qlib format.
@@ -80,21 +83,24 @@ def process_predicted(df, col_name):
"""
df_res = df.copy()
df_res = df_res.rename(columns={"forecast_time": "datetime", "identifier": "instrument", "t+0": col_name})
df_res = df_res.set_index(['datetime','instrument']).sort_index()
df_res = df_res.set_index(["datetime", "instrument"]).sort_index()
df_res = df_res[[col_name]]
return df_res
def format_score(forecast_df, col_name='pred', label_shift=5):
def format_score(forecast_df, col_name="pred", label_shift=5):
pred = process_predicted(forecast_df, col_name=col_name)
pred = get_shifted_label(pred, shifts=-label_shift, col_shift=col_name)
pred = pred.dropna()[col_name]
return pred
def transform_df(df, col_name='LABEL0'):
df_res = df['feature']
df_res[col_name] = df['label']
def transform_df(df, col_name="LABEL0"):
df_res = df["feature"]
df_res[col_name] = df["label"]
return df_res
class TFTModel(ModelFT):
"""TFT Model"""
@@ -110,9 +116,9 @@ class TFTModel(ModelFT):
def fit(
self,
dataset: DatasetH,
DATASET = 'Alpha158',
MODEL_FOLDER = 'qlib_alpha158_model',
LABEL_COL = 'LABEL0',
DATASET="Alpha158",
MODEL_FOLDER="qlib_alpha158_model",
LABEL_COL="LABEL0",
LABEL_SHIFT=5,
USE_GPU_ID=0,
**kwargs
@@ -125,7 +131,6 @@ class TFTModel(ModelFT):
dtrain.loc[:, LABEL_COL] = get_shifted_label(dtrain, shifts=LABEL_SHIFT, col_shift=LABEL_COL)
dvalid.loc[:, LABEL_COL] = get_shifted_label(dvalid, shifts=LABEL_SHIFT, col_shift=LABEL_COL)
train = process_qlib_data(dtrain, DATASET, fillna=True).dropna()
valid = process_qlib_data(dvalid, DATASET, fillna=True).dropna()
@@ -143,8 +148,9 @@ class TFTModel(ModelFT):
ModelClass = libs.tft_model.TemporalFusionTransformer
if not isinstance(self.data_formatter, data_formatters.base.GenericDataFormatter):
raise ValueError(
"Data formatters should inherit from" +
"AbstractDataFormatter! Type={}".format(type(self.data_formatter)))
"Data formatters should inherit from"
+ "AbstractDataFormatter! Type={}".format(type(self.data_formatter))
)
default_keras_session = tf.keras.backend.get_session()
@@ -164,7 +170,7 @@ class TFTModel(ModelFT):
if not os.path.exists(self.model_folder):
os.makedirs(self.model_folder)
params['model_folder'] = self.model_folder
params["model_folder"] = self.model_folder
print("*** Begin training ***")
best_loss = np.Inf
@@ -179,17 +185,14 @@ class TFTModel(ModelFT):
self.sess.run(tf.global_variables_initializer())
self.model.fit(train_df=train, valid_df=valid)
print("*** Finished training ***")
saved_model_dir = self.model_folder+'/'+'saved_model'
saved_model_dir = self.model_folder + "/" + "saved_model"
if not os.path.exists(saved_model_dir):
os.makedirs(saved_model_dir)
self.model.save(saved_model_dir)
def extract_numerical_data(data):
"""Strips out forecast time and identifier columns."""
return data[[
col for col in data.columns
if col not in {"forecast_time", "identifier"}
]]
return data[[col for col in data.columns if col not in {"forecast_time", "identifier"}]]
# p50_loss = utils.numpy_normalised_quantile_loss(
# extract_numerical_data(targets), extract_numerical_data(p50_forecast),
@@ -218,7 +221,6 @@ class TFTModel(ModelFT):
params = self.data_formatter.get_default_model_params()
params = {**params, **fixed_params}
print("*** Begin predicting ***")
tf.reset_default_graph()
@@ -230,8 +232,8 @@ class TFTModel(ModelFT):
p90_forecast = self.data_formatter.format_predictions(output_map["p90"])
tf.keras.backend.set_session(default_keras_session)
predict = format_score(p90_forecast, 'pred', self.label_shift)
label = format_score(targets, 'label', self.label_shift)
predict = format_score(p90_forecast, "pred", self.label_shift)
label = format_score(targets, "label", self.label_shift)
# ===========================Predicting Process===========================
return predict, label

View File

@@ -63,23 +63,28 @@ if __name__ == "__main__":
"class": "DatasetH",
"module_path": "qlib.data.dataset",
"kwargs": {
'handler': {
"handler": {
"class": "Alpha158",
"module_path": "qlib.contrib.data.handler",
"kwargs": DATA_HANDLER_CONFIG
"kwargs": DATA_HANDLER_CONFIG,
},
"segments": {
"train": ("2008-01-01", "2014-12-31"),
"valid": (
"2015-01-01",
"2016-12-31",
),
"test": (
"2017-01-01",
"2020-08-01",
),
},
},
'segments': {
'train': ("2008-01-01", "2014-12-31"),
'valid': ("2015-01-01", "2016-12-31",),
'test': ("2017-01-01", "2020-08-01",),
}
}
}
# You shoud record the data in specific sequence
# "record": ['SignalRecord', 'SigAnaRecord', 'PortAnaRecord'],
}
model = TFTModel()
dataset = init_instance_by_config(task["dataset"])
model.fit(dataset)
@@ -91,7 +96,6 @@ if __name__ == "__main__":
pred_score_path.parent.mkdir(exist_ok=True, parents=True)
pred_score.to_pickle(pred_score_path)
###################################
# backtest
###################################
@@ -126,5 +130,3 @@ if __name__ == "__main__":
)
analysis_df = pd.concat(analysis) # type: pd.DataFrame
print(analysis_df)