基于偏微分方程的金融建模的傅立叶神经算子
到目前为止,本博客上的每个神经网络都近似于一个“函数”:输入特征,输出数字。傅里叶神经算子近似于算子——无限维函数空间之间的映射,其中输入是整个波动率表面,输出是整个价格表面。这是一个不同的对象,它需要不同的机制,而机制是本文的重点:应用于最低傅里叶模式的可学习复值内核,通过 FFT 往返进行评估 。
期权定价是金融领域的重点。 Black-Scholes、Heston、局部波动性——所有偏微分方程,今天都一次解决一组参数。操作员立即学习整个参数系列,并且生成的前向传递是单个批处理的 GPU 内核,而不是时间推进循环。
这就是承诺。本文的诚实版本将机制与性能声明区分开来,这些机制是可靠的,并且可以从下面的代码中重现,在 FNO 文献中,这些性能声明是根据本系列中没有人会接受未经检查的基线进行报告的。机械是第一位的;测量议程位于最后,标记为未运行。
从函数逼近到算子学习
经典神经网络近似函数:给定输入 ,它们产生一个输出 。这是强大的,但当感兴趣的对象本身就是函数时,它从根本上受到限制。在金融偏微分方程求解中,输入不是单个数字,而是描述初始/边界条件、波动率表面或期限结构的函数。输出是另一个函数:价格表面 空间。
算子学习将问题提升到无限维空间。而不是学习 ,我们学习一个运算符:
在哪里 和 是巴拿赫函数空间。对于期权定价, 可能是波动面的空间 和 对应价格面的空间 。
两种架构在操作员学习领域占据主导地位:
-
DeepONet (Lu et al., 2021):使用分支网络对输入函数进行编码,并使用主干网络对查询位置进行编码。输出是它们的内积。以 Chen 和 Chen (1995) 的算子万能逼近定理为基础。
-
傅里叶神经算子(Li et al., 2021):参数化傅里叶空间中的积分核,使用 FFT 进行高效的全局卷积。结构分辨率不变。
两者都是连续算子的通用逼近器,但 FNO 对于 PDE 问题具有结构优势:它的谱偏差自然地捕获了 PDE 解的平滑、全局结构。它对于期权收益也有一个结构性的“缺点”,我们稍后会讨论这一点——截断的傅里叶基础和罢工的扭结并不是天然的盟友。
FNO 架构详细信息
Li 等人提出的傅立叶神经算子。在 ICLR 2021 上,建立在一个简单但强大的观察之上:许多偏微分方程的格林函数(积分核)在傅立叶空间中具有紧凑的表示。而不是在物理空间中学习内核——这需要 参数为 网格点——FNO 在频率空间中仅用最低的值来学习它 模式,将复杂性降低到 通过FFT。
迭代架构
FNO 包括:
-
提升层 :将输入从原始通道维度投影到更高维度的潜在表示的逐点线性映射: 。
-
傅里叶层(重复 次):每层适用:
在哪里 是局部线性变换(逐点 卷积)和 是通过 FFT 实现的全局积分运算符:
这里 表示 FFT, 是一个可学习的复值权重张量,应用于最低 傅里叶模式,以及 是逐点非线性激活(通常是 GELU)。
- 投影层 :将潜在表示映射回输出维度: 。
为什么选择傅里叶空间?
谱卷积 是频域中的乘法,相当于物理空间中的全局卷积 - 但计算为 而不是 。这不仅仅是一个效率技巧。 PDE 解通常是平滑的并且以低频分量为主。通过截断到 模式中,FNO 充当可学习的低通滤波器,自然地规范解决方案并避免高频伪影。
至关重要的是,FNO 据称具有离散化不变:一旦在一定大小的网格上进行训练 ,可以在任何分辨率上进行评估 通过简单地调整 FFT 大小和零填充或截断频谱权重。这种零样本超分辨率属性在神经 PDE 求解器中是独一无二的 - 这是本文中第一个值得测量而不是引用的主张。请参阅下面的测量议程。
我们正在学习算子的偏微分方程
Black-Scholes PDE — 逐项推导、剖析,并在 The Black-Scholes Formula 中给出其封闭式看涨/看跌解决方案 — 是运算符目标:
接下来的一切都将其视为一个具有已知答案的黑匣子。这个已知的答案正是为什么它是正确的测试用例:可以根据有限差分输出训练的操作员进行评分 norm.cdf 准确的价格,文献中几乎没有 FNO 基准可以做到。
FNO 配方
我们将问题重新定义为操作员学习。定义:
- 输入功能 :对 PDE 参数进行编码。这可以包括波动率表面 、支付函数和无风险利率作为通道堆叠在 网格。
- 输出功能 :期权价格面。
FNO 学习 从数据集 由传统求解器生成的对。训练后,任何新参数配置的推理都是一次前向传递。
训练数据生成
import numpy as np
from scipy.stats import norm
def black_scholes_fd(sigma, r, K, T, S_max=300, N_S=256, N_t=256):
"""Solve Black-Scholes PDE via explicit finite differences.
NOTE: this is an interpreted double loop — the *worst* CPU baseline,
exactly the kind called out in /en/blog/post/when-gpu-pays-off-sweep-roofline.
It is fine for generating training data offline. It is NOT the baseline
any speedup claim should be measured against; vectorize the inner loop
over i (or use scipy sparse + implicit stepping) before timing anything.
"""
dS = S_max / N_S
dt = T / N_t
S = np.linspace(0, S_max, N_S + 1)
V = np.maximum(S - K, 0).astype(np.float64)
for j in range(N_t):
V_new = V.copy()
for i in range(1, N_S):
delta = (V[i+1] - V[i-1]) / (2 * dS)
gamma = (V[i+1] - 2*V[i] + V[i-1]) / (dS**2)
V_new[i] = V[i] + dt * (
0.5 * sigma**2 * S[i]**2 * gamma
+ r * S[i] * delta
- r * V[i]
)
V_new[0] = 0
V_new[N_S] = S_max - K * np.exp(-r * (T - (j+1)*dt))
V = V_new
return S, V
为了进行训练,我们对数千种参数配置进行了采样——不同的 , , , - 并用有限差分法求解每个问题。 FNO 可以从中学习输入输出对的结果数据集。
扩大规模:赫斯顿随机波动率模型
持续波动性是一个已知错误的假设,而它失败的原因——微笑、肥尾——是布莱克-斯科尔斯公式“严酷的现实”部分的主题。 Heston 通过将方差作为第二个状态变量来修复它:
和 。期权价格对应的 PDE 在空间中是二维的:
这就是算子学习发挥作用的地方,而且这个论点是结构性的,而不是经验性的。有限差分方案 网格尺度为 ,交叉导数项 使离散化变得复杂,并且每个新的参数集 再次支付全部费用。操作员在培训时支付一次。 Heston PDE 的 2D 空间结构也直接映射到傅里叶层中的 2D FFT,因此下面的架构通过维度更改而不是重新设计进行概括。
用于期权定价的 FNO:PyTorch 实现
下面是一个完整的、独立的 FNO 实现,用于学习 Black-Scholes 算子。该架构遵循 Li 等人的观点。 (2021)并针对金融环境进行了调整。
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.fft import rfft, irfft
class SpectralConv1d(nn.Module):
"""1D Fourier layer: spectral convolution via FFT."""
def __init__(self, in_channels: int, out_channels: int, modes: int):
super().__init__()
self.in_channels = in_channels
self.out_channels = out_channels
self.modes = modes # Number of Fourier modes to keep
scale = 1.0 / (in_channels * out_channels)
self.weights = nn.Parameter(
scale * torch.randn(in_channels, out_channels, modes, dtype=torch.cfloat)
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
batch_size = x.shape[0]
x_ft = rfft(x, dim=-1)
out_ft = torch.zeros(
batch_size, self.out_channels, x_ft.size(-1),
dtype=torch.cfloat, device=x.device
)
out_ft[:, :, :self.modes] = torch.einsum(
"bix,iox->box", x_ft[:, :, :self.modes], self.weights
)
return irfft(out_ft, n=x.size(-1), dim=-1)
class FNOBlock(nn.Module):
"""Single Fourier Neural Operator block."""
def __init__(self, channels: int, modes: int):
super().__init__()
self.spectral_conv = SpectralConv1d(channels, channels, modes)
self.pointwise = nn.Conv1d(channels, channels, kernel_size=1)
self.norm = nn.InstanceNorm1d(channels)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return F.gelu(self.norm(self.spectral_conv(x) + self.pointwise(x)))
class FNO1d(nn.Module):
"""
Fourier Neural Operator for 1D PDE problems.
Learns the mapping: PDE parameters -> solution function
"""
def __init__(
self,
in_channels: int = 3, # e.g., sigma(S), payoff(S), grid(S)
out_channels: int = 1, # V(S)
hidden_channels: int = 64,
modes: int = 32,
num_layers: int = 4,
):
super().__init__()
self.lift = nn.Linear(in_channels, hidden_channels)
self.blocks = nn.ModuleList(
[FNOBlock(hidden_channels, modes) for _ in range(num_layers)]
)
self.proj = nn.Sequential(
nn.Linear(hidden_channels, 128),
nn.GELU(),
nn.Linear(128, out_channels),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.lift(x) # -> (batch, spatial, hidden)
x = x.permute(0, 2, 1) # -> (batch, hidden, spatial)
for block in self.blocks:
x = block(x)
x = x.permute(0, 2, 1) # -> (batch, spatial, hidden)
return self.proj(x) # -> (batch, spatial, out_channels)
训练循环
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
def train_fno_black_scholes():
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
N_samples = 5000
N_S = 256
S_max = 300.0
S_grid = np.linspace(0, S_max, N_S + 1)
inputs, targets = [], []
for _ in range(N_samples):
sigma = np.random.uniform(0.05, 0.80)
r = np.random.uniform(0.01, 0.10)
K = np.random.uniform(50, 150)
T = np.random.uniform(0.1, 2.0)
_, V = black_scholes_fd(sigma, r, K, T, S_max=S_max, N_S=N_S)
sigma_field = np.full(N_S + 1, sigma)
payoff = np.maximum(S_grid - K, 0)
grid_norm = S_grid / S_max
inp = np.stack([sigma_field, payoff, grid_norm], axis=-1)
inputs.append(inp)
targets.append(V[:, None])
X = torch.tensor(np.array(inputs), dtype=torch.float32)
Y = torch.tensor(np.array(targets), dtype=torch.float32)
dataset = TensorDataset(X, Y)
loader = DataLoader(dataset, batch_size=64, shuffle=True)
model = FNO1d(in_channels=3, out_channels=1, hidden_channels=64, modes=32).to(device)
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=200)
for epoch in range(200):
model.train()
total_loss = 0.0
for batch_x, batch_y in loader:
batch_x, batch_y = batch_x.to(device), batch_y.to(device)
pred = model(batch_x)
loss = torch.mean(
torch.norm(pred - batch_y, dim=1)
/ torch.norm(batch_y, dim=1).clamp(min=1e-8)
)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
scheduler.step()
if (epoch + 1) % 20 == 0:
avg = total_loss / len(loader)
print(f"Epoch {epoch+1:3d} | Relative L2 Loss: {avg:.6f}")
return model
推理:实时定价
@torch.no_grad()
def price_option(model, sigma, K, S_grid, device="cuda"):
"""
Price a European call for given sigma and strike.
Returns prices for all S in S_grid — single forward pass.
"""
S_max = S_grid[-1]
payoff = np.maximum(S_grid - K, 0)
grid_norm = S_grid / S_max
sigma_field = np.full_like(S_grid, sigma)
inp = np.stack([sigma_field, payoff, grid_norm], axis=-1)
x = torch.tensor(inp, dtype=torch.float32).unsqueeze(0).to(device)
pred = model(x)
return pred.squeeze().cpu().numpy()
FNO 与 PINN:实际比较
物理信息神经网络和傅立叶神经算子代表了使用神经网络求解偏微分方程的根本不同的哲学。了解他们的权衡是关心算子学习的实际原因。
PINN:每个实例优化
PINN 将 PDE 残差直接放入损失中 - 纳维-斯托克斯问题 涵盖了自动差分机制 physics_loss 实现,以及 DeepMind 奇点搜索结果。这里唯一重要的属性是结构性的:PINN 根据参数集进行训练。 更改 , , 或者 然后你从头开始再次优化。
优点:无需标记数据。直接强制执行 PDE 结构。适用于您可以写下的任何偏微分方程。
弱点:必须针对每个新参数集重新训练 。训练涉及平衡多个损失项(PDE 残差、边界条件、初始条件),这通常会导致优化病态。收敛速度可能很慢——通常每个问题实例需要 10,000–100,000 个梯度步。在损失景观变得高度非凸的多尺度和混沌系统上失败。
FNO:摊销算子学习
FNO 从数据中学习解算子。它需要由经典求解器生成的训练数据集,但一旦训练完毕,它就会泛化到整个参数空间。
优势:已发布的基准中的亚毫秒级推理。无需重新训练即可推广到看不见的参数。分辨率不变——以低分辨率训练,以高分辨率评估。通过谱偏差自然地处理平滑的 PDE 解决方案。
弱点:需要来自经典求解器的训练数据(真正新颖的偏微分方程的先有鸡还是先有蛋的问题)。近似误差有界但非零。与偏微分方程约束方法相比,其可解释性较差。有助于平滑解决方案的光谱偏差却是回报扭结的一种负担。
头对头比较
下表是文献报道的,此处未测量 - 准确度和加速比行尤其来自 Li 等人。 (2021)以及流动基准的后续工作,而不是来自我们硬件上的期权定价。将结构行(所需数据、泛化、分辨率不变性)读取为可靠行。
| 标准 | 平 | 飞诺 |
|---|---|---|
| 所需的培训数据 | 无(无人监督) | 求解器生成的对 |
| 推理成本 | 每个实例的完整再训练 | 单次前传 |
| 概括 | 单参数设置 | 整个参数系列 |
| 分辨率不变性 | 没有 | 声称是——参见下面的议程 |
| 多尺度偏微分方程 | 经常失败 | 报告稳健 |
| 准确度(相对 、文学) | 到 | 到 |
混合体:基于物理的神经算子 (PINO)
PINO(Li et al., 2024)结合了这两种方法。它使用 FNO 架构,但通过 PDE 残差项增强了数据驱动的损失:
分解是有用的部分。 将运算符锚定到解算器输出(无论您拥有什么); 在您不这样做的任何地方对其进行约束,包括您从未采样过的参数空间区域。对于期权定价,第二项由于金融特定的原因而具有吸引力:PDE 残差是一个“硬”约束,您也可以在推理时评估作为自检,这是下面残差监控回退的基础。
生产部署的实际注意事项
其中通用的一半——推理延迟预算、构建数据管道、监控漂移、定期再训练——已经在DeepLOB:限价订单簿上的深度学习的生产部分的交易系统中的神经模型中涵盖,并且它不改变地适用。以下内容仅针对操作员。
数据管道:对参数空间进行采样
生成训练数据是主要瓶颈,与市场数据模型不同,你可以选择自己的分布——这意味着你可能会以一种在损失中不可见的方式出错。对于具有 4 个参数的 Black-Scholes 算子,5,000–10,000 个样本就足够了。对于具有 5 个参数加上 2D 空间域的 Heston,典型情况是 20,000–50,000 个样本。使用自适应采样:将样本集中在解决方案快速变化的参数区域(接近货币、短期、高波动性),因为盒子的均匀采样将大部分预算花费在深度 ITM 和深度 OTM 区域,在这些区域中,算子几乎是线性的,并且可以从很少的示例中很好地学习。
架构调整
- 模式 (): 开始于 在哪里 是空间网格大小。为了 ,使用 32–64 模式。太少的模式会在走向边界附近丢失细节;太多对噪声的过度拟合。
- 层:4 个傅立叶层是标准配置。更深的网络 (6-8) 有助于解决像 Heston 这样的 2D 问题,但会增加内存。
- 隐藏通道:1D Black-Scholes 为 64,2D Heston 为 128。随着问题的复杂性而扩展。
精度:fp32 问题
SpectralConv1d 分配 torch.cfloat — 单精度复数 — 每个 FFT 往返都以该精度运行。本博客已经在 GPU 精度陷阱 中看到了 fp32 金融管道返回无声垃圾,其中数学上正确的前缀和公式在 fp32 量级上发生了灾难性的损失。这里的类似问题是直接的:在 以美分报价,FP32 光谱往返是否成立 绝对值,还是 FFT 的中间幅度增长会吃掉最后的有效数字?
错误控制
对于生产期权定价,您需要可以防御的误差界限。
- 校准的不确定性:集合标准偏差不是覆盖范围保证,并且本博客有适当的机制来做到这一点 - 在保留的参数网格上分割保形,对于不可交换的情况使用 ACI/DtACI,在风险意识头寸规模调整的保形预测中。将 FNO 包裹在分裂保形中 网格给出了价格的无分布区间。
- 残差监控:计算 FNO 预测的 PDE 残差作为事后检查。如果 ,回到经典求解器。这在推理时是免费的——残差是您已有的数组上的有限差分模板。
- 主动学习:将高不确定性输入路由到经典求解器,将结果添加到训练集中,并定期重新训练。 (1) 中的保形间隔宽度是自然路由信号。
衡量议程
这就是决定上面的架构是否值得部署的部分,目前还没有完成。它被列在这里而不是被埋没,因为另一种选择——声称标题加速——正是这个博客要避免的寄存器。
1.加速比是一条曲线,而不是一个数字。 当 GPU 发挥作用时 建立了形状: ,从开销主导上升 到计算密集型平台,它显示标题 167 倍分解为 27 倍算法乘以 6.2 倍硬件。 FNO 测量必须遵循该模板:挂钟前向传播 并报告整个曲线。至关重要的是,基线必须是一个“矢量化、多核”有限差分求解器—— black_scholes_fd 上面是一个解释的双循环,即文章所命名的确切“最差 CPU 实现”基线,将批处理 GPU 运算符与它进行比较将产生一个毫无意义的数字。
2.针对封闭形式的准确性。 这是 Black-Scholes 几乎免费进行的实验,而流体动力学基准根本无法做到:训练 FD 生成的数据,然后根据精确值进行评分 norm.cdf 各地的价格 盒子。举报亲属 ,并报告错误分布 - 特别是在接近罢工和接近到期时,其中解决方案最不平滑,并且截断的频谱基础应该最困难。
3.无套利违规。 学习算子没有结构性原因来遵守价格表面必须满足的形状约束:单调性 ,凸性在 , 和 。测量参数框中的违规率将本文自己的开放性问题(我们能否保证学习运算符中的无套利条件?)从挥手变成了数字。
4.离散化不变性,经过测试而不是断言。 训练 ,评估于 ,报告错误。预期的故障模式已命名且具体:吉布斯扭结振铃 。截断傅里叶基重建具有不连续一阶导数的函数,并在其周围振荡,而零样本超分辨率会暴露训练分辨率从未见过的模式,从而使情况变得更糟而不是更好。如果超分辨率在接近罢工时降级,那么这种负面结果比它所取代的营销宣传更有价值——FNO 文献中没有人以回报扭结来定价期权。
如果实验 1-4 无法运行,本文不应发布。没有它们,剩下的是对其他人 ICLR 论文的精心阐述。
超越普通选项
假设上述议程在与测量的接触中仍然存在,FNO 框架自然会扩展到更复杂的仪器:
- 美式期权:添加早期行权边界作为额外的输出通道。 FNO 同时学习价格面和最佳执行边界。
- 屏障选项:将屏障级别编码为输入通道。请注意,障碍是第二个不连续性,测量议程中的吉布斯关注更加适用,而不是更少。
- 多资产篮子:使用 2D 或 3D FNO 作为 2-3 个底层证券的篮子期权。维数灾难没有基于网格的求解器那么严重,因为 FNO 运行在固定数量的模式上。
- 局部波动率:输入完整的 Dupire 局部波动率表面 作为空间函数。这是算子学习最自然的用例——输入是一个函数,而不是一个标量参数。
结论
傅里叶神经算子的贡献在计算之前是概念性的:您不是一次求解一个偏微分方程,而是参数化解算子本身,并且在傅里叶空间中执行此操作会产生一种架构,该架构是 ,在连续算子上通用,并且结构上分辨率灵活。这是李等人的结论。 (2021) 以及上面运行的代码。
接下来是加速数字。文献中的 100x-1000x 数字是在流动基准上根据很少说明质量的基线来测量的,本系列用整篇文章来展示这样的标题通常有多少是算法而不是硬件。证明将算子放入定价引擎中的合理性的主张——美分级 fp32 精度、有限的无套利违规率、在支付扭结中幸存的超分辨率,以及针对“有能力的”CPU 求解器的加速曲线——都是可测量的,在 Black-Scholes 上都很便宜,因为封闭形式存在,而且所有这些在这里仍然无法测量。
这就是这份草案的状态:该机制是真实的,承诺是可信的,证据有待确定。
参考文献和进一步阅读:
- Li, Z.、Kovachki, N.、Azizzadenesheli, K. 等人。 “参数偏微分方程的傅里叶神经算子。” ICLR 2021。arXiv:2010.08895
- Lu, L.、Jin, P.、Pang, G.、Zhang, Z. 和 Karniadakis, G.E. “通过 DeepONet 学习非线性算子。”自然机器智能,2021。doi:10.1038/s42256-021-00302-5
- Li, Z. 等人。 “用于学习偏微分方程的物理信息神经算子。” ACM/IMS 数据科学杂志,2024 年。OpenReview
neuraloperatorPyTorch 库:github.com/neuraloperator/neuraloperator- 萨尔瓦多,M.,等人。 “用于期权定价的 Black-Scholes 方程的神经网络学习。” arXiv:2405.05780
- Bai,Y.,等人。 “AI Black-Scholes:金融信息神经网络。” arXiv:2412.12213
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.