Qlib Meta Controller 深度解析:用 Meta-Task、Meta-Dataset 与 Meta-Model 引导预测模型工作流
本文围绕 Qlib 的 Meta Controller 组件(文档见 meta.rst)展开:先讲清 Meta-Task、Meta-Dataset、Meta-Model 三类抽象的职责边界与 API 契约,再结合仓库中自带的 DDG-DA 完整实现,剖析"元信息生产 → 元模型训练 → 推理引导 → 改写基座任务"的端到端调用链,并给出运行基准样例所需的配置与命令,帮助你理解如何基于 Meta Controller 自定义自己的元学习模型来适应市场动态变化。
1. Meta Controller 的定位:给预测模型"导学"
Meta Controller 的核心目标是:学习一系列预测任务(forecasting tasks)之间的规律性模式,并利用学到的模式去引导后续的预测任务。文档原文将其描述为"provides guidance to Forecast Model"。
在 Qlib 的常规工作流中,一个任务(task)由数据集(dataset)与模型(model)配置组成,由 Trainer 串行训练。而 Meta Controller 在任务之上加了一层"元层"(meta-level):元模型不直接预测股票收益,而是观察"多个基座任务各自学得怎么样",从中提炼出可迁移的知识,再反过来影响基座预测模型的工作流。
从源码结构看,这一组件的抽象骨架位于 qlib/model/meta/ 包,包含三个文件:
| 抽象 | 源码位置 | 职责 |
|---|---|---|
MetaTask |
qlib/model/meta/task.py | 元学习框架的基本元素,保存可喂给元模型的数据 |
MetaTaskDataset |
qlib/model/meta/dataset.py | 控制元信息(meta-information)的生成过程,负责为元模型提供训练数据 |
MetaModel 及子类 |
qlib/model/meta/model.py | 控制工作流:用 fit 训练、用 inference 给出引导信息 |
三者构成一条清晰的数据流:MetaTaskDataset.prepare_tasks() 产出若干 MetaTask → 元模型 fit() 消费这些元任务完成训练 → 元模型 inference() 输出"引导信息"(如改写后的任务定义、样本权重等)→ 引导信息被应用回基座预测模型。
2. MetaTask:元学习的基本数据单元
文档指出:一个 Meta Task 实例保存了可供 Meta Model 使用的数据;多个 Meta Task 实例可以共享同一个 Data Handler,这一共享关系由 Meta Dataset 控制。
从 qlib/model/meta/task.py 的源码看,MetaTask 的构造签名是:
MetaTask(task: dict, meta_info: object, mode: str = PROC_MODE_FULL)
task:被元模型增强的 Qlib 任务定义(dict 配置);meta_info:喂给元模型的原始输入信息,初始化时会保存原始值,后续再做处理;mode:数据处理模式,源码定义了三种:
| 模式常量 | 取值 | 含义(源码注释) |
|---|---|---|
PROC_MODE_FULL |
"full" |
训练用:训练任务需要 X、y、X_test、y_test 齐全 |
PROC_MODE_TEST |
"test" |
测试用:测试任务不需要完整的训练/测试对 |
PROC_MODE_TRANSFER |
"transfer" |
元模型可迁移到其他数据集时:只需要 meta_info |
源码中还提供了两个关键方法:get_dataset() 通过 init_instance_by_config(self.task["dataset"]) 从任务配置中初始化出 Dataset 实例(这就是"多个 MetaTask 共享 Data Handler"的实现入口——数据集配置相同即可复用);get_meta_input() 返回处理后的 meta_info,供元模型直接消费(文档中提到的 prepare_task_data() 在现行源码中对应的就是这条"取出可直接喂入元模型的数据"的语义)。
3. MetaDataset:元信息的生成器
Meta Dataset 负责元信息的生成流程,"为训练 Meta Model 提供数据"。用户应通过 prepare_tasks 获取一组 MetaTask 实例。
看 qlib/model/meta/dataset.py 中 MetaTaskDataset 的契约:
- 构造参数
segments(Dict[Text, Tuple]或float)指示数据如何切分;初始化时元数据集就已维护好 meta-tasks 列表; prepare_tasks(segments):传入单个 segment 名或 segment 列表,返回对应的一批 meta-task(多 segment 时返回嵌套列表[[tasks in seg1], ...]);源码中内置了如下调用示例:
# get the train segment and the test segment, both of them are lists
train_meta_tasks, test_meta_tasks = meta_dataset.prepare_tasks(["train", "test"])
- 抽象方法
_prepare_seg(segment)留给具体实现:负责为单个 segment 准备好训练数据。
值得注意的是源码类注释中明确写出的迁移性设计意图:"A meta-model trained on meta-dataset A and then applied to meta-dataset B"——即在元数据集 A 上训练的元模型,其输入特征应当能在元数据集 B 上复用,因此实现 _prepare_seg 时必须保证 meta 输入的结构在数据集之间是可对齐的。
仓库自带的完整实现是 qlib/contrib/meta/data_selection/dataset.py 中的 MetaDatasetDS,它把"任务模板如何展开成滚动任务序列、每个任务的元输入如何计算"全部落地:
task_tpl:可以是单个任务模板 dict(此时用RollingGen按step/trunc_days滚动生成任务序列),也可以是已排好时间顺序的任务 list(直接使用);step:滚动步长;trunc_days:基于测试起点截断的天数,源码注释特别强调 "trunc_days is very important",用于屏蔽与未来信息重叠的数据;exp_name:元信息来源,可以是实验名(str,自动用InternalData.setup()训练一批代理模型并统计其表现)或已准备好的InternalData实例;segments:float 表示用于训练的 meta-task 比例;str 则保证该日期落在测试集中;hist_step_n:元信息的历史步数(数据相似性信息的回溯长度);task_mode/fill_method:分别控制元任务的数据处理模式(对应MetaTask的三种模式)与元信息缺失值的填充策略("max"、"maxseg"、"zero")。
其中元输入的核心数据结构是 MetaTaskDS.processed_meta_input,包含:
time_perf:形状<hist_step_n * step, data pieces>的时间表现矩阵,刻画"各数据段上历史代理模型的表现";time_belong:<sample, data pieces>的 0/1 归属矩阵,标记每个训练样本属于哪个时间数据段(见 MetaTaskDS 的 docstring);FULL模式下还会附带X / y / X_test / y_test / test_idx,即该基座任务的训练/测试特征标签数据。
4. MetaModel:三种引导基座工作流的方式
文档将 Meta Model 的使用归纳为两步:用 fit 训练元模型;元模型实例通过 inference 给出有用信息来引导工作流。抽象基类 qlib/model/meta/model.py 只有两个抽象方法:
@abc.abstractmethod
def fit(self, *args, **kwargs): ...
@abc.abstractmethod
def inference(self, *args, **kwargs) -> object: ...
基类注释进一步按"干预基座学习流程的阶段"把引导分成两类,对应两个子类:
4.1 MetaTaskModel:直接改写任务定义
"This type of meta-model may interact with task definitions directly... They guide the base tasks by modifying the base task definitions."
MetaTaskModel 约定了签名:fit(meta_dataset) 从 meta-dataset 取出准备好的 meta-task 并学习知识;inference(meta_dataset) -> List[dict] 在推理时输出一批修改后的任务定义,这些任务可以直接被 Qlib trainer 执行。也就是说,这一类元模型的作用方式是"任务级"的——它把学到的知识物化成对基座任务的修改(例如给样本加权、调整数据范围),而不是在训练循环内部插入逻辑。DDG-DA 正是这一类。
4.2 MetaGuideModel:参与基座模型的训练过程
"This type of meta-model participates in the training process of the base forecasting model... guide the base forecasting models during their training to improve their performances."
MetaGuideModel 与 MetaTaskModel 同样继承 MetaModel 的 fit/inference 抽象,但从源码结构看,它的定位是让元模型在基座预测模型的训练期间持续互动(例如在训练循环中提供正则项、损失调制等),属于"训练过程级"的引导。用户实现自定义元模型时,可按需选择继承哪一个子类。
4.3 自定义元模型的实现要点
综合以上契约,实现一个自己的元模型大致是三步:
- 继承
MetaTaskDataset,实现_prepare_seg(segment),返回一批构造好的MetaTask(task, meta_info, mode); - 继承
MetaModel(或MetaTaskModel/MetaGuideModel),实现fit与inference; - 在滚动/在线工作流中,把元模型的
inference输出接回基座任务或基座训练循环。
5. 完整示例:DDG-DA 自适应市场动态
文档给出 Qlib 内置的元模型实现 DDG-DA(Data Distribution Generation for Predictable Concept Drift Adaptation),它"adapts to the market dynamics"。文档描述的四个步骤与源码 qlib/contrib/rolling/ddgda.py 中 DDGDA.run() 的调用一一对应:
| 文档步骤 | 源码方法 | 产出 |
|---|---|---|
1. 计算 meta-information 并封装为 MetaTask 实例,所有 meta-task 组成 MetaDatasetDS |
_dump_data_for_proxy_model()、_dump_meta_ipt()、MetaDatasetDS.__init__ |
文件 handler_proxy.pkl、internal_data_s{step}.pkl;mlflow 实验 data_sim_s{step} |
| 2. 基于元数据集的训练数据训练 DDG-DA | _train_meta_model() |
mlflow 实验 DDG-DA 中保存的 MetaModelDS |
| 3. 对 DDG-DA 做推理得到 guide information | get_task_list() 中 meta_model.inference(mds) |
文件 tasks_s{step}.pkl(每个任务被加上了 reweighter) |
| 4. 将 guide information 应用到预测模型 | 继承自 Rolling 的 super().run() |
训练滚动预测模型并汇总预测 |
几个值得注意的实现细节:
- 代理模型(proxy model)机制:DDG-DA 先用一个简化代理模型(默认 LightGBM 计算特征重要性、按
sim_task_model选择 linear/gbdt 统计相似性)在历史数据上滚动训练,再用InternalData.setup()把"每个代理模型在不同日期上的日频 Spearman 相关性(IC)表现"整理成data_ic_df,这就是 meta-information 的原始形态(见 qlib/contrib/meta/data_selection/dataset.py)。_prepare_meta_ipt()随后用mask_overlap()掩掉与自身训练期重叠(含 horizon 泄漏窗口)的列,防止信息穿越。 - 元模型本体:
MetaModelDS(qlib/contrib/meta/data_selection/model.py)内部是PredNet(net.py)——一个"时间权重网络 + 加权岭回归闭式解"的组合:TimeWeightMeta从time_perf序列预测每个数据段的时间权重,preds_to_weight_with_clamp用tanh/clamp/sigmoid方式裁剪权重;损失函数ICLoss(utils.py)对每个交易日计算预测与标签的相关性并取负 IC 均值,loss_skip_thresh参数用于跳过样本数过少的交易日。 - 引导的落地形式:
MetaModelDS._prepare_task()对每个基座任务浅拷贝后挂上一个TimeReweighter,即"改写任务定义"具体体现为按时间分布对训练样本加权。这与MetaTaskModel.inference返回List[dict]的契约完全吻合。 - 知识迁移:
get_task_list()中训练侧与推理侧的MetaDatasetDS参数(step、trunc_days、hist_step_n、fill_method)刻意保持一致,推理侧则设置segments=0.0与task_mode=MetaTask.PROC_MODE_TRANSFER,正好呼应了第 2 节中"元模型可迁移到其他数据集,只需要 meta_info"的设计。
运行 DDG-DA 基准
入口脚本是 examples/benchmarks_dynamic/DDG-DA/workflow.py,它定义 DDGDABench(DDGDA),默认使用 baseline/workflow_config_linear_Alpha158.yaml(Linear 基座,效率优先),也支持 LightGBM 配置。按 examples/benchmarks_dynamic/DDG-DA/README.md 的说明,运行命令为:
python workflow.py run
# 换用 LightGBM 基座模型:
python workflow.py --conf_path=../baseline/workflow_config_lightgbm_Alpha158.yaml run
运行前提与限制:
- 需要先准备 Qlib 数据(脚本中在未设置
PROVIDER_URI时会通过GetData().qlib_data(exists_skip=True)自动下载;设置PROVIDER_URI环境变量可指向已有数据); - 硬件最低要求:约 45G 内存、4G 磁盘,CPU 版 PyTorch 即可;
- 基类的
Rolling初始化参数(conf_path、horizon=20、step=20、train_start、test_end等,见 qlib/contrib/rolling/base.py)与 DDG-DA 特有参数(sim_task_model、alpha、loss_skip_thresh、fea_imp_n=30、segments=0.62、hist_step_n=30、meta_data_proc="V01"等,见 DDGDA 构造器)均可通过fire命令行覆盖; - 注意源码中的提示:由于 mlflow 实验难以彻底删除(同名实验会移入 .trash 导致创建失败),运行示例前建议清理旧结果(删除
mlruns目录)。
6. 小结与延伸阅读
回到文档的主线:Meta Controller 用三个最小抽象把"元层"与"基座层"解耦——MetaTask 定义"一个元任务长什么样"(FULL/TEST/TRANSFER 三态),MetaTaskDataset 定义"如何批量生产元任务"(prepare_tasks + _prepare_seg),MetaModel 定义"元模型如何被训练与如何使用"(fit/inference),并在 MetaTaskModel(改任务定义)与 MetaGuideModel(介入训练过程)之间给出两条落地路径。仓库中 DDG-DA 的 qlib/contrib/meta/ 与 qlib/contrib/rolling/ddgda.py 提供了从元信息构造、元模型训练到任务级加权的完整参考实现,是自定义元模型时最值得对照阅读的代码路径;配套的滚动基类 qlib/contrib/rolling/base.py 则说明了 RollingGen/task_generator 这一层在 Meta 模块与滚动重训之间共享的基础设施。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00