尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

量化校准为何在金融时序预测中面临挑战?PTQ与分布漂移解析

量化校准为何在金融时序预测中面临挑战?PTQ与分布漂移解析 这两年做深度学习模型部署的同学对训练后量化Post-Training QuantizationPTQ应该不会陌生。模型训练完之后不重新训练直接在推理阶段把权重甚至激活从 FP32 压缩到 INT8换来更小的体积、更低的显存占用和更高的吞吐。这个流程放到普通图像模型上通常很快就能跑通但放到金融时间序列预测Financial Time-Series Forecasting场景里问题会变得微妙很多。因为 PTQ 里有一个绕不开的环节叫校准Calibration而校准依赖的数据必须从历史里选取。模型要预测的却是未来。一旦“历史的分布”和“未来的分布”不一致量化参数就可能失效精度甚至会明显下滑。本文会先拆解 PTQ、校准、时间序列预测这三者的关系然后解释为什么说“Calibration Bets on the Past”校准在押注过去最后用 PyTorch 做一个带分布漂移的合成金融序列实验对比不同校准策略在量化后的效果差异。整个过程包含完整可复现代码适合正在做模型压缩、时序预测部署或对量化原理感兴趣的读者。1. 这个标题到底在说什么1.1 三个关键词拆开看先把标题拆成三个部分。Post-Training Quantization训练后量化这是一种模型压缩技术。训练好的浮点模型不需要重新训练或微调直接通过量化映射把权重和激活值从 FP32 转成 INT8。它最大的优点是成本低适合已经训练好、不方便再动训练流程的模型。Calibration校准在静态量化里校准是指用一小部分有代表性的数据去跑一遍模型统计每一层激活值的数值范围从而确定量化参数。校准集选得好不好直接决定量化后的精度损失。Time-Series Forecasting时间序列预测用过去一段时间的观测数据预测未来一段时间的数值。金融领域中常见的例子包括股票收益率预测、波动率预测、成交量预测等。三者的关系可以这样理解PTQ 需要校准校准需要“过去的数据”而时间序列预测面对的是“未来的数据”。如果过去和未来的统计分布不一致那么校准得到的量化参数就是过时的。1.2 金融时序预测为什么需要量化金融场景对推理延迟和资源消耗非常敏感。一个风控模型可能需要同时在线服务大量请求一个高频策略可能需要把单次推理延迟压到毫秒级一个交易终端可能部署在性能有限的边缘设备上。这些都要求模型在保证精度的前提下尽可能小、尽可能快。训练后量化正好满足这种需求模型体积变小通常能降到原来的四分之一左右推理速度提升INT8 在支持加速的硬件上有明显的吞吐优势显存占用降低同一块 GPU 可以部署更多模型副本部署成本下降对硬件算力要求更低。1.3 校准与“过去”之间的冲突我们常说“用历史数据训练模型用历史数据做验证”这在时间序列预测里是常规操作。但量化校准有一个特殊之处它相当于用一小段历史数据去给整个模型的数值分布“定格”。如果未来数据和这段历史数据分布一致那没问题但如果市场状态切换、波动率放大、出现极端行情激活值的分布就会偏离校准时的范围量化参数就失效了。这就是标题里“Bets on the Past”想表达的意思校准本质上是在赌历史分布还能代表未来分布。这个赌注在图像分类等分布相对稳定的任务上通常能赢在金融时间序列上却经常输。2. 训练后量化PTQ的原理拆解2.1 量化要解决什么问题量化的大方向是把连续取值范围的浮点数映射到离散取值范围的整数。最常见的做法是把 FP32 权重映射到 INT8。INT8 只有 256 个离散取值而 FP32 有约 42 亿个可表示值所以这个映射过程必然会有精度损失。我们的目标是让精度损失足够小小到业务可以接受。量化之所以有效是因为神经网络在训练之后权重和激活值的分布通常集中在一个有限范围内很多参数实际上是冗余的。我们不需要那么高的数值精度也能让模型保持较好的预测能力。2.2 量化参数scale 与 zero_point量化常见的公式如下非对称量化q clamp(round(x / scale) zero_point, qmin, qmax)反量化x_approx (q - zero_point) * scale其中scale是缩放因子决定浮点数值映射到整数时的步长zero_point是零点偏移让浮点 0 精确映射到整数 0qmin和qmax是量化后的整数范围INT8 就是 -128 到 127。对称量化更简单zero_point固定为 0q clamp(round(x / scale), qmin, qmax)对称量化实现简单在权重量化里很常用。scale通常通过统计激活值或权重的绝对值最大值得到scale max(|tensor|) / 127可以看到scale的取值完全取决于我们观察到的一组数据范围。这组数据选得好不好直接影响量化误差。2.3 PTQ 的三种模式训练后量化并不是只有一种玩法按量化范围可以分为三类。动态量化只把权重量化成 INT8激活在推理时仍是浮点但会根据实际输入动态计算量化参数。它不需要校准数据实现成本最低适合 LSTM、Transformer 这类结构。静态量化权重和激活都量化成 INT8。激活的量化范围必须在部署前确定这就需要通过校准数据提前统计激活分布。静态量化通常比动态量化速度更快但流程更复杂。混合量化对精度敏感的层保持 FP16 或 FP32对不敏感的层用 INT8。这是工业界常见的折中方案。在本文的实验中我会先演示动态量化然后自定义一个校准模拟流程用来解释静态量化中的校准为什么关键。2.4 校准量化前最关键的一步校准本质上是在回答一个问题模型每一层激活值的分布范围到底是多少校准流程一般是这样准备一小部分有代表性的数据通常从训练集或验证集里抽取以推理模式跑一遍模型记录每一层激活值的数值分布根据统计结果为每一层计算scale和zero_point。常用的统计方法包括MinMax直接取激活值绝对值的最大值。实现简单但容易被离群点带偏。Percentile取绝对值分布的第 99 百分位或 99.9 百分位抗离群点能力更强。MSE / Entropy尝试不同的候选scale选择量化前后误差最小或信息损失最小的那个。在校准这一步有两个关键点常常被忽略第一校准不是训练。校准过程不会更新模型参数只是统计数值范围。第二校准集不是越大越好。校准集的“代表性”比“样本量”更重要。如果校准集内部包含了不同分布状态的数据统计出来的范围可能覆盖过宽导致正常区间的量化分辨率变差。3. 为什么说校准是在“押注过去”3.1 校准数据集天然来自历史无论你从训练集、验证集还是最近一段真实观测里取校准数据一个无法回避的事实是校准数据一定是已经发生过的数据。它不可能来自未来。这本身不是问题。问题在于量化参数一旦确定部署后的模型就会一直用这组参数去处理新数据。如果新数据的分布和校准数据分布不一致原本“为了这段分布精心设计”的量化参数就失去了意义。3.2 金融时序数据的非平稳性图像分类的输入分布相对稳定猫的图片在不同时间拍出来像素分布不会有太大差异。所以图像模型的校准集可以几个月不更新。金融时间序列则完全不同。它有几个典型的非平稳特征波动率聚集高波动往往伴随高波动低波动往往伴随低波动状态切换市场可能从温和状态突然切换到剧烈波动状态结构性变化政策调整、市场规则变化、突发事件都会让数据分布发生突变极端值价格数据中经常出现远超正常范围的异常值。这些特征决定了校准集里统计到的激活值范围很可能在部署后的某个时间点被突破。对比维度图像分类金融时间序列预测输入分布相对稳定非平稳存在状态切换校准集有效期通常可以长期使用可能很快失效离群值影响小大且离群值本身可能包含关键信息重校准频率低需要高频监控3.3 校准失效时的直观表现校准失效的直接后果是量化误差变大。假如校准集来自一段低波动时期激活值范围很小算出来的scale偏小。当未来出现高波动数据时激活值绝对值很容易超过scale * 127于是发生截断。被截断的信息无法恢复模型输出自然偏离预期。反过来如果校准集来自一段高波动时期scale偏大正常时期的激活值只能落在整数区间的一小部分里量化分辨率被浪费同样会引入不必要的误差。这正是“押注过去”的风险你赌的是校准集分布能延续到部署时但金融时序经常会打破这个赌注。4. 环境准备与实验设计4.1 运行环境本文代码基于以下环境Python 3.9 或更高版本PyTorch 2.xNumPy、Pandas。版本可以根据你的实际环境调整重点在于理解流程和实验思路。建议在一个干净的虚拟环境里运行pip install torch pandas numpy如果你用的是 CPU 环境本文实验也能正常运行因为模型规模很小。4.2 实验设计思路为了把“校准押注过去”这个问题可视化我设计了一个对比实验。实验用合成数据模拟一段带分布漂移的金融时间序列前一段平稳中段波动率放大后段再次放大。然后训练一个 LSTM 模型做收益率预测。关键步骤是准备两组不同的校准数据校准集 A从训练集内部选取代表“陈旧的过去”校准集 B从验证集前端选取代表“最近已知的新分布”验证集选取分布漂移之后的数据。最后对比用校准集 A 和校准集 B 分别计算量化参数在验证集上评估量化后的预测误差。这样设计的好处是两组校准集来自同一模型、同一数据集唯一区别是“校准数据与验证数据的分布接近程度”可以很直观地看出校准效果如何受数据分布漂移影响。5. 完整实战金融时序模型的 PTQ 校准对比下面代码建议按顺序保存到同一个脚本文件例如ptq_calibration_demo.py然后直接运行。5.1 生成带分布漂移的金融序列首先构造一个合成的收益率序列。我故意在中间和末尾注入波动率放大模拟金融时间序列常见的 regime switch。import os import numpy as np import pandas as pd import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset # 固定随机种子保证可复现 np.random.seed(42) torch.manual_seed(42) # 生成 5000 个收益率数据点 n 5000 returns np.random.normal(loc0.0002, scale0.01, sizen) # 注入波动率漂移 # 800~1600 波动放大 2.5 倍 returns[800:1600] returns[800:1600] * 2.5 # 2400 之后波动再次放大 3 倍 returns[2400:] returns[2400:] * 3.0 price 100 * np.exp(np.cumsum(returns)) df pd.DataFrame({ ret: returns, price: price, pct_change: np.concatenate([[0], np.diff(price) / price[:-1]]), vol: pd.Series(returns).rolling(20).std().fillna(returns.std()).values }) print(df.head())这里的vol是滚动标准差用来刻画阶段性波动水平pct_change是价格涨跌幅。这样构造出来的序列具有明显的阶段特征平稳段、高波动段、再次高波动段。5.2 构造特征窗口与数据集时间序列预测一般用过去window个时刻的特征预测下一时刻的收益率。这里窗口大小设为 20。def build_windows(df, window20): ret df[ret].values vol df[vol].values pct df[pct_change].values X, y [], [] for i in range(window, len(df)): X.append(np.stack([ ret[i - window:i], vol[i - window:i], pct[i - window:i] ], axis-1)) y.append(ret[i]) return np.array(X), np.array(y) WINDOW 20 X, y build_windows(df, WINDOW) print(X shape:, X.shape) # (N, 20, 3) print(y shape:, y.shape) # (N,)接下来划分数据集。训练集取前 2000 个样本校准集 A 来自训练集内部校准集 B 来自验证集前端验证集取漂移后的区间。train_X, train_y X[:2000], y[:2000] # 校准集 A陈旧历史数据来自训练集内部 calib_a_X, calib_a_y X[1000:1200], y[1000:1200] # 校准集 B最近观测数据来自验证集前端 calib_b_X, calib_b_y X[2700:2900], y[2700:2900] # 验证集分布漂移后的数据 val_X, val_y X[2900:4000], y[2900:4000] def make_loader(X, y, batch_size64, shuffleTrue): dataset TensorDataset( torch.tensor(X, dtypetorch.float32), torch.tensor(y, dtypetorch.float32) ) return DataLoader(dataset, batch_sizebatch_size, shuffleshuffle) train_loader make_loader(train_X, train_y, batch_size64, shuffleTrue) calib_a_loader make_loader(calib_a_X, calib_a_y, batch_size64, shuffleFalse) calib_b_loader make_loader(calib_b_X, calib_b_y, batch_size64, shuffleFalse) val_loader make_loader(val_X, val_y, batch_size64, shuffleFalse)需要注意的是验证集并不参与训练校准集 B 与验证集之间存在时间
返回列表