从因子到Transformer:资产定价模型的深度学习实践与避坑指南
发布时间:2026/9/6 22:02:31 锦皓数字建站

简介基于Transformer架构的资产定价前沿研究NBER工作论文面向金融工程与量化投资领域的研究人员、机构从业者及进阶投资者旨在解决传统机器学习模型在股票回报预测中定价误差偏大、跨资产信息利用不足的问题。论文提出人工智能定价模型AIPM将Transformer嵌入随机贴现因子通过线性和非线性投资组合转换器实现跨资产信息共享研究采用1963—2022年美国股票市场数据在与多种经典机器学习模型的对比中取得更低的定价误差和更高的预测精度并进一步剖析了信息共享机制与模型复杂度对收益提升的贡献同时指出线性变换也能从特征交互中获益。压缩包内共1个PDF文件大小681KB内容覆盖模型设定、理论推导、实证流程与结果解读适合用于了解注意力机制在资产定价中的最新应用也可为量化策略研发和学术研究提供方法参考。目前已有124人学习下载。 Transformer 在自然语言处理里已经成了标配这两年越来越多的量化团队开始尝试把它引入金融市场。具体到资产定价这个场景深度学习模型要做的事情就是从一堆行情、基本面、另类数据里学到资产未来收益率的条件分布并给出比传统因子模型更精准的预测。这篇文章不聊玄乎的理论直接围绕“金融市场中的 Transformer 模型”这个主题讲清楚它到底怎么改进资产定价以及我实际落地时踩过的坑和验证过的做法适合正在做量化研究、想用深度学习替换传统多因子框架的同学参考。1. 为什么资产定价需要Transformer1.1 传统资产定价模型的底牌资产定价这个领域学术上绕不开 CAPM 和 Fama-French 因子模型。CAPM 的核心假设是资产的预期超额收益只跟市场组合的 beta 相关风险溢价用一个线性系数就能刻画。这玩意儿在课堂上讲得通拿到实盘数据上就有点尴尬因为单一因子对横截面收益差异的解释力相当有限。后来三因子、五因子模型加上了规模、账面市值比、盈利和投资因子逐步把 R² 拉上去但本质上还是线性回归的框架。线性模型的问题在于它假设因子和预期收益之间是直线关系因子之间是独立叠加的。实际上 A 股市场里小市值因子在特定流动性环境下非常强换手率因子在牛市和熊市的符号甚至可能反转这些特征用固定线性系数根本表达不出来。机器学习的思路就不一样树模型和神经网络默认去拟合非线性关系不需要人工指定交互项。这也是为什么从 2018 年开始Gu, Kelly 和 Xiu 那篇用神经网络做资产定价的论文出来后量化圈开始大规模尝试用深度学习替代传统线性因子模型。1.2 Transformer 的核心优势与金融场景的契合点Transformer 相比 LSTM 和 GRU 这类序列模型最大的特点是用自注意力机制直接建模序列中任意两个位置的关系。LSTM 的隐状态传递是逐步进行的长距离信息要经过很多时间步才能传导训练时容易出现梯度衰减。Transformer 的注意力矩阵一次计算就建立全局依赖无论两个时间点相隔多远都能直接交互。金融数据恰恰是一个高度依赖长程依赖和变量交互的领域。一只股票的收益不仅受过去几天走势影响还受宏观经济变量、行业联动、市场情绪等多种因素共同作用。传统因子模型很难自动发现这些复杂关系而 Transformer 的多头注意力机制可以并行地去学习不同维度的特征交互一个头可能学到行业联动另一个头可能学到波动率聚集效应。另一个很实际的优势是计算效率。LSTM 必须逐步迭代Transformer 的 attention 计算可以高度并行在 GPU 上训练要快得多。对量化研究来说这意味着可以更快地迭代特征和超参数组合实验效率直接影响研究产出。2. 整体设计从因子到收益率的端到端方案2.1 数据准备特征工程与样本构造要把 Transformer 用到资产定价第一步就是把原始行情数据加工成模型能吃的结构化样本。这里最常用的数据组织形式是面板数据每一行对应某只股票在某个时间点的特征值整体是一个三维结构时间维度、股票维度、特征维度。特征工程方面常见输入包括动量类因子过去 5 日、20 日、60 日收益率、波动率类因子已实现波动率、下行波动率、成交量类因子换手率、成交量 Z-Score、流动性因子Amihud 非流动性指标以及基本面因子市盈率、市净率、ROE 等。特征不是越多越好我实际测试下来先从一个经过 IC 筛选的特征池开始规模控制在 20 到 50 个之间效果最稳太多噪声特征会让注意力机制学到虚假关联。标签构造是资产定价模型的关键环节。通常用未来 N 日的收益率作为预测目标短周期用未来 5 日收益长周期用未来 20 日收益。这里必须强调一个核心原则标签只能包含 T 时刻之后的信息特征只能包含 T 时刻及之前的信息。如果标签用了未来数据模型在回测里表现再好都是假的属于典型的未来函数问题。样本集的划分也要格外小心。时间序列数据不能像图像分类那样随机打乱否则训练集里会出现未来样本验证集的意义就完全失效了。正确做法是按时间顺序划分前 60% 时间作为训练集中间 20% 作为验证集最后 20% 作为测试集。2.2 模型架构Transformer 在定价任务中的具体形态Transformer 在资产定价中怎么搭业界其实有两套主流做法。第一种是把单只股票的时间序列作为输入序列类似文本处理方式每个时间步的特征相当于一个 token位置编码表示时间先后顺序最终输出该股票未来收益的预测值。第二种是把横截面数据作为输入序列即在同一时间点把不同股票当作一个序列每个股票的特征作为 token通过注意力机制让模型自动发现股票之间的联动关系。我的实践经验是时间序列模式更适合单股票择时横截面模式更适合横截面选股。做资产定价的量化策略横截面模式往往是更好的起点因为它直接从选股角度出发和组合构建衔接更自然。具体架构上我在横截面模式中会用行业信息作为辅助编码让模型知道哪些股票属于同一行业注意力机制就能更容易学到行业内联动。位置编码的选择上时间维度需要加上时间位置编码但我不太推荐用原版 Transformer 的固定正弦位置编码。金融数据是高度非平稳的不同年份的市场状态差异很大固定的位置编码对模型来说意思不大。我倾向于使用可学习的位置嵌入让模型自己去决定时间顺序在预测中的重要性。2.3 训练策略与损失函数的选择逻辑损失函数的选择直接影响模型的预测目标。最直接的是均方误差损失让模型去拟合未来 N 日收益率的具体数值。但金融数据信噪比极低收益率预测误差普遍很大MSE 很容易让模型只顾着拟合高波动股票而忽略低波动股票。所以我在实际项目中会在标签构造时做一次截面标准化即在同一时间点上将所有股票的收益率减去均值再除以标准差让模型预测的是截面上的相对强弱而不是绝对收益水平。从业务角度看选股策略本质上关心的是股票收益的排序而不是具体数值。因此除了 MSE我还经常用 RankIC 作为辅助监督信号。具体做法是同时对预测值和真实值计算秩相关系数并把排名损失作为正则项加到总损失里。这样模型能在数值预测精度和排序能力之间做权衡实际回测下来排序能力强的模型在分层回测中表现更稳定。优化器的选择上也有些讲究。Transformer 的训练对学习率比较敏感直接用 Adam 很容易在训练前期出现 loss 振荡。我一般用 AdamW配合学习率 warmup 策略前 5% 的步数让学习率从很小的值线性增长到峰值之后用余弦退火逐步衰减。这个做法来自 NLP 领域的经验但在金融数据上也同样有效。批量大小方面横截面模式下每个 batch 对应一个时间点上的全部股票这样能在保证样本量的同时保持时间一致性。如果 GPU 显存不够可以按行业分组切分但要保证同一 batch 里的股票来自同一个时间点否则注意力机制可能会学到跨时间的虚假关联。3. 实操过程与核心环节实现3.1 环境准备与依赖搭建实际开发中我一般直接用 Python 3.9 加 PyTorch 2.x 作为主力框架配合 pandas 做数据处理、scikit-learn 做标准化和指标计算、matplotlib 做可视化。搭建深度学习环境时建议用 conda 创建一个独立的环境避免依赖冲突。conda create -n fin_transformer python3.9 conda activate fin_transformer pip install torch pandas numpy scikit-learn matplotlib tqdm如果有一张 8GB 显存的 GPU跑横截面 Transformer 已经非常够用了。没有 GPU 也能跑只是训练会慢不少可以先在小规模数据上验证逻辑再上 GPU 跑全量数据。3.2 数据加载与预处理代码用一个例子说明数据预处理的核心流程。假设原始数据是 CSV 文件包含 date、stock_code、feature_1 到 feature_n 和 forward_return 这几个字段。加载后首先要按时间排序然后做截面标准化。import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler df pd.read_csv(stock_panel.csv) df df.sort_values([date, stock_code]).reset_index(dropTrue) # 构造特征列表排除标识符和标签列 feature_cols [c for c in df.columns if c.startswith(feature_)] # 按日期分组做截面标准化 def zscore_group(group): scaler StandardScaler() group[feature_cols] scaler.fit_transform(group[feature_cols]) return group df df.groupby(date, group_keysFalse).apply(zscore_group)这里有个非常容易踩的坑截面标准化必须按日期分组每一行股票的特征是在这个时间点的横截面上做标准化的。如果对整个数据集做全局标准化等于是用了未来数据的信息会造成数据泄漏。这个细节我在初版代码里就犯过错误回测指标被虚高了很多。时间序列的划分也要注意不能用 train_test_split 随机切分而是按日期边界切train_end df[date].quantile(0.6) val_end df[date].quantile(0.8) train_df df[df[date] train_end] val_df df[(df[date] train_end) (df[date] val_end)] test_df df[df[date] val_end]3.3 核心模型代码与训练循环下面是一个精简但可用的横截面 Transformer 定价模型代码。核心思路是输入股票数特征数把每只股票当作一个 token特征当作 token 的 embedding经过位置编码和多层 TransformerEncoder最后输出每只股票的预测收益率。import torch import torch.nn as nn import math class FinancialTransformer(nn.Module): def __init__(self, feature_dim, d_model64, nhead4, num_layers3, dropout0.1): super().__init__() self.input_proj nn.Linear(feature_dim, d_model) self.pos_embed nn.Parameter(torch.randn(1, 500, d_model) * 0.02) encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforward128, dropoutdropout, batch_firstTrue ) self.encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) self.head nn.Sequential( nn.Linear(d_model, 32), nn.GELU(), nn.Linear(32, 1) ) def forward(self, x): # x: (batch_size, num_stocks, feature_dim) batch_size, num_stocks, _ x.shape x self.input_proj(x) x x self.pos_embed[:, :num_stocks, :] x self.encoder(x) pred self.head(x).squeeze(-1) return pred这里用batch_firstTrue让输入维度更直观。位置嵌入我设置了一个最大股票数 500这个数字可以根据实际股票池规模调整但要留出一定余量。预测头用了 GELU 激活函数比 ReLU 在训练前期更平滑不容易出现神经元死亡的问题。训练循环里有几个细节值得注意。每个 batch 对应一个时间点的全部股票也就是说我在 DataLoader 里按日期分组每个样本是一个日期的横截面数据。def train_one_epoch(model, train_loader, optimizer, criterion, device): model.train() total_loss 0 for x, y in train_loader: x x.to(device) y y.to(device) optimizer.zero_grad() pred model(x) # 标签也做截面标准化 y_std (y - y.mean(dim-1, keepdimTrue)) / (y.std(dim-1, keepdimTrue) 1e-8) loss criterion(pred, y_std) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() total_loss loss.item() return total_loss / len(train_loader)梯度裁剪那行代码很多人会忽略但对 Transformer 来说非常重要。金融数据的噪声大偶尔会出现异常样本导致梯度爆炸梯度裁剪能有效稳住训练过程。我一般把 max_norm 设置在 1.0 到 5.0 之间具体值根据训练稳定性调整。3.4 评估指标与回测要点模型训练完成后评估环节比训练本身更能决定策略是否可用。我最常用的指标有三个IC、RankIC 和分层回测。IC 的计算方式是在每个时间截面上计算预测收益率和真实收益率的 Pearson 相关系数然后对时间维度取均值。RankIC 则用 Spearman 秩相关系数更关注排序关系是否准确。def compute_ic(pred_df, true_df): # pred_df: date x stock 的预测值表 # true_df: date x stock 的真实值表 ic_series pred_df.apply(lambda x: x.corr(true_df.loc[x.name]), axis1) rank_ic_series pred_df.apply( lambda x: x.rank().corr(true_df.loc[x.name].rank()), axis1 ) return ic_series.mean(), rank_ic_series.mean()分层回测则是把预测值按十分位分组计算每组等权组合的未来收益率均值。一个有效的模型分组收益应该是单调递增或递减的多头组和空头组的收益差就是多空组合的收益。只看 IC 有一个问题它只衡量了线性相关程度无法反映预测值在极端分位上的区分能力所以一定要配合分层回测看单调性。我自己的标准是测试集上 RankIC 达到 0.03 以上且分层回测中多头组收益显著优于空头组模型才值得进入下一步的因子合成或组合优化流程。4. 常见问题与排查技巧实录4.1 过拟合与时间序列泄漏我在这个项目里遇到的最典型的问题就是训练集 IC 很高、测试集 IC 趋近于零甚至变成负数。这种症状十有八九是过拟合或者数据泄漏。数据泄漏的排查可以从几个角度入手。检查代码里是否对全样本做了标准化检查标签是否意外包含了未来信息检查训练集和验证集是否真的按时间切开了。我记得有一次 debug 了半天最后发现是 pandas 的 groupby 默认排序把数据顺序打乱了导致时间切分失效。这个教训让我养成了一个习惯在任何数据处理前后都打印一下日期范围确认时间顺序。过拟合的应对策略有几个层面。最基础的是提高 dropout 比例Transformer 的 dropout 我一般设在 0.1 到 0.3 之间。其次是减小模型容量把 d_model 从 128 降到 64、层数从 6 层降到 3 层往往效果立竿见影。金融数据比文本数据的样本量小得多模型太大很容易记住噪声。再者就是早停在验证集 RankIC 连续 10 个 epoch 没有提升时就停止训练。4.2 训练不稳定与超参数调整Transformer 训练不稳定的表现通常是 loss 像过山车一样上下震荡或者训练到一半梯度爆炸导致 loss 变成 NaN。这类问题的主要原因有三个学习率过大、warmup 步数不够、数据里有极端异常值。学习率的设置上我试过从 1e-4 到 3e-4 这个区间通常比较安全超过 5e-4 大概率会震荡。warmup 步数占训练总步数的比例建议在 5% 到 10% 之间太少达不到预热效果太多会让训练变慢。数据异常值方面特征虽然做了截面标准化但极端行情下仍可能出现偏离均值 10 个标准差的值可以在数据预处理时做 winsorize 处理把上下 1% 分位以外的值截断。还有一个容易被忽略的问题注意力机制的权重退化。如果位置编码初始化不当模型可能学会忽略大部分股票、只关注少数几个 token导致有效感受野很小。我检查这个问题的办法是把注意力权重矩阵打印出来做可视化如果发现某些头总是集中在同一个维度上就需要调整初始化或增加 dropout。4.3 业务落地中的坑模型在回测里表现不错不等于实盘能用。我在落地过程中遇到过几个很现实的坑。第一个是交易成本。高频调仓的模型在回测里换手率可能非常高但实盘交易成本会把这些收益吃掉。需要在训练时加入换手率惩罚或者在回测时用更保守的成本假设。我通常的做法是在回测中按双边千分之三的费率扣除成本虽然比实际偏高但至少不会过度乐观。第二个是 Alpha 衰减。市场对因子的认识和拥挤度会随时间变化一个在历史数据上有效的模型未来几个月可能失效。所以模型需要定期重训。我一般是每两周重训一次每次用最新的数据滚动更新训练集保证模型能跟上市场风格变化。第三个是行业和风格暴露。如果模型的预测结果在某一个行业上过度集中组合的风险就会很大。解决方案是在特征中加入行业哑变量让模型显式感知行业信息同时在组合构建阶段做行业中性化处理把行业暴露压到零。5. 实操总结与个人体会做金融 Transformer 这个项目最大的体会是不要一开始就把模型搞复杂。先跑通一个简单的线性基准比如直接用线性回归预测收益率记录下它的 IC 和分层回测结果。然后在这个基础上逐步加复杂度——先试一个浅层全连接网络再加一层 Transformer。这样每一步的改进都能归因到具体的技术变化而不是被模型的随机性干扰判断。我在实际测试中发现模型到了一个复杂程度之后继续堆层数带来的收益会非常有限反而过拟合风险直线上升。金融数据的信噪比决定了模型的容量天花板比 NLP 任务低得多一个 3 层、hidden size 64 的 Transformer 往往就能达到不错的效果没必要追求大模型。最后分享一个小技巧每次实验的训练参数、数据切分日期、特征列表、评估指标一定要完整记录下来。我用的是最简单的 CSV 日志方式每次跑完实验就把关键指标追加到一行记录里。这样回溯的时候能快速对比不同版本的效果差异排查问题时也能快速定位是哪一次改动引入了 bug。做量化模型实验管理的规范性直接决定了研究效率这一点长期做下来体会特别深。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。