Qlib Recorder 实验管理系统MLflow 后端之上的三层实验管理与记录模板实战指南【免费下载链接】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本文基于 Qlib 官方文档 docs/component/recorder.rst 展开系统讲解 Qlib 内置的实验管理系统QlibRecorderExpManager/Experiment/Recorder三层结构、全局入口R的高层 API、MLflow 后端的具体实现细节以及SignalRecord、SigAnaRecord、PortAnaRecord三类记录模板如何自动产出预测结果、IC 分析与回测报告并结合源码逐层剖析每个 API 背后的真实行为帮助你在量化研究中规范地追踪、复现和对比每一次模型实验。一、系统概览实验管理的三层结构Qlib 包含一个名为QlibRecorder的实验管理系统旨在帮助用户高效地管理实验、分析实验结果。该系统由三个层次的组件构成ExperimentManager代码中类名为ExpManager管理所有实验的顶层类Experiment实验类每个实例负责一个实验Recorder记录器类每个实例负责单次运行single run的详细记录。系统的整体结构如下ExperimentManager - Experiment 1 - Recorder 1 - Recorder 2 - ... - Experiment 2 - Recorder 1 - Recorder 2 - ... - ...该体系定义了一套接口并提供了一个基于机器学习平台 MLflow 的具体实现MLflowExpManager。用户只需将ExpManager实现设置为MLflowExpManager这本身就是 Qlib 的默认配置见 qlib/config.py即可在实验完成后执行mlflow ui命令可视化查看实验结果具体用法参考 MLflow 官方 CLI 文档。从源码注释qlib/workflow/init.py可以看出Qlib 选择不直接裸用 MLflow 而封装一层的核心动机有三点更好的对象化设计相比 MLflow 中到处传run_id的方式Qlib 提供了带丰富方法的Recorder对象log、start等接口更直观更贴合场景的附加特性例如在 run 开始时自动记录未提交的代码 diff、提供面向 Python 对象的log_object/load_object而非 MLflow 的log_artifact/download_artifact支持多样化后端接口与后端解耦理论上可以更换存储实现而无需改动上层代码。二、全局入口 RQlibRecorder 高层 APIQlibRecorder为实验管理系统提供了高层 API。这些接口被封装在 Qlib 的全局变量R中用户导入后即可直接使用from qlib.workflow import RR并非普通实例而是一个带校验的包装器RecorderWrapper见 qlib/workflow/init.py#L656-L681如果在已有实验处于激活状态时重新执行qlib.init它会抛出RecorderInitializationError防止实验的存储位置uri在运行中途被改写。R的真正注册发生在qlib.init触发的 Config.register 中系统按全局配置实例化exp_manager用其构造QlibRecorder并挂载到R上。2.1 启动与结束实验start / start_exp / end_expR.start是一个上下文管理器只能配合with语句使用。正常退出时 recorder 状态被置为FINISHED发生异常时自动置为FAILED并向上抛出。完整参数语义源自 QlibRecorder.start 源码from qlib.workflow import R # 启动新实验和新 recorder with R.start(experiment_nametest, recorder_namerecorder_1): model.fit(dataset) R.log_metrics(train_loss0.33, step1) # 恢复resume之前同名实验下的 recorder # 注意必须给出与之前完全一致的 experiment 和 recorder 名称 with R.start(experiment_nametest, recorder_namerecorder_1, resumeTrue): ...参数说明experiment_id/experiment_name要启动的实验的 id / 名称recorder_id/recorder_name实验下要启动的 recorder 的 id / 名称uri实验的 tracking uri所有 artifacts/metrics 都存储在该 uri 下。默认值来自qlib.config该参数不会修改配置文件中的默认值因此同一实验再次调用时需传相同值否则可能出现 uri 不一致resume是否恢复resume给定实验下给定名称的 recorder如果需要手动控制生命周期可以使用更底层的start_exp/end_exp组合R.start_exp(experiment_nametest, recorder_namerecorder_1) ... # further operations R.end_exp(FINISHED) # 等价于 R.end_exp(Recorder.STATUS_FI)end_exp(status)接收的status取值包括SCHEDULED、RUNNING、FINISHED、FAILED对应Recorder类中定义的状态常量qlib/workflow/recorder.py#L36-L40STATUS_S、STATUS_R、STATUS_FI、STATUS_FA。2.2 获取实验与记录器get_exp / get_recorder / list_*R.get_exp(experiment_idNone, experiment_nameNone, createTrue, startFalse)是最核心的检索 API其完整判定逻辑在 QlibRecorder.get_exp 源码 中有详细描述createTrue默认时找不到指定实验会自动创建若未指定 id/name 且当前无激活实验则创建默认实验createFalse时只检索找不到则抛错startTrue时若实验尚未激活会将其设为激活状态该参数主要为R.log_params等自动启动实验的接口设计。典型用法# 用法 1在 with 块内取当前激活实验 with R.start(test): exp R.get_exp() recorders exp.list_recorders() # 用法 2取指定名称的实验 with R.start(test): exp R.get_exp(experiment_nametest1) # 用法 3不带参数 - 返回或创建默认实验 exp R.get_exp() # 用法 4取指定实验 exp R.get_exp(experiment_nametest) # 用法 5仅检索不创建 exp R.get_exp(createFalse)R.get_recorder(...)用于获取 recorder若存在激活 recorder 且未指定 id/name返回激活 recorder指定 id 但无激活实验上下文时必须同时给出experiment_name否则会报错详见 QlibRecorder.get_recorder 源码。当多个 recorder 匹配查询例如按名称查询时若使用 MLflow 后端将返回start_time最新的那个——因为底层依赖 MLflowsearch_runs默认的按start_time DESC排序保证。此外还有R.list_experiments()列出所有未被删除的实验返回dict(name - experiment)R.list_recorders(experiment_idNone, experiment_nameNone)列出指定实验下所有 recorder返回dict(id - recorder)若不给实验 id/name会先取必要时创建默认实验再列出其 recorderR.delete_exp(...)、R.delete_recorder(...)按 id 或 name 删除至少需提供一个。R.search_records(experiment_ids, **kwargs)返回符合搜索条件的 pandas DataFrame其中每个 metric、param、tag 会展开为metrics.*、params.*、tags.*列。MLflow 实现下支持filter_string、run_view_type、max_results、order_by等参数例如R.log_metrics(m2.50, step0) records R.search_records([experiment_id], order_by[metrics.m DESC])该调用链最终落到 MLflowExpManager.search_records 的client.search_runs(...)run_view_type默认 1ACTIVE_ONLYmax_results默认 100000。2.3 记录参数、指标、标签与对象以下 API 遵循同一模式存在激活 recorder 时经其记录不存在时自动创建默认实验和新 recorder 再记录。# 记录参数 with R.start(test): R.log_params(learning_rate0.01) # 也可以脱离 with 块直接调用自动创建默认实验recorder R.log_params(learning_rate0.01) # 记录指标 R.log_metrics(train_loss0.33, step1) # 设置标签 R.set_tags(release_version2.2.0) # 保存对象两种互斥方式传本地路径 或 直接传对象 with R.start(experiment_nametest): pred model.predict(dataset) R.save_objects(**{pred.pkl: pred}, artifact_pathprediction) rid R.get_recorder().id # 之后任意时刻可从 artifact 加载回来 R.get_recorder(recorder_idrid).load_object(prediction/pred.pkl) # 保存本地文件/目录 with R.start(experiment_nametest): R.save_objects(local_pathresults/pred.pkl, artifact_pathprediction)注意R.save_objects中local_path与**kwargs二选一同时提供会抛出ValueError源码校验逻辑。另有R.log_artifact(local_path, artifact_pathNone)与R.download_artifact(path, dst_pathNone)处理原始文件级别的 artifact 上传/下载。2.4 uri 管理get_uri / set_uri / uri_contextR.get_uri()获取当前实验管理器的 tracking uriR.set_uri(uri)重置默认uri注意uri 指向文件路径时必须使用绝对路径后端不支持~/mlruns/这类写法R.uri_context(uri)上下文管理器临时切换默认 uri退出后自动还原。从源码结构看ExpManager.default_uri实际上与 Qlib 全局配置C.exp_manager[kwargs][uri]共享同一份数据qlib/workflow/expm.py#L282-L304运行时生效的 uri 优先取实验期间的“specific uri”_active_exp_uri否则回落到默认 uri。默认 uri 为file:加当前工作目录下的mlruns默认实验名为Experimentqlib/config.py。三、Experiment Manager 层ExpManager 与 MLflowExpManagerExpManager模块负责管理不同实验其多数 API 与QlibRecorder类似R层基本是透传代理最重要的 API 是get_exp。实现类 MLflowExpManager 的关键行为client 惰性构造self.client属性每次按需创建MlflowClient(tracking_uriself.uri)仓库内 tests/dependency_tests/test_mlflow.py 中有专门测试确保创建 client 的速度不会成为瓶颈实验的 get-or-create 与并发安全_get_or_create_exp 先尝试检索失败后自动创建。由于 MLflow 本身在并发记录时不加锁Qlib 在接口层做了补充当 uri 是file:方案时用FileLock串行化创建过程对http等其他方案则通过捕获ExpAlreadyExistError后回查来避免创建冲突list_experiments会依据 MLflow 大版本选择search_experimentsv2或list_experimentsv1只返回ACTIVE_ONLY的实验delete_exp支持按 id 或 name 删除删除前会校验实验是否存在。create_exp、delete_exp、search_records等其余接口的完整签名参见官方 API 参考文档 docs/reference/api.rst。四、Experiment 层单实验的操控Experiment类负责单个实验的全部操作包括start、end等基本方法以及与 recorder 相关的方法get_recorder、list_recorders。MLflow 实现类 MLflowExperiment 的关键细节start给定recorder_name缺省为mlflow_recorderresumeTrue时复用既有 recorder否则create_recorder新建然后start_run()并设为激活 recorderlist_recorders(rtypedict, statusNone, filter_string)底层调用search_runs默认按start_time DESC, run_id排序支持按status过滤如list_recorders(statusRecorder.STATUS_FI)只看成功的 run和 MLflow 过滤串如params.my_parama and tags.my_tagb。从源码结构看max_results上限被设为 50000exp.py 中的UNLIMITED常量这是 MLflow 本身的列表上限get_recorder的 create/start 语义与R.get_recorder类似但面向实验粒度search_records、delete_recorder按 id 或 name 删除 run同样在此层提供。默认实验Default ExperimentQlib 提供了一个默认Experiment当用户使用log_metrics、get_exp等 API 而未指定实验时系统会自动创建并使用它。默认实验名在qlib配置文件C.exp_manager.kwargs.default_exp_name或 qlib 初始化 时设置默认值为Experiment。使用默认实验时运行日志中会有相应提示。五、Recorder 层单次运行run的详细记录Recorder类负责单次 run 的细粒度操作如log_metrics、log_params等其设计目标是帮助用户轻松追踪一次运行中产出的结果与过程信息。QlibRecorder未覆盖的重要 API定义在 qlib/workflow/recorder.py 中recorder.list_artifacts(artifact_pathNone) # 列出该 run 的所有 artifact 路径 recorder.list_metrics() # 返回已记录的 metrics 字典 recorder.list_params() # 返回已记录的 params 字典 recorder.list_tags() # 返回已记录的 tags 字典save_objects、load_object、log_artifact、download_artifact、delete_tags等其余接口参见 docs/reference/api.rst。5.1 start_run自动记录代码 diff 与环境信息MLflowRecorder.start_run 在启动 run 时做了不少 MLflow 原生 API 不会做的事这也是 Qlib 封装层价值的集中体现设置 tracking uri 并mlflow.start_run把run_id、artifact_uri、开始时间、状态RUNNING写回 recorder自动记录未提交的代码MLflow 原生只记录当前仓库的 commit id但研究代码常常有大量未提交改动。_log_uncommitted_code 会执行git diff、git status、git diff --cached三条命令把输出分别保存为code_diff.txt、code_status.txt、code_cached.txt三个 artifact保证实验可复现自动记录运行上下文log_params记录cmd-sys.argv产生该实验的完整命令行并记录所有以_QLIB_开头的环境变量异步日志log_params、log_metrics、set_tags都通过AsyncCaller装饰源码提交到异步队列避免记录操作阻塞训练主流程。代价是上传结果可能有延迟、时间戳不够精确end_run时会先async_log.wait()排空队列再调用mlflow.end_run(status)否则 MLflow 会报错end_run 实现。另外由于使用qrun时参数串可能较长Qlib 把 MLflow 的参数值长度上限从 500 放宽到了 1000recorder.py#L24-L25。5.2 save_objects / load_object基于 pickle 的对象存取save_objects(local_pathNone, artifact_pathNone, **kwargs)local_path为目录时整体log_artifacts为文件时log_artifact直接传对象时先经Serializable.general_dump序列化到临时目录再上传随后清理临时目录实现load_object(name, unpicklerpickle.Unpickler)下载 artifact 后反序列化返回并支持传入自定义unpickler以适配特殊加载需求异常统一包装为LoadObjectError。get_local_dir()还能解析出本地文件系统后端下该 recorder 的目录路径非本地存储会抛RuntimeError。六、Record Template标准化的实验结果生成RecordTemp类用于以统一格式生成实验结果如 IC 与回测分析。record_temp.py中提供了多个模板类其中三个核心模板SignalRecord生成模型的prediction结果保存pred.pkl以及数据集为DatasetH时的label.pklSigAnaRecord生成模型的IC、ICIR、Rank IC、Rank ICIR指标artifact 路径前缀sig_analysis开启ana_long_short时还会输出长短期年化收益/夏普等PortAnaRecord生成backtest结果artifact 路径前缀portfolio_analysis保存各频率的report_normal_*.pkl、positions_normal_*.pkl、port_analysis_*.pkl等并把风险指标打平后log_metrics到 recorder。更完整的策略与回测机制可参考 策略文档。除三者外源码中还有面向多轮回测稳健性的MultiPassPortAnaRecord打乱首日预测分数随机化初始仓位统计annualized_return、information_ratio的 mean/std与高频场景的HFSignalRecord在 IC 之外补充 Long/Short precision、Long-Short Average Return 等指标。6.1 手动计算 IC / Rank IC / Long-Short ReturnSigAnaRecord的核心计算可以脱离模板直接复用适合自己已有 pred 与 label 的场景from qlib.contrib.eva.alpha import calc_ic, calc_long_short_return ic, ric calc_ic(pred.iloc[:, 0], label.iloc[:, 0]) long_short_r, long_avg_r calc_long_short_return(pred.iloc[:, 0], label.iloc[:, 0])在 SigAnaRecord._generate 中其指标定义为IC ic.mean()、ICIR ic.mean() / ic.std()、Rank IC ric.mean()、Rank ICIR ric.mean() / ric.std()长短期指标则按ann_scaler默认 252年化Long-Short Ann Return long_short_r.mean() * ann_scaler、Long-Short Ann Sharpe long_short_r.mean() / long_short_r.std() * ann_scaler**0.5等。6.2 手动回测与风险分析PortAnaRecord的本质是基于自己的 prediction 与 label 做回测from qlib.contrib.strategy.strategy import TopkDropoutStrategy from qlib.contrib.evaluate import ( backtest as normal_backtest, risk_analysis, ) # backtest STRATEGY_CONFIG { topk: 50, n_drop: 5, } BACKTEST_CONFIG { limit_threshold: 0.095, account: 100000000, benchmark: BENCHMARK, deal_price: close, open_cost: 0.0005, close_cost: 0.0015, min_cost: 5, } strategy TopkDropoutStrategy(**STRATEGY_CONFIG) report_normal, positions_normal normal_backtest(pred_score, strategystrategy, **BACKTEST_CONFIG) # analysis analysis dict() analysis[excess_return_without_cost] risk_analysis(report_normal[return] - report_normal[bench]) analysis[excess_return_with_cost] risk_analysis(report_normal[return] - report_normal[bench] - report_normal[cost]) analysis_df pd.concat(analysis) # type: pd.DataFrame print(analysis_df)在模板内部PortAnaRecord未显式传config时使用一套日线交易默认配置record_temp.py 源码策略为TopkDropoutStrategy(topk50, n_drop5, signalPRED)执行器为SimulatorExecutor(time_per_stepday, generate_portfolio_metricsTrue)回测账户 1 亿元、基准SH000300交易成本与上面示例一致limit_threshold0.095、open_cost0.0005、close_cost0.0015、min_cost5。其中PRED是占位符_generate时会用 recorder 中保存的pred.pkl替换若未设置start_time/end_time会自动从预测数据的日期范围推断且end_time会向前回移一个交易日Qlib 需要额外的一个日历步来确定 bar 的右边界并打印相应 warning。更多 Record Template API 参见 docs/reference/api.rst。七、实战串联一次完整的实验工作流examples/workflow_by_code.py 演示了R与三类记录模板在纯代码方式下的完整串联与qrun XXX.yaml配置文件方式几乎等价import qlib from qlib.constant import REG_CN from qlib.utils import init_instance_by_config, flatten_dict from qlib.workflow import R from qlib.workflow.record_temp import SignalRecord, PortAnaRecord, SigAnaRecord from qlib.tests.data import GetData from qlib.tests.config import CSI300_BENCH, CSI300_GBDT_TASK if __name__ __main__: provider_uri ~/.qlib/qlib_data/cn_data GetData().qlib_data(target_dirprovider_uri, regionREG_CN, exists_skipTrue) qlib.init(provider_uriprovider_uri, regionREG_CN) model init_instance_by_config(CSI300_GBDT_TASK[model]) dataset init_instance_by_config(CSI300_GBDT_TASK[dataset]) # 回测配置executor / strategy / backtest 三段 port_analysis_config { executor: {class: SimulatorExecutor, module_path: qlib.backtest.executor, kwargs: {time_per_step: day, generate_portfolio_metrics: True}}, strategy: {class: TopkDropoutStrategy, module_path: qlib.contrib.strategy.signal_strategy, kwargs: {signal: (model, dataset), topk: 50, n_drop: 5}}, backtest: {start_time: 2017-01-01, end_time: 2020-08-01, account: 100000000, benchmark: CSI300_BENCH, exchange_kwargs: {freq: day, limit_threshold: 0.095, deal_price: close, open_cost: 0.0005, close_cost: 0.0015, min_cost: 5}}, } # start exp with R.start(experiment_nameworkflow): R.log_params(**flatten_dict(CSI300_GBDT_TASK)) # 记录全部超参 model.fit(dataset) R.save_objects(**{params.pkl: model}) # 保存训练好的模型 recorder R.get_recorder() sr SignalRecord(model, dataset, recorder); sr.generate() # 生成 pred.pkl / label.pkl sar SigAnaRecord(recorder); sar.generate() # 生成 IC / Rank IC 分析 par PortAnaRecord(recorder, port_analysis_config, day) # 生成回测报告 par.generate()在配置方式下同样的模板通过 yaml 的record段声明例如 examples/benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yamltask: 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其中MODEL、DATASET、PRED为工作流占位符由 Qlib 在运行时自动填充。该模板类继承自ACRecordTemp自动检查型模板generate时先check依赖文件是否齐全SigAnaRecord/PortAnaRecord的depend_cls均指向SignalRecord即依赖pred.pkl、label.pkl缺失则跳过并告警配合skip_existingTrue还能在产物已存在时直接跳过重新生成适合断点续跑。运行完成后所有实验数据params、metrics、tags、artifacts、代码 diff都落在默认 uri当前工作目录的mlruns中执行mlflow ui即可跨实验对比各 recorder 的IC、ICIR、Rank IC以及1d.annualized_return、1d.information_ratio等回测指标。八、已知限制Known Limitations对象基于 pickle 保存save_objects/load_object底层使用 pickle 序列化当保存对象的环境与加载对象的环境不一致如依赖库版本、注册表差异时可能出现加载问题。加载时可通过load_object的unpickler参数传入自定义反序列化器缓解。九、相关源码与文档索引内容路径本文档对应的官方文档docs/component/recorder.rst全局入口R与QlibRecorder全部高层 APIqlib/workflow/init.pyExpManager/MLflowExpManagerqlib/workflow/expm.pyExperiment/MLflowExperimentqlib/workflow/exp.pyRecorder/MLflowRecorderqlib/workflow/recorder.pyRecordTemp及 Signal / SigAna / PortAna 模板qlib/workflow/record_temp.py默认exp_manager配置uri、默认实验名qlib/config.py完整代码工作流示例examples/workflow_by_code.py配置方式示例LightGBM Alpha158examples/benchmarks/LightGBM/workflow_config_lightgbm_Alpha158.yamlMLflow 依赖与 client 创建性能测试tests/dependency_tests/test_mlflow.py实验管理相关 API 参考docs/reference/api.rst综上Qlib 的实验管理系统在 MLflow 之上构建了一个“Manager - Experiment - Recorder”的三层对象模型用户通过全局R以最少的心智负担完成实验的启动、参数/指标记录与对象存取底层实现则自动补全了代码 diff、命令行、环境变量等复现信息再配合SignalRecord/SigAnaRecord/PortAnaRecord记录模板一次模型工作流即可沉淀出结构统一、可跨实验横向对比的预测结果、信号分析与回测报告。【免费下载链接】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),仅供参考