Wang's blog

基础 - 模型

Published on

简介

模型用于计算股票的预测分数。Qlib提供了基类Model,所有模型都必须继承它;另外还提供了基类ModelFT,包含了用于微调的方法。

例子

Qlib的模型库中包含了LightGBMMLPLSTM等模型,它们被视为基线模型。下面的例子展示了如何将LightGBM作为独立模块使用:

from qlib.contrib.model.gbdt import LGBModel
from qlib.contrib.data.handler import Alpha158
from qlib.utils import init_instance_by_config, flatten_dict
from qlib.workflow import R
from qlib.workflow.record_temp import SignalRecord, PortAnaRecord

market = "csi300"
benchmark = "SH000300"

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,
}

task = {
    "model": {
        "class": "LGBModel",
        "module_path": "qlib.contrib.model.gbdt",
        "kwargs": {
            "loss": "mse",
            "colsample_bytree": 0.8879,
            "learning_rate": 0.0421,
            "subsample": 0.8789,
            "lambda_l1": 205.6999,
            "lambda_l2": 580.9768,
            "max_depth": 8,
            "num_leaves": 210,
            "num_threads": 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"),
            },
        },
    },
}

# 初始化模型
model = init_instance_by_config(task["model"])
dataset = init_instance_by_config(task["dataset"])

# 开始实验
with R.start(experiment_name="workflow"):
    # 训练
    R.log_params(**flatten_dict(task))
    model.fit(dataset)

    # 预测
    recorder = R.get_recorder()
    sr = SignalRecord(model, dataset, recorder)
    sr.generate()

严格来说,模型的预测分数的意义依赖于用户对标签的设计。在默认情况下,通常是模型对股票的排序,分数越高的股票收益越高。

自定义模型

通过以下步骤,用户可以编写自定义模型:

  • 编写一个自定义模型类,它应该继承Model
  • 编写一个配置文件描述该模型的路径与参数
  • 测试自定义模型

编写模型类

自定义模型类需要继承Model并重写它的方法:

  • 重写__init__方法
    • Qlib将初始化参数传给__init__方法
    • 配置文件中的参数必须与__init__方法中定义的参数一致

例子如下,对应的配置文件中需要包含loss等参数:

def __init__(self, loss='mse', **kwargs):
    if loss not in {'mse', 'binary'}:
        raise NotImplementedError
    self._scorer = mean_squared_error if loss == 'mse' else roc_auc_score
    self._params.update(objective=loss, **kwargs)
    self._model = None
  • 重写fit方法
    • Qlib调用fit方法训练模型
    • 参数必须包含训练数据集dataset
    • 可以包含一些带有默认值的可选参数

例子如下,其中num_boost_round = 1000是可选参数:

def fit(self, dataset: DatasetH, num_boost_round = 1000, **kwargs):

    # 为lgb训练与评估准备数据
    df_train, df_valid = dataset.prepare(
        ["train", "valid"], col_set=["feature", "label"], data_key=DataHandlerLP.DK_L
    )
    x_train, y_train = df_train["feature"], df_train["label"]
    x_valid, y_valid = df_valid["feature"], df_valid["label"]

    # Lightgbm需要1D数组作为标签
    if y_train.values.ndim == 2 and y_train.values.shape[1] == 1:
        y_train, y_valid = np.squeeze(y_train.values), np.squeeze(y_valid.values)
    else:
        raise ValueError("LightGBM doesn't support multi-label training")

    dtrain = lgb.Dataset(x_train.values, label=y_train)
    dvalid = lgb.Dataset(x_valid.values, label=y_valid)

    # 训练模型
    self.model = lgb.train(
        self.params,
        dtrain,
        num_boost_round=num_boost_round,
        valid_sets=[dtrain, dvalid],
        valid_names=["train", "valid"],
        early_stopping_rounds=early_stopping_rounds,
        verbose_eval=verbose_eval,
        evals_result=evals_result,
        **kwargs
    )
  • 重写predict方法
    • 参数必须包含测试数据集dataset
    • 返回预测分数

例子如下:

def predict(self, dataset: DatasetH, **kwargs)-> pandas.Series:
    if self.model is None:
        raise ValueError("model is not fitted yet!")
    x_test = dataset.prepare("test", col_set="feature", data_key=DataHandlerLP.DK_I)
    return pd.Series(self.model.predict(x_test.values), index=x_test.index)
  • 重写finetune方法(可选)
    • 需要使用此方法时,应当继承ModelFT
    • 参数必须包含数据集dataset

例子如下:

def finetune(self, dataset: DatasetH, num_boost_round=10, verbose_eval=20):
    # 基于现有模型,通过训练更多轮次来进行微调
    dtrain, _ = self._prepare_data(dataset)
    self.model = lgb.train(
        self.params,
        dtrain,
        num_boost_round=num_boost_round,
        init_model=self.model,
        valid_sets=[dtrain],
        valid_names=["train"],
        verbose_eval=verbose_eval,
    )

编写配置文件

为了在自动工作流中使用自定义模型,用户需要修改配置文件中的模型部分。对于上面的自定义模型,配置文件如下:

model:
    class: LGBModel
    module_path: qlib.contrib.model.gbdt
    args:
        loss: mse
        colsample_bytree: 0.8879
        learning_rate: 0.0421
        subsample: 0.8789
        lambda_l1: 205.6999
        lambda_l2: 580.9768
        max_depth: 8
        num_leaves: 210
        num_threads: 20

用户可以在examples/benchmarks目录下找到所有基线模型的配置文件。

测试

可以使用如下命令测试一个模型:

cd examples
qrun benchmarks/LightGBM/workflow_config_lightgbm.yaml

也可以将模型作为一个独立模块进行测试,一个例子在examples/workflow_by_code.ipynb中。