1.

从天气预报到股票涨跌:为什么我们需要概率预测?
大家好,我是老张,在AI和时序数据这块摸爬滚打了十来年。
今天想和大家聊聊一个听起来有点“玄乎”,但实际应用价值巨大的技术——多元时间序列的概率预测。
咱们先从一个最熟悉的场景说起:天气预报。
你肯定遇到过这种情况,手机上的天气App显示“明天降水概率60%”。
它不会铁口直断“明天下雨”,而是告诉你一个可能性。
这就是概率预测的魅力所在:它承认未来存在不确定性,并尝试量化这种不确定性。
在现实世界里,无论是明天的气温、下个月的用电量,还是一支股票未来的价格,都充满了随机性。
传统的点预测模型(比如告诉你“明天股价是100元”)往往过于自信,一旦预测失误,可能会带来巨大的决策风险。
而TimeGrad模型,就是为解决这个问题而生的。
它本质上是一个“高级版的天气预报模型”,专门用来处理像股票价格、服务器负载、能源消耗这类多元时间序列数据。
所谓“多元”,就是指同时预测多个相关的指标,比如预测一个数据中心未来24小时里CPU、内存、网络流量各自的变化趋势。
这比预测单个指标要复杂得多,因为你需要考虑这些指标之间千丝万缕的关联。
TimeGrad的核心创新,在于它巧妙地将两大前沿技术——自回归模型和扩散模型——给“嫁接”到了一起。
自回归模型(比如咱们熟悉的LSTM、GRU)擅长捕捉时间上的依赖关系,就像一个人能根据前几天的天气,大致推测明天的趋势。
而扩散模型(就是最近在AI绘画领域大放异彩的那个技术)则是个“生成大师”,特别擅长从一片噪声中,一步步“雕刻”出复杂且多样的数据分布。
你可以这么理解:自回归模型是那个把握大方向、记住历史规律的“老舵手”,而扩散模型则是那个能根据舵手指令,画出无数种可能未来天气图的“天才画师”。
两者结合,TimeGrad不仅能给出一个最可能的未来趋势(比如“股价大概率上涨”),还能生成一整套可能的未来走势图,告诉你“上涨5%的概率是30%,上涨10%的概率是50%,下跌的概率是20%”。
这种概率化的输出,对于风险控制、资源调度等需要做“最坏打算”的场景来说,简直是如获至宝。
2.
TimeGrad技术内核:自回归与扩散的“双人舞”
好了,概念讲清楚了,咱们得往深里挖一挖,看看TimeGrad这个“黑盒子”里面到底是怎么运转的。
这部分我会尽量用大白话和类比给你讲明白,涉及到关键公式和代码的地方,咱们再细看。
2.1
自回归部分:用RNN记住“故事的上下文”
想象一下,你要预测一部电视剧下一集的剧情。
一个很自然的做法是什么?肯定是先回顾一下前面几集发生了什么,主角们关系如何,矛盾焦点在哪。
TimeGrad里的自回归部分,干的就是这个“回顾历史”的活儿。
具体来说,它使用一个循环神经网络(RNN),通常是LSTM或GRU,来编码过去一段时间的历史数据。
我把它比作一个记忆力超群的“故事梗概记录员”。
假设我们每小时记录一次数据中心的CPU使用率、内存占用和网络流入量。
这个记录员会按时间顺序,一小时一小时地“阅读”这些数据。
每读入一个新的时间点(比如下午2点)的数据,它就会更新自己脑子里的“隐藏状态”。
这个隐藏状态是一个浓缩的向量,包含了截止到当前时间点,所有历史信息的精华。
用公式可以粗略表示为:h_t
=
h_{t-1})这里的x_t是t时刻的观测值(比如CPU使用率),c_t是协变量,h_{t-1}是上一时刻的记忆。
协变量c_t是个非常重要的帮手,它包含了那些我们提前知道、且对未来有影响的信息,比如“当前是不是节假日”、“是一天中的哪个时段”、“是不是电商大促日”。
这就像记录员不仅看剧情,还知道今天是不是周末、是不是主角的生日,这些背景信息能帮助他更好地理解剧情走向。
这个不断更新的隐藏状态h_t,就是模型对“到目前为止故事发展到哪了”的总结。
接下来,这个总结将成为指导扩散模型进行“剧情创作”的核心大纲。
2.2
扩散模型部分:从噪声中“想象”无数种未来
现在,我们有了故事大纲(隐藏状态h_t),该让“天才画师”——扩散模型登场了。
扩散模型最近在图像生成领域火得一塌糊涂,它的核心思想是一个“先破坏再重建”的学习过程。
我举个不那么严谨但很形象的例子:假设你想让AI学会画猫。
传统方法是直接给它看猫的图片让它模仿。
扩散模型的做法更绝:它先拿一张清晰的猫图,一步步地往上加高斯噪声,直到图片变成一片完全随机的雪花点(这个过程叫前向扩散)。
然后,它再学习如何从这片雪花点开始,一步步地把噪声去掉,最终还原回一张清晰的猫图(这个过程叫反向去噪)。
神奇的是,一旦模型学会了这个“去噪”的本领,你就可以随便给它一片雪花点,它都能“去噪”出一张全新的、它从未见过的猫图来!
TimeGrad正是借鉴了这个思想。
在训练阶段,对于我们要预测的未来时间序列(比如未来24小时的服务器指标),我们把它当作“清晰的猫图”。
然后我们:
- 对这个未来序列逐步加噪声,直到它变成纯噪声。
- 让模型(一个神经网络)学习如何根据“故事大纲”(RNN的隐藏状态
h_t)和当前噪声程度,来预测出这一步所添加的噪声是什么。
训练完成后,在预测阶段,过程就反过来了:
- 我们从一片纯噪声开始(这代表我们对未来一无所知)。
- 然后,我们让训练好的去噪网络,在“故事大纲”(基于历史数据计算出的
h_t)的指导下,一步步地对这片噪声进行“去噪”。 - 每去噪一步,数据就变得更清晰一点,更像一个合理的未来序列。
经过几十步或上百步后,我们就得到了一个完整的、可能的未来序列样本。
最关键的一步来了:由于我们是从随机噪声开始的,而每次的随机噪声都不同,所以这个过程可以重复无数次,从而生成无数个不同的未来序列样本!这些样本集合起来,就构成了我们对未来概率分布的估计。
我们可以计算这些样本的均值作为“最可能”的预测,也可以计算分位数来得到“未来有90%的可能性不会超过这个值”的预测区间。
2.3
两者的结合:一个精妙的协作流程
现在我们把这两部分串起来,看看TimeGrad在训练和预测时完整的“双人舞”流程:
训练时(学习阶段):
- 输入:一段历史序列
x_1,和对应的协变量。x_2,
x_t
- RNN编码:用RNN(如GRU)处理历史数据,得到浓缩了所有历史信息的隐藏状态
h_t。 - 扩散目标:取出真实的未来序列
x_{t+1},,对其执行前向扩散过程,在某个随机选定的噪声步数...,
x_{t+T}
n上,得到加噪后的数据。 - 去噪学习:模型的目标是,根据
h_t和噪声步数n的信息,去预测出这一步添加到未来序列上的噪声。通过最小化这个预测噪声和真实添加噪声的差距(如均方误差)来训练网络。
预测时(生成阶段):
- 同样编码历史:用训练好的RNN对同样的历史数据编码,得到相同的
h_t。 - 从噪声开始:准备一个形状符合未来序列的、完全随机的噪声张量。
- 迭代去噪:从最大的噪声步数
N开始(比如N=100),重复以下步骤直到n=0:- 将当前噪声序列、噪声步数
n和隐藏状态h_t一起输入训练好的去噪网络。 - 网络预测出这一步的噪声成分。
- 根据扩散模型的采样算法(如DDPM),从当前噪声中减去一部分预测的噪声,得到“更清晰”一点的序列。
- 噪声步数
n减1。
- 将当前噪声序列、噪声步数
- 得到样本:当
n=0时,过程结束,我们得到了一个未来序列的样本。 - 重复采样:回到第2步,换一个随机噪声起点,重复生成过程。
重复成百上千次,就得到了一个未来概率分布的样本集合。
这个流程的代码骨架,用PyTorch风格可以这样示意(极度简化,突出逻辑):
importtorch
SomeDenoisingNetwork(hidden_dim,
input_dim)
torch.randn_like(future_series_template)
for
reversed(range(self.num_steps)):
去噪网络预测噪声
返回所有样本,用于计算概率分布
3.
实战演练:用TimeGrad预测股票价格(概念版)
理论说得再多,不如动手试一试。
这一章,我带大家走一遍用TimeGrad模型进行多元时间序列预测的实战流程。
我们会以一个概念性的股票价格预测为例,请注意,这仅用于技术演示,绝不构成任何投资建议,金融市场极其复杂,实际应用需要考虑无数额外因素。
我们的目标是:利用过去N天的开盘价、最高价、最低价、收盘价和成交量(这就是一个5维的多元序列),来预测未来M天收盘价的概率分布。
3.1
数据准备与预处理
第一步,永远是和数据打交道。
假设我们已经从某个合规数据源获取了清洗后的股票日线数据。
importpandas
是一个DataFrame,列包括:['date',
'open',
pd.to_datetime(df['date'])
inplace=True)
时间相关协变量:星期几、月中第几天、季度等(模型能学到周期性)
=
可以加入技术指标作为滞后特征(需注意避免未来数据泄露)
df['ma_10']
df['close'].rolling(10).mean().shift(1)
covariates
对于价格类数据,我习惯用收益率而不是绝对价格,这里为简化用标准化
scaler
小心!在实际滚动预测中,应该用历史窗口来fit,避免用到未来信息
def
X.append(data[i:i+history_len])
历史序列
X_cov.append(covariates[i:i+history_len+forecast_len])
历史+未来的协变量(未来协变量如星期几是已知的)
Y.append(data[i+history_len
i+history_len+forecast_len])
未来序列
{Y.shape[1]}")
注意:金融数据预处理极其讲究,要严防“未来信息泄露”。
上面代码中的标准化操作
scaler.fit_transform(data)一次性用了全量数据,这在严谨的实验中是不允许的。正确做法是在每个训练/验证/测试的时间分割上,分别用其之前的数据来拟合scaler。
同时,协变量中如果包含滞后技术指标,也必须确保其计算不包含未来数据。
3.2
模型构建与关键参数解析
接下来,我们参照论文和开源实现,搭建一个简化版的TimeGrad模型。
这里会重点讲解几个关键超参数的选择,这些参数直接影响了模型的表现和效率。
importtorch
"""将扩散步数n编码为向量,这是扩散模型的标准操作"""
def
torch.log(torch.tensor(10000.0))
(half_dim
torch.exp(torch.arange(half_dim,
device=n.device)
torch.cat([torch.sin(embeddings),
dim=-1)
"""去噪网络,核心是一个MLP,输入是带噪序列、扩散步嵌入和上下文,输出是预测的噪声"""
def
DiffusionEmbedding(diffusion_embed_dim)
self.net
self.diffusion_embedding(diffusion_step)
[batch,
step_embed.unsqueeze(1).repeat(1,
noisy_data.size(1),
"""完整的TimeGrad模型"""
def
beta_schedule='linear'):
super().__init__()
torch.sqrt(self.alphas_cumprod)
self.sqrt_one_minus_alphas_cumprod
=
"""前向扩散过程:在真实数据x_start上添加第n步的噪声"""
noise
self.sqrt_alphas_cumprod[diffusion_step].view(-1,
sqrt_one_minus
self.sqrt_one_minus_alphas_cumprod[diffusion_step].view(-1,
=
self.context_proj(rnn_hidden.squeeze(0))
[batch,
reversed(range(self.num_steps)):
n_tensor
(beta_t/sqrt(1-alpha_cumprod_t))
pred_noise)
torch.stack(samples)
关键参数解析:
num_diffusion_steps:这是扩散过程的总步数。(N)
论文里做了个有趣的实验,发现N不需要太大。
在电力数据集上,N从2增加到256,性能先提升后基本饱和,甚至N≈10时性能损失也不大,最佳值在N≈100左右,再大反而可能不利。
这给我们一个实用提示:不要盲目追求大的N,50-200步可能是个不错的起点,既能保证生成质量,又不至于让采样过程太慢。
beta_schedule:噪声调度表,控制每一步加噪声的强度。线性调度(从很小值线性增加到较大值)简单常用。
更平滑的Cosine调度可能在图像上效果更好,但在时间序列上,线性调度通常足够且稳定。
- RNN隐藏层维度与去噪网络维度:这决定了模型的容量。
rnn_hidden_dim需要足够大以捕捉复杂的历史动态,denoise_hidden_dim则决定了去噪网络的表达能力。根据数据复杂度和计算资源调整,可以从128/256开始尝试。
- 历史长度
(
:模型能“看”多远的历史。history_len)对于日频股票数据,60天(约3个月)可能捕捉到一些中期趋势。
对于高频数据(如分钟级),可能需要几百甚至上千个时间点。
3.3
训练技巧与损失函数
训练扩散模型相对直观,核心就是让网络预测的噪声尽可能接近真实添加的噪声。
但有些技巧能让你训练得更稳、更快。
deftrain_epoch(model,
torch.nn.utils.clip_grad_norm_(model.parameters(),
max_norm=1.0)
torch.optim.AdamW(model.parameters(),
lr=1e-3)
torch.optim.lr_scheduler.CosineAnnealingLR(optimizer,
T_max=50)
余弦退火
关于损失函数:TimeGrad原文和大多数扩散模型一样,使用**噪声预测的均方误差(MSE)**作为损失。
这简单有效。
也有一些研究尝试更复杂的损失,比如结合序列重建损失,或者在噪声预测上使用Huber损失以增强鲁棒性,但对于入门来说,MSE足矣。
一个我踩过的坑:扩散模型训练初期,预测的噪声可能非常不准确,导致损失很大且波动剧烈。
这时不要轻易放弃或大幅调低学习率。
耐心等它训练几个epoch,通常损失会快速下降并稳定。
监控损失曲线比看绝对数值更重要。
3.4
评估与结果解读:超越单点预测
模型训练好后,我们最关心的就是它预测得准不准。
对于概率预测模型,评估指标和点预测模型完全不同。
defevaluate_probabilistic_forecast(model,
test_loader,
all_predictions.append(prediction_mean.cpu().numpy())
all_targets.append(future.cpu().numpy())
拼接所有批次
np.concatenate(all_predictions,
axis=0)
计算点预测指标(虽然不完全合适,但可作为参考)
from
np.sqrt(mean_squared_error(targets[:,
3],
calculate_coverage_rate(samples,
targets,
例如,alpha=0.1,计算90%区间的覆盖率。
"""
calculate_coverage_rate(all_samples,
targets,
理想情况下,覆盖率应该接近90%。
如果远低于90%,说明区间太窄(低估不确定性);如果远高于90%,说明区间太宽(过于保守)。
结果解读:
假设我们对未来20天的收盘价做了预测,生成了1000条可能的路径。
- 点估计:我们可以取这1000条路径在每个时间点的中位数或均值,得到一条“最可能”的走势线。
用MAE/RMSE等指标评估这条线与真实值的差距。
- 不确定性量化:这是TimeGrad的真正价值。
我们可以计算,在每一个未来时间点上,1000条路径形成的分布。
例如,我们可以画出第5天收盘价的概率密度图,看到它是一个分布,而不是一个点。
- 预测区间:我们可以轻松给出“有90%把握认为,第5天的收盘价会在
[a,
元之间”这样的结论。
通过计算样本的5%和95%分位数,就得到了这个区间。
评估这个区间的覆盖率(实际值落在这个区间的比例)和区间宽度,是衡量概率预测好坏的关键。
一个好的概率预测模型,应该在保证覆盖率接近目标(如90%)的同时,让区间宽度尽可能窄。
可视化:画出几条采样路径作为“可能发生的未来情景”,同时用深浅不同的色带表示不同置信水平(如50%,90%)的预测区间,将真实值叠加上去。
一张这样的图,比任何数字都更能直观展示概率预测的力量和模型的不确定性估计能力。
4.
优势、局限与适用场景
用了这么长时间,也踩过不少坑,我觉得是时候对TimeGrad这类模型做一个更客观的总结了。
它绝非银弹,但在合适的场景下,威力惊人。
4.1
核心优势:为什么选择TimeGrad?
- 强大的不确定性量化能力:这是它最亮眼的优点。
在需要评估风险的领域,比如量化交易(计算在险价值VaR)、电网调度(准备备用容量)、供应链管理(应对需求波动),知道“最坏情况可能有多坏”比知道“平均情况有多好”更重要。
TimeGrad直接给出了未来分布的样本,计算任何风险指标都变得非常直接。
- 建模复杂分布:传统方法常假设未来数据服从高斯分布等简单分布。
但现实世界的时间序列,经常出现尖峰厚尾、多模态(比如股价可能因突发消息暴涨或暴跌,形成两种可能模式)等复杂情况。
扩散模型作为强大的生成模型,理论上可以逼近任意复杂的数据分布,这是基于高斯假设或归一化流的方法难以比拟的。
- 与自回归框架自然融合:TimeGrad的设计非常巧妙,它把扩散模型“嵌”进了自回归预测的范式里。
RNN处理历史信息的思路成熟且有效,扩散模型负责复杂的生成任务,两者分工明确,协同工作,使得整个模型架构清晰,易于理解和改进。
- 在多个基准数据集上表现优异:在原论文中,TimeGrad在电力、交通、零售等多个公开的多元时间序列数据集上,在CRPS(连续排名概率分数,一种概率预测的综合评估指标)等关键指标上,都达到了当时领先的水平,证明了其有效性。
4.2
不得不提的局限与挑战
- 采样速度慢:这是扩散模型通用的“阿喀琉斯之踵”。
生成一个样本需要迭代进行数十甚至数百步的去噪网络前向传播。
如果你需要快速生成成千上万个样本进行蒙特卡洛分析,这可能会成为计算瓶颈。
尽管有研究致力于加速扩散模型采样(如DDIM),但在时间序列预测中应用的成熟方案还不多。
- 训练和调参成本较高:相比一个简单的LSTM或Transformer点预测模型,TimeGrad有更多的组件(RNN、扩散嵌入、去噪网络)和超参数(扩散步数、噪声调度、网络维度等)。
想要训出一个好模型,需要更多的计算资源和调参经验。
- 对历史编码器的依赖:TimeGrad预测的质量,很大程度上取决于RNN编码的上下文向量
h_t是否足够好。如果历史模式非常复杂,或者存在长期依赖,RNN可能无法完美编码,从而误导后续的扩散生成过程。
可以考虑用更强大的序列编码器(如Transformer)来替换RNN,但这又会增加模型复杂度。
- “黑箱”程度较高:虽然架构清晰,但扩散模型具体的生成过程依然缺乏直观的解释性。
我们很难说清模型是基于历史的哪一部分特征,“想象”出了某一条特定的未来路径。
这在某些需要高解释性的领域(如金融风控)可能是个问题。
4.3
它最适合用在哪儿?
根据我的经验,TimeGrad在以下场景中尤其值得尝试:
- 风险敏感的预测任务:任何需要对预测不确定性进行定价或决策的场景。
例如:
- 能源领域:预测风电/光伏发电量(间歇性极强),为电网的备用机组调度提供概率依据。
- 运维与监控:预测服务器集群的负载或故障概率,实现基于风险的弹性资源伸缩。
- 金融市场:(再次强调,仅技术探讨)资产价格的波动率预测、投资组合的风险价值计算。
- 数据具有复杂模式:当你发现数据残差不符合正态分布,或者历史图表中经常出现难以用简单模型捕捉的突变、周期混合模式时,可以试试TimeGrad的生成能力。
- 需要“情景生成”:比如在宏观压力测试中,需要生成多种可能的经济指标路径;在游戏AI中,需要预测对手多种可能的行为序列。
TimeGrad的多样本输出天生适合这类任务。
相反,在以下场景,你可能需要谨慎考虑或选择更简单的模型:
- 对预测延迟要求极高(如毫秒级高频交易)。
- 计算资源极其有限(边缘设备)。
- 数据量非常小,复杂模型极易过拟合。
- 任务只需要一个准确的点预测,且不确定性无关紧要。
说到底,TimeGrad为我们提供了一把处理时间序列不确定性的强大新工具。
它把“预测未来”这件事,从给出一个标准答案,变成了描绘一幅充满可能性的概率地图。
在实际项目中引入它,往往不仅仅是模型指标的提升,更能推动整个团队从“确定性思维”向“概率性思维”转变,这在应对真实世界的不确定性时,本身就是一种巨大的进步。


