4. 金融数据实验跟踪实战

在金融量化研究中,股票价格预测模型的训练往往不是一次性的“跑通即可”,而是一个反复迭代、不断对比的过程。数据窗口、特征构造方式、标签定义、模型超参数、随机种子,任何一个环节的微小变化都会导致评估指标波动。如果没有实验跟踪机制,研究员很快会陷入“上次那个效果不错的模型到底用了哪些特征”的困境。本节以股票价格预测为场景,完整演示如何用MLflow记录数据版本、特征工程步骤和评估指标,让每一次训练都可追溯、可复现、可比较。

核心原理:实验跟踪记录的是什么

MLflow Tracking的核心抽象是实验(Experiment)与运行(Run)。一个实验对应一个研究主题,例如“沪深300日频收益率预测”;一次运行对应一次具体的训练尝试。每次运行可以写入四类信息:

  • 参数(Params):训练前确定的配置,如学习率、树深度、回看窗口长度、特征列表版本号。
  • 指标(Metrics):训练中或训练后产生的数值,如RMSE、MAE、IC、夏普比率,支持按step记录形成曲线。
  • 标签(Tags):用于检索和分类的元信息,如数据版本、特征工程脚本commit、研究员姓名。
  • 制品(Artifacts):文件级产物,如特征重要性图、残差分布图、模型文件、数据快照。

金融场景的特殊性在于:数据本身具有时间属性,特征工程步骤往往比模型结构更影响结果。因此,把“数据版本”和“特征工程步骤”作为一等公民记录下来,比单纯记录超参数更重要。

股票预测实验的MLflow跟踪对象结构 数据版本 行情快照 / 复权方式 特征工程 窗口 / 标准化 / 滞后 模型训练 超参数 / 随机种子 评估 RMSE / IC MLflow Run 内部记录 Params lookback=20 lr=0.05 max_depth=4 Metrics rmse=0.0182 mae=0.0131 ic=0.043 Tags data_ver=v3 feat_ver=v2 author=quant01 Artifacts model.pkl feature_imp.png residual.png

方法流程:从数据加载到指标落库

下面以“用LightGBM预测未来5日收益率”为例,给出可运行的跟踪骨架。关键点在于:把数据版本和特征工程步骤显式写入tags,而不是只记录模型参数。

import mlflow
import mlflow.lightgbm
import pandas as pd
import numpy as np
from sklearn.metrics import mean_squared_error, mean_absolute_error

mlflow.set_tracking_uri("http://127.0.0.1:5000")
mlflow.set_experiment("stock_return_prediction")

DATA_VERSION = "hs300_daily_v3_20240101"
FEAT_VERSION = "feat_v2_lag20_std"

with mlflow.start_run(run_name="lgbm_lag20_v2") as run:
    # 1. 记录数据版本与特征工程步骤
    mlflow.set_tags({
        "data_version": DATA_VERSION,
        "feature_version": FEAT_VERSION,
        "feature_steps": "lag20,rolling_std,volume_zscore",
        "label": "future_5d_return",
        "split": "time_series_70_15_15"
    })

    # 2. 记录超参数
    params = {"lookback": 20, "learning_rate": 0.05,
              "max_depth": 4, "num_leaves": 31, "seed": 42}
    mlflow.log_params(params)

    # 3. 训练并记录指标
    model.fit(X_train, y_train)
    pred = model.predict(X_valid)
    rmse = mean_squared_error(y_valid, pred, squared=False)
    mae = mean_absolute_error(y_valid, pred)
    ic = np.corrcoef(pred, y_valid)[0, 1]

    mlflow.log_metrics({"rmse": rmse, "mae": mae, "ic": ic})

    # 4. 记录模型与图表制品
    mlflow.lightgbm.log_model(model, "model")
    mlflow.log_artifact("feature_importance.png")
    mlflow.log_artifact("residual_dist.png")

运行结束后,在MLflow UI中即可按data_version或feature_version筛选运行,快速定位“同一数据版本下哪组特征工程效果最好”。

案例场景:对比两组特征工程方案

假设研究员构造了两套特征:方案A仅使用价格滞后项,方案B在滞后项基础上加入成交量Z分数和滚动波动率。两次运行写入同一实验,记录如下:

Run名称 特征版本 特征步骤 RMSE IC
lgbm_lag20_v1 feat_v1 lag20 0.0215 0.021
lgbm_lag20_v2 feat_v2 lag20,rolling_std,volume_zscore 0.0182 0.043
lgbm_lag20_v2_seed7 feat_v2 lag20,rolling_std,volume_zscore 0.0184 0.041

通过表格可以直观看到:加入量价衍生特征后IC提升约一倍,而更换随机种子带来的波动远小于特征工程带来的差异。这正是金融实验中“特征优先于调参”的典型证据。

建议把特征工程脚本的Git commit hash也写入tags,例如mlflow.set_tag("feat_commit", "a1b2c3d")。当特征逻辑变更时,可以精确回溯到对应代码版本。

误区与复盘

  • 只记录模型参数,不记录数据版本:金融数据会因复权、停牌处理、成分股调整而变化,同一份代码在不同数据快照上结果不可比。
  • 把特征工程步骤写在代码注释里:注释不会进入MLflow,检索时无法按特征步骤过滤。应把步骤摘要写入tags或作为artifact保存特征清单。
  • 用随机切分代替时间序列切分:股票数据存在时序依赖,随机切分会造成未来信息泄露,指标虚高。应在tags中明确split=time_series。
  • 指标只记一个RMSE:金融预测更关注排序能力和风险调整后收益,建议同时记录IC、RankIC、多空组合夏普。
  • 忽略运行命名:默认运行名是随机ID,后期无法辨认。应使用run_name体现数据版本与特征版本组合。
MLflow默认将制品存储在本地./mlruns目录。团队协作时应配置远端跟踪服务器和对象存储(如S3、MinIO),否则本地文件丢失会导致实验记录不可恢复。

复盘一次完整的实验跟踪,核心不是“用了MLflow”,而是让每一次训练都回答三个问题:用了哪份数据、做了哪些特征变换、得到了什么指标。当这三个问题都能在UI中一键检索时,金融模型研究才真正从“手工作坊”走向可积累的实验体系。