ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

量化投资AI深度学习框架搭建:从数据管线到模型训练的完整实践

量化投资AI深度学习框架搭建:从数据管线到模型训练的完整实践 第四届金融人工智能国际会议前后量化圈里讨论最多的一个话题就是博时基金展示的那套量化投资AI深度学习框架。说实话我一开始以为这又是一次例行成果汇报但认真看完架构资料之后反而有一种“该来的终于来了”的感觉。传统量化投研正在被深度学习工程化改造而且已经进入持牌机构的正式生产环境。这篇文章不打算复述新闻而是从一个从业者的视角把这类框架背后的技术脉络、搭建思路和实操经验完整拆一遍。后面所有方法都是我实际跑过、调过、踩过坑之后沉淀下来的主要适配股票和期货类中低频量化策略偏多因子与机器学习方向。1. 量化AI框架的行业定位与技术动因1.1 传统量化投研流程正在发生的三个变化过去做量化研究主流路线是“数据清洗—因子构建—IC分析—线性打分—组合回测—实盘跟踪”。这条流水线本身没什么问题但瓶颈非常明显因子挖掘靠研究员手工做模型关系靠线性假设组合构建和模型训练是割裂的。深度学习框架的介入改变的是后面三个核心环节。第一个变化是特征从“手工制造”走向“自动提取”。手工因子通常来自价量统计、财务指标和事件驱动这些东西研究员已经挖得非常充分边际收益越来越低。深度学习模型可以在原始量价序列上直接学习交互关系和非线性模式等于把一部分研究员的工作前置到了模型内部。第二个变化是模型从“线性打分”走向“多层非线性”。传统多因子组合的预测值本质上是因子暴露的加权求和能表达的关系有限。深度学习网络可以把几百个特征映射成一个动态的预测分数而且特征之间的交互不需要人工指定。第三个变化是研究流程从“单人单机”走向“平台化生产”。一套成型框架要解决数据版本管理、特征存储、实验追踪、自动化重训、模拟交易等工程问题不再是一次性写脚本跑结果。这三个变化叠加起来本质上就是把量化投研从一个偏艺术的手工作坊推向了偏工程的工业化体系。1.2 金融数据的四个特殊约束决定框架形态一提到深度学习很多人第一反应是把PyTorch里的图像模型拿过来跑股票数据。但金融数据有一个致命特点它和图像、语音完全不一样。图像样本动辄百万张金融数据即便把全市场十年日线都拉出来有效样本也就几万个而且这些样本还远非独立。第一个约束是样本量有限。A股大约五千只股票十年日线数据量看起来很大但按一个预测周期一个样本算每个交易日截面上也就几千个样本有效训练量远远不够。所以框架里必须考虑数据增强、多任务学习、样本加权这些手段不能硬套大模型。第二个约束是非平稳性。市场状态会变波动率会聚集风格会切换。模型在2015年学到的规律放到2017年可能完全失效。因此框架必须支持滚动训练、快速重训和模型失效监控不能训一次用一年。第三个约束是信噪比极低。预测未来收益这件事标的本身的噪音远大于真实的预测信号。同样一个模型在图像任务上样本内准确率可以做到95%在量化任务上样本内IC能到0.1就已经算非常优秀样本外可能只有0.03。第四个约束是前视偏差无处不在。数据对齐、特征计算、样本筛选每一个环节都可能引入未来信息。这不是模型问题是工程问题但往往比模型问题更容易致命。上述约束决定了量化AI框架不能是通用深度学习框架的简单套壳必须在数据管线和训练逻辑上做大量定制化设计。1.3 为什么必须做“自研框架”而不是套开源MLOps行业里其实有很多成熟的MLOps平台比如MLflow、Kubeflow也有很成熟的深度学习框架比如PyTorch、TensorFlow。那为什么还要自研一套薄薄的量化框架层最核心的原因是通用框架不理解“截面”和“时间序列”这两种结构。量化模型输入是面板数据相同一组特征在同一时刻作用于上千只股票不同时刻又是不同的截面。通用MLOps平台把数据当成普通表格处理不关心日期对齐、不关心停牌标志也不提供横截面中性化这些操作。另一个原因是回测逻辑远复杂于普通模型评估。你需要处理交易成本、涨跌停限制、订单延时、停牌股剔除、组合优化约束这在通用框架里完全不存在。所以实践中的做法通常是保留PyTorch做模型训练加一层自研数据处理和评估逻辑。这一层可能只有几千行代码但决定了策略的生死。博时基金这类机构对外展示AI深度学习框架在我看来最重要的不是某一个模型有多强而是把数据、训练、回测、风控沉淀成了一个稳定可复用的工程体系。2. 核心模块拆解从数据到模型训练2.1 数据层schema设计、滚动标准化与标签构造量化AI框架的地基是数据层这一层出了问题后面模型再先进也白搭。我习惯把所有原始数据统一成一张面板宽表核心字段包括交易日期、资产代码、预测用到的全部特征、当日收益、未来N日收益、停牌标志、涨跌停标志。这里要特别强调一下标签构造。大多数人直接用close-to-close的未来N日收益作为标签但这样做有两个隐患一是当日收盘价可能买不到尤其一字板和涨停板二是尾盘冲击会影响成交价格。更稳妥的做法是用未来N日VWAP收益作为标签也就是从下一期VWAP到未来第N期VWAP的相对收益。单一标签容易过拟合我通常同时构造多个N比如5日、10日、20日让模型做多任务学习等于一种低成本正则化。特征处理有一个最容易踩坑的地方滚动标准化。很多新手拿到历史数据直接在全样本上算均值和标准差然后做z-score。这个操作在样本内回测里看起来没什么问题但实盘上线之后会持续用未来数据污染训练集导致模型表现明显衰减。正确做法是只用截止到当前交易日的数据计算均值和方差再应用到下一个交易日。去极值也不可忽视。金融特征经常出现极端值比如某只股票单日涨幅巨大导致成交额特征爆炸。直接用z-score会被极端值带偏我常采用MAD去极值法也就是用中位数和绝对中位差做稳健标准化效果比用均值方差稳定得多。2.2 特征工程与模型结构选型特征工程在深度学习框架里不再是单纯的“构造新列”而是对输入做三件很重要的事基础特征统一、行业市值中性化、时序序列化。基础特征建议分成几组动量反转类过去5日、10日、20日收益率、波动类实现波动率、振幅、量价结构类成交量、换手、量比、估值财务类PE、PB、ROE变化、资金流类大单净流入等。每一类特征在送入模型前都要做横截面上的行业市值中性化。原因很简单如果特征与行业和市值高度相关模型学出来的可能不是个股Alpha而是行业轮动或市值风格这类收益在实盘里受风格切换影响极大。模型结构我从最近的实践里总结出一条比较稳妥的路线先跑树模型做基线再跑序列模型做增强。LightGBM在表格数据上非常强能快速给出特征重要性和基线IC。序列模型方面LSTM和Transformer是主流选择但我不建议一上来就上大模型。我自己的常用配置是两层LSTM加一个线性输出头hidden_size在64到256之间输入序列长度取40到60个交易日。这里有个关键设计树模型和神经网络的结果要做融合。最简单的做法是把两者的预测值分别做截面秩归一化然后线性加权。权重可以根据验证集IC动态调整也可以固定0.5比0.5。融合之后再砍掉一部分极端预测可以有效降低单模型过拟合带来的风险。2.3 损失函数与训练机制的金融化改造通用深度学习默认使用MSE或交叉熵但在量化任务里直接套用MSE有一个问题收益标签分布长尾且噪声极重模型会拼命拟合一堆不可预测的极端样本。更贴近实际需求的其实是排序比较也就是我们真正关心的是预测收益的排名而不是绝对收益数值。所以损失函数我优先推荐RankIC近似变体。一个简单可靠的实现是在一个batch内部对一对样本比较预测值和真实值是否同向不一致时计算对称Hinge损失。这样模型优化的目标和评估指标IC直接挂钩。这个损失在PyTorch里只要十几行代码就能实现但效果比MSE稳定不少。训练机制方面量化领域有个独特要求验证集必须严格按时间滚动。绝对不能随机打乱数据后切分验证集否则同一时刻的截面样本会互相泄漏。我常用的方案是滚动窗口训练训练集取过去12到24个月验证集取接下来3个月依次向后滚动4到6次最后看平均验证IC。早停条件也要改。通用深度学习用验证集loss判断但量化任务里验证集loss和样本外IC经常不协同。我习惯用滑动的RankIC作为早停指标连续20个epoch不提升就停并且把最佳模型参数保存下来。回看实验记录时这个操作往往比精心调学习率带来的收益更大。3. 实操记录一个最小可落地的量化深度学习框架3.1 可以抄的代码骨架下面这份代码是我在个人研究环境里常用的最小骨架跑通再逐步加模块。输入是一张面板DataFrame包含特征列和标签列输出是每日截面上的预测分。import torch import torch.nn as nn import numpy as np import pandas as pd # 将面板数据按资产分组构造时序样本 def make_sequences(df, feature_cols, seq_len40): xs, ys, keys [], [], [] for asset, group in df.groupby(asset): vals group[feature_cols].values.astype(np.float32) labels group[label].values.astype(np.float32) for i in range(len(group) - seq_len): xs.append(vals[i:iseq_len]) ys.append(labels[iseq_len]) keys.append((asset, group[date].values[iseq_len])) return np.array(xs), np.array(ys), keys # 两层LSTM加输出的量化预测模型 class QuantSeqModel(nn.Module): def __init__(self, input_dim, hidden_dim128, num_layers2): super().__init__() self.lstm nn.LSTM(input_dim, hidden_dim, num_layers, batch_firstTrue) self.head nn.Sequential(nn.Linear(hidden_dim, 64), nn.ReLU(), nn.Linear(64, 1)) def forward(self, x): out, _ self.lstm(x) return self.head(out[:, -1, :]).squeeze(-1) # RankIC近似损失的简化版本 def rank_ic_loss(pred, target, margin0.1): diff pred.unsqueeze(0) - pred.unsqueeze(1) tdiff target.unsqueeze(0) - target.unsqueeze(1) sign_loss torch.relu(-tdiff.sign() * diff margin) mask 1 - torch.eye(pred.shape[0], devicepred.device) return (sign_loss * mask).sum() / (mask.sum() * pred.shape[0])这份代码里最值得留意的是损失函数部分。我刻意用了两两对比的方式而不是网络输出直接和标签做MSE就是为了让模型优先学排序关系。排序对了后面做组合打分才有意义。3.2 标签构造与中性化最容易出错的环节很多人在标签上犯的错误非常基础但隐蔽。第一个常见错误是没有剔除不可交易样本。训练时应该先排除停牌、涨跌停的股票因为这些股票在实盘里根本买不进模型学到的是不可执行的预测。第二个容易出错的地方是收益窗口。用T日收盘作为买入价看起来没问题但如果真实交易是T1开盘才下单那么T日收盘到TN日收盘的收益带了未来一天的偏差。严谨的做法是买入价用T1开盘价或T1的VWAP卖出价用TN1的VWAP。窗口错一天短期预测的IC差异会非常明显。第三个问题是中性化没有贯彻到底。特征中性化和标签中性化必须同时做。有些框架只对特征做了行业市值中性化标签还是原始收益结果模型会隐含学回行业收益率。正确的做法是对标签同样做横截面中性化或者把行业哑变量和市值作为特征送进去让模型自己学。我个人习惯是先做特征中性化再对标签做中性化双重保险。中性化方法就是横截面最小二乘回归取残差。这一步用Polars或pandas的groupby加回归都能完成但一定要按交易日分组回归不是全时段回归。3.3 评估口径IC、IR、分组收益与换手深度学习模型训练完之后评估不能只看一个指标。我日常盯一套组合指标每一项都很关键。RankIC是最重要的排序指标统计每日预测值与未来收益的Spearman相关系数然后看序列均值。ICIR则是RankIC均值除以标准差衡量稳定度我一般要求训练好的样本外ICIR至少大于0.1否则没有上线价值。分组收益更直观。把每日所有股票按预测分从高到低分成十组看每一组的未来收益均值。正常情况下前两组明显高于后两组而且最好有单调性。如果只有头部和尾部突出中间很乱说明模型其实只学到了极端样本的特征稳定性堪忧。换手率是很多人容易忽略的指标。预测排名每天都会变组合换手率过高会把收益全吃在交易成本里。我最常看到的案例是模型样本外IC不错但调仓换手来回百分之七十扣掉成本后超额收益只剩一半。所以在评估阶段就要把换手率记下来后续做组合优化时加换手惩罚。给一个比较完整的最小评估表指标计算方式我的经验阈值RankIC每日预测与未来收益的Spearman相关样本外均值大于0.03ICIRIC均值 / IC标准差大于0.15较稳分组单调性十组收益排序前高后低且单调换手率Top组每日股票变动比例越低越好目标小于30%分年收益按年拆分多空净值至少两年为正4. 常见问题与排查技巧4.1 四个过拟合信号与自救步骤过拟合在量化深度学习里是常态不是异常。关键是尽早识别。第一个信号是训练IC很高验证IC很低。这种情况说明模型把训练期的噪声背下来了泛化能力为零。第二个信号是验证IC看起来不错但分年份看时一年赚钱一年亏钱极不稳定说明模型依赖于特定市场状态。第三个信号是特征重要性极端集中比如全部预测主要由一个量价因子驱动一旦那个因子失效整个策略崩盘。第四个信号是样本外衰减极快模型训练后第一个月IC尚可第二个月迅速归零。我的处理顺序是先加正则化L2权重衰减调大加dropout到0.3以上。然后缩短序列长度因为长序列更容易记忆噪声。接着做模型集成不再追求单模型最强而是把树模型、LSTM、MLP的预测拉平做平均每个模型的极端观点都会被稀释。最后再做一次最严苛的测试把训练区间完全前移假设模型在2021年底训练预测2022到2023年的数据看是不是依然有稳定IC。如果这部分扛不住就说明框架本身有问题不是调参能解决的。4.2 回测与实盘对不上的原因排查回测结果好看但实盘处处碰壁这是量化圈最经典的问题。我复盘过很多次之后发现问题往往不出在模型而出在模拟环境。第一是成本模型太乐观。回测只按双边万二点五佣金计算没有考虑冲击成本。大单进出一定会推高买入价、压低卖出价尤其是在小市值股票上。解决办法是回测时对组合权重超过一定阈值的个股增加额外冲击惩罚或者直接用VWAP滑点模型。第二是成交假设太理想。回测默认收盘价能完全成交但实际下单时有集合竞价机制有大单排队有涨跌停无法交易。至少要加两条规则涨停股无法买入跌停股无法卖出停牌股自动移出组合。第三是预测和执行的延迟。模型基于T日收盘后数据计算预测下单却在T1如果T1开盘跳空那么回测里用T日收盘价计算的收益就失真了。我在框架里统一把交易价格错位一天和实盘对齐。有一个很简单的实盘校验方法模拟盘跑两周之后把模拟盘的成交价和当日VWAP做比较看平均滑点是多少然后把这部分滑点加回到回测模型里重新测算。如果加上之后收益依然覆盖成本再考虑实盘资金上线。4.3 实验管理与复现团队协作的底线单人研究时模型训完参数忘掉也无所谓。但一旦进入团队协作实验管理就成了框架级的硬需求。我自己经历过的惨痛教训是同一份代码在不同机器上训练结果完全不一样最后发现是CUDA和GPU型号不同导致少量浮点差异。为了复现我把整个流程拆成三个强制步骤。第一步是配置文件化所有重要参数都放到YAML里包括数据路径、特征列表、窗口长度、模型结构、学习率、随机种子。第二步是实验追踪用MLflow记录每次训练的指标和参数模型文件按实验ID归档。如果没有条件部署MLflow至少用dict记录实验结果导入CSV任何一次实验都不能只留口头结论。第三步是数据版本管理。原始数据、清洗后数据、特征数据三层分离每一层用hash值作为版本标识。因为行情源更新后历史数据可能被修订如果数据变了但模型没有重跑记录在案的实验结果就全部失效。团队内部的回测代码必须经过Review尤其是标签和交易成本这两段逻辑哪怕改动一行也要重新生成一份评估报告。最后再分享一个我自己养成的习惯每次模型训练完都把每日预测值的分布保存下来。实盘运行阶段每天计算新预测的分位数如果发现预测分布出现明显漂移比如原本在0到1之间突然拉到2以上第一件事不是调模型而是检查数据源、复权因子和停牌标志是不是出了问题。我在这个环节上拦截过好几次数据事故。量化深度学习框架发展到今天真正拉开差距的已经不是模型结构而是谁能在数据、工程和风控上少犯错。把基础打扎实模型自然会在样本外给出回报。
返回列表