From 3520d3b108c814f4be88204dfab778832bb2ec65 Mon Sep 17 00:00:00 2001 From: Jactus Date: Wed, 25 Nov 2020 14:58:23 +0800 Subject: [PATCH] Add SFM config --- README.md | 1 + examples/benchmarks/SFM/requirements.txt | 4 + .../benchmarks/SFM/workflow_config_sfm.yaml | 73 ++++++++++++++ examples/workflow_by_code_sfm.py | 10 +- qlib/contrib/model/pytorch_sfm.py | 94 +++++++++---------- requirements.txt | 3 +- 6 files changed, 131 insertions(+), 54 deletions(-) create mode 100644 examples/benchmarks/SFM/requirements.txt create mode 100644 examples/benchmarks/SFM/workflow_config_sfm.yaml diff --git a/README.md b/README.md index 7c7e58a1c..4383dea26 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,7 @@ Here is a list of models built on `Qlib`. - [LSTM based on pytorcn](qlib/contrib/model/pytorch_lstm.py) - [GATs based on pytorch](qlib/contrib/model/pytorch_gats.py) - [TabNet based on pytorch](qlib/contrib/model/tabnet.py) +- [SFM based on pytorch](qlib/contrib/model/pytorch_sfm.py) Your PR of new Quant models is highly welcomed. diff --git a/examples/benchmarks/SFM/requirements.txt b/examples/benchmarks/SFM/requirements.txt new file mode 100644 index 000000000..6a3d13097 --- /dev/null +++ b/examples/benchmarks/SFM/requirements.txt @@ -0,0 +1,4 @@ +pandas==1.1.2 +numpy==1.17.4 +scikit_learn==0.23.2 +torch==1.7.0 \ No newline at end of file diff --git a/examples/benchmarks/SFM/workflow_config_sfm.yaml b/examples/benchmarks/SFM/workflow_config_sfm.yaml new file mode 100644 index 000000000..9086bab4a --- /dev/null +++ b/examples/benchmarks/SFM/workflow_config_sfm.yaml @@ -0,0 +1,73 @@ +provider_uri: "~/.qlib/qlib_data/cn_data" +region: cn +market: &market csi300 +benchmark: &benchmark SH000300 +data_handler_config: &data_handler_config + start_time: 2008-01-01 + end_time: 2020-08-01 + fit_start_time: 2008-01-01 + fit_end_time: 2014-12-31 + instruments: *market +port_analysis_config: &port_analysis_config + strategy: + class: TopkDropoutStrategy + module_path: qlib.contrib.strategy.strategy + kwargs: + topk: 50 + n_drop: 5 + backtest: + verbose: False + limit_threshold: 0.095 + account: 100000000 + benchmark: *benchmark + deal_price: close + open_cost: 0.0005 + close_cost: 0.0015 + min_cost: 5 +task: + model: + class: SFM + module_path: qlib.contrib.model.pytorch_sfm + kwargs: + d_feat: 6 + hidden_size: 64 + output_dim: 1 + freq_dim: 15 + dropout_W: 0.5 + dropout_U: 0.5 + n_epochs: 10 + lr: 1e-3 + batch_size: 800 + early_stop: 20 + eval_steps: 5 + loss: mse + lr_decay: 0.96 + lr_decay_steps: 100 + optimizer: gd + GPU: 1 + seed: 0 + dataset: + class: DatasetH + module_path: qlib.data.dataset + kwargs: + handler: + class: ALPHA360_Denoise + module_path: qlib.contrib.data.handler + 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] + record: + - class: SignalRecord + module_path: qlib.workflow.record_temp + kwargs: {} + - class: SigAnaRecord + module_path: qlib.workflow.record_temp + kwargs: + ana_long_short: False + ann_scaler: 252 + - class: PortAnaRecord + module_path: qlib.workflow.record_temp + kwargs: + config: *port_analysis_config \ No newline at end of file diff --git a/examples/workflow_by_code_sfm.py b/examples/workflow_by_code_sfm.py index 45f34f012..1942bfb33 100644 --- a/examples/workflow_by_code_sfm.py +++ b/examples/workflow_by_code_sfm.py @@ -62,8 +62,8 @@ if __name__ == "__main__": "kwargs": { "d_feat": 6, "hidden_size": 64, - "output_dim" : 1, - "freq_dim" : 15, + "output_dim": 1, + "freq_dim": 15, "dropout_W": 0.5, "dropout_U": 0.5, "n_epochs": 10, @@ -72,9 +72,9 @@ if __name__ == "__main__": "early_stop": 20, "eval_steps": 5, "loss": "mse", - "lr_decay" : 0.96, - "lr_decay_steps" : 100, - "optimizer" : "gd", + "lr_decay": 0.96, + "lr_decay_steps": 100, + "optimizer": "gd", "GPU": 1, "seed": 0, }, diff --git a/qlib/contrib/model/pytorch_sfm.py b/qlib/contrib/model/pytorch_sfm.py index 04a5ad33f..8564c491c 100644 --- a/qlib/contrib/model/pytorch_sfm.py +++ b/qlib/contrib/model/pytorch_sfm.py @@ -21,11 +21,12 @@ from ...model.base import Model from ...data.dataset import DatasetH from ...data.dataset.handler import DataHandlerLP + class SFM_Model(nn.Module): - def __init__(self, d_feat=6, output_dim = 1, freq_dim = 10, hidden_size = 64, dropout_W = 0.0, dropout_U = 0.0, device = "cpu"): + def __init__(self, d_feat=6, output_dim=1, freq_dim=10, hidden_size=64, dropout_W=0.0, dropout_U=0.0, device="cpu"): super().__init__() - self.input_dim = d_feat + self.input_dim = d_feat self.output_dim = output_dim self.freq_dim = freq_dim self.hidden_dim = hidden_size @@ -56,22 +57,22 @@ class SFM_Model(nn.Module): self.W_p = nn.Parameter(init.xavier_uniform_(torch.empty(self.hidden_dim, self.output_dim))) self.b_p = nn.Parameter(torch.zeros(self.output_dim)) - + self.activation = nn.Tanh() self.inner_activation = nn.Hardsigmoid() self.dropout_W, self.dropout_U = (dropout_W, dropout_U) self.fc_out = nn.Linear(self.output_dim, 1) self.states = [] - + def forward(self, input): - input = input.reshape(len(input), self.input_dim, -1) # [N, F, T] - input = input.permute(0, 2, 1) # [N, T, F] + input = input.reshape(len(input), self.input_dim, -1) # [N, F, T] + input = input.permute(0, 2, 1) # [N, T, F] time_step = input.shape[1] - + for ts in range(time_step): - x = input[:, ts,:] - if(len(self.states)==0): #hasn't initialized yet + x = input[:, ts, :] + if len(self.states) == 0: # hasn't initialized yet self.init_states(x) self.get_constants(x) p_tm1 = self.states[0] @@ -88,77 +89,79 @@ class SFM_Model(nn.Module): x_fre = torch.matmul(x * B_W[0], self.W_fre) + self.b_fre x_c = torch.matmul(x * B_W[0], self.W_c) + self.b_c x_o = torch.matmul(x * B_W[0], self.W_o) + self.b_o - - i = self.inner_activation(x_i + torch.matmul(h_tm1 * B_U[0], self.U_i)) # not sure whether I am doing in the right unsquuze - + + i = self.inner_activation( + x_i + torch.matmul(h_tm1 * B_U[0], self.U_i) + ) # not sure whether I am doing in the right unsquuze ste = self.inner_activation(x_ste + torch.matmul(h_tm1 * B_U[0], self.U_ste)) fre = self.inner_activation(x_fre + torch.matmul(h_tm1 * B_U[0], self.U_fre)) ste = torch.reshape(ste, (-1, self.hidden_dim, 1)) fre = torch.reshape(fre, (-1, 1, self.freq_dim)) - + f = ste * fre - + c = i * self.activation(x_c + torch.matmul(h_tm1 * B_U[0], self.U_c)) time = time_tm1 + 1 omega = torch.tensor(2 * np.pi) * time * frequency - re = torch.cos(omega) + re = torch.cos(omega) im = torch.sin(omega) - + c = torch.reshape(c, (-1, self.hidden_dim, 1)) S_re = f * S_re_tm1 + c * re S_im = f * S_im_tm1 + c * im - + A = torch.square(S_re) + torch.square(S_im) A = torch.reshape(A, (-1, self.freq_dim)).float() A_a = torch.matmul(A * B_U[0], self.U_a) A_a = torch.reshape(A_a, (-1, self.hidden_dim)) a = self.activation(A_a + self.b_a) - + o = self.inner_activation(x_o + torch.matmul(h_tm1 * B_U[0], self.U_o)) h = o * a p = torch.matmul(h, self.W_p) + self.b_p self.states = [p, h, S_re, S_im, time, None, None, None] - self.states = [] + self.states = [] return self.fc_out(p).squeeze() def init_states(self, x): reducer_f = torch.zeros((self.hidden_dim, self.freq_dim)).to(self.device) reducer_p = torch.zeros((self.hidden_dim, self.output_dim)).to(self.device) - + init_state_h = torch.zeros(self.hidden_dim).to(self.device) init_state_p = torch.matmul(init_state_h, reducer_p) - + init_state = torch.zeros_like(init_state_h).to(self.device) init_freq = torch.matmul(init_state_h, reducer_f) init_state = torch.reshape(init_state, (-1, self.hidden_dim, 1)) init_freq = torch.reshape(init_freq, (-1, 1, self.freq_dim)) - + init_state_S_re = init_state * init_freq init_state_S_im = init_state * init_freq - + init_state_time = torch.tensor(0).to(self.device) self.states = [init_state_p, init_state_h, init_state_S_re, init_state_S_im, init_state_time, None, None, None] def get_constants(self, x): constants = [] - constants.append([torch.tensor(1.).to(self.device) for _ in range(6)]) - constants.append([torch.tensor(1.).to(self.device) for _ in range(7)]) - array = np.array([float(ii)/self.freq_dim for ii in range(self.freq_dim)]) + constants.append([torch.tensor(1.0).to(self.device) for _ in range(6)]) + constants.append([torch.tensor(1.0).to(self.device) for _ in range(7)]) + array = np.array([float(ii) / self.freq_dim for ii in range(self.freq_dim)]) constants.append(torch.tensor(array).to(self.device)) self.states[5:] = constants + class SFM(Model): """SFM Model @@ -185,7 +188,7 @@ class SFM(Model): d_feat=6, hidden_size=64, output_dim=1, - freq_dim = 10, + freq_dim=10, dropout_W=0.0, dropout_U=0.0, n_epochs=200, @@ -221,7 +224,7 @@ class SFM(Model): self.lr_decay_steps = lr_decay_steps self.optimizer = optimizer.lower() self.loss_type = loss - self.device = 'cuda:%d'%(GPU) if torch.cuda.is_available() else 'cpu' + self.device = "cuda:%d" % (GPU) if torch.cuda.is_available() else "cpu" self.use_gpu = torch.cuda.is_available() self.seed = seed @@ -229,7 +232,7 @@ class SFM(Model): "SFM parameters setting:" "\nd_feat : {}" "\nhidden_size : {}" - "\nfrequency_dimension : {}" + "\nfrequency_dimension : {}" "\ndropout_W: {}" "\ndropout_U: {}" "\nn_epochs : {}" @@ -269,14 +272,14 @@ class SFM(Model): self._scorer = mean_squared_error if loss == "mse" else roc_auc_score self.sfm_model = SFM_Model( - d_feat=self.d_feat, - output_dim = self.output_dim, - hidden_size = self.hidden_size, - freq_dim = self.freq_dim, - dropout_W=self.dropout_W, - dropout_U = self.dropout_U, - device = self.device - ) + d_feat=self.d_feat, + output_dim=self.output_dim, + hidden_size=self.hidden_size, + freq_dim=self.freq_dim, + dropout_W=self.dropout_W, + dropout_U=self.dropout_U, + device=self.device, + ) if optimizer.lower() == "adam": self.train_optimizer = optim.Adam(self.sfm_model.parameters(), lr=self.lr) elif optimizer.lower() == "gd": @@ -301,14 +304,7 @@ class SFM(Model): self._fitted = False self.sfm_model.to(self.device) - def fit( - self, - dataset: DatasetH, - evals_result=dict(), - verbose=True, - save_path=None, - **kwargs - ): + def fit(self, dataset: DatasetH, evals_result=dict(), verbose=True, save_path=None, **kwargs): df_train, df_valid = dataset.prepare( ["train", "valid"], col_set=["feature", "label"], data_key=DataHandlerLP.DK_L @@ -398,12 +394,12 @@ class SFM(Model): # update learning rate self.scheduler.step(cur_loss_val) - if self.device != 'cpu': + if self.device != "cpu": torch.cuda.empty_cache() def get_loss(self, pred, target, loss_type): if loss_type == "mse": - sqr_loss = (pred - target)**2 + sqr_loss = (pred - target) ** 2 loss = sqr_loss.mean() return loss elif loss_type == "binary": @@ -424,7 +420,7 @@ class SFM(Model): self.sfm_model.eval() with torch.no_grad(): - if self.device != 'cpu': + if self.device != "cpu": preds = self.sfm_model(x_test).detach().cpu().numpy() else: preds = self.sfm_model(x_test).detach().numpy() @@ -447,8 +443,10 @@ class SFM(Model): self.sfm_model.load_state_dict(torch.load(_model_path)) self._fitted = True + class AverageMeter(object): """Computes and stores the average and current value""" + def __init__(self): self.reset() diff --git a/requirements.txt b/requirements.txt index 638ce22f4..d3511d780 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,4 +22,5 @@ scikit_learn==0.23.2 torch==1.6.0 tqdm==4.49.0 yahooquery==2.2.7 -mlflow==1.12.1 \ No newline at end of file +mlflow==1.12.1 +pytorch-tabnet==2.0.1 \ No newline at end of file