资讯动态

LSTM与Transformer时序预测源码实战:从数据构造到避坑指南

发布时间:2026/10/2 4:04:04 来源:尧图企业网站定制
简介这一时序预测源码包基于长短期记忆网络LSTM与Transformer构建面向有一定Python基础、希望进阶深度学习时序建模的开发者。资源完整覆盖数据读取与清洗、归一化与序列划分、模型构建与训练、验证集评估及最终预测等环节并分别给出RNN、LSTM、Transformer三种实现便于对比不同架构在长距离依赖捕捉上的差异。源码包共31个文件含3个Python脚本、3个CSV示例数据、7个XML工程配置、14张流程图和1份README说明压缩包约28.4MB目录结构清晰方便运行和二次开发。目前已有216人学习下载可用于大气污染指数、股票行情、交通流量等典型时序预测场景。通过阅读源码可掌握数据预处理细节、损失函数与优化器配置、MSE与RMSE指标评估等方法为自建预测模型打下扎实基础。1. 用LSTM和Transformer做时序预测为什么源码在手翻车率还是那么高我接过一个设备寿命预测的项目团队一开始用LSTM跑通了基线后来听说Transformer效果好连夜把模型换掉结果在验证集上比LSTM还差。问题不在模型而在数据形态和预测策略。时序预测的源码并不复杂LSTM和Transformer两个模型加起来不到两百行但真正决定预测质量的是输入输出怎么构造、归一化怎么做、预测步长怎么设定。这篇笔记我就围绕“使用LSTM和Transformer模型进行时序预测源码”这个主题把从数据准备到模型训练再到验证的完整过程拆开讲哪些参数值得调哪些坑我踩过一并写清楚。适合正在做金融时序预测、设备寿命预测、流量预测的从业者也适合刚接触深度学习时序预测、想用源码快速跑通对比的新手。2. 预测任务拆解与数据形态先想清楚你要预测什么很多人在写模型源码之前根本没想清楚预测任务的具体定义。LSTM和Transformer吃的是序列输出什么取决于你如何构造监督信号。这个环节决定了模型上限后面调参只是在下限附近挣扎。2.1 输入输出结构单变量多步预测是最常见的起点时序预测按输入输出关系可以拆成几类单变量输入预测单步、单变量输入预测多步、多变量输入预测单步或多步。作为一套能复用的源码我一般按“过去N步预测未来M步”的通用结构来写因为无论是金融时序预测还是设备寿命预测这个结构都能覆盖。构造训练样本的核心是滑动窗口。下面这个函数是我常用的写法import numpy as np def make_windows(data, input_len, pred_len): 将一维时序数据转换为 (X, y) 监督学习样本 data: 一维数组长度 L input_len: 用过去多少个时间步作为特征 pred_len: 预测未来多少个时间步 返回: X shape (样本数, input_len), y shape (样本数, pred_len) X, y [], [] for i in range(len(data) - input_len - pred_len 1): X.append(data[i : i input_len]) y.append(data[i input_len : i input_len pred_len]) return np.array(X), np.array(y)这段代码的逻辑是从索引 i 开始取 input_len 个点作为输入 X取接下来的 pred_len 个点作为预测目标 y。循环终止条件是窗口不越界最后一个样本的 y 正好落在数据末尾。参数说明input_len 一般取周期长度的 1 到 2 倍比如日粒度数据有年周期性取 365 或 730 比较合理如果数据没有明显周期取 50 到 100 起步比较稳妥。pred_len 是你要预测的步长金融时序预测里常用 5 或 20设备寿命预测里常用剩余寿命的分位数这个要根据业务需求定。注意 input_len 不要远大于 pred_len否则模型学到的是“把输入尾巴复制过去”预测会退化成延迟效果。2.2 归一化与数据划分只对训练集 fit按时间顺序切LSTM 对输入数据的尺度极其敏感tanh 和 sigmoid 激活函数的梯度在输入过大或过小时都会出问题。Transformer 虽然对尺度稍好一些但多头注意力的点积结果也会被大数值扰动。所以归一化是必须的。我见过太多人在这里踩坑直接对全量数据调用 MinMaxScaler 的 fit_transform这在后面的避坑章节里会详细说。正确的做法是from sklearn.preprocessing import MinMaxScaler # 假设 data 是原始一维数组train_ratio0.8 split_idx int(len(data) * train_ratio) train_data data[:split_idx] test_data data[split_idx:] scaler MinMaxScaler() scaler.fit(train_data.reshape(-1, 1)) # 只在训练集上计算 min 和 max train_scaled scaler.transform(train_data.reshape(-1, 1)) test_scaled scaler.transform(test_data.reshape(-1, 1)) # 再构造成滑窗样本 X_train, y_train make_windows(train_scaled.flatten(), input_len64, pred_len16) X_test, y_test make_windows(test_scaled.flatten(), input_len64, pred_len16)这里的逻辑分三步第一步按时间顺序切分不能用随机切分第二步 scaler 用训练集的 min 和 max 做 fit测试集只在 transform 阶段参与第三步把缩放后的数据送入滑窗函数。注意 X_train 和 X_test 的第一维样本数量会因为滑窗而减少这是正常的不用惊慌。如果数据有较强的趋势或周期性可以考虑用差分先去除趋势再归一化。做法是对原始序列做一阶差分对差分结果做归一化预测完再把差分值累加回去。我一般会在强趋势数据上这么做单纯 MinMaxScaler 在处理趋势项时会把近期的小波动压缩得几乎不可见。2.3 选对评价指标MAE 会骗人基线必须对比模型训完看 MAE 是 0.03你觉得效果很好但没对比基线就可能白高兴一场。时序预测有一个最容易被忽略的基线直接用最后一个观测值作为预测结果也就是持久性预测。如果你的模型连这个基线都打不过说明它没有学到任何时序规律。指标适用场景注意事项MAE一般回归任务对离群点不敏感在波动大时数值显得很小容易误判RMSE需要惩罚大误差时对离群点敏感峰值预测偏弱时 RMSE 会很大MAPE相对误差适合看业务百分比值接近 0 时 MAPE 会爆炸要小心R²判断是否优于均值预测用训练集和测试集分别计算避免虚高我建议在写训练循环之前就把“持久性预测”的指标算出来。比如你的测试集最后 100 个点直接用第 t 个点的值预测第 t1 个点算出 MAE。模型训练的 MAE 只有低于这个值才有意义否则就是做了个寂寞。3. 用 PyTorch 实现 LSTM从模型定义到训练循环的完整源码这一章给出能直接跑通的 LSTM 时序预测源码。PyTorch 的 nn.LSTM 封装得比较完整但很多新手在参数选择上全靠猜导致模型不收敛或收敛后预测结果滞后。3.1 LSTM 模型定义与参数选择先看模型定义我习惯把 LSTM 和输出层拆开写方便后面接不同的输出头import torch import torch.nn as nn class LSTMPredictor(nn.Module): 单变量多步预测的 LSTM 模型 input_size: 每个时间步的特征维度单变量为 1 hidden_size: LSTM 隐藏层维度 num_layers: LSTM 堆叠层数 output_size: 输出维度等于 pred_len def __init__(self, input_size1, hidden_size64, num_layers2, output_size16, dropout0.2): super(LSTMPredictor, self).__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout ) self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x shape: (batch, seq_len, input_size) out, _ self.lstm(x) # 取最后一个时间步的隐藏状态 out out[:, -1, :] out self.fc(out) return out代码逻辑输入 x 是三维张量batch_firstTrue 时形状为 (batch, seq_len, input_size)。经过 LSTM 后输出 out 的形状是 (batch, seq_len, hidden_size)我们只取序列最后一个时间步的隐藏状态因为这是模型“看过整个输入序列”之后的综合表示。最后通过一个全连接层把 hidden_size 映射到 output_size。参数说明hidden_size 我一般起步设 64数据量大或序列长时提到 128。num_layers 设 2 层通常足够超过 3 层在时序预测里容易过拟合且训练变慢。dropout 只在 num_layers 大于 1 时生效注意 PyTorch 的这个细节单层 LSTM 设定 dropout 不会报错但实际不起作用。input_size 在单变量场景是 1多变量场景是特征数量。LSTM 的输入还需要 reshape在训练循环里配合 DataLoader 处理。下面这个函数把二维滑窗样本变成三维张量def preprocess_for_lstm(X, y): X: shape (样本数, input_len) y: shape (样本数, pred_len) 返回: X 变为 (样本数, input_len, 1) X X.reshape(X.shape[0], X.shape[1], 1) y y.reshape(y.shape[0], y.shape[1]) return torch.FloatTensor(X), torch.FloatTensor(y)第三个维度是 input_size单变量就是 1。多变量时把多个特征在最后一维拼起来即可此时 input_size 等于特征数。3.2 训练循环学习率、梯度裁剪与 LSTM 的收敛问题LSTM 训练时最容易遇到的问题就是 loss 震荡或直接变 NaN。常见原因有两个学习率太大导致梯度爆炸输入数据没有归一化。梯度裁剪是必须加的这是血泪经验。import torch.optim as optim def train_model(model, X_train, y_train, epochs50, lr1e-3): 标准训练循环 optimizer optim.Adam(model.parameters(), lrlr) scheduler optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5 ) criterion nn.MSELoss() dataset torch.utils.data.TensorDataset(X_train, y_train) loader torch.utils.data.DataLoader(dataset, batch_size64, shuffleFalse) for epoch in range(epochs): model.train() epoch_loss 0.0 for batch_X, batch_y in loader: optimizer.zero_grad() pred model(batch_X) loss criterion(pred, batch_y) loss.backward() # 梯度裁剪防止 LSTM 梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() epoch_loss loss.item() * batch_X.size(0) avg_loss epoch_loss / len(dataset) scheduler.step(avg_loss) if (epoch 1) % 10 0: print(fEpoch {epoch1}/{epochs}, Loss: {avg_loss:.6f}) return model训练循环的逻辑每个 batch 先清零梯度前向传播计算预测值用 MSE 计算损失反向传播后做梯度裁剪再更新参数。这里有两个关键点第一shuffleFalse这是时序预测和普通分类任务的最大区别。打乱样本会破坏时间顺序模型学到的是“用未来预测过去”的假规律。第二clip_grad_norm_ 的 max_norm 我设 1.0这是一个保守值。如果 loss 频繁震荡可以降到 0.5如果训练稳定可以适当放开到 2.0。梯度裁剪只对 LSTM 这类循环网络特别重要Transformer 的训练主要靠学习率热身和 dropout。ReduceLROnPlateau 的作用是当 loss 连续 5 个 epoch 不下降时把学习率减半。时序预测的 loss 曲线经常是阶梯式下降这个调度器能有效避免模型在局部最优附近震荡。注意学习率初始值我用 1e-3 是默认经验。如果数据波动特别剧烈建议降到 5e-4。3.3 多步预测与结果还原递归预测的误差累积训练完模型后预测阶段有个容易忽略的细节预测未来 16 步是把最后 64 个点输入模型一次性得到 16 个输出还是先预测 1 步再把预测值拼进输入继续预测两种方式结果不同。def recursive_predict(model, last_window, pred_len): 递归多步预测 last_window: shape (input_len, 1)训练好的模型需要先归一化 返回: 预测的 pred_len 个值归一化尺度 model.eval() preds [] current_window last_window.clone() with torch.no_grad(): for _ in range(pred_len): # 当前窗口 shape: (1, input_len, 1) input_tensor current_window.unsqueeze(0) next_pred model(input_tensor) # shape (1, pred_len) # 取第一步预测值加入窗口丢掉窗口最前面的值 pred_val next_pred[0, 0] preds.append(pred_val.item()) current_window torch.cat([current_window[1:], pred_val.reshape(1, 1)], dim0) return np.array(preds)递归预测的逻辑是每次只取模型输出的一步把它追加到窗口末尾同时丢弃窗口最前面的一个点保持窗口长度不变然后继续预测下一步。这样做的好处是模型每次都在完整的上下文上做预测坏处是误差会累积第一步预测的误差会进入第二步的输入越往后越不可信。我一般在金融时序预测里会用直接多步预测一次性输出 16 步因为金融数据噪声大递归累积误差会让后面几步完全失真在设备寿命预测里递归预测更常用因为剩余寿命预测需要一条连续曲线而且预测步数通常很长直接多步预测的输出层维度会非常大。选择哪个策略取决于你的 pred_len 和数据噪声水平。预测完成后记得用训练时的 scaler 做 inverse_transform把结果还原到原始尺度。这一步忘记的话你得到的预测值全是 0 到 1 之间的小数业务上完全没法看。4. 用 Transformer 做时序预测位置编码与注意力机制的落地细节Transformer 在自然语言处理里的地位不用多说但用在时序预测上很多人套用 NLP 里 Encoder-Decoder 结构结果训练慢、效果差。其实时序预测用 Encoder-only 结构就够了关键在位置编码和输入映射。4.1 为什么 Encoder-only 结构就够用标准 Transformer 是 Encoder-Decoder 结构在机器翻译里 Decoder 负责逐步生成目标序列。但在时序预测里我们不是“翻译”序列而是“根据过去推断未来”。输入和输出是同一套数值空间Decoder 的交叉注意力机制在这里没有额外信息可挖。我一般只用 TransformerEncoder 堆叠输入是过去 N 步的数值输出是最后一步对应的隐藏状态再接一个全连接层预测未来 M 步。这和第 3 章 LSTM 的做法在结构上对齐。Transformer 的优势在于第一并行计算训练速度比 LSTM 快一个量级第二注意力机制能直接建模序列中任意两个位置的关系对长距离依赖比 LSTM 更友好。LSTM 要按顺序处理序列信息传递要经过所有中间步骤长度超过 100 步时前面的信息基本被遗忘了这是 LSTM 的天生短板。4.2 位置编码时序信息靠它注入Transformer 没有循环结构它本身对序列顺序完全不敏感。把输入序列顺序打乱注意力计算的结果一模一样。因此位置编码是 Transformer 用于时序预测的命门这是网上问得最多的一个问题transformer 的位置信息怎么计算。最常用的是三角函数位置编码也就是 The Illustrated Transformer 里介绍的那套公式是位置 pos 的维度 2i 上取 sin(pos / 10000^(2i/d_model))维度 2i1 上取 cos(...)。PyTorch 实现如下import math class PositionalEncoding(nn.Module): 三角函数位置编码 d_model: 编码维度需要和输入映射后的维度一致 max_len: 最大序列长度设置一个足够大的值比如 5000 def __init__(self, d_model, dropout0.1, max_len5000): super(PositionalEncoding, self).__init__() self.dropout nn.Dropout(pdropout) pe torch.zeros(max_len, d_model) position torch.arange(0, max_len, dtypetorch.float).unsqueeze(1) # 计算 1 / 10000^(2i/d_model)等价于 exp(-2i * log(10000) / d_model) div_term torch.exp( torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model) ) pe[:, 0::2] torch.sin(position * div_term) pe[:, 1::2] torch.cos(position * div_term) pe pe.unsqueeze(0) # shape (1, max_len, d_model) self.register_buffer(pe, pe) def forward(self, x): # x shape: (batch, seq_len, d_model) x x self.pe[:, :x.size(1), :] return self.dropout(x)代码逻辑先生成一个 max_len 行 d_model 列的矩阵 pe偶数列填充 sin奇数列填充 cos。div_term 的写法避免了直接计算 10000 的大数次幂导致数值溢出。register_buffer 把 pe 注册为模型的持久缓冲区它不会参与梯度更新但会随模型一起迁移到 GPU。在推理时必须注意一个细节位置编码是绝对位置训练时序列长度是 100推理时如果输入长度也是 100位置索引完全对得上但如果推理时想输入 150 个点位置 100 到 149 在训练时从未见过模型会在这几个位置上输出异常值。这是 Transformer 用于时序预测时一个非常隐蔽的坑后面避坑章节会详细展开。4.3 Transformer 模型定义d_model、nhead、num_layers 怎么定完整的 Transformer 时序预测模型代码如下class TransformerPredictor(nn.Module): Encoder-only Transformer 做时序预测 input_size: 输入特征维度单变量为 1 d_model: 编码维度一般取 64 或 128 nhead: 多头注意力头数需要能被 d_model 整除 num_layers: Encoder 层数 output_size: 预测步长 pred_len def __init__(self, input_size1, d_model64, nhead8, num_layers3, output_size16, dropout0.1): super(TransformerPredictor, self).__init__() # 输入映射把 input_size 维映射到 d_model 维 self.input_fc nn.Linear(input_size, d_model) self.pos_encoder PositionalEncoding(d_model, dropout) encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforward256, 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 shape: (batch, seq_len, input_size) x self.input_fc(x) # (batch, seq_len, d_model) x self.pos_encoder(x) # 加入位置信息 x self.encoder(x) # (batch, seq_len, d_model) x x[:, -1, :] # 取最后一个位置 x self.fc(x) return x模型逻辑输入先经过一个线性层把原始数值映射到 d_model 维的高维空间然后叠加位置编码进入 TransformerEncoder。Encoder 内部是自注意力加前馈网络输出序列中每个位置对应的编码向量。我们只取最后一个位置的向量送给全连接层输出预测值和 LSTM 取最后隐藏状态的做法一致。参数选择的经验值d_model 取 64如果数据长度超过 500 或特征多提到 128。nhead 取 8d_model 必须能被 nhead 整除这是硬性约束。num_layers 取 3 层起步超过 6 层在中小数据集上容易过拟合。dim_feedforward 是 Encoder 内部前馈网络的隐层维度取 d_model 的 4 倍256是默认做法。训练循环和 LSTM 部分几乎一样把模型替换即可。有一个区别值得注意Transformer 对学习率更敏感建议用 Noam 风格的学习率热身或者把初始学习率降到 5e-4 并配合 ReduceLROnPlateau。我刚开始用 Transformer 做金融时序预测时1e-3 的学习率前几个 batch 就把 loss 打到 NaN降到 5e-4 后一切正常这是血泪经验。5. 避坑时序预测源码里最容易翻车的 5 个细节这一章把我在实战里踩过的坑集中列出每一条都按“现象 → 原因 → 解决”的顺序写希望能让你少走弯路。5.1 归一化泄漏验证集指标虚高现象验证集 loss 低得离谱预测曲线几乎贴着真实值走模型上线后效果一落千丈。原因对全量数据一次性调用 scaler.fit_transform(data)训练集和测试集混在一起计算 min 和 max。测试集的信息被泄进训练阶段模型相当于提前“知道”了未来数据的数值范围。解决严格做到 scaler.fit(train_data) 只在训练集上计算统计量测试集和未来的新数据都只用 transform。这条规则对新数据也生效你在模型上线后接到的每一条新样本都要用同一个 scaler 做变换。在工程上把 scaler 和模型一起保存推理脚本里加载同一个 scaler是最稳妥的做法。5.2 随机打乱样本时序顺序被破坏现象训练 loss 正常下降但验证集预测结果出现奇怪的“提前反应”预测曲线比真实曲线提前变化。原因DataLoader 里设了 shuffleTrue。滑窗样本之间共享大量重叠数据打乱后训练集的某个样本可能来自测试集时间段模型学到了跨时间的“偷看”能力。验证时恢复时间顺序这种能力立刻失效。解决训练时设置 shuffleFalse按时间顺序逐个 batch 喂给模型。如果担心模型过拟合不是在 shuffle 上做文章而是增加 dropout、减小 hidden_size、加早停。5.3 标签 shift 错位预测成了滞后复制现象预测曲线形状和真实曲线几乎一致但整体向右平移了一个窗口像一条延迟的复制品。原因构造滑窗数据时索引写错了最常见的是 y[i] 直接取了 x[i]也就是用当前值预测当前值。loss 虽然很低但模型只是学到了恒等复制。有些库里 shift 函数默认参数不同也会造成类似错位。解决在构造窗口函数后立刻打印一组 X[i] 和 y[i] 核对。确认 y 的第一个元素是 X 的最后一个元素之后至少一个时间步。这个检查看似多余但能省掉后面所有的排查时间我每次写新数据集的预处理都会做这一步。5.4 Transformer 位置编码外推失效现象Transformer 训练时 loss 正常测试阶段输入长度和训练一致时效果很好一旦预测时需要更长的输入或更长的序列预测值迅速发散甚至出现数万倍的异常数值。原因三角函数位置编码虽然理论上可以外推但绝对位置编码在超出训练长度的区域注意力分布是模型从未见过的。位置 120 的编码向量和位置 30 的相对关系模型没有学会如何处理。解决把预测步数约束在训练窗口长度范围内训练时窗口长度比预测时的输入长度多留 20% 的余量。具体到代码上训练时用长度为 128 的输入推理时也只用 128。如果业务需要更长输入应该重新训练而不是直接改推理长度。5.5 按时间切分了但切分点没有留 gap现象验证集前几个样本的误差明显小于后面样本整体指标虚高约 10% 到 20%。原因训练集最后一段和验证集第一段紧挨着以输入长度 64 为例验证集第一个样本的输入窗口包含了训练集最后 63 个点数。你以为是验证实际上大部分输入信息都来自训练集。更严重的场景是切分点没有错开 pred_len验证集前 pred_len 个样本的预测目标也在训练集范围内。解决切分后丢弃两个集合交界处的样本。常见做法是训练集结束位置和验证集开始位置之间空出 input_len pred_len 个点的间隔。代码上写train_data data[:split_idx - gap]test_data data[split_idx:]gap 取 input_len 即可。6. 验证预测效果的三个进阶技巧让源码真正可信模型训练完不等于工作结束前面讲的所有细节最后都要落到“预测结果是否真的可信”上。这一章分享三个我一直在用的验证技巧能帮你更客观地判断模型好坏。6.1 直接多步预测和递归预测做对比我在 3.3 节提到递归预测存在误差累积实际使用时建议把两种策略都跑一遍。训练时模型输出是 pred_len 个值这叫直接多步预测推理时改成递归模式每次只输出第一步。对比两种方式在验证集上的表现# 直接预测模型一次性输出 16 步 pred_direct model(last_window.unsqueeze(0)) # shape (1, 16) # 递归预测用 3.3 节的 recursive_predict pred_recursive recursive_predict(model, last_window, pred_len16) # 对比前 4 步和最后 4 步的误差 print(Direct 前4步误差:, np.mean(np.abs(pred_direct[0, :4] - y_true[:4]))) print(Recursive 前4步误差:, np.mean(np.abs(pred_recursive[:4] - y_true[:4]))) print(Direct 后4步误差:, np.mean(np.abs(pred_direct[0, -4:] - y_true[-4:]))) print(Recursive 后4步误差:, np.mean(np.abs(pred_recursive[-4:] - y_true[-4:])))如果直接预测的后段误差明显小于递归预测说明数据噪声主导递归累积误差不可接受线上应改用直接预测。反之如果两者差距不大递归预测可以给你一条更平滑的完整曲线方便展示趋势。6.2 滚动回测模拟真实预测节奏时序预测在线上是不断重复的每来一批新数据就要预测一次未来。滚动回测就是模拟这个过程避免一次性预测的偶然性。def rolling_backtest(model, scaled_data, input_len, pred_len, step8): 从数据中间开始每次往前推进 step 个时间步重复预测 返回每个窗口的预测误差 errors [] for start in range(input_len, len(scaled_data) - pred_len, step): window torch.FloatTensor(scaled_data[start - input_len:start]).reshape(1, input_len, 1) with torch.no_grad(): pred model(window).numpy().flatten() true scaled_data[start:start pred_len].flatten() errors.append(np.abs(pred - true)) return np.concatenate(errors)step 参数控制每次推进的步数。训练消耗不大时step 取 1 最接近线上节奏但预测次数会很多耗时长我一般取 pred_len 的一半平衡耗时和真实性。滚动回测得到的误差分布比单次预测的指标更能反映模型在持续运行中的真实表现。6.3 误差分段分析找出系统性偏差平均误差只能告诉你“整体还行”但还不能告诉你在什么场景下不行。我习惯把预测值和真实值按区间分段查看每个区间的误差真实值区间样本数平均误差观察0.0 - 0.21830.012低值区预测稳定0.2 - 0.41260.018略有偏差0.4 - 0.6980.035偏差变大0.6 - 0.8410.072高值区严重低估0.8 - 1.0150.118峰值基本靠猜如果高值区误差明显放大说明模型对极值不敏感此时不要盲目调模型结构先检查数据分布。峰值样本在训练集中占比太少模型根本没有足够的信号去学会预测极值。常见做法是对训练数据做加权采样让峰值样本在训练中被更多次看到或者专门单独建模预测残差。我把这套验证流程固化成习惯了每次训练完必做三个动作对比直接和递归预测的尾段误差、跑一次滚动回测、按区间统计误差分布。这三个动作做完模型能不能上线我心里基本有数。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑