1
0
mirror of https://github.com/microsoft/qlib.git synced 2026-07-22 03:37:34 +08:00

Compare commits

..

7 Commits

Author SHA1 Message Date
Young
4c057f645e Successfully run training 2024-07-10 06:25:30 +00:00
Young
a9fc3435ab Almost success to run GRU 2024-07-10 05:59:49 +00:00
Young
e2879d9b1e We choose another mode as the initial version 2024-07-10 05:42:27 +00:00
Young
a67a6134b4 We must align with previous results 2024-07-10 05:34:09 +00:00
Young
f4674ef98c Add model template; 2024-07-10 05:31:14 +00:00
Young
0f9312593d Remove some deprecated code 2024-07-09 09:11:06 +00:00
Young
4405cb784f Init model for both dataset 2024-07-08 06:11:51 +00:00
14 changed files with 406 additions and 229 deletions

View File

@@ -16,7 +16,7 @@ jobs:
# Since macos-latest changed from 12.7.4 to 14.4.1, # Since macos-latest changed from 12.7.4 to 14.4.1,
# the minimum python version that matches a 14.4.1 version of macos is 3.10, # the minimum python version that matches a 14.4.1 version of macos is 3.10,
# so we limit the macos version to macos-12. # so we limit the macos version to macos-12.
os: [windows-latest, ubuntu-20.04, ubuntu-22.04, macos-12] os: [windows-latest, ubuntu-20.04, ubuntu-22.04, macos-11, macos-12]
# not supporting 3.6 due to annotations is not supported https://stackoverflow.com/a/52890129 # not supporting 3.6 due to annotations is not supported https://stackoverflow.com/a/52890129
python-version: [3.7, 3.8] python-version: [3.7, 3.8]

View File

@@ -17,7 +17,7 @@ jobs:
# Since macos-latest changed from 12.7.4 to 14.4.1, # Since macos-latest changed from 12.7.4 to 14.4.1,
# the minimum python version that matches a 14.4.1 version of macos is 3.10, # the minimum python version that matches a 14.4.1 version of macos is 3.10,
# so we limit the macos version to macos-12. # so we limit the macos version to macos-12.
os: [windows-latest, ubuntu-20.04, ubuntu-22.04, macos-12] os: [windows-latest, ubuntu-20.04, ubuntu-22.04, macos-11, macos-12]
# not supporting 3.6 due to annotations is not supported https://stackoverflow.com/a/52890129 # not supporting 3.6 due to annotations is not supported https://stackoverflow.com/a/52890129
python-version: [3.7, 3.8] python-version: [3.7, 3.8]

View File

@@ -17,7 +17,7 @@ jobs:
# Since macos-latest changed from 12.7.4 to 14.4.1, # Since macos-latest changed from 12.7.4 to 14.4.1,
# the minimum python version that matches a 14.4.1 version of macos is 3.10, # the minimum python version that matches a 14.4.1 version of macos is 3.10,
# so we limit the macos version to macos-12. # so we limit the macos version to macos-12.
os: [windows-latest, ubuntu-20.04, ubuntu-22.04, macos-12] os: [windows-latest, ubuntu-20.04, ubuntu-22.04, macos-11, macos-12]
# not supporting 3.6 due to annotations is not supported https://stackoverflow.com/a/52890129 # not supporting 3.6 due to annotations is not supported https://stackoverflow.com/a/52890129
python-version: [3.7, 3.8] python-version: [3.7, 3.8]

View File

@@ -11,7 +11,6 @@
Recent released features Recent released features
| Feature | Status | | Feature | Status |
| -- | ------ | | -- | ------ |
| 🔥LLM-driven Auto Quant Factory🔥 | 🚀 Released in [RD-Agent](https://github.com/microsoft/RD-Agent) on Aug 8, 2024 |
| KRNN and Sandwich models | :chart_with_upwards_trend: [Released](https://github.com/microsoft/qlib/pull/1414/) on May 26, 2023 | | KRNN and Sandwich models | :chart_with_upwards_trend: [Released](https://github.com/microsoft/qlib/pull/1414/) on May 26, 2023 |
| Release Qlib v0.9.0 | :octocat: [Released](https://github.com/microsoft/qlib/releases/tag/v0.9.0) on Dec 9, 2022 | | Release Qlib v0.9.0 | :octocat: [Released](https://github.com/microsoft/qlib/releases/tag/v0.9.0) on Dec 9, 2022 |
| RL Learning Framework | :hammer: :chart_with_upwards_trend: Released on Nov 10, 2022. [#1332](https://github.com/microsoft/qlib/pull/1332), [#1322](https://github.com/microsoft/qlib/pull/1322), [#1316](https://github.com/microsoft/qlib/pull/1316),[#1299](https://github.com/microsoft/qlib/pull/1299),[#1263](https://github.com/microsoft/qlib/pull/1263), [#1244](https://github.com/microsoft/qlib/pull/1244), [#1169](https://github.com/microsoft/qlib/pull/1169), [#1125](https://github.com/microsoft/qlib/pull/1125), [#1076](https://github.com/microsoft/qlib/pull/1076)| | RL Learning Framework | :hammer: :chart_with_upwards_trend: Released on Nov 10, 2022. [#1332](https://github.com/microsoft/qlib/pull/1332), [#1322](https://github.com/microsoft/qlib/pull/1322), [#1316](https://github.com/microsoft/qlib/pull/1316),[#1299](https://github.com/microsoft/qlib/pull/1299),[#1263](https://github.com/microsoft/qlib/pull/1263), [#1244](https://github.com/microsoft/qlib/pull/1244), [#1169](https://github.com/microsoft/qlib/pull/1169), [#1125](https://github.com/microsoft/qlib/pull/1125), [#1076](https://github.com/microsoft/qlib/pull/1076)|
@@ -309,19 +308,19 @@ Qlib provides a tool named `qrun` to run the whole workflow automatically (inclu
2. Graphical Reports Analysis: Run `examples/workflow_by_code.ipynb` with `jupyter notebook` to get graphical reports 2. Graphical Reports Analysis: Run `examples/workflow_by_code.ipynb` with `jupyter notebook` to get graphical reports
- Forecasting signal (model prediction) analysis - Forecasting signal (model prediction) analysis
- Cumulative Return of groups - Cumulative Return of groups
![Cumulative Return](https://github.com/microsoft/qlib/blob/main/docs/_static/img/analysis/analysis_model_cumulative_return.png) ![Cumulative Return](http://fintech.msra.cn/images_v070/analysis/analysis_model_cumulative_return.png?v=0.1)
- Return distribution - Return distribution
![long_short](https://github.com/microsoft/qlib/blob/main/docs/_static/img/analysis/analysis_model_long_short.png) ![long_short](http://fintech.msra.cn/images_v070/analysis/analysis_model_long_short.png?v=0.1)
- Information Coefficient (IC) - Information Coefficient (IC)
![Information Coefficient](https://github.com/microsoft/qlib/blob/main/docs/_static/img/analysis/analysis_model_IC.png) ![Information Coefficient](http://fintech.msra.cn/images_v070/analysis/analysis_model_IC.png?v=0.1)
![Monthly IC](https://github.com/microsoft/qlib/blob/main/docs/_static/img/analysis/analysis_model_monthly_IC.png) ![Monthly IC](http://fintech.msra.cn/images_v070/analysis/analysis_model_monthly_IC.png?v=0.1)
![IC](https://github.com/microsoft/qlib/blob/main/docs/_static/img/analysis/analysis_model_NDQ.png) ![IC](http://fintech.msra.cn/images_v070/analysis/analysis_model_NDQ.png?v=0.1)
- Auto Correlation of forecasting signal (model prediction) - Auto Correlation of forecasting signal (model prediction)
![Auto Correlation](https://github.com/microsoft/qlib/blob/main/docs/_static/img/analysis/analysis_model_auto_correlation.png) ![Auto Correlation](http://fintech.msra.cn/images_v070/analysis/analysis_model_auto_correlation.png?v=0.1)
- Portfolio analysis - Portfolio analysis
- Backtest return - Backtest return
![Report](https://github.com/microsoft/qlib/blob/main/docs/_static/img/analysis/report.png) ![Report](http://fintech.msra.cn/images_v070/analysis/report.png?v=0.1)
<!-- <!--
- Score IC - Score IC
![Score IC](docs/_static/img/score_ic.png) ![Score IC](docs/_static/img/score_ic.png)
@@ -500,7 +499,7 @@ Qlib data are stored in a compact format, which is efficient to be combined into
Join IM discussion groups: Join IM discussion groups:
|[Gitter](https://gitter.im/Microsoft/qlib)| |[Gitter](https://gitter.im/Microsoft/qlib)|
|----| |----|
|![image](https://github.com/microsoft/qlib/blob/main/docs/_static/img/qrcode/gitter_qr.png)| |![image](http://fintech.msra.cn/images_v070/qrcode/gitter_qr.png)|
# Contributing # Contributing
We appreciate all contributions and thank all the contributors! We appreciate all contributions and thank all the contributors!

View File

@@ -7,13 +7,9 @@ What is GeneralPtNN
- Now you can just replace the Pytorch model structure to run a NN model. - Now you can just replace the Pytorch model structure to run a NN model.
We provide an example to demonstrate the effectiveness of the current design. We provide an example to demonstrate the effectiveness of the current design.
- `workflow_config_gru.yaml` align with previous results [GRU(Kyunghyun Cho, et al.)](../README.md#Alpha158-dataset) - `workflow_config_gru.yaml` align with previous results [GRU(Kyunghyun Cho, et al.)](../README.md#Alpha158 dataset)
- `workflow_config_gru2mlp.yaml` to demonstrate we can convert config from time-series to tabular data with minimal changes - `workflow_config_mlp.yaml` align with previous results [MLP](../README.md#Alpha158 dataset)
- You only have to change the net & dataset class to make the conversion.
- `workflow_config_mlp.yaml` achieved similar functionality with [MLP](../README.md#Alpha158-dataset)
# TODO # TODO
- We will align existing models to current design. We will align existing models to current design.
- The result of `workflow_config_mlp.yaml` is different with the result of [MLP](../README.md#Alpha158-dataset) since GeneralPtNN has a different stopping method compared to previous implementations. Specificly, GeneralPtNN controls training according to epoches, whereas previous methods controlled by max_steps.

View File

@@ -55,6 +55,10 @@ task:
class: GeneralPTNN class: GeneralPTNN
module_path: qlib.contrib.model.pytorch_general_nn module_path: qlib.contrib.model.pytorch_general_nn
kwargs: kwargs:
d_feat: 20
hidden_size: 64
num_layers: 2
dropout: 0.0
n_epochs: 200 n_epochs: 200
lr: 2e-4 lr: 2e-4
early_stop: 10 early_stop: 10
@@ -63,13 +67,6 @@ task:
loss: mse loss: mse
n_jobs: 20 n_jobs: 20
GPU: 0 GPU: 0
pt_model_uri: "qlib.contrib.model.pytorch_gru_ts.GRUModel"
pt_model_kwargs: {
"d_feat": 20,
"hidden_size": 64,
"num_layers": 2,
"dropout": 0.,
}
dataset: dataset:
class: TSDatasetH class: TSDatasetH
module_path: qlib.data.dataset module_path: qlib.data.dataset

View File

@@ -1,93 +0,0 @@
qlib_init:
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
infer_processors:
- class: FilterCol
kwargs:
fields_group: feature
col_list: ["RESI5", "WVMA5", "RSQR5", "KLEN", "RSQR10", "CORR5", "CORD5", "CORR10",
"ROC60", "RESI10", "VSTD5", "RSQR60", "CORR60", "WVMA60", "STD5",
"RSQR20", "CORD60", "CORD10", "CORR20", "KLOW"
]
- class: RobustZScoreNorm
kwargs:
fields_group: feature
clip_outlier: true
- class: Fillna
kwargs:
fields_group: feature
learn_processors:
- class: DropnaLabel
- class: CSRankNorm
kwargs:
fields_group: label
label: ["Ref($close, -2) / Ref($close, -1) - 1"]
port_analysis_config: &port_analysis_config
strategy:
class: TopkDropoutStrategy
module_path: qlib.contrib.strategy
kwargs:
signal: <PRED>
topk: 50
n_drop: 5
backtest:
start_time: 2017-01-01
end_time: 2020-08-01
account: 100000000
benchmark: *benchmark
exchange_kwargs:
limit_threshold: 0.095
deal_price: close
open_cost: 0.0005
close_cost: 0.0015
min_cost: 5
task:
model:
class: GeneralPTNN
module_path: qlib.contrib.model.pytorch_general_nn
kwargs:
lr: 1e-3
n_epochs: 1
batch_size: 800
loss: mse
optimizer: adam
pt_model_uri: "qlib.contrib.model.pytorch_nn.Net"
pt_model_kwargs:
input_dim: 20
layers: [20,]
dataset:
class: DatasetH
module_path: qlib.data.dataset
kwargs:
handler:
class: Alpha158
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:
model: <MODEL>
dataset: <DATASET>
- 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

View File

@@ -60,13 +60,13 @@ task:
class: GeneralPTNN class: GeneralPTNN
module_path: qlib.contrib.model.pytorch_general_nn module_path: qlib.contrib.model.pytorch_general_nn
kwargs: kwargs:
# FIXME: wrong parameters.
lr: 2e-3
batch_size: 8192
loss: mse loss: mse
weight_decay: 0.0002 lr: 0.002
optimizer: adam optimizer: adam
pt_model_uri: "qlib.contrib.model.pytorch_nn.Net" max_steps: 8000
batch_size: 8192
GPU: 0
weight_decay: 0.0002
pt_model_kwargs: pt_model_kwargs:
input_dim: 157 input_dim: 157
dataset: dataset:

View File

@@ -3,16 +3,19 @@
from __future__ import division from __future__ import division
from __future__ import print_function from __future__ import print_function
from torch.utils.data import DataLoader from torch.utils.data import DataLoader, RandomSampler, StackDataset
import os
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from typing import Union from typing import Callable, Optional, Text, Union
import copy from sklearn.metrics import roc_auc_score, mean_squared_error
import torch import torch
import torch.nn as nn
import torch.optim as optim import torch.optim as optim
from torch.utils.data import StackDataset
from qlib.data.dataset.weight import Reweighter from qlib.data.dataset.weight import Reweighter
@@ -21,14 +24,336 @@ from ...model.base import Model
from ...data.dataset import DatasetH, TSDatasetH from ...data.dataset import DatasetH, TSDatasetH
from ...data.dataset.handler import DataHandlerLP from ...data.dataset.handler import DataHandlerLP
from ...utils import ( from ...utils import (
auto_filter_kwargs,
init_instance_by_config, init_instance_by_config,
unpack_archive_with_buffer,
save_multiple_parts_file,
get_or_create_path, get_or_create_path,
) )
from ...log import get_module_logger from ...log import get_module_logger
from ...workflow import R
from qlib.contrib.meta.data_selection.utils import ICLoss
from torch.nn import DataParallel
class GeneralPTNN(Model):
"""General Pytorch Neural Network Model
Parameters
----------
input_dim : int
input dimension
output_dim : int
output dimension
layers : tuple
layer sizes
lr : float
learning rate
optimizer : str
optimizer name
GPU : int
the GPU ID used for training
"""
def __init__(
self,
lr=0.001,
max_steps=300,
batch_size=2000,
early_stop_rounds=50,
eval_steps=20,
optimizer="gd",
loss="mse",
GPU=0,
seed=None,
weight_decay=0.0,
data_parall=False,
scheduler: Optional[Union[Callable]] = "default", # when it is Callable, it accept one argument named optimizer
init_model=None,
eval_train_metric=False,
pt_model_uri="qlib.contrib.model.pytorch_nn.Net",
pt_model_kwargs={
"input_dim": 360,
"layers": (256,),
},
valid_key=DataHandlerLP.DK_L,
# TODO: Infer Key is a more reasonable key. But it requires more detailed processing on label processing
):
# Set logger.
self.logger = get_module_logger("DNNModelPytorch")
self.logger.info("DNN pytorch version...")
# set hyper-parameters.
self.lr = lr
self.max_steps = max_steps
self.batch_size = batch_size
self.early_stop_rounds = early_stop_rounds
self.eval_steps = eval_steps
self.optimizer = optimizer.lower()
self.loss_type = loss
if isinstance(GPU, str):
self.device = torch.device(GPU)
else:
self.device = torch.device("cuda:%d" % (GPU) if torch.cuda.is_available() and GPU >= 0 else "cpu")
self.seed = seed
self.weight_decay = weight_decay
self.data_parall = data_parall
self.eval_train_metric = eval_train_metric
self.valid_key = valid_key
self.best_step = None
self.logger.info(
"DNN parameters setting:"
f"\nlr : {lr}"
f"\nmax_steps : {max_steps}"
f"\nbatch_size : {batch_size}"
f"\nearly_stop_rounds : {early_stop_rounds}"
f"\neval_steps : {eval_steps}"
f"\noptimizer : {optimizer}"
f"\nloss_type : {loss}"
f"\nseed : {seed}"
f"\ndevice : {self.device}"
f"\nuse_GPU : {self.use_gpu}"
f"\nweight_decay : {weight_decay}"
f"\nenable data parall : {self.data_parall}"
f"\npt_model_uri: {pt_model_uri}"
f"\npt_model_kwargs: {pt_model_kwargs}"
)
if self.seed is not None:
np.random.seed(self.seed)
torch.manual_seed(self.seed)
if loss not in {"mse", "binary"}:
raise NotImplementedError("loss {} is not supported!".format(loss))
self._scorer = mean_squared_error if loss == "mse" else roc_auc_score
if init_model is None:
self.dnn_model = init_instance_by_config({"class": pt_model_uri, "kwargs": pt_model_kwargs})
if self.data_parall:
self.dnn_model = DataParallel(self.dnn_model).to(self.device)
else:
self.dnn_model = init_model
self.logger.info("model:\n{:}".format(self.dnn_model))
self.logger.info("model size: {:.4f} MB".format(count_parameters(self.dnn_model)))
if optimizer.lower() == "adam":
self.train_optimizer = optim.Adam(self.dnn_model.parameters(), lr=self.lr, weight_decay=self.weight_decay)
elif optimizer.lower() == "gd":
self.train_optimizer = optim.SGD(self.dnn_model.parameters(), lr=self.lr, weight_decay=self.weight_decay)
else:
raise NotImplementedError("optimizer {} is not supported!".format(optimizer))
if scheduler == "default":
# Reduce learning rate when loss has stopped decrease
self.scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
self.train_optimizer,
mode="min",
factor=0.5,
patience=10,
verbose=True,
threshold=0.0001,
threshold_mode="rel",
cooldown=0,
min_lr=0.00001,
eps=1e-08,
)
elif scheduler is None:
self.scheduler = None
else:
self.scheduler = scheduler(optimizer=self.train_optimizer)
self.dnn_model.to(self.device)
@property
def use_gpu(self):
return self.device != torch.device("cpu")
def _eval_valid_dl(self, valid_loader, val_index):
with torch.no_grad():
self.dnn_model.eval()
val_loss = []
val_pred = []
val_label = []
for x_batch, y_batch in valid_loader:
x_batch = x_batch.to(self.device)
y_batch = y_batch.to(self.device)
cur_loss = self.get_loss(preds, y_batch, self.loss_type)
val_loss.append(cur_loss.detach().cpu().numpy().item())
val_loss = np.mean(val_loss)
val_pred = torch.cat(val_pred, axis=0).detach().cpu().numpy()
val_label = torch.cat(val_label, axis=0).detach().cpu().numpy()
val_metric = self.get_metric(val_pred, val_label, val_index).detach().cpu().numpy().item()
return val_loss, val_metric
def fit(
self,
dataset: Union[DatasetH, TSDatasetH],
verbose=True,
save_path=None,
):
ists = isinstance(dataset, TSDatasetH) # is this time series dataset
# prepare training
train_x = dataset.prepare("train", col_set="feature", data_key=DataHandlerLP.DK_L)
train_y = dataset.prepare("train", col_set="label", data_key=DataHandlerLP.DK_L)
train_ds = StackDataset(train_x, train_y)
train_sampler = RandomSampler(train_ds)
train_loader = DataLoader(train_ds, batch_size=self.batch_size, sampler=train_sampler)
# prepare validation
valid_x = dataset.prepare("train", col_set="feature", data_key=DataHandlerLP.DK_L)
valid_y = dataset.prepare("train", col_set="label", data_key=DataHandlerLP.DK_L)
valid_ds = StackDataset(valid_x, valid_y)
valid_loader = DataLoader(valid_ds, batch_size=self.batch_size, shuffle=False)
if ists:
val_index = valid_x.data_index
else:
val_index = valid_x.index
save_path = get_or_create_path(save_path)
stop_steps = 0
train_loss = 0
best_loss = np.inf
# train
self.logger.info("training...")
for step in range(1, self.max_steps + 1):
if stop_steps >= self.early_stop_rounds:
if verbose:
self.logger.info("\tearly stop")
break
loss = AverageMeter()
self.dnn_model.train()
self.train_optimizer.zero_grad()
for x_batch, y_batch in train_loader:
x_batch = x_batch.to(self.device)
y_batch = y_batch.to(self.device)
# forward
preds = self.dnn_model(x_batch)
cur_loss = self.get_loss(preds, y_batch, self.loss_type)
cur_loss.backward()
self.train_optimizer.step()
loss.update(cur_loss.item())
R.log_metrics(train_loss=loss.avg, step=step)
# validation
train_loss += loss.val
# for every `eval_steps` steps or at the last steps, we will evaluate the model.
if step % self.eval_steps == 0 or step == self.max_steps:
stop_steps += 1
train_loss /= self.eval_steps
val_loss, val_metric = self._eval_valid_dl(valid_loader, val_index)
R.log_metrics(val_loss=val_loss, step=step)
R.log_metrics(val_metric=val_metric, step=step)
if val_loss < best_loss:
if verbose:
self.logger.info(
"\tvalid loss update from {:.6f} to {:.6f}, save checkpoint.".format(
best_loss, val_loss
)
)
best_loss = val_loss
self.best_step = step
R.log_metrics(best_step=self.best_step, step=step)
stop_steps = 0
torch.save(self.dnn_model.state_dict(), save_path)
train_loss = 0
# update learning rate
if self.scheduler is not None:
auto_filter_kwargs(self.scheduler.step, warning=False)(metrics=val_loss, epoch=step)
R.log_metrics(lr=self.get_lr(), step=step)
# restore the optimal parameters after training
self.dnn_model.load_state_dict(torch.load(save_path, map_location=self.device))
if self.use_gpu:
torch.cuda.empty_cache()
def get_lr(self):
assert len(self.train_optimizer.param_groups) == 1
return self.train_optimizer.param_groups[0]["lr"]
def get_loss(self, pred, target, loss_type, w=None):
pred, target = pred.reshape(-1), target.reshape(-1)
if w is None:
# make it ones and the same size with pred
w = torch.ones_like(pred).to(pred.device)
if loss_type == "mse":
sqr_loss = torch.mul(pred - target, pred - target)
loss = torch.mul(sqr_loss, w).mean()
return loss
elif loss_type == "binary":
loss = nn.BCEWithLogitsLoss(weight=w)
return loss(pred, target)
else:
raise NotImplementedError("loss {} is not supported!".format(loss_type))
def get_metric(self, pred, target, index):
# NOTE: the order of the index must follow <datetime, instrument> sorted order
return -ICLoss()(pred, target, index) # pylint: disable=E1130
def _nn_predict(self, data, return_cpu=True):
"""Reusing predicting NN.
Scenarios
1) test inference (data may come from CPU and expect the output data is on CPU)
2) evaluation on training (data may come from GPU)
"""
if not isinstance(data, torch.Tensor):
if isinstance(data, pd.DataFrame):
data = data.values
data = torch.Tensor(data)
data = data.to(self.device)
preds = []
self.dnn_model.eval()
with torch.no_grad():
batch_size = 8096
for i in range(0, len(data), batch_size):
x = data[i : i + batch_size]
preds.append(self.dnn_model(x.to(self.device)).detach().reshape(-1))
if return_cpu:
preds = np.concatenate([pr.cpu().numpy() for pr in preds])
else:
preds = torch.cat(preds, axis=0)
return preds
def predict(self, dataset: DatasetH, segment: Union[Text, slice] = "test"):
x_test_pd = dataset.prepare(segment, col_set="feature", data_key=DataHandlerLP.DK_I)
preds = self._nn_predict(x_test_pd)
return pd.Series(preds.reshape(-1), index=x_test_pd.index)
class AverageMeter:
"""Computes and stores the average and current value"""
def __init__(self):
self.reset()
def reset(self):
self.val = 0
self.avg = 0
self.sum = 0
self.count = 0
def update(self, val, n=1):
self.val = val
self.sum += val * n
self.count += n
self.avg = self.sum / self.count
from ...model.utils import ConcatDataset from ...model.utils import ConcatDataset
class GeneralPTNN(Model): class GeneralPTNN(Model):
""" """
Motivation: Motivation:
@@ -56,7 +381,6 @@ class GeneralPTNN(Model):
batch_size=2000, batch_size=2000,
early_stop=20, early_stop=20,
loss="mse", loss="mse",
weight_decay=0.0,
optimizer="adam", optimizer="adam",
n_jobs=10, n_jobs=10,
GPU=0, GPU=0,
@@ -66,7 +390,7 @@ class GeneralPTNN(Model):
"d_feat":6, "d_feat":6,
"hidden_size":64, "hidden_size":64,
"num_layers":2, "num_layers":2,
"dropout": 0.0, "dropout":0.,
}, },
): ):
# Set logger. # Set logger.
@@ -81,7 +405,6 @@ class GeneralPTNN(Model):
self.early_stop = early_stop self.early_stop = early_stop
self.optimizer = optimizer.lower() self.optimizer = optimizer.lower()
self.loss = loss self.loss = loss
self.weight_decay = weight_decay
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
self.seed = seed self.seed = seed
@@ -101,7 +424,6 @@ class GeneralPTNN(Model):
"\ndevice : {}" "\ndevice : {}"
"\nn_jobs : {}" "\nn_jobs : {}"
"\nuse_GPU : {}" "\nuse_GPU : {}"
"\nweight_decay : {}"
"\nseed : {}" "\nseed : {}"
"\npt_model_uri: {}" "\npt_model_uri: {}"
"\npt_model_kwargs: {}".format( "\npt_model_kwargs: {}".format(
@@ -115,11 +437,11 @@ class GeneralPTNN(Model):
self.device, self.device,
n_jobs, n_jobs,
self.use_gpu, self.use_gpu,
weight_decay,
seed, seed,
pt_model_uri, pt_model_uri,
pt_model_kwargs, pt_model_kwargs,
) )
) )
if self.seed is not None: if self.seed is not None:
@@ -130,9 +452,9 @@ class GeneralPTNN(Model):
self.logger.info("model size: {:.4f} MB".format(count_parameters(self.dnn_model))) self.logger.info("model size: {:.4f} MB".format(count_parameters(self.dnn_model)))
if optimizer.lower() == "adam": if optimizer.lower() == "adam":
self.train_optimizer = optim.Adam(self.dnn_model.parameters(), lr=self.lr, weight_decay=weight_decay) self.train_optimizer = optim.Adam(self.dnn_model.parameters(), lr=self.lr)
elif optimizer.lower() == "gd": elif optimizer.lower() == "gd":
self.train_optimizer = optim.SGD(self.dnn_model.parameters(), lr=self.lr, weight_decay=weight_decay) self.train_optimizer = optim.SGD(self.dnn_model.parameters(), lr=self.lr)
else: else:
raise NotImplementedError("optimizer {} is not supported!".format(optimizer)) raise NotImplementedError("optimizer {} is not supported!".format(optimizer))
@@ -166,6 +488,7 @@ class GeneralPTNN(Model):
raise ValueError("unknown metric `%s`" % self.metric) raise ValueError("unknown metric `%s`" % self.metric)
def _get_fl(self, data: torch.Tensor): def _get_fl(self, data: torch.Tensor):
""" """
get feature and label from data get feature and label from data
@@ -215,7 +538,9 @@ class GeneralPTNN(Model):
losses = [] losses = []
for data, weight in data_loader: for data, weight in data_loader:
feature, label = self._get_fl(data) feature = data[:, :, 0:-1].to(self.device)
# feature[torch.isnan(feature)] = 0
label = data[:, -1, -1].to(self.device)
with torch.no_grad(): with torch.no_grad():
pred = self.dnn_model(feature.float()) pred = self.dnn_model(feature.float())
@@ -299,8 +624,6 @@ class GeneralPTNN(Model):
evals_result["train"].append(train_score) evals_result["train"].append(train_score)
evals_result["valid"].append(val_score) evals_result["valid"].append(val_score)
if step == 0:
best_param = copy.deepcopy(self.dnn_model.state_dict())
if val_score > best_score: if val_score > best_score:
best_score = val_score best_score = val_score
stop_steps = 0 stop_steps = 0
@@ -324,30 +647,17 @@ class GeneralPTNN(Model):
raise ValueError("model is not fitted yet!") raise ValueError("model is not fitted yet!")
dl_test = dataset.prepare("test", col_set=["feature", "label"], data_key=DataHandlerLP.DK_I) dl_test = dataset.prepare("test", col_set=["feature", "label"], data_key=DataHandlerLP.DK_I)
dl_test.config(fillna_type="ffill+bfill")
if isinstance(dataset, TSDatasetH):
dl_test.config(fillna_type="ffill+bfill") # process nan brought by dataloader
index = dl_test.get_index()
else:
# If it is a tabular, we convert the dataframe to numpy to be indexable by DataLoader
index = dl_test.index
dl_test = dl_test.values
test_loader = DataLoader(dl_test, batch_size=self.batch_size, num_workers=self.n_jobs) test_loader = DataLoader(dl_test, batch_size=self.batch_size, num_workers=self.n_jobs)
self.dnn_model.eval() self.dnn_model.eval()
preds = [] preds = []
for data in test_loader: for data in test_loader:
feature, _ = self._get_fl(data) feature = data[:, :, 0:-1].to(self.device)
feature = feature.to(self.device)
with torch.no_grad(): with torch.no_grad():
pred = self.dnn_model(feature.float()).detach().cpu().numpy() pred = self.dnn_model(feature.float()).detach().cpu().numpy()
preds.append(pred) preds.append(pred)
preds_concat = np.concatenate(preds) return pd.Series(np.concatenate(preds), index=dl_test.get_index())
if preds_concat.ndim != 1:
preds_concat = preds_concat.ravel()
return pd.Series(preds_concat, index=index)

View File

@@ -317,6 +317,7 @@ class GRU(Model):
class GRUModel(nn.Module): class GRUModel(nn.Module):
def __init__(self, d_feat=6, hidden_size=64, num_layers=2, dropout=0.0): def __init__(self, d_feat=6, hidden_size=64, num_layers=2, dropout=0.0):
super().__init__() super().__init__()

View File

@@ -256,7 +256,7 @@ class HIST(Model):
raise ValueError("Empty data from dataset, please check your dataset config.") raise ValueError("Empty data from dataset, please check your dataset config.")
if not os.path.exists(self.stock2concept): if not os.path.exists(self.stock2concept):
url = "https://github.com/SunsetWolf/qlib_dataset/releases/download/v0/qlib_csi300_stock2concept.npy" url = "http://fintech.msra.cn/stock_data/downloads/qlib_csi300_stock2concept.npy"
urllib.request.urlretrieve(url, self.stock2concept) urllib.request.urlretrieve(url, self.stock2concept)
stock_index = np.load(self.stock_index, allow_pickle=True).item() stock_index = np.load(self.stock_index, allow_pickle=True).item()

View File

@@ -41,7 +41,6 @@ class DataLoader(abc.ABC):
---------- ----------
instruments : str or dict instruments : str or dict
it can either be the market name or the config file of instruments generated by InstrumentProvider. it can either be the market name or the config file of instruments generated by InstrumentProvider.
If the value of instruments is None, it means that no filtering is done.
start_time : str start_time : str
start of the time range. start of the time range.
end_time : str end_time : str
@@ -51,11 +50,6 @@ class DataLoader(abc.ABC):
------- -------
pd.DataFrame: pd.DataFrame:
data load from the under layer source data load from the under layer source
Raise
-----
KeyError:
if the instruments filter is not supported, raise KeyError
""" """
@@ -326,13 +320,7 @@ class NestedDataLoader(DataLoader):
def load(self, instruments=None, start_time=None, end_time=None) -> pd.DataFrame: def load(self, instruments=None, start_time=None, end_time=None) -> pd.DataFrame:
df_full = None df_full = None
for dl in self.data_loader_l: for dl in self.data_loader_l:
try:
df_current = dl.load(instruments, start_time, end_time) df_current = dl.load(instruments, start_time, end_time)
except KeyError:
warnings.warn(
"If the value of `instruments` cannot be processed, it will set instruments to None to get all the data."
)
df_current = dl.load(instruments=None, start_time=start_time, end_time=end_time)
if df_full is None: if df_full is None:
df_full = df_current df_full = df_current
else: else:

View File

@@ -7,10 +7,8 @@ import qlib
from pathlib import Path from pathlib import Path
sys.path.append(str(Path(__file__).resolve().parent)) sys.path.append(str(Path(__file__).resolve().parent))
from qlib.data.dataset.loader import NestedDataLoader, QlibDataLoader from qlib.data.dataset.loader import NestedDataLoader
from qlib.data.dataset.handler import DataHandlerLP
from qlib.contrib.data.loader import Alpha158DL, Alpha360DL from qlib.contrib.data.loader import Alpha158DL, Alpha360DL
from qlib.data import D
class TestDataLoader(unittest.TestCase): class TestDataLoader(unittest.TestCase):
@@ -46,35 +44,6 @@ class TestDataLoader(unittest.TestCase):
assert "LABEL0" in columns_list assert "LABEL0" in columns_list
# Then you can use it wth DataHandler; # Then you can use it wth DataHandler;
# NOTE: please note that the data processors are missing!!! You should add based on your requirements
"""
dataset.to_pickle("test_df.pkl")
nested_data_loader = NestedDataLoader(
dataloader_l=[
{
"class": "qlib.contrib.data.loader.Alpha158DL",
"kwargs": {"config": {"label": (["Ref($close, -2)/Ref($close, -1) - 1"], ["LABEL0"])}},
},
{
"class": "qlib.contrib.data.loader.Alpha360DL",
},
{
"class": "qlib.data.dataset.loader.StaticDataLoader",
"kwargs": {"config": "test_df.pkl"},
},
]
)
data_handler_config = {
"start_time": "2008-01-01",
"end_time": "2020-08-01",
"instruments": "csi300",
"data_loader": nested_data_loader,
}
data_handler = DataHandlerLP(**data_handler_config)
data = data_handler.fetch()
print(data)
"""
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -1,17 +1,15 @@
import unittest import unittest
from qlib.contrib.model.pytorch_general_nn import GeneralPTNN
from qlib.data.dataset import DatasetH, TSDatasetH
from qlib.data.dataset.handler import DataHandlerLP
from qlib.tests import TestAutoData from qlib.tests import TestAutoData
class TestNN(TestAutoData): class TestNN(TestAutoData):
def test_both_dataset(self):
try:
from qlib.contrib.model.pytorch_general_nn import GeneralPTNN
from qlib.data.dataset import DatasetH, TSDatasetH
from qlib.data.dataset.handler import DataHandlerLP
except ImportError:
print("Import error.")
return
def test_both_dataset(self):
data_handler_config = { data_handler_config = {
"start_time": "2008-01-01", "start_time": "2008-01-01",
"end_time": "2020-08-01", "end_time": "2020-08-01",
@@ -20,24 +18,35 @@ class TestNN(TestAutoData):
"class": "QlibDataLoader", # Assuming QlibDataLoader is a string reference to the class "class": "QlibDataLoader", # Assuming QlibDataLoader is a string reference to the class
"kwargs": { "kwargs": {
"config": { "config": {
"feature": [["$high", "$close", "$low"], ["H", "C", "L"]], "feature": [
"label": [["Ref($close, -2)/Ref($close, -1) - 1"], ["LABEL0"]], ["$high", "$close", "$low"],
}, ["H", "C", "L"]
"freq": "day", ],
"label": [
["Ref($close, -2)/Ref($close, -1) - 1"],
["LABEL0"]
]
}, },
"freq": "day"
}
}, },
# TODO: processors # TODO: processors
"learn_processors": [ "learn_processors": [
{ {
"class": "DropnaLabel", "class": "DropnaLabel",
}, },
{"class": "CSZScoreNorm", "kwargs": {"fields_group": "label"}}, {
], "class": "CSZScoreNorm",
"kwargs": {
"fields_group": "label"
}
}
]
} }
segments = { segments = {
"train": ["2008-01-01", "2014-12-31"], "train": ["2008-01-01", "2014-12-31"],
"valid": ["2015-01-01", "2016-12-31"], "valid": ["2015-01-01", "2016-12-31"],
"test": ["2017-01-01", "2020-08-01"], "test": ["2017-01-01", "2020-08-01"]
} }
data_handler = DataHandlerLP(**data_handler_config) data_handler = DataHandlerLP(**data_handler_config)
@@ -55,7 +64,7 @@ class TestNN(TestAutoData):
"d_feat":3, "d_feat":3,
"hidden_size":8, "hidden_size":8,
"num_layers":1, "num_layers":1,
"dropout": 0.0, "dropout":0.,
}, },
), ),
GeneralPTNN( GeneralPTNN(
@@ -67,9 +76,10 @@ class TestNN(TestAutoData):
), ),
] ]
for ds, model in list(zip((tsds, tbds), model_l)): for ds, model in reversed(list(zip((tsds, tbds), model_l))):
model.fit(ds) # It works model.fit(ds) # It works
model.predict(ds) # It works model.predict(ds) # It works
break
if __name__ == "__main__": if __name__ == "__main__":