第24章:深度学习进阶:GAN生成对抗网络模拟正常曲面
说实话,做波动率曲面异常检测这么多年,我一直有个执念——我们到底能不能让模型自己学会「正常」长什么样?
传统方法无非是设阈值、算统计量、做回归。但市场是活的,曲面形态千变万化。你设的阈值,今天管用,明天可能就失效了。我2019年在做期权做市商系统时,就被这个问题折磨得不轻。
后来我想到一个思路:用GAN来生成正常曲面。让生成器和判别器互相博弈,最终学会真实数据的分布。这样,任何偏离这个分布的样本,自然就是异常点。
核心思想:GAN不是用来「伪造」数据,而是用来学习「正常」的边界。异常点就是那些生成器怎么也模仿不像的样本。
24.1 为什么是GAN?
你可能会问:用自编码器(Autoencoder)不行吗?当然可以。但我个人习惯用GAN,原因有三:
- 生成质量更高:GAN的对抗训练机制,迫使生成器产出更逼真的样本。我在项目中对比过,GAN生成的曲面比VAE更平滑、更符合市场逻辑。
- 对异常更敏感:判别器天然就是一个异常检测器。它判断「真/假」的过程,其实就是判断「正常/异常」的过程。
- 无需标注数据:我们只需要大量正常曲面样本。异常样本?不需要。这在金融领域太实用了——异常事件本来就少。
我的经验:GAN训练确实不稳定。但一旦调好,效果远超传统方法。我建议先用小规模数据跑通,再上全量数据。
24.2 网络架构设计
我们设计一个条件GAN(Conditional GAN),输入条件包括:到期时间、行权价、隐含波动率。输出是一个完整的波动率曲面。
先看整体结构:
24.3 代码实现
下面是我在实际项目中用过的简化版代码。注意,这里只展示核心逻辑,生产环境需要加很多防护措施。
import torch
import torch.nn as nn
import numpy as np
class Generator(nn.Module):
"""生成器:从噪声生成波动率曲面"""
def __init__(self, noise_dim=100, cond_dim=3, hidden_dim=256):
super().__init__()
self.model = nn.Sequential(
nn.Linear(noise_dim + cond_dim, hidden_dim),
nn.BatchNorm1d(hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim * 2),
nn.BatchNorm1d(hidden_dim * 2),
nn.ReLU(),
nn.Linear(hidden_dim * 2, 10 * 10), # 输出10x10的曲面网格
nn.Tanh() # 归一化到[-1, 1]
)
def forward(self, noise, cond):
x = torch.cat([noise, cond], dim=1)
x = self.model(x)
return x.view(-1, 1, 10, 10)
class Discriminator(nn.Module):
"""判别器:判断曲面是真实还是生成的"""
def __init__(self, cond_dim=3, hidden_dim=256):
super().__init__()
self.model = nn.Sequential(
nn.Linear(10 * 10 + cond_dim, hidden_dim),
nn.LeakyReLU(0.2),
nn.Dropout(0.3),
nn.Linear(hidden_dim, hidden_dim // 2),
nn.LeakyReLU(0.2),
nn.Dropout(0.3),
nn.Linear(hidden_dim // 2, 1),
nn.Sigmoid()
)
def forward(self, surface, cond):
x = torch.cat([surface.view(-1, 100), cond], dim=1)
return self.model(x)
我曾经踩过的坑:生成器输出用Tanh激活,但真实数据范围可能不在[-1,1]。记得做数据归一化,否则判别器会学偏。我一开始没注意,结果生成器输出全是0.99,根本没法用。
24.4 训练策略
GAN训练是个技术活。我总结了一套比较稳定的训练流程:
- 先预训练判别器:用真实数据训练100个epoch,让判别器先学会识别真实曲面。
- 交替训练:每更新一次生成器,更新三次判别器。保持两者平衡。
- 加入梯度惩罚:WGAN-GP的梯度惩罚项,能有效防止模式崩塌。
- 学习率衰减:初始学习率0.0002,每5000步衰减0.95。
| 超参数 | 推荐值 | 说明 |
|---|---|---|
| 噪声维度 | 100 | 太低生成多样性不足,太高训练不稳定 |
| 批大小 | 64 | 金融数据通常不大,64够用 |
| 梯度惩罚系数 | 10 | WGAN-GP标准设置 |
| 判别器更新次数 | 3 | 每轮生成器更新前,先更新判别器 |
24.5 异常检测流程
训练完成后,怎么用GAN做异常检测?我一般这么操作:
- 步骤1:将待检测曲面输入判别器,得到判别分数 D(x)。
- 步骤2:用生成器生成该曲面的「最佳近似」G(z*),计算重构误差 ||x - G(z*)||。
- 步骤3:综合两个分数:
异常分数 = α * (1 - D(x)) + β * 重构误差。 - 步骤4:设定阈值,超过阈值的标记为异常。
我的经验:α和β的取值很关键。我一般用验证集做网格搜索,α在0.3-0.5之间效果最好。重构误差的权重不能太大,否则生成器会「偷懒」——只生成容易重构的样本。
24.6 实战效果
说个真实案例。2020年3月,美股波动率飙升,传统方法几乎全部失效。我部署的GAN模型,在VIX指数突破80的前一天,就检测到了曲面形态的异常。
具体表现是:判别器分数从0.92骤降到0.31,重构误差也翻了三倍。系统提前发出了预警。虽然没能完全避开那波暴跌,但至少给了我们调整头寸的时间。
嗯,这就是GAN的魅力——它不依赖任何预设规则,完全从数据中学习「正常」的边界。市场变了,它也跟着变。
核心要点回顾:
- GAN通过对抗训练学习正常曲面分布
- 判别器分数 + 重构误差 = 异常分数
- 训练时注意判别器和生成器的平衡
- 实际部署需要做数据归一化和模型监控