13. 深度学习应用:LSTM/Transformer建模曲面与宏观数据的非线性关系
说实话,传统方法做波动率曲面预测,我用了好多年。线性回归、PCA降维、甚至带正则化的岭回归,都试过。但有个问题一直绕不开——宏观数据和曲面形态之间,根本不是线性关系。
举个例子。非农数据超预期,短期ATM波动率可能跳升,但长期曲面尾部反而可能压缩。这种非对称、非线性的响应,传统模型很难捕捉。直到我把LSTM和Transformer搬进来,才真正打开了局面。
13.1 为什么需要深度学习?
我个人习惯把问题拆成两步:第一步,理解曲面本身的时序规律;第二步,理解宏观数据如何影响曲面。传统模型做第一步还行,但第二步就吃力了。
你想想看,宏观数据有几十个维度:利率、通胀、就业、PMI、信贷利差...每个变量对曲面的不同区域影响都不一样。而且这些影响还有滞后效应——今天的CPI数据,可能三天后才完全反映在曲面尾部。
这就是典型的非线性时序问题。LSTM擅长捕捉长短期依赖,Transformer能并行处理多变量交互。说白了,它们就是为这类问题设计的。
13.2 LSTM:捕捉曲面时序依赖
先说说LSTM。我在项目中遇到过一个问题:用过去20个交易日的曲面数据预测下一个交易日的曲面,LSTM的效果明显优于ARIMA和VAR。
为什么?因为LSTM有遗忘门和记忆门。它能自动学习哪些历史信息该保留,哪些该丢弃。比如市场突然出现极端波动,LSTM会快速调整记忆权重,而不会像传统模型那样被历史数据拖累。
下面是我常用的LSTM结构:
import torch
import torch.nn as nn
class VolSurfaceLSTM(nn.Module):
def __init__(self, input_dim, hidden_dim, num_layers, output_dim):
super().__init__()
self.lstm = nn.LSTM(
input_size=input_dim,
hidden_size=hidden_dim,
num_layers=num_layers,
batch_first=True,
dropout=0.2
)
self.fc = nn.Sequential(
nn.Linear(hidden_dim, 64),
nn.ReLU(),
nn.Dropout(0.1),
nn.Linear(64, output_dim)
)
def forward(self, x):
# x shape: (batch, seq_len, input_dim)
lstm_out, (h_n, c_n) = self.lstm(x)
# 取最后一个时间步的输出
last_out = lstm_out[:, -1, :]
return self.fc(last_out)
这里有个细节要注意。input_dim包含了曲面参数和宏观数据。我通常把曲面参数(比如SVI的5个参数)和宏观数据(比如10个关键指标)拼接在一起,作为每个时间步的输入。
13.3 Transformer:并行处理宏观变量交互
LSTM有个短板——它是串行处理的。每个时间步依赖前一个时间步的输出,训练速度慢,而且对长序列的记忆会衰减。
Transformer就不一样了。它的自注意力机制可以同时看到所有时间步,还能捕捉不同宏观变量之间的交互关系。比如利率和通胀之间、就业和消费之间,这些交互对曲面形态的影响,Transformer能自动学习。
我常用的Transformer结构是这样的:
class VolSurfaceTransformer(nn.Module):
def __init__(self, d_model, nhead, num_layers, dim_feedforward):
super().__init__()
self.embedding = nn.Linear(input_dim, d_model)
self.pos_encoder = PositionalEncoding(d_model)
encoder_layer = nn.TransformerEncoderLayer(
d_model=d_model,
nhead=nhead,
dim_feedforward=dim_feedforward,
dropout=0.1,
batch_first=True
)
self.transformer = nn.TransformerEncoder(
encoder_layer,
num_layers=num_layers
)
self.fc_out = nn.Linear(d_model, output_dim)
def forward(self, x):
x = self.embedding(x)
x = self.pos_encoder(x)
x = self.transformer(x)
# 取全局平均池化
x = x.mean(dim=1)
return self.fc_out(x)
13.4 宏观数据预处理:一个容易被忽视的环节
嗯,这里要注意。宏观数据的频率和发布节奏跟曲面数据不一样。曲面数据是日频的,但CPI是月频的,非农是月频的,利率决议是季度性的。
我建议做三步处理:
- 对齐频率: 把低频宏观数据向前填充,保证每个交易日都有值
- 标准化: 宏观数据量纲差异大,必须做Z-score标准化
- 滞后特征: 加入宏观数据的滞后项(比如滞后1天、3天、5天),让模型自己学习滞后效应
举个例子,CPI数据发布后,市场可能需要几天才能完全消化。如果你只输入当天的CPI值,模型可能学不到这种滞后影响。
13.5 训练策略与评估
训练这类模型,我踩过不少坑。分享几个关键点:
| 策略 | 说明 | 我的建议 |
|---|---|---|
| 数据划分 | 按时间顺序划分,不能随机打乱 | 前70%训练,中间15%验证,最后15%测试 |
| 损失函数 | 曲面不同区域的误差权重不同 | 对近月ATM区域加权更高 |
| 早停法 | 防止过拟合 | 验证集loss连续5个epoch不降就停 |
| 集成学习 | 多个模型取平均 | LSTM+Transformer集成效果最好 |
13.6 一个完整的训练流程
最后,给你看看我实际项目中用的完整流程。这个流程跑过实盘,效果还不错。
# 伪代码:完整训练流程
def train_vol_surface_model():
# 1. 加载数据
surface_data = load_surface_params() # SVI参数
macro_data = load_macro_indicators() # 宏观指标
# 2. 数据预处理
aligned_data = align_frequencies(surface_data, macro_data)
normalized_data = normalize(aligned_data)
# 3. 构建序列
X, y = create_sequences(normalized_data, seq_len=20)
# 4. 划分数据集
X_train, X_val, X_test = split_by_time(X, y)
# 5. 初始化模型
model = VolSurfaceTransformer(
d_model=128,
nhead=8,
num_layers=4,
dim_feedforward=512
)
# 6. 训练
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer)
for epoch in range(100):
train_loss = train_epoch(model, X_train, y_train)
val_loss = validate(model, X_val, y_val)
scheduler.step(val_loss)
if early_stopping(val_loss):
break
# 7. 评估
test_loss = evaluate(model, X_test, y_test)
print(f"Test RMSE: {test_loss:.4f}")
这个流程跑下来,我通常能得到比传统方法低15-20%的预测误差。当然,具体效果取决于你的数据质量和参数调优。
这张图展示了整个建模流程。从数据输入到预处理,再到模型选择和集成,最后输出预测的曲面参数。每一步都有坑,但每一步也都有优化空间。