Qlib 自定义模型集成实战从继承 Model 基类到 qrun 一键运行【免费下载链接】qlibQlib is an AI-oriented Quant investment platform that aims to use AI tech to empower Quant Research, from exploring ideas to implementing productions. Qlib supports diverse ML modeling paradigms, including supervised learning, market dynamics modeling, and RL, and is now equipped with https://github.com/microsoft/RD-Agent to automate RD process.项目地址: https://gitcode.com/GitHub_Trending/qli/qlibQlib 内置了 LightGBM、MLP、LSTM 等开箱即用的预测模型Forecast Model但真实研究中往往需要把自己的算法接入 Qlib 的工作流。本文基于仓库文档 Custom Model Integration 展开完整讲解“定义自定义模型类 → 编写配置 → 测试运行”三步集成流程并结合 qlib/model/base.py、qlib/contrib/model/gbdt.py、qlib/model/trainer.py 等源码说明每个接口背后真实的调用链与参数传递机制读完后你可以独立把一个 PyTorch/自研模型接入 Qlib用qrun跑通训练、预测、回测与分析的完整闭环。一、集成总览三步接入自定义模型Qlib 的Model Zooexamples/benchmarks下的各个基准模型本质上都是对同一个模型抽象的实例化。要集成自己的模型只需完成三件事定义一个自定义模型类继承 qlib.model.base.Model覆写fit与predict方法finetune可选编写配置文件在 workflow YAML 中用model字段描述模型类所在模块、类名和初始化参数测试模型通过qrun命令以完整工作流方式运行或按单模块方式在代码中单独调用。这三步分别对应“模型层”“配置层”“执行层”。下文逐层展开并对照仓库源码说明 Qlib 是如何解析配置、实例化模型、组织训练流程的。二、模型基类体系Model 与 ModelFT自定义模型的接口契约定义在 qlib/model/base.py 中类层次结构如下Serializable # qlib/utils/serial.py控制对象如何被 pickle 落盘 └── BaseModel # 定义抽象方法 predict__call__ 语法糖 └── Model # 定义 fit(dataset, reweighter) 抽象 predict └── ModelFT # 额外定义抽象方法 finetune用于在线/增量微调几个关键接口约定均可在 qlib/model/base.py 中确认fit(dataset, reweighter)训练入口。dataset是 Qlib 的Dataset对象通常是DatasetH由它负责产出处理后的特征与标签reweighter是可选的样本重加权器用于样本加权训练。predict(dataset, segmenttest)预测入口。segment指定使用数据集的哪个分段默认test返回值通常是pandas.Series形式的预测得分。finetune(dataset)仅当你的模型支持基于已有权重继续训练时才需要实现实现时改为继承ModelFT而非Model。Model的fitdocstring 里有一条极易踩坑的注释训练得到的模型属性名不应以_开头否则模型无法被 dump 到磁盘。这是由基类BaseModel继承的Serializable序列化规则决定的。从 qlib/utils/serial.py 的_is_kept逻辑可以看出属性名以_开头时默认dump_allFalse会被丢弃不参与 pickle。所以模型权重、训练好的内部对象必须挂在无下划线前缀的属性上例如self.model否则qrun流程中R.save_objects(**{params.pkl: model})保存的模型将丢失权重。三、自定义模型类的四个方法下面以仓库内置的LGBModelqlib/contrib/model/gbdt.py为参照逐一讲解各方法的实现要点。LGBModel继承自ModelFT同时实现了finetune是“完整形态”的示例。3.1 覆写__init__接收配置中的超参数Qlib 会把配置文件中model字段下的参数原样传给__init__。文档给出的约定是配置文件里的超参数必须与__init__定义的参数一致。最简写法如文档示例def __init__(self, lossmse, **kwargs): if loss not in {mse, binary}: raise NotImplementedError self._scorer mean_squared_error if loss mse else roc_auc_score self._params.update(objectiveloss, **kwargs) self._model None对照仓库中LGBModel的实际实现class LGBModel(ModelFT, LightGBMFInt): LightGBM Model def __init__(self, lossmse, early_stopping_rounds50, num_boost_round1000, **kwargs): if loss not in {mse, binary}: raise NotImplementedError self.params {objective: loss, verbosity: -1} self.params.update(kwargs) self.early_stopping_rounds early_stopping_rounds self.num_boost_round num_boost_round self.model None可以看到colsample_bytree、learning_rate、subsample等未在签名中显式列出的参数都通过**kwargs兜底接收并合并进self.params。这种“显式参数 kwargs 兜底”的写法是自定义模型接收任意超参的推荐模式。3.2 覆写fit训练入口fit方法被 Qlib 在训练阶段调用约束是参数必须包含训练特征dataset这是接口设计的一部分可以包含若干带默认值的可选参数例如 GBDT 场景的num_boost_round 1000。LGBModel.fit的核心流程摘自 qlib/contrib/model/gbdt.pydef fit(self, dataset: DatasetH, num_boost_roundNone, early_stopping_roundsNone, verbose_eval20, evals_resultNone, reweighterNone, **kwargs): # 1) 从 dataset 取出 train / valid 两个分段的 feature 与 label ds_l self._prepare_data(dataset, reweighter) # 内部调用 dataset.prepare(...) ds, names list(zip(*ds_l)) ... # 2) 调用底层框架训练 self.model lgb.train(self.params, ds[0], num_boost_round..., valid_setsds, valid_namesnames, ...) # 3) 把每个 epoch 的评估指标回写到 Recorder for k in names: for key, val in evals_result[k].items(): name f{key}.{k} for epoch, m in enumerate(val): R.log_metrics(**{name.replace(, _): m}, stepepoch)其中_prepare_data展示了从DatasetH取数的标准姿势df dataset.prepare(key, col_set[feature, label], data_keyDataHandlerLP.DK_L) x, y df[feature], df[label]文档给出的通用示例与之等价df_train, df_valid dataset.prepare( [train, valid], col_set[feature, label], data_keyDataHandlerLP.DK_L ) x_train, y_train df_train[feature], df_train[label] x_valid, y_valid df_valid[feature], df_valid[label]两个细节值得注意data_keyDataHandlerLP.DK_LDK_L表示取“学习用”Learnable即经过 Processor 全流程处理的数据。这与predict中使用的DK_IInference只取推理所需的处理后数据形成对照也是 Qlib 数据层区分训练/推理路径的机制。标签维度LightGBM 只支持一维标签所以源码中显式np.squeeze了 shape 为(n, 1)的 label如果你的模型是多标签/多任务模型则不需要这一步。另外fit中调用R.log_metrics说明 Qlib 的 Recorder实验记录器会自动把训练曲线写入实验记录无需你额外搭建日志系统。3.3 覆写predict返回预测得分predict的参数必须包含dataset用于获取测试集返回值是预测得分。文档示例def predict(self, dataset: DatasetH, **kwargs) - pd.Series: if self.model is None: raise ValueError(model is not fitted yet!) x_test dataset.prepare(test, col_setfeature, data_keyDataHandlerLP.DK_I) return pd.Series(self.model.predict(x_test.values), indexx_test.index)仓库中LGBModel.predictqlib/contrib/model/gbdt.py与其一致只是多了segment参数以支持按分段预测def predict(self, dataset: DatasetH, segment: Union[Text, slice] test): if self.model is None: raise ValueError(model is not fitted yet!) x_test dataset.prepare(segment, col_setfeature, data_keyDataHandlerLP.DK_I) return pd.Series(self.model.predict(x_test.values), indexx_test.index)返回值必须是pandas.Series且index与数据对齐(instrument, datetime)多级索引因为下游的策略、回测与记录组件都依赖这一对齐关系。3.4 覆写finetune可选继承 ModelFT 才有资格如果你的模型支持“基于已有权重继续训练”例如 GBDT 再 boost 若干轮、神经网络用新数据增量学习应继承ModelFT并实现finetune(dataset, ...)。文档示例def finetune(self, dataset: DatasetH, num_boost_round10, verbose_eval20): # 基于已有模型再训练若干轮 dtrain, _ self._prepare_data(dataset) self.model lgb.train(self.params, dtrain, num_boost_roundnum_boost_round, init_modelself.model, valid_sets[dtrain], valid_names[train], verbose_evalverbose_eval)ModelFT.finetune的 docstringqlib/model/base.py还给出了配合 Recorder 的典型用法先在一个实验里fit并R.save_objects(init_modelmodel)再在新实验里recorder.load_object(init_model)加载模型后调用model.finetune(dataset, num_boost_round10)。这是滚动训练、在线更新场景的标准模式参见 examples/online_srv/update_online_pred.py。四、配置文件用 model 字段声明式描述模型文档说明要把自定义模型接入 Qlib只需修改 workflow 配置中的model字段它描述“使用哪个模型、如何初始化”。以文档中的 LightGBM 示例为基础结合仓库中真实使用的 examples/benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yamlmodel字段的完整写法为task: model: class: LGBModel # 类名 module_path: qlib.contrib.model.gbdt # 类所在模块路径 kwargs: # 传入 __init__ 的参数 loss: mse colsample_bytree: 0.8879 learning_rate: 0.2 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] 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几个需要与仓库实现对照的要点字段名是kwargs文档早期版本示例里写作args但当前仓库中真正被解析的是kwargs字段——qlib/utils/mod.py 的get_callable_kwargs中明确是kwargs config.get(kwargs, {})。仓库里examples/benchmarks下所有基准模型的配置也统一使用kwargs自定义模型请照此书写。参数去向kwargs中的参数全部进入__init__loss: mse这类显式命名的参数走形参其余colsample_bytree等经**kwargs兜底。这与 qlib/utils/mod.py 中init_instance_by_config的行为一致它按module_path导入模块、getattr取出类、用kwargs实例化。占位符record段里的MODEL、DATASET是特殊占位符运行期会被真实的模型/数据集对象替换。配置里所有基准模型的参照可以按文档指引到examples/benchmarks目录查找各模型对应一个文件夹配置与依赖requirements.txt齐备非常适合照葫芦画瓢地修改为自己的模型。五、测试运行qrun 的底层调用链文档给出的测试命令是cd examples # 避免在包含 qlib 源码的目录下运行 qrun benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yaml注意文档示例路径为workflow_config_lightgbm.yaml的旧命名当前仓库中的实际文件是workflow_config_lightgbm_Alpha158.yamlcd examples是为了避免当前目录下的qlib包目录与已安装的包发生导入冲突。qrun是 Qlib 内置命令入口在 pyproject.toml 中注册为qrun qlib.cli.run:run。其执行链路为qlib/cli/run.py 的workflow()读取 YAML并支持 Jinja2 模板与BASE_CONFIG_PATH基配置合并随后调用qlib.init(**config[qlib_init])初始化数据环境与实验管理器task_train(config.get(task), ...)进入 qlib/model/trainer.py 的task_train其中真正执行任务的是_exe_taskqlib/model/trainer.pydef _exe_task(task_config: dict): rec R.get_recorder() # model dataset initialization model: Model init_instance_by_config(task_config[model], accept_typesModel) dataset: Dataset init_instance_by_config(task_config[dataset], accept_typesDataset) reweighter: Reweighter task_config.get(reweighter, None) # model training auto_filter_kwargs(model.fit)(dataset, reweighterreweighter) R.save_objects(**{params.pkl: model}) # 保存 dataset 供在线推理使用 dataset.config(dump_allFalse, recursiveTrue) R.save_objects(**{dataset: dataset}) # 填充 MODEL / DATASET 占位符 task_config fill_placeholder(task_config, {MODEL: model, DATASET: dataset}) # 依次生成 prediction / 分析 / 回测记录 for record in records: r init_instance_by_config(record, ..., try_kwargs{model: model, dataset: dataset}) r.generate()这条链路揭示了自定义模型在运行时经历的完整生命周期实例化init_instance_by_config按第四节的classmodule_pathkwargs规则把字符串配置变成你的模型实例训练auto_filter_kwargs(model.fit)(dataset, ...)是一个防御性包装实现见 qlib/utils/init.py——如果fit的签名不接受某个可选关键字参数它会被自动过滤并给出警告而不是抛TypeError。这就是为什么文档强调fit的可选参数“必须带默认值”落盘训练完成后的模型以params.pkl保存进 Recorder这里正好呼应第二节提到的序列化规则——以_开头的属性会被丢弃记录与回测MODEL、DATASET被替换为真实对象后配置中列出的SignalRecord、SigAnaRecord、PortAnaRecord依次执行产出预测信号、信号分析与组合回测报告。也就是说只要你的模型类实现了约定接口qrun的其余部分实验记录、指标曲线、回测、报告都会自动生效这正是声明式配置的回报。六、单模块方式测试不跑完整 workflow文档同时指出模型也可以作为单模块独立测试。仓库中的对应示例是 examples/workflow_by_code.py它演示了不用 YAML、纯代码搭建同一套流程的方式核心片段qlib.init(provider_uriprovider_uri, regionREG_CN) # 与 qrun 相同的“配置即代码”实例化方式 model init_instance_by_config(CSI300_GBDT_TASK[model]) dataset init_instance_by_config(CSI300_GBDT_TASK[dataset]) with R.start(experiment_nameworkflow): R.log_params(**flatten_dict(CSI300_GBDT_TASK)) model.fit(dataset) # 直接调用你实现的 fit R.save_objects(**{params.pkl: model}) sr SignalRecord(model, dataset, R.get_recorder()) sr.generate() # 预测 SigAnaRecord(R.get_recorder()).generate() PortAnaRecord(R.get_recorder(), port_analysis_config, day).generate()调试自定义模型时这种“代码即积木”的方式比反复qrun更快定位问题你可以单独dataset.prepare(train)检查特征矩阵单独model.fit/model.predict验证接口行为。仓库的自动化测试也覆盖这一路径例如 tests/test_contrib_workflow.py 与 tests/test_all_pipeline.py 中即以init_instance_by_config方式加载模型并跑通训练与记录流程可作为自定义模型通过 CI 的参照标准。七、常见集成问题排查清单结合上述源码证据集成自定义模型时最常遇到的几类问题现象原因依据加载params.pkl后模型没有权重模型权重挂在了以_开头的属性上序列化时被丢弃qlib/utils/serial.py 的_is_kept规则启动即报ModuleNotFoundError/AttributeErrormodule_path拼写错误或类名不匹配确认模块可导入、类可getattrqlib/utils/mod.py 的get_callable_kwargsfit收到不认识的参数却未生效fit签名既无该形参也无**kwargs被auto_filter_kwargs过滤并打 warningqlib/utils/init.pypredict报model is not fitted yet!未经fit直接预测或权重属性被下划线前缀“藏”住导致重载丢失qlib/contrib/model/gbdt.py训练取到的数据不符合预期data_key用错训练应用DK_L推理应用DK_I上述fit/predict源码对照八、延伸阅读Forecast Model 组件文档预测模型在 Qlib 中的定位、训练与预测接口的完整说明Model API 参考qlib.model.base等模块的 API 文档是fit参数类型的权威参考Workflow 文档model所在配置文件的完整结构与record机制说明qlib/model/base.py、qlib/model/trainer.py模型抽象与任务训练引擎的源码实现。按本文步骤操作后你的自定义模型将和 Qlib 内置基准模型享有完全同等待遇同一套 YAML 配置语法、同一个qrun执行入口、同一套实验记录与回测分析链路。【免费下载链接】qlibQlib is an AI-oriented Quant investment platform that aims to use AI tech to empower Quant Research, from exploring ideas to implementing productions. Qlib supports diverse ML modeling paradigms, including supervised learning, market dynamics modeling, and RL, and is now equipped with https://github.com/microsoft/RD-Agent to automate RD process.项目地址: https://gitcode.com/GitHub_Trending/qli/qlib创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考