资讯动态

LSTM与Transformer时间序列预测:选型、实现与调参实战

发布时间:2026/10/28 1:03:48 来源:尧图企业网站定制
简介这套基于Pytorch实现的时序预测方案融合LSTM与Transformer网络结构支持多特征输入与单变量输出可直接用于风电功率、光伏发电、设备寿命、浓度变化等回归预测场景。代码注释清晰内置csv与xlsx数据读取接口替换本地数据集即可运行适合具有一定深度学习基础、希望快速搭建预测模型的研究生或工程师。压缩包共160个文件大小约1.94MB其中包含31个py源码、87个pyc编译文件、多个zbak备份、4个csv示例数据以及xml工程配置等便于用户对照源码、备份与数据进行学习调试。包内提供ETTm2、ETTh2等公开数据集并附有真实值与预测值对比文件可直观评估模型效果。目前已有145人学习使用适合作为LSTM与Transformer结合的入门参考和实践模板。1. LSTM和Transformer做时间序列预测别急着选大模型做时间序列预测选型时一个常被忽略的事实是在样本量只有几百条的小型序列上精心调过的LSTM往往打得过默认参数的Transformer后者数据不够时甚至会把验证集loss训成一条平线。这个标题对应的是「LSTM、Transformer做时间序列预测的PyTorch完整源码和数据」这类能直接跑起来的工程包价值不在「哪个模型更高级」而在把数据处理、模型实现、训练评估一次走通让你几天内拿到一张能对比的预测曲线。适合三类人做销量、电量、流量预测的工程师时间序列方向的求职者从NLP转过来的算法同学。下文按实际动手顺序展开两种模型的分工逻辑、数据怎么切窗口、可直接复制的PyTorch代码、训练阶段容易翻车的五个场景。2. 两种模型的分工逻辑先搞清楚LSTM和Transformer各自擅长什么2.1 时间序列预测的两个根本难点长程依赖和局部波动时间序列预测本质是学习一个条件分布给定过去若干时刻的观测预测未来若干时刻的值写成P(y_{t1} | y_{t-k1}, ..., y_t)。这个条件分布里有两种难学的成分。第一种是长程依赖月度数据里往往存在年周期要预测下个月关键信息可能在12步、24步甚至更远之前日粒度数据还有周周期、节假日效应这些信息离当前时刻很远普通RNN在反向传播时梯度沿时间步逐层衰减几个月前的信号到当前时刻已经弱到学不动。第二种是局部波动促销、断货、突发流量这类短期扰动要求模型对最近几个时刻的变化非常敏感但又不能被单点噪声带偏。LSTM和Transformer解决这两种难点的方式完全不同。LSTM靠门控机制让信息沿着细胞状态这条「高速公路」跨时间步传递梯度不容易消失长程依赖学得进去同时对局部波动用输入门动态决定记住多少、忘掉多少。Transformer的做法更直接自注意力允许任意两个时间步直接建立连接远距离信息一跳就到不用像RNN那样逐层搬运。这种差异带来一个实际后果——序列足够长、样本足够多时Transformer上限更高但数据量和序列长度都不够时LSTM的归纳偏置让它收敛更快、更稳。所以这类源码工程里两个模型都实现就是为了让你在同一个数据集上把这条「分水岭」用自己的数据验证一遍而不是拍脑袋跟风。再补充一个多变量场景的区别。当输入包含温度、价格、促销等外部变量时LSTM按时间顺序逐步融合这些特征天然保留了「上一时刻特征影响下一时刻」的因果链条Transformer则把所有特征和所有时间步放在一个二维平面上并行打分特征之间的交互更灵活但也更容易学到虚假的相关性。实际业务里如果你的外部变量不多3到5个、样本量不大LSTM往往更稳如果特征是几十维、几百维Transformer的并行特征交互才有发挥空间。选型之前先数一下你手里有多少条样本、多少个特征、序列覆盖几个完整周期这三个数字直接决定哪种模型更合适。2.2 LSTM的门控机制细胞状态就是一条「记忆高速路」LSTM的单元里有一个细胞状态c_t它是一条贯穿整个序列的传送带遗忘门决定上一时刻的状态保留多少输入门决定当前时刻的新信息写进去多少输出门决定当前时刻对外输出什么。相比原始RNN这种结构让梯度可以沿着细胞状态这条通道直达很远的时间步这就是LSTM能处理长程依赖的根本原因。用PyTorch的nn.LSTM时你真正需要关心的超参数只有三个hidden_size、num_layers、是否双向。hidden_size决定传送带的宽度。hidden_size64意味着每个时刻的细胞状态向量长度是64模型参数规模近似4 * hidden_size * (input_size hidden_size 1) * num_layers。拿单变量输入来算hidden_size64、两层时单层参数约1.7万两层加全连接总共不到4万这个量级在小数据集上不容易过拟合如果hidden_size拉到256参数会涨到接近30万上千条样本就有点吃力了。所以源码工程里给的默认值通常不大不是作者小气是小样本时序任务的参数容量本来就该小。num_layers堆到2层一般足够再往上对时序任务收益很小反而让梯度路径更长、训练更慢。双向LSTM是一个隐蔽的坑。它在NLP里常见因为一个词的意思要结合前后文但在时间序列预测里要慎用训练时双向可以看未来窗口验证阶段也看不出问题可一旦上线未来数据还没发生模型直接「失明」。常见的做法是预测任务保持单向如果有人坚持用双向也只允许它在特征提取阶段用预测头仍然只吃最后一个时间步的隐状态。取状态时注意区分out和h_nout包含所有时间步的输出取最后一步用out[:, -1, :]h_n是最后一层的最终隐状态用h_n[-1]也可以两者在最后一步上数值相同但out要保存整段序列内存开销大长序列上h_n[-1]更省。网上搜「lstm模型代码」能看到的实现五花八门差异通常就出现在这些细节上。2.3 Transformer的自注意力O(n²)复杂度换长程直达Transformer的核心是自注意力把输入序列的每个位置映射成Query、Key、Value三个向量用Q与K的内积除以sqrt(d_k)作为注意力权重再对V做加权求和。除以sqrt(d_k)是为了防止内积过大把softmax推到饱和区这行在时间序列里同样关键——数值范围大的特征一进注意力权重容易变成one-hot多头就废了。放到时序里第t个时刻的输出等于所有历史时刻输出的加权和权重由「当前时刻与历史时刻的相似度」决定。多头注意力让模型同时用多个子空间去做这件事一个头关注滞后7天的周期一个头关注滞后30天的月度趋势一个头关注最近三天的突变这个特性在强周期、多变量数据上往往比LSTM更能挖掘潜在的周期性结构。代价是复杂度。自注意力的矩阵运算是O(n²)n是序列长度。语言模型里句子通常几百个token可以接受时间序列动辄几千步n²的增长非常快这是Transformer落地时序场景时最先碰到的瓶颈。所以很多人会在长序列上引入窗口注意力或稀疏注意力那就是另一个话题了。位置编码也是必踩的坎自注意力本身对位置不敏感必须显式注入顺序信息。常见做法是sinusoidal位置编码用不同频率的正弦余弦标记步数或者用可学习的position embedding。sinusoidal的好处是可以外推到比训练更长的序列可学习位置嵌入遇到更长的序列就得做插值表现会打折。很多人是看了「图解Transformer」和The Illustrated Transformer入门的但那些教程以NLP为例输入是token embedding输出接的是词表。换成时间序列后输入投影、位置编码、输出头都得自己重写——单变量数据要先把1维扩到d_model维预测值还要从d_model维压回1维。这一步是转时序时最容易出问题的地方很多「跑不起来」的报错根源都是这几个形状没对齐。维度LSTMTransformer长程依赖靠细胞状态逐级传递中等长度够用注意力直达超长序列优势明显样本量要求小样本能收敛适合几千量级数据越多越好小样本容易过拟合序列长度线性计算长序列慢但可接受O(n²)注意力几千步成本剧增参数敏感性对学习率敏感梯度需裁剪对d_model/nhead/dropout都敏感推荐场景中小数据、单变量、快速上线多变量、大样本、强周期长序列3. 数据和预处理滑动窗口、归一化、划分训练集的一个标准套路3.1 把一维序列切成窗口样本seq_len、pred_len、step拿到源码包和数据集之后第一件事不是跑模型而是先看数据形状。这类工程里的数据常见的是一份CSV——一列时间戳、一列或多列数值。模型不能直接吃「一整段历史」它吃的是固定长度的窗口用过去seq_len个时刻预测未来pred_len个时刻。这一步叫滑窗几乎所有时序源码里都有类似函数区别在于边界处理和step的设计。def make_windows(data, seq_len, pred_len, step1): 把一维或多维时序数据切成 (过去seq_len步, 未来pred_len步) 的样本对。 data: 形状为 (T, F) 或 (T,) 的数组F是特征数。 返回 X: (样本数, seq_len, F)y: (样本数, pred_len) data np.asarray(data, dtypenp.float32) if data.ndim 1: data data.reshape(-1, 1) X, y [], [] for i in range(0, len(data) - seq_len - pred_len 1, step): X.append(data[i:i seq_len]) # 过去 seq_len 步 y.append(data[i seq_len:i seq_len pred_len, 0]) # 未来 pred_len 步取第1列 return np.array(X), np.array(y)这段代码有三处值得说明。第一循环终止条件是len(data) - seq_len - pred_len 1保证每个样本都有完整的「过去seq_len 未来pred_len」长度末尾不足的数据直接丢弃比尾部补零更干净。第二y取的是[:, 0]默认只预测数据集的第一列指标如果要同时预测多个变量把它改成完整切片data[iseq_len:iseq_lenpred_len]。第三step控制窗口移动步长。数据量少时用step1相邻窗口大量重叠样本量能放大好几倍数据量充裕或担心样本高度相关时把step调大到seq_len窗口不相交训练更稳。窗口长度怎么定这是新手最容易拍脑袋的地方。我一般遵循两条经验第一seq_len至少覆盖一个完整周期日数据要预测下一天窗口至少要包含过去7天月度数据至少要12个月否则模型永远看不见完整周期预测「躺平」是必然的第二pred_len越大能切出来的样本越少样本数约等于T - seq_len - pred_len 1预测未来30天和预测未来1天样本量差出近30倍。如果你的目标是30天先考虑直接预测还是递归预测第6章会讲但数据切片的逻辑是一样的。3.2 归一化选MinMax还是Standard关键是把scaler留到推理阶段时序预测模型对数值范围很敏感。LSTM的激活函数是tanh和sigmoid输入太大时梯度饱和Transformer的注意力在数值范围不一致时softmax权重容易被个别大值主导。所以进模型前一定要归一化。常见做法是MinMaxScaler缩放到[0,1]或[-1,1]预测完再反变换回原始尺度。这里有一个决定成败的顺序问题必须先切分训练集和验证集再对训练集fit然后用同一个scaler去transform验证集而不是对全体数据先fit再切分。前者叫无泄漏后者叫归一化泄漏第5章会展开讲后果。from sklearn.preprocessing import MinMaxScaler train_size int(len(series) * 0.7) # 前70%做训练 train_raw series[:train_size] val_raw series[train_size:] # 后30%做验证 scaler MinMaxScaler(feature_range(0, 1)) train_scaled scaler.fit_transform(train_raw.reshape(-1, 1)).reshape(-1) val_scaled scaler.transform(val_raw.reshape(-1, 1)).reshape(-1)这段代码里fit_transform只出现在训练集上验证集只用transform验证集的数值范围由训练集的min和max决定不会把未来信息带进训练过程。如果原始数据有明显的大异常值MinMax会被拉到极端正常数据挤在一小块区间里这时换成StandardScaler做z-score标准化或RobustScaler按分位数缩放。选择依据很简单数据分布稳定、无大尖峰用MinMax有异常值或分布偏态用Robust。另一个必须养成的习惯是把scaler对象保存下来joblib.dump(scaler, scaler.joblib)。线上推理时新数据要用同一个scaler先变换再进模型预测完再用它反变换否则预测值永远不会落回原始量纲再准的模型输出也是一堆没有意义的0.6、0.7。提示scaler一定要保存到磁盘训练脚本里用joblib.dump推理脚本里joblib.load两次一定用同一个对象。这个细节决定了预测值能不能还原成「吨」「度」「元」。3.3 时间序列的train/val划分不要随机打散数据很多人在图像分类里习惯了train_test_split(shuffleTrue)这个习惯搬到时序任务上来就是灾难。时间序列的验证集必须忠实模拟上线后的状态用前70%训练用后面时间段验证中间不能打乱。两个理由第一打散后验证集里会出现「时间上早于训练集」的样本模型等于见过考试答案验证指标失去参考意义第二真实业务里你不可能拿未来数据去训练随机打散等价于把数据泄漏做进了评估流程。def split_series(series, train_ratio0.7, val_ratio0.15): 按时间顺序切分返回训练、验证、测试三段原始序列。 n len(series) train_end int(n * train_ratio) val_end int(n * (train_ratio val_ratio)) train series[:train_end] val series[train_end:val_end] test series[val_end:] return train, val, test按时间切分后如果数据有强趋势比如销量逐年上涨训练集的均值会明显低于验证集和测试集这是正常现象不要以为是代码写错了。需要特别注意用make_windows切窗口时应该分别对train、val、test三段切而不是先切完窗口再随机分配。原因是切完窗口后相邻样本高度重叠随机分配会让验证集里混入和训练集几乎重复的窗口评估结果虚高得离谱。如果想做更严格的评估可以用滚动起点的方式训练起点每次往后挪一段重新训练重新评估最后取平均误差这种方法能减小单次切分的运气成分代价是训练次数成倍增加。小项目固定切分够用滚动多折留给对指标精度要求高的场景。4. 模型实现用PyTorch手写LSTM和Transformer的完整代码4.1 LSTM模型类input_size、hidden_size、num_layers怎么对齐先贴LSTM模型。这个类把nn.LSTM和全连接输出层包在一起forward里只做一件事喂入窗口取最后一个时间步的输出过全连接得到预测值。用纯PyTorch实现的好处是不依赖第三方时序库断点调试方便也方便你后续把hidden_state拿出来做特征分析。import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size, hidden_size, num_layers, output_size, dropout0.2): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0.0, ) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x 形状: (batch, seq_len, input_size) out, (h_n, c_n) self.lstm(x) # out: (batch, seq_len, hidden_size) last out[:, -1, :] # 取最后一个时间步的输出 return self.fc(last)三个参数按数据情况定。input_size就是make_windows里的特征数F单变量数据集是1多变量是特征列数hidden_size推荐从32到128之间起步先取64跑一轮看验证集loss再增减num_layers取2时别忘了传dropout但PyTorch的规则是只有num_layers 1时dropout才生效所以上面用条件表达式而不是直接传dropout。batch_firstTrue让输入输出都变成(batch, seq_len, hidden)形状和滑窗函数的输出直接对齐很多来源不明的源码在这一行省了batch_first导致模型一跑就报维度错误。提示batch_firstTrue不是语法糖它直接决定你的数据要不要在喂模型前做transpose(0,1)。源码包里的数据预处理如果是按(batch, seq_len, features)切的这里就必须写batch_firstTrue。LSTM和Transformer都可以加一个打印参数量的辅助函数方便你判断模型是不是过大。用sum(p.numel() for p in model.parameters())一行就能算出来。单变量、hidden_size64、两层LSTM的参数量在3万上下训练很快如果模型参数飙到百万级而你只有几千条样本先检查是不是hidden_size设大了或者故意加了多层双向。4.2 Transformer模型类位置编码和输出投影必须自己写Transformer侧比LSTM多三样东西输入投影、位置编码、编码器堆叠。这里用Encoder-only结构就够——我们要的只是把窗口编码成特征向量不用像NLP翻译那样自回归生成。位置编码用sinusoidal实现如下import math import torch import torch.nn as nn class PositionalEncoding(nn.Module): Sinusoidal位置编码给序列注入顺序信息。 def __init__(self, d_model, max_len5000): super().__init__() pe torch.zeros(max_len, d_model) pos torch.arange(0, max_len, dtypetorch.float32).unsqueeze(1) div_term torch.exp( torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model) ) pe[:, 0::2] torch.sin(pos * div_term) pe[:, 1::2] torch.cos(pos * div_term) self.register_buffer(pe, pe.unsqueeze(0)) # 不参与梯度更新 def forward(self, x): return x self.pe[:, :x.size(1), :] class TransformerPredictor(nn.Module): def __init__(self, input_size, d_model, nhead, num_layers, output_size, dropout0.1): super().__init__() self.input_proj nn.Linear(input_size, d_model) # 把输入特征升维到 d_model self.pos_enc PositionalEncoding(d_model) encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforwardd_model * 4, dropoutdropout, batch_firstTrue, ) self.encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) self.fc nn.Linear(d_model, output_size) def forward(self, x): x self.input_proj(x) # (batch, seq_len, d_model) x self.pos_enc(x) # 加上位置编码 x self.encoder(x) # (batch, seq_len, d_model) last x[:, -1, :] # 取最后一个位置的输出 return self.fc(last)d_model、nhead、num_layers是Transformer侧最核心的三个超参推荐初值d_model64、nhead4、num_layers2。d_model必须是nhead的整数倍这是多头注意力的维度划分要求改参数时最容易在这里翻车。dim_feedforward默认是d_model*4太大容易过拟合小数据集上可以手动压到d_model*2。batch_firstTrue需要PyTorch 1.9以上版本如果你的环境比较老而报错优先升级torch而不是改代码绕开。单变量数据里input_proj把1维升到64维是一种可学习的特征嵌入这是Transformer比LSTM更「费参数」的地方之一也是它小样本更容易过拟合的根源。这个Encoder-only结构没有causal mask因为单步或直接多步预测不需要自回归如果要做逐token的递归生成才需要加上三角掩码防止模型看未来的token。4.3 训练循环MSE、Adam、早停和梯度裁剪模型定义好后训练循环是通用套路但有几个参数值得单独说明。损失函数用MSE它让模型优先拟合大误差样本适合销量这类希望惩罚严重偏差的指标如果业务对离群点不敏感、更看重整体形状可以换Huber损失。优化器用Adam它对学习率不敏感但仍建议配ReduceLROnPlateau验证集loss连续不降时自动把学习率减半。import numpy as np import torch from torch.utils.data import TensorDataset, DataLoader torch.manual_seed(42) model LSTMPredictor(input_size1, hidden_size64, num_layers2, output_sizepred_len) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3, weight_decay1e-5) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5 ) train_ds TensorDataset(torch.tensor(X_train), torch.tensor(y_train)) train_loader DataLoader(train_ds, batch_size32, shuffleTrue) for epoch in range(200): model.train() total_loss 0.0 for xb, yb in train_loader: optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() total_loss loss.item() * xb.size(0) train_loss total_loss / len(train_ds) model.eval() with torch.no_grad(): val_pred model(torch.tensor(X_val)) val_loss criterion(val_pred, torch.tensor(y_val)).item() scheduler.step(val_loss) if epoch % 20 0: print(fepoch {epoch}: train {train_loss:.4f}, val {val_loss:.4f})这段代码里weight_decay1e-5就是L2正则等价于在loss里加权重平方惩罚小样本上能明显抑制过拟合——很多PyTorch的L2正则代码都藏在优化器的这个参数里不用手写额外项。clip_grad_norm_把梯度范数裁到1.0LSTM在batch较小时权重更新容易被梯度噪声放大不裁几轮就会看到loss变成NaN。训练循环里shuffleTrue打乱的只是样本顺序不是时间顺序——窗口内部的时间关系没有变只是样本出现的先后顺序被打乱这和第3章说的数据切分是两码事。我在训练代码里没有写完整版早停但实际跑实验时一定会补一个。最简单的版本是记录历史最佳val_loss连续patience个epoch没刷新就break同时保存model.state_dict()到best_model.pt这样即使后面过拟合了也能回滚到最佳点。另外注意device如果你的显卡支持CUDA把模型和tensor都to(device)否则CPU上跑一个几百条样本的实验也要等半天。5. 训练中常见问题排查5个容易翻车的场景与修复路径5.1 短序列上Transformer打不过LSTM模型选型判断错了现象同样一套数据LSTM在100个epoch里把验证集loss降到0.01Transformer调到0.02就下不去了加大d_model反而更差。原因Transformer的参数量集中在多头注意力和前馈网络里表达能力需要足够多的样本约束几百条窗口样本对它来说太小而LSTM的结构约束天然限制了模型自由度小数据下反而学得更稳。解决先把LSTM作为baseline跑通用它的loss当及格线如果LSTM已经满足业务要求没必要硬上Transformer。如果你的场景确实需要Transformer给它配dropout0.2、把dim_feedforward压到d_model*2再把seq_len加长注意力在更长的窗口上优势才明显。判断依据很简单把两条验证集loss曲线画在一个图里看谁先稳定、谁最终更低。5.2 归一化泄漏验证集loss好看上线就翻车现象训练时验证集loss很低但拿训练好的模型去预测未来一个月曲线整体偏离、量级完全不对。原因数据预处理时先对全体数据做了MinMax归一化再切分scaler在fit时已经见过验证集和测试集的min/max相当于把未来的数值范围泄漏给了训练过程。线上新数据的min/max大概率落在历史范围之外归一化后的输入分布变了模型自然失灵。解决严格按3.2节的顺序——先按时间切分再对训练集fit_transform验证集和测试集只transform。检查办法是在训练脚本里打印scaler.data_min_和scaler.data_max_确认它只从训练集统计出来而不是从全体数据统计的。5.3 反归一化维度不匹配预测曲线整体平移现象预测值和真实值画在一起形状对得上但整体差了一个常数或者预测结果大到离谱。原因model(x)输出的形状是(batch, pred_len)而MinMaxScaler的inverse_transform要求输入形状为(样本数, 特征数)。当pred_len大于1时直接把二维输出塞给scaler它会按列做反变换而你可能只想对第一列做其他列被当成了特征维度。解决预测后先pred[:, 0]取主列再reshape(-1, 1)最后scaler.inverse_transform多变量预测时逐列处理。这里有一个血泪经验如果后面要做pytorch转onnx部署这种形状不匹配问题在导出阶段会以更隐蔽的方式暴露建议在训练脚本里写一个单元测试输入一组已知原始值确认正反变换的误差在1e-6以内再继续训练。5.4 LSTM的loss变成NaN梯度爆炸现象训练到某个epochloss打印出现nan之后所有loss都是nan模型权重已经崩溃。原因LSTM反向传播随时间步累积梯度范数会指数增长尤其在学习率偏高、batch_size偏小、数据里有极端值的时候。解决训练循环里加clip_grad_norm_(model.parameters(), max_norm1.0)把学习率从1e-3降到3e-4重跑检查归一化后的输入有没有残留的Inf用np.isinf扫一遍数据里一个NaN能让整个batch的loss变成NaN。如果加了裁剪还崩把num_layers降到1、hidden_size降到32先排除容量过大导致的动态不稳定再逐步加回去。这里不要抱有幻想梯度爆炸不是玄学就是这几条原因逐一排除就好。5.5 预测结果是一条直线模型「躺平」了现象验证集loss在0.03附近降不下去把预测曲线画出来发现它几乎是一条水平直线只输出训练集均值附近的值。原因最常出现在小样本和强周期数据上——模型发现输出均值已经能把loss压得很低再学周期波动收益不大梯度信号弱就躺平了。另一个常见原因是归一化范围太窄比如目标值都在0.5到0.6之间模型只需要输出常数就能拿到不错的loss。解决把归一化范围从(0,1)改为(-1,1)扩大输出动态范围换Huber损失降低模型对「输出均值也能拿合格分」的满足感增大seq_len让输入包含完整周期如果数据有年周期而窗口只有12个月模型永远看不见周期躺平是必然的。画预测曲线的时候别只画测试集那几十个点把训练集尾巴和验证集整段连在一起画一眼就能看出模型是不是在「抄均值」。6. 进阶验证方法多步预测的三条路线与滚动评估6.1 多步预测的三条路线递归、直接、多输出前面所有代码都在做单步预测。业务上要预测未来7天、30天时有三条常见路线。递归预测最简单把上一步的预测值作为下一步的输入循环pred_len次缺点是误差逐轮累积预测越远越飘。直接预测更稳一次性输出未来pred_len个值把模型output_size设成pred_len就是代价是模型要同时拟合多个未来时刻训练难度略高。多输出变体是给每个未来时刻配一个独立输出头参数量更大、拟合能力更强适合数据充足的场景。实际落地时我一般先跑直接预测代码改动最小效果通常比递归稳定得多。6.2 滚动评估像线上一样评测模型单点对比验证集loss很容易被平均值掩盖。建议做滚动评估按时间顺序每次用前一段训练、后一段验证依次滚动把每个窗口的预测误差画出来而不是只报一个总loss。def rolling_evaluate(model, series, seq_len, pred_len, step30): 从序列中不断滚动取窗口模拟线上逐段预测。 losses [] for start in range(0, len(series) - seq_len - pred_len, step): train_part series[:start seq_len] test_part series[start seq_len:start seq_len pred_len] pred predict(model, train_part[-seq_len:]) losses.append(mse(pred, test_part)) return np.array(losses)这个函数暴露一个很现实的问题模型可能在验证集前段表现好、后段退化说明概念漂移这时候就要定期重训。我自己现在拿到一套时序源码第一件事永远是先画验证集的预测曲线和滚动误差而不是看Loss数字——数字会说谎曲线不会。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑