跳到正文

神经网络:诚实的版本

神经网络就是一串矩阵乘法加非线性,用梯度下降训出来。它的卖点是灵活——而在量化数据上,灵活是一笔负债,除非你的数据自己带结构。

第 6 篇 · 共 8 篇 约 10 分钟

神经网络:诚实的版本

神经网络不是魔法。它是一串矩阵乘法加非线性,用梯度下降去最小化一个损失。所谓”智能”藏在架构选择、数据规模和正则化里。把包装剥掉,神经网络就是一个非常灵活的函数,配一套非常昂贵的训练流程。

灵活性既是它的魅力也是它的陷阱。参数给够,它灵活到能拟合出一只长颈鹿。它学到的是长颈鹿,还是把你的训练数据背了下来,取决于品味和正则化——而在量化数据上,这个区别几乎是这门手艺的全部。这篇文章讲机制,也讲为什么机制学会之后,大多数量化 ML 还是在用梯度提升。

一个神经元

单个人工神经元计算:

y=σ(w⋅x+b)y = \sigma(w \cdot x + b)

其中 xx 是输入向量,ww 是权重向量,bb 是偏置标量,σ\sigma 是非线性激活函数。整个神经元就这些:拿输入的线性组合,加上偏置,过一个非线性。一个这样的神经元能解线性可分的分类问题;XOR 不行,但那是另一篇文章的故事。原始的感知机(Rosenblatt, 1958)用阶跃函数当 σ\sigma,现代网络改用平滑的非线性,正是这个改动让它们能被梯度下降训练。

堆起来就是 MLP

把神经元一层层堆起来,你就得到多层感知机:

h(1)=σ(W(1)x+b(1))h^{(1)} = \sigma(W^{(1)} x + b^{(1)}) h(2)=σ(W(2)h(1)+b(2))h^{(2)} = \sigma(W^{(2)} h^{(1)} + b^{(2)}) y^=W(3)h(2)+b(3)\hat{y} = W^{(3)} h^{(2)} + b^{(3)}

这里是三层——输入、一个隐藏层、输出——但层数可以是几十甚至几百。每一层都是一次矩阵乘法接一个非线性。输出层看任务定:二分类用 sigmoid,多分类用 softmax,回归用线性层。

为什么要深?只要神经元够多,一个单隐藏层的浅网络就能逼近紧致域上的任何连续函数,这是万能逼近定理。问题在于对某些函数,“够多”可能是指数级个。深网络——层多、每层窄一些——学层次表示更高效:一个图像分类器先学边、再学纹理、再学物体部件、再学物体,一层一层来;一个单隐藏层网络得在一个巨大的层里把这一切全学完,需要荒谬的宽度。

不过深度有帮助但不神奇。从 3 层加到 10 层有帮助;在全连接网络加表格数据上,从 50 层加到 100 层通常什么也不给你。

激活函数:关键在会不会饱和

σ\sigma 的选择比大家承认的更重要,而挑选标准基本就一条:它在大输入下会不会把梯度掐死。

Sigmoid σ(x)=1/(1+e−x)\sigma(x) = 1 / (1 + e^{-x}) 平滑、有界在 (0,1)(0,1),历史上的默认选择,也带着一个糟糕的属性:∣x∣|x| 一大,梯度基本是零,训练就停在那儿不动了。这就是梯度消失问题,也是 sigmoid 今天很少出现在隐藏层的原因。Tanh 跟它类似但有界在 (−1,1)(-1, 1),零中心,对梯度流稍微友好一点,大输入下照样饱和。

ReLU σ(x)=max⁡(0,x)\sigma(x) = \max(0, x) 是现代默认:负输入输出零,正输入保持线性,正侧不饱和,算起来还便宜。它的代价是另一种死法——如果一个神经元的输入一直是负的,它的梯度永远为零、永远不更新,这叫”死 ReLU”,缓解手段是 leaky ReLU(max⁡(0.01x,x)\max(0.01x, x))和小心的初始化。GELU 和 SwiGLU 是现代 Transformer 用的更平滑变体,优化性质略好、准确度略高,以后要微调语言模型值得知道,对表格量化数据基本是过度配置。

对表格特征的量化 ML,ReLU 就够了。把时间花在数据质量和正则化上,别花在激活函数选择上。

前向传播基本免费

前向传播就是把一个输入按顺序跑过各层,把中间值存下来供反向传播用。一个 LL 层的网络就是 LL 次矩阵乘法和 LL 次非线性,在现代 GPU 上对一个典型 MLP 只要微秒到毫秒。

给个量级:batch 大小 BB、输入特征 DD 个、隐藏宽度 HH,第一层的计算量是 B×D×HB \times D \times H 次乘加。B=256,D=100,H=64B=256, D=100, H=64 时是 160 万次乘加,而现代 GPU 每秒做几万亿次。也就是说,训练的成本压根不在前向这一侧。

反向传播就是链式法则

反向传播是反向模式自动微分应用到神经网络的损失上。它一次性算出损失对网络里每个参数的梯度,成本跟前向传播差不多。概念上是三步:

  1. 前向:算预测 y^\hat{y} 和损失 L(y,y^)L(y, \hat{y})。
  2. 反向:从损失出发,递归用链式法则算每一层 ℓ\ell 的 ∂L/∂W(ℓ)\partial L / \partial W^{(\ell)}。
  3. 更新:W(ℓ)←W(ℓ)−α⋅∂L/∂W(ℓ)W^{(\ell)} \leftarrow W^{(\ell)} - \alpha \cdot \partial L / \partial W^{(\ell)},其中 α\alpha 是学习率。

数学就是微积分,实现被每个现代框架(PyTorch、JAX、TensorFlow)自动化了,你几乎不会手写它。但你得知道它是链式法则,而且它是精确的、不是近似的——这两点是分辨一个人是不是真懂在训什么的分水岭。(Goodfellow, Bengio, Courville, Deep Learning, 第 6 章。ISBN 978-0262035613。)

训练循环,以及围着它的那几个旋钮

训练循环很短,你会写几百次:

for epoch in range(num_epochs):
    for batch in dataloader:
        predictions = model(batch.x)
        loss = loss_fn(predictions, batch.y)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

就这样。前向、算损失、反向、更新、清梯度、重复。剩下的都是 bookkeeping、日志、验证和存 checkpoint。

真正要你拿主意的是围着这个循环的三个旋钮。第一个是优化器。SGD with momentum 是经典默认,慢但稳,在调好的问题上最终收敛质量好。Adam(Kingma & Ba, 2014)是实战默认,每个参数自适应学习率,在大多数问题上比 SGD 快,长时间训练下不那么稳。AdamW 是 Adam 加解耦的权重衰减,泛化比 vanilla Adam 好,是现在训 Transformer 的默认。RMSprop 跟 Adam 类似、稍老,一些 RL 场景还在用。对量化 ML,Adam 是稳妥的选择,学习率从 10−310^{-3} 开始往下调。

第二个是学习率调度,因为你开始用的学习率很少是你该结束时用的那个。常数最简单,能 work 但很少最优;阶梯衰减每 kk 个 epoch 把学习率乘 0.1,有效且简单;余弦退火按余弦曲线平滑衰减,是很多现代框架的默认;warmup 加衰减从一个小学习率起步、几百步内 ramp up 再衰减,主要用在 Transformer 训练里。对基于 Adam 的 MLP 训练,余弦退火或阶梯衰减通常能把最终表现推上去一截——幅度看具体问题,别指望数量级的变化。Arithmion SDK 的 train_mlp() 接受一个 lr_schedule 参数来干这件事。

第三个是归一化层,现代网络靠它才能稳定训练。Batch normalization 在 batch 维度上归一化激活:对一层输出里的每个特征减 batch 均值、除以 batch 标准差,再施加可学的缩放和平移。它稳定训练、允许更大的学习率,附带一点轻微的正则化效果。Layer normalization 换个方向,在每个样本内部的特征维度上归一化;Transformer 用它,因为它跟 batch 大小无关,这在 batch 小或序列长度不固定时很要紧。表格数据上的 MLP 用 batch normalization,Transformer 用 layer normalization,两者对现代架构基本都是必需品。

到这里,机制部分讲完了。机制不难。难的是下一节。

神经网络在量化里为什么难

这是量化供应商会跳过的部分。神经网络在图像、文本、语音上很奇妙,在表格金融数据上,它们常规地被梯度提升打败。四个原因,而且这四个是互相加强的。

第一是数据稀缺。ImageNet 有 1400 万张标注图片,Common Crawl 有万亿级 token;一个 20 年的美股面板,宽裕地说有 500 万个 firm-day 观测,而且这还没扣掉幸存者、退市,以及这些观测彼此高度相关这件事。神经网络是数据饥渴型的,它要的比你有的多。

第二是过拟合,而它跟第一条是同一枚硬币的两面。一个 5 层、每层 100 个隐藏单元的 MLP 大约有 5 万个参数。你拿 1 万个样本去训它,目标变量的 R2R^2 是 0.5%(收益预测的典型水平),那你的参数量远远超过数据里的信号量。结果不是模型学得慢,是模型把噪声背了下来。

第三是非平稳性。金融数据里的 pattern 会变——价值股和收益的关系 2007 年跟 2020 年不是一回事。神经网络学的是你给它的那些,它不知道世界已经换了。滚动重训和 regime 特征能缓解,但这是个根本性挑战。树在这件事上稍好一点,因为它们划分特征空间、能在局部自适应。

第四是可解释性。线性模型告诉你它的系数,决策树告诉你它的分裂,神经网络给你一张 SHAP 图——有用,但你永远没法完整解释某一次预测究竟为什么是那样。对风险管理和合规,这条不是学术洁癖,是能不能上线的问题。

所以你得先假设它会过拟合

在量化数据上它一定会。这些是你能拉的杠杆,按大致有效顺序排:

  1. 更多数据:唯一真正的修法。实在拿不到,就用增强造合成数据。
  2. Dropout:dense 层里 0.2 到 0.5,只在训练时启用。
  3. 权重衰减(权重上的 L2 正则化,AdamW 里是解耦的):10−410^{-4} 到 10−210^{-2} 这种小值。
  4. 早停:验证损失不再改善就停。
  5. 更小的架构:更少的层、每层更少的神经元。
  6. 数据增强:噪声注入、mixup、特征 dropout。
  7. Batch normalization:附带的轻微正则化效果。

量化里几乎每个实用的神经网络都在用前四条。剩下三条看情况有帮助。注意这个排序本身就在说事:前两名都不是模型技巧,是数据和随机性。

那什么时候它确实该用

上面那些问题的共同点是”表格数据上没有结构可供利用”。所以答案也顺着这一点走:当数据自带梯度提升啃不动的结构时,神经网络就赢了。

另类数据里的图像是最干净的例子——停车场的卫星图、商店收据、农作物田,CNN 在这上面没有对手(我们的另类数据系列里有例子)。另类数据里的文本同理:业绩电话会、新闻标题、10-K 文件、社交媒体,Transformer 是 SOTA。带强时间依赖的时间序列是第三类,LSTM、时序卷积网络、Transformer 在限价订单簿建模和高频信号上都有用武之地。第四类是多资产、跨资产信号:输入里有很多平行序列(横截面股票特征、多币种对)时,注意力机制帮得上忙。第五类是表示学习,用 autoencoder 和对比学习在杂乱的另类数据上做特征工程。

还有一个场景值得单独说,因为它是神经网络对表格数据为数不多的真优势:高基数类别特征。行业代码、股票代码、国家标识符这种有成百上千唯一值的特征,one-hot 编码会把输入维度炸开。Embedding 层的做法是给每个类别学一个 Rd\mathbb{R}^d 里的稠密低维向量,训练期间这些向量通过反向传播更新,在相似上下文里有用的类别最终会得到相似的 embedding。对股票代码来说,学出来的 embedding 能隐式抓住行业、市值、风格关系,是显式横截面特征的一种替代。LightGBM 和 XGBoost 能按目标均值给类别排序,但学不出语义 embedding——这是一条实打实的能力差距,不是营销话术。

对纯表格信号(价格比、技术指标、基本面、行业哑变量),梯度提升通常更好也更快。这不是猜测,这是 Kaggle 表格类竞赛和公开的量化研究里反复出现的结论。

真要在表格数据上用,从这个架构起步

如果你确实需要一个表格数据上的神经网络——比如要跟 embedding 组合,或者它只是更大系统里的一块——这是个合理的起点:

Input (numeric features, standardized)
    ↓
Dense(256, ReLU) + BatchNorm + Dropout(0.3)
    ↓
Dense(128, ReLU) + BatchNorm + Dropout(0.3)
    ↓
Dense(64, ReLU) + Dropout(0.2)
    ↓
Dense(1, linear)  # regression output

几点说明。Batch normalization 通过归一化层输入来稳定训练,几乎总有帮助。Dropout 在 0.2 到 0.5 之间积极正则化,这个值要仔细调,太多会直接欠拟合。数值输入喂进去之前要标准化到零均值、单位方差——神经网络对特征缩放的敏感度远高于梯度提升。类别特征走 embedding 层,一个有 kk 个 level 的类别取一个维度为 min⁡(50,(k+1)//2)\min(50, (k+1)//2) 的 embedding,网络在训练中自己学它们。

这种结构的小 MLP 大约有 5 万个参数:足够装下一个中等复杂度的信号,不足以背下百万级样本——在量化数据上,这个”不足以”是你要的。Arithmion SDK 把它包成了一行 train_mlp(hidden_layers=[256, 128, 64], dropout=0.3, learning_rate=1e-3),标准化和类别 embedding 也一并处理了。

SDK 里另外两个入口是 train_lstm(),用于时间序列特征上的序列预测(上面说的过拟合风险照样适用),和 train_transformer(),小 Transformer 做序列到序列任务,对大多数量化场景偏重,但需要时在那儿。而对表格信号,train_lightgbm() 才是你该先伸手去拿的那个。

所以怎么办

如果你是量化研究员、要在表格数据上开一个新项目,用 LightGBM 或 XGBoost,把它训好,然后用特征工程去打败它,而不是用神经网络。这个建议听着保守,但它有一个很硬的诊断价值:如果你用特征工程打不败 GBM,问题就不在模型上——是信号本来就不在那儿。换个更复杂的模型只会让你更晚发现这件事。

只有当你手上有另类数据——卫星图、电话会、tick 数据——神经网络才从”可选”变成”必需”。那时候本文前半段那些机制才真正开始还账。但那是另一个问题,另一系列文章。

参考文献

  • Goodfellow, I., Bengio, Y., Courville, A., Deep Learning, 第 6–8 章。ISBN 978-0262035613。
  • Bishop, C. M., Pattern Recognition and Machine Learning, 第 5 章。ISBN 978-0387310732。
  • Hastie, Tibshirani, Friedman, The Elements of Statistical Learning, 第 2 版, 第 11 章(神经网络)。ISBN 978-0387848570。
  • Rosenblatt, F. (1958). “The Perceptron: A Probabilistic Model for Information Storage and Organization in the Brain.”
  • Kingma, D. P., Ba, J. (2014). “Adam: A Method for Stochastic Optimization.”