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

Update TCTS. (#495)

* Update TCTS Model.

Co-authored-by: lewwang <lwwang@microsoft.com>
This commit is contained in:
Lewen Wang
2021-07-04 16:45:05 +08:00
committed by GitHub
parent 2d4f0e80f9
commit ace7484304
10 changed files with 117 additions and 156 deletions

View File

@@ -17,6 +17,7 @@ Recent released features
| High-frequency trading example | [Part of code released](https://github.com/microsoft/qlib/pull/227) on Jan 28, 2021 | | High-frequency trading example | [Part of code released](https://github.com/microsoft/qlib/pull/227) on Jan 28, 2021 |
| High-frequency data(1min) | [Released](https://github.com/microsoft/qlib/pull/221) on Jan 27, 2021 | | High-frequency data(1min) | [Released](https://github.com/microsoft/qlib/pull/221) on Jan 27, 2021 |
| Tabnet Model | [Released](https://github.com/microsoft/qlib/pull/205) on Jan 22, 2021 | | Tabnet Model | [Released](https://github.com/microsoft/qlib/pull/205) on Jan 22, 2021 |
| TCTS Model | [Released](https://github.com/microsoft/qlib/pull/491) on July 1, 2021 |
Features released before 2021 are not listed here. Features released before 2021 are not listed here.
@@ -288,6 +289,7 @@ Here is a list of models built on `Qlib`.
- [TFT based on tensorflow (Bryan Lim, et al. 2019)](examples/benchmarks/TFT/tft.py) - [TFT based on tensorflow (Bryan Lim, et al. 2019)](examples/benchmarks/TFT/tft.py)
- [TabNet based on pytorch (Sercan O. Arik, et al. 2019)](qlib/contrib/model/pytorch_tabnet.py) - [TabNet based on pytorch (Sercan O. Arik, et al. 2019)](qlib/contrib/model/pytorch_tabnet.py)
- [DoubleEnsemble based on LightGBM (Chuheng Zhang, et al. 2020)](qlib/contrib/model/double_ensemble.py) - [DoubleEnsemble based on LightGBM (Chuheng Zhang, et al. 2020)](qlib/contrib/model/double_ensemble.py)
- [TCTS based on pytorch (Xueqing Wu, et al. 2021)](qlib/contrib/model/pytorch_tcts.py)
Your PR of new Quant models is highly welcomed. Your PR of new Quant models is highly welcomed.

View File

@@ -61,7 +61,6 @@ task:
metric: loss metric: loss
loss: mse loss: mse
base_model: LSTM base_model: LSTM
with_pretrain: True
model_path: "benchmarks/LSTM/csi300_lstm_ts.pkl" model_path: "benchmarks/LSTM/csi300_lstm_ts.pkl"
GPU: 0 GPU: 0
dataset: dataset:

View File

@@ -54,7 +54,6 @@ task:
metric: loss metric: loss
loss: mse loss: mse
base_model: LSTM base_model: LSTM
with_pretrain: True
model_path: "benchmarks/LSTM/model_lstm_csi300.pkl" model_path: "benchmarks/LSTM/model_lstm_csi300.pkl"
GPU: 0 GPU: 0
dataset: dataset:
@@ -81,4 +80,4 @@ task:
- class: PortAnaRecord - class: PortAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp
kwargs: kwargs:
config: *port_analysis_config config: *port_analysis_config

View File

@@ -22,6 +22,7 @@ The numbers shown below demonstrate the performance of the entire `workflow` of
| GATs (Petar Velickovic, et al.) | Alpha360 | 0.0475±0.00 | 0.3515±0.02| 0.0592±0.00 | 0.4585±0.01 | 0.0876±0.02 | 1.1513±0.27| -0.0795±0.02 | | GATs (Petar Velickovic, et al.) | Alpha360 | 0.0475±0.00 | 0.3515±0.02| 0.0592±0.00 | 0.4585±0.01 | 0.0876±0.02 | 1.1513±0.27| -0.0795±0.02 |
| DoubleEnsemble (Chuheng Zhang, et al.) | Alpha360 | 0.0407±0.00| 0.3053±0.00 | 0.0490±0.00 | 0.3840±0.00 | 0.0380±0.02 | 0.5000±0.21 | -0.0984±0.02 | | DoubleEnsemble (Chuheng Zhang, et al.) | Alpha360 | 0.0407±0.00| 0.3053±0.00 | 0.0490±0.00 | 0.3840±0.00 | 0.0380±0.02 | 0.5000±0.21 | -0.0984±0.02 |
| TabNet (Sercan O. Arik, et al.)| Alpha360 | 0.0192±0.00 | 0.1401±0.00| 0.0291±0.00 | 0.2163±0.00 | -0.0258±0.00 | -0.2961±0.00| -0.1429±0.00 | | TabNet (Sercan O. Arik, et al.)| Alpha360 | 0.0192±0.00 | 0.1401±0.00| 0.0291±0.00 | 0.2163±0.00 | -0.0258±0.00 | -0.2961±0.00| -0.1429±0.00 |
| TCTS (Xueqing Wu, et al.)| Alpha360 | 0.0485±0.00 | 0.3689±0.04| 0.0586±0.00 | 0.4669±0.02 | 0.0816±0.02 | 1.1572±0.30| -0.0689±0.02 |
## Alpha158 dataset ## Alpha158 dataset
| Model Name | Dataset | IC | ICIR | Rank IC | Rank ICIR | Annualized Return | Information Ratio | Max Drawdown | | Model Name | Dataset | IC | ICIR | Rank IC | Rank ICIR | Annualized Return | Information Ratio | Max Drawdown |

View File

@@ -1,52 +0,0 @@
# Temporally Correlated Task Scheduling for Sequence Learning
We provide the [code](https://github.com/microsoft/qlib/blob/main/qlib/contrib/model/pytorch_tcts.py) for reproducing the stock trend forecasting experiments.
### Background
Sequence learning has attracted much research attention from the machine learning community in recent years. In many applications, a sequence learning task is usually associated with multiple temporally correlated auxiliary tasks, which are different in terms of how much input information to use or which future step to predict. In stock trend forecasting, as demonstrated in Figure1, one can predict the price of a stock in different future days (e.g., tomorrow, the day after tomorrow). In this paper, we propose a framework to make use of those temporally correlated tasks to help each other.
<p align="center">
<img src="task_description.png" width="600" height="200"/>
</p>
### Method
Given that there are usually multiple temporally correlated tasks, the key challenge lies in which tasks to use and when to use them in the training process. In this work, we introduce a learnable task scheduler for sequence learning, which adaptively selects temporally correlated tasks during the training process. The scheduler accesses the model status and the current training data (e.g., in current minibatch), and selects the best auxiliary task to help the training of the main task. The scheduler and the model for the main task are jointly trained through bi-level optimization: the scheduler is trained to maximize the validation performance of the model, and the model is trained to minimize the training loss guided by the scheduler. The process is demonstrated in Figure2.
<p align="center">
<img src="workflow.png"/>
</p>
At step <img src="https://render.githubusercontent.com/render/math?math=s">, with training data <img src="https://render.githubusercontent.com/render/math?math=x_s,y_s">, the scheduler <img src="https://render.githubusercontent.com/render/math?math=\varphi"> chooses a suitable task <img src="https://render.githubusercontent.com/render/math?math=T_{i_s}"> (green solid lines) to update the model <img src="https://render.githubusercontent.com/render/math?math=f"> (blue solid lines). After <img src="https://render.githubusercontent.com/render/math?math=S"> steps, we evaluate the model <img src="https://render.githubusercontent.com/render/math?math=f"> on the validation set and update the scheduler <img src="https://render.githubusercontent.com/render/math?math=\varphi"> (green dashed lines).
### DataSet
* We use the historical transaction data for 300 stocks on [CSI300](http://www.csindex.com.cn/en/indices/index-detail/000300) from 01/01/2008 to 08/01/2020.
* We split the data into training (01/01/2008-12/31/2013), validation (01/01/2014-12/31/2015), and test sets (01/01/2016-08/01/2020) based on the transaction time.
### Experiments
#### Task Description
* The main tasks <img src="https://render.githubusercontent.com/render/math?math=T_k"> (<img src="https://render.githubusercontent.com/render/math?math=task_k"> in Figure1) refers to forecasting return of stock <img src="https://render.githubusercontent.com/render/math?math=i"> as following,
<div align=center>
<img src="https://render.githubusercontent.com/render/math?math=r_{i}^k = \frac{\price_i^{t+k}}{\price_i^{t+k-1}} - 1">
</div>
* Temporally correlated task sets <img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_k = \{T_1, T_2, ... , T_k\}">, in this paper, <img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_3">, <img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_5"> and <img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_10"> are used.
#### Baselines
* GRU/MLP/LightGBM (LGB)/Graph Attention Networks (GAT)
* Multi-task learning (MTL): In multi-task learning, multiple tasks are jointly trained and mutually boosted. Each task is treated equally, while in our setting, we focus on the main task.
* Curriculum transfer learning (CL): Transfer learning also leverages auxiliary tasks to boost the main task. [Curriculum transfer learning](https://arxiv.org/pdf/1804.00810.pdf) is one kind of transfer learning which schedules auxiliary tasks according to certain rules. Our problem can also be regarded as a special kind of transfer learning, where the auxiliary tasks are temporally correlated with the main task. Our learning process is dynamically controlled by a scheduler rather than some pre-defined rules. In the CL baseline, we start from the task <img src="https://render.githubusercontent.com/render/math?math=T_1" >, then <img src="https://render.githubusercontent.com/render/math?math=T_2" >, and gradually move to the last one.
#### Result
| Methods | <img src="https://render.githubusercontent.com/render/math?math=T_1" > | <img src="https://render.githubusercontent.com/render/math?math=T_2"> | <img src="https://render.githubusercontent.com/render/math?math=T_3"> |
| :----: | :----: | :----: | :----: |
| GRU | 0.049 / 1.903 | 0.018 / 1.972 | 0.014 / 1.989 |
| MLP | 0.023 / 1.961 | 0.022 / 1.962 | 0.015 / 1.978 |
| LGB | 0.038 / 1.883 | 0.023 / 1.952 | 0.007 / 1.987 |
| GAT | 0.052 / 1.898 | 0.024 / 1.954 | 0.015 / 1.973 |
| MTL(<img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_3">) | 0.061 / 1.862 | 0.023 / 1.942 | 0.012 / 1.956 |
| CL(<img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_3">) | 0.051 / 1.880 | 0.028 / 1.941 | 0.016 / 1.962 |
| Ours(<img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_3">) | 0.071 / 1.851 | 0.030 / 1.939 | 0.017 / 1.963 |
| MTL(<img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_5">) | 0.057 / 1.875 | 0.021 / 1.939 | 0.017 / 1.959 |
| CL(<img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_5">) | 0.056 / 1.877 | 0.028 / 1.942 | 0.015 / 1.962 |
| Ours(<img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_5">) | 0.075 / 1.849 | 0.032 /1.939 | 0.021 / 1.955 |
| MTL(<img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_{10}">) | 0.052 / 1.882 | 0.020 / 1.947 | 0.019 / 1.952 |
| CL(<img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_{10}">) | 0.051 / 1.882 | 0.028 / 1.950 | 0.016 / 1.961 |
| Ours(<img src="https://render.githubusercontent.com/render/math?math=\mathcal{T}_{10}">) | 0.067 / 1.867 | 0.030 / 1.960 | 0.022 / 1.942|

View File

@@ -22,11 +22,9 @@ data_handler_config: &data_handler_config
- class: CSRankNorm - class: CSRankNorm
kwargs: kwargs:
fields_group: label fields_group: label
label: ["Ref($close, -2) / Ref($close, -1) - 1", label: ["Ref($close, -1) / $close - 1",
"Ref($close, -3) / Ref($close, -1) - 1", "Ref($close, -2) / Ref($close, -1) - 1",
"Ref($close, -4) / Ref($close, -1) - 1", "Ref($close, -3) / Ref($close, -2) - 1"]
"Ref($close, -5) / Ref($close, -1) - 1",
"Ref($close, -6) / Ref($close, -1) - 1"]
port_analysis_config: &port_analysis_config port_analysis_config: &port_analysis_config
strategy: strategy:
class: TopkDropoutStrategy class: TopkDropoutStrategy
@@ -61,11 +59,12 @@ task:
GPU: 0 GPU: 0
fore_optimizer: adam fore_optimizer: adam
weight_optimizer: adam weight_optimizer: adam
output_dim: 5 output_dim: 3
fore_lr: 5e-7 fore_lr: 5e-4
weight_lr: 5e-7 weight_lr: 5e-4
steps: 3 steps: 3
target_label: 0 target_label: 1
lowest_valid_performance: 0.993
dataset: dataset:
class: DatasetH class: DatasetH
module_path: qlib.data.dataset module_path: qlib.data.dataset
@@ -87,7 +86,8 @@ task:
kwargs: kwargs:
ana_long_short: False ana_long_short: False
ann_scaler: 252 ann_scaler: 252
label_col: 1
- class: PortAnaRecord - class: PortAnaRecord
module_path: qlib.workflow.record_temp module_path: qlib.workflow.record_temp
kwargs: kwargs:
config: *port_analysis_config config: *port_analysis_config

View File

@@ -53,7 +53,6 @@ class GATs(Model):
early_stop=20, early_stop=20,
loss="mse", loss="mse",
base_model="GRU", base_model="GRU",
with_pretrain=True,
model_path=None, model_path=None,
optimizer="adam", optimizer="adam",
GPU=0, GPU=0,
@@ -76,7 +75,6 @@ class GATs(Model):
self.optimizer = optimizer.lower() self.optimizer = optimizer.lower()
self.loss = loss self.loss = loss
self.base_model = base_model self.base_model = base_model
self.with_pretrain = with_pretrain
self.model_path = model_path self.model_path = model_path
self.device = torch.device("cuda:%d" % (GPU) if torch.cuda.is_available() and GPU >= 0 else "cpu") self.device = torch.device("cuda:%d" % (GPU) if torch.cuda.is_available() and GPU >= 0 else "cpu")
self.seed = seed self.seed = seed
@@ -94,7 +92,6 @@ class GATs(Model):
"\noptimizer : {}" "\noptimizer : {}"
"\nloss_type : {}" "\nloss_type : {}"
"\nbase_model : {}" "\nbase_model : {}"
"\nwith_pretrain : {}"
"\nmodel_path : {}" "\nmodel_path : {}"
"\ndevice : {}" "\ndevice : {}"
"\nuse_GPU : {}" "\nuse_GPU : {}"
@@ -110,7 +107,6 @@ class GATs(Model):
optimizer.lower(), optimizer.lower(),
loss, loss,
base_model, base_model,
with_pretrain,
model_path, model_path,
self.device, self.device,
self.use_gpu, self.use_gpu,
@@ -253,24 +249,22 @@ class GATs(Model):
evals_result["valid"] = [] evals_result["valid"] = []
# load pretrained base_model # load pretrained base_model
if self.with_pretrain: if self.base_model == "LSTM":
if self.model_path == None: pretrained_model = LSTMModel()
raise ValueError("the path of the pretrained model should be given first!") elif self.base_model == "GRU":
self.logger.info("Loading pretrained model...") pretrained_model = GRUModel()
if self.base_model == "LSTM": else:
pretrained_model = LSTMModel() raise ValueError("unknown base model name `%s`" % self.base_model)
pretrained_model.load_state_dict(torch.load(self.model_path))
elif self.base_model == "GRU":
pretrained_model = GRUModel()
pretrained_model.load_state_dict(torch.load(self.model_path))
else:
raise ValueError("unknown base model name `%s`" % self.base_model)
model_dict = self.GAT_model.state_dict() if self.model_path is not None:
pretrained_dict = {k: v for k, v in pretrained_model.state_dict().items() if k in model_dict} self.logger.info("Loading pretrained model...")
model_dict.update(pretrained_dict) pretrained_model.load_state_dict(torch.load(self.model_path))
self.GAT_model.load_state_dict(model_dict)
self.logger.info("Loading pretrained model Done...") model_dict = self.GAT_model.state_dict()
pretrained_dict = {k: v for k, v in pretrained_model.state_dict().items() if k in model_dict}
model_dict.update(pretrained_dict)
self.GAT_model.load_state_dict(model_dict)
self.logger.info("Loading pretrained model Done...")
# train # train
self.logger.info("training...") self.logger.info("training...")

View File

@@ -29,8 +29,8 @@ class DailyBatchSampler(Sampler):
def __init__(self, data_source): def __init__(self, data_source):
self.data_source = data_source self.data_source = data_source
self.data = self.data_source.data.loc[self.data_source.get_index()] # calculate number of samples in each batch
self.daily_count = self.data.groupby(level=0).size().values # calculate number of samples in each batch self.daily_count = pd.Series(index=self.data_source.get_index()).groupby("datetime").size().values
self.daily_index = np.roll(np.cumsum(self.daily_count), 1) # calculate begin index of each batch self.daily_index = np.roll(np.cumsum(self.daily_count), 1) # calculate begin index of each batch
self.daily_index[0] = 0 self.daily_index[0] = 0
@@ -72,7 +72,6 @@ class GATs(Model):
early_stop=20, early_stop=20,
loss="mse", loss="mse",
base_model="GRU", base_model="GRU",
with_pretrain=True,
model_path=None, model_path=None,
optimizer="adam", optimizer="adam",
GPU="0", GPU="0",
@@ -96,7 +95,6 @@ class GATs(Model):
self.optimizer = optimizer.lower() self.optimizer = optimizer.lower()
self.loss = loss self.loss = loss
self.base_model = base_model self.base_model = base_model
self.with_pretrain = with_pretrain
self.model_path = model_path self.model_path = model_path
self.device = torch.device("cuda:%d" % (GPU) if torch.cuda.is_available() and GPU >= 0 else "cpu") self.device = torch.device("cuda:%d" % (GPU) if torch.cuda.is_available() and GPU >= 0 else "cpu")
self.n_jobs = n_jobs self.n_jobs = n_jobs
@@ -115,7 +113,6 @@ class GATs(Model):
"\noptimizer : {}" "\noptimizer : {}"
"\nloss_type : {}" "\nloss_type : {}"
"\nbase_model : {}" "\nbase_model : {}"
"\nwith_pretrain : {}"
"\nmodel_path : {}" "\nmodel_path : {}"
"\nvisible_GPU : {}" "\nvisible_GPU : {}"
"\nuse_GPU : {}" "\nuse_GPU : {}"
@@ -131,7 +128,6 @@ class GATs(Model):
optimizer.lower(), optimizer.lower(),
loss, loss,
base_model, base_model,
with_pretrain,
model_path, model_path,
GPU, GPU,
self.use_gpu, self.use_gpu,
@@ -270,28 +266,22 @@ class GATs(Model):
evals_result["valid"] = [] evals_result["valid"] = []
# load pretrained base_model # load pretrained base_model
if self.with_pretrain: if self.base_model == "LSTM":
if self.model_path == None: pretrained_model = LSTMModel(d_feat=self.d_feat, hidden_size=self.hidden_size, num_layers=self.num_layers)
raise ValueError("the path of the pretrained model should be given first!") elif self.base_model == "GRU":
self.logger.info("Loading pretrained model...") pretrained_model = GRUModel(d_feat=self.d_feat, hidden_size=self.hidden_size, num_layers=self.num_layers)
if self.base_model == "LSTM": else:
pretrained_model = LSTMModel( raise ValueError("unknown base model name `%s`" % self.base_model)
d_feat=self.d_feat, hidden_size=self.hidden_size, num_layers=self.num_layers
)
pretrained_model.load_state_dict(torch.load(self.model_path))
elif self.base_model == "GRU":
pretrained_model = GRUModel(
d_feat=self.d_feat, hidden_size=self.hidden_size, num_layers=self.num_layers
)
pretrained_model.load_state_dict(torch.load(self.model_path))
else:
raise ValueError("unknown base model name `%s`" % self.base_model)
model_dict = self.GAT_model.state_dict() if self.model_path is not None:
pretrained_dict = {k: v for k, v in pretrained_model.state_dict().items() if k in model_dict} self.logger.info("Loading pretrained model...")
model_dict.update(pretrained_dict) pretrained_model.load_state_dict(torch.load(self.model_path))
self.GAT_model.load_state_dict(model_dict)
self.logger.info("Loading pretrained model Done...") model_dict = self.GAT_model.state_dict()
pretrained_dict = {k: v for k, v in pretrained_model.state_dict().items() if k in model_dict}
model_dict.update(pretrained_dict)
self.GAT_model.load_state_dict(model_dict)
self.logger.info("Loading pretrained model Done...")
# train # train
self.logger.info("training...") self.logger.info("training...")

View File

@@ -9,12 +9,13 @@ import os
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import copy import copy
import random
from sklearn.metrics import roc_auc_score, mean_squared_error from sklearn.metrics import roc_auc_score, mean_squared_error
import logging import logging
from ...utils import ( from ...utils import (
unpack_archive_with_buffer, unpack_archive_with_buffer,
save_multiple_parts_file, save_multiple_parts_file,
create_save_path, get_or_create_path,
drop_nan_by_y_index, drop_nan_by_y_index,
) )
from ...log import get_module_logger, TimeInspector from ...log import get_module_logger, TimeInspector
@@ -60,8 +61,9 @@ class TCTS(Model):
weight_lr=5e-7, weight_lr=5e-7,
steps=3, steps=3,
GPU=0, GPU=0,
seed=None, seed=0,
target_label=0, target_label=0,
lowest_valid_performance=0.993,
**kwargs **kwargs
): ):
# Set logger. # Set logger.
@@ -85,6 +87,9 @@ class TCTS(Model):
self.weight_lr = weight_lr self.weight_lr = weight_lr
self.steps = steps self.steps = steps
self.target_label = target_label self.target_label = target_label
self.lowest_valid_performance = lowest_valid_performance
self._fore_optimizer = fore_optimizer
self._weight_optimizer = weight_optimizer
self.logger.info( self.logger.info(
"TCTS parameters setting:" "TCTS parameters setting:"
@@ -113,40 +118,6 @@ class TCTS(Model):
) )
) )
if self.seed is not None:
np.random.seed(self.seed)
torch.manual_seed(self.seed)
self.fore_model = GRUModel(
d_feat=self.d_feat,
hidden_size=self.hidden_size,
num_layers=self.num_layers,
dropout=self.dropout,
)
self.weight_model = MLPModel(
d_feat=360 + 2 * self.output_dim + 1,
hidden_size=self.hidden_size,
num_layers=self.num_layers,
dropout=self.dropout,
output_dim=self.output_dim,
)
if fore_optimizer.lower() == "adam":
self.fore_optimizer = optim.Adam(self.fore_model.parameters(), lr=self.fore_lr)
elif fore_optimizer.lower() == "gd":
self.fore_optimizer = optim.SGD(self.fore_model.parameters(), lr=self.fore_lr)
else:
raise NotImplementedError("optimizer {} is not supported!".format(fore_optimizer))
if weight_optimizer.lower() == "adam":
self.weight_optimizer = optim.Adam(self.weight_model.parameters(), lr=self.weight_lr)
elif weight_optimizer.lower() == "gd":
self.weight_optimizer = optim.SGD(self.weight_model.parameters(), lr=self.weight_lr)
else:
raise NotImplementedError("optimizer {} is not supported!".format(weight_optimizer))
self.fitted = False
self.fore_model.to(self.device)
self.weight_model.to(self.device)
def loss_fn(self, pred, label, weight): def loss_fn(self, pred, label, weight):
loc = torch.argmax(weight, 1) loc = torch.argmax(weight, 1)
@@ -258,11 +229,9 @@ class TCTS(Model):
def fit( def fit(
self, self,
dataset: DatasetH, dataset: DatasetH,
evals_result=dict(),
verbose=True, verbose=True,
save_path=None, save_path=None,
): ):
df_train, df_valid, df_test = dataset.prepare( df_train, df_valid, df_test = dataset.prepare(
["train", "valid", "test"], ["train", "valid", "test"],
col_set=["feature", "label"], col_set=["feature", "label"],
@@ -274,7 +243,62 @@ class TCTS(Model):
x_test, y_test = df_test["feature"], df_test["label"] x_test, y_test = df_test["feature"], df_test["label"]
if save_path == None: if save_path == None:
save_path = create_save_path(save_path) save_path = get_or_create_path(save_path)
best_loss = np.inf
while best_loss > self.lowest_valid_performance:
if best_loss < np.inf:
print("Failed! Start retraining.")
self.seed = random.randint(0, 1000) # reset random seed
if self.seed is not None:
np.random.seed(self.seed)
torch.manual_seed(self.seed)
best_loss = self.training(
x_train, y_train, x_valid, y_valid, x_test, y_test, verbose=verbose, save_path=save_path
)
def training(
self,
x_train,
y_train,
x_valid,
y_valid,
x_test,
y_test,
verbose=True,
save_path=None,
):
self.fore_model = GRUModel(
d_feat=self.d_feat,
hidden_size=self.hidden_size,
num_layers=self.num_layers,
dropout=self.dropout,
)
self.weight_model = MLPModel(
d_feat=360 + 2 * self.output_dim + 1,
hidden_size=self.hidden_size,
num_layers=self.num_layers,
dropout=self.dropout,
output_dim=self.output_dim,
)
if self._fore_optimizer.lower() == "adam":
self.fore_optimizer = optim.Adam(self.fore_model.parameters(), lr=self.fore_lr)
elif self._fore_optimizer.lower() == "gd":
self.fore_optimizer = optim.SGD(self.fore_model.parameters(), lr=self.fore_lr)
else:
raise NotImplementedError("optimizer {} is not supported!".format(self._fore_optimizer))
if self._weight_optimizer.lower() == "adam":
self.weight_optimizer = optim.Adam(self.weight_model.parameters(), lr=self.weight_lr)
elif self._weight_optimizer.lower() == "gd":
self.weight_optimizer = optim.SGD(self.weight_model.parameters(), lr=self.weight_lr)
else:
raise NotImplementedError("optimizer {} is not supported!".format(self._weight_optimizer))
self.fitted = False
self.fore_model.to(self.device)
self.weight_model.to(self.device)
best_loss = np.inf best_loss = np.inf
best_epoch = 0 best_epoch = 0
@@ -291,7 +315,8 @@ class TCTS(Model):
val_loss = self.test_epoch(x_valid, y_valid) val_loss = self.test_epoch(x_valid, y_valid)
test_loss = self.test_epoch(x_test, y_test) test_loss = self.test_epoch(x_test, y_test)
print("valid %.6f, test %.6f" % (val_loss, test_loss)) if verbose:
print("valid %.6f, test %.6f" % (val_loss, test_loss))
if val_loss < best_loss: if val_loss < best_loss:
best_loss = val_loss best_loss = val_loss
@@ -316,6 +341,8 @@ class TCTS(Model):
if self.use_gpu: if self.use_gpu:
torch.cuda.empty_cache() torch.cuda.empty_cache()
return best_loss
def predict(self, dataset): def predict(self, dataset):
if not self.fitted: if not self.fitted:
raise ValueError("model is not fitted yet!") raise ValueError("model is not fitted yet!")

View File

@@ -227,10 +227,11 @@ class SigAnaRecord(SignalRecord):
artifact_path = "sig_analysis" artifact_path = "sig_analysis"
def __init__(self, recorder, ana_long_short=False, ann_scaler=252, **kwargs): def __init__(self, recorder, ana_long_short=False, ann_scaler=252, label_col=0, **kwargs):
super().__init__(recorder=recorder, **kwargs) super().__init__(recorder=recorder, **kwargs)
self.ana_long_short = ana_long_short self.ana_long_short = ana_long_short
self.ann_scaler = ann_scaler self.ann_scaler = ann_scaler
self.label_col = label_col
def generate(self, **kwargs): def generate(self, **kwargs):
try: try:
@@ -243,7 +244,7 @@ class SigAnaRecord(SignalRecord):
if label is None or not isinstance(label, pd.DataFrame) or label.empty: if label is None or not isinstance(label, pd.DataFrame) or label.empty:
logger.warn(f"Empty label.") logger.warn(f"Empty label.")
return return
ic, ric = calc_ic(pred.iloc[:, 0], label.iloc[:, 0]) ic, ric = calc_ic(pred.iloc[:, 0], label.iloc[:, self.label_col])
metrics = { metrics = {
"IC": ic.mean(), "IC": ic.mean(),
"ICIR": ic.mean() / ic.std(), "ICIR": ic.mean() / ic.std(),
@@ -252,7 +253,7 @@ class SigAnaRecord(SignalRecord):
} }
objects = {"ic.pkl": ic, "ric.pkl": ric} objects = {"ic.pkl": ic, "ric.pkl": ric}
if self.ana_long_short: if self.ana_long_short:
long_short_r, long_avg_r = calc_long_short_return(pred.iloc[:, 0], label.iloc[:, 0]) long_short_r, long_avg_r = calc_long_short_return(pred.iloc[:, 0], label.iloc[:, self.label_col])
metrics.update( metrics.update(
{ {
"Long-Short Ann Return": long_short_r.mean() * self.ann_scaler, "Long-Short Ann Return": long_short_r.mean() * self.ann_scaler,