第24章:深度学习进阶:GAN生成对抗网络模拟正常曲面

说实话,做波动率曲面异常检测这么多年,我一直有个执念——我们到底能不能让模型自己学会「正常」长什么样?

传统方法无非是设阈值、算统计量、做回归。但市场是活的,曲面形态千变万化。你设的阈值,今天管用,明天可能就失效了。我2019年在做期权做市商系统时,就被这个问题折磨得不轻。

后来我想到一个思路:用GAN来生成正常曲面。让生成器和判别器互相博弈,最终学会真实数据的分布。这样,任何偏离这个分布的样本,自然就是异常点。

核心思想:GAN不是用来「伪造」数据,而是用来学习「正常」的边界。异常点就是那些生成器怎么也模仿不像的样本。

24.1 为什么是GAN?

你可能会问:用自编码器(Autoencoder)不行吗?当然可以。但我个人习惯用GAN,原因有三:

  • 生成质量更高:GAN的对抗训练机制,迫使生成器产出更逼真的样本。我在项目中对比过,GAN生成的曲面比VAE更平滑、更符合市场逻辑。
  • 对异常更敏感:判别器天然就是一个异常检测器。它判断「真/假」的过程,其实就是判断「正常/异常」的过程。
  • 无需标注数据:我们只需要大量正常曲面样本。异常样本?不需要。这在金融领域太实用了——异常事件本来就少。
我的经验:GAN训练确实不稳定。但一旦调好,效果远超传统方法。我建议先用小规模数据跑通,再上全量数据。

24.2 网络架构设计

我们设计一个条件GAN(Conditional GAN),输入条件包括:到期时间、行权价、隐含波动率。输出是一个完整的波动率曲面。

先看整体结构:

GAN生成波动率曲面架构图 生成器 G 输入:随机噪声 + 条件 输出:生成曲面 判别器 D 输入:真实/生成曲面 输出:真/假概率 真实曲面数据 生成曲面 对抗损失反馈 条件:T, K, σ 异常分数输出

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训练是个技术活。我总结了一套比较稳定的训练流程:

  1. 先预训练判别器:用真实数据训练100个epoch,让判别器先学会识别真实曲面。
  2. 交替训练:每更新一次生成器,更新三次判别器。保持两者平衡。
  3. 加入梯度惩罚:WGAN-GP的梯度惩罚项,能有效防止模式崩塌。
  4. 学习率衰减:初始学习率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通过对抗训练学习正常曲面分布
  • 判别器分数 + 重构误差 = 异常分数
  • 训练时注意判别器和生成器的平衡
  • 实际部署需要做数据归一化和模型监控

公众号:蓝海资料掘金营,微信deep3321