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

资讯详情

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

Transformer时间序列预测实战:Pytorch实现与电力负荷预测详解

Transformer时间序列预测实战:Pytorch实现与电力负荷预测详解 简介时间序列预测是数据分析与工程实践中的经典课题从统计学模型到深度学习方法技术演进不断突破精度与效率的边界。传统循环神经网络如LSTM虽能捕捉时序依赖却受限于长序列下的梯度衰减与并行计算瓶颈。自注意力机制的提出让模型能够直接建立序列中任意位置间的关联Transformer架构由此成为长程建模的有力工具。这种架构凭借多头注意力和位置编码可有效提取多尺度时序特征广泛应用于电力负荷预测、气象预报、金融时序分析等场景。本文基于Pytorch框架以公开电力负荷数据集为例系统讲解数据预处理、位置编码、多头注意力、Encoder-Decoder结构及训练调参的完整流程帮助读者掌握从单变量到多变量的预测模型构建方法并理解Transformer相比LSTM的优势与适用边界。 Transformer做时间序列预测最近一两年确实火得不行。我最早用LSTM做多步预测调了一堆参数换各种trick效果始终差口气。后来把Transformer跑通才算真正打开了局面。这篇文章我会把整套Pytorch实现从头到尾拆开讲包括数据怎么处理、模型怎么搭、训练时有哪些坑以及为什么Transformer在这个场景下值得用——当然最后也会说说它适合什么情况、不适合什么情况。整套代码我按照“完整可用、拿来就能跑”的标准整理过数据也用了一份公开的电力负荷数据集这样大家复现起来比较顺手不用自己折腾数据格式。1. 方案选型为什么用Transformer做时间序列预测1.1 LSTM遇到瓶颈Transformer为什么能顶上如果你做过时间序列预测应该对LSTM那套很熟了。LSTM的核心思想是通过门控机制逐时间步传递隐状态让网络“记住”重要的历史信息。这在序列长度几百以内时表现还不错但一旦序列变长问题就来了梯度传播路径太长。虽然LSTM有门控缓解梯度消失但信息从序列头部传到尾部仍然要经过很多步前期的关键特征容易被稀释。无法并行训练。因为每个时间步依赖前一个时间步的隐状态GPU的并行能力被浪费了训练很慢。长距离依赖捕捉能力有限。对于间隔很远的相关事件比如一周前和今天的用电模式LSTM很难直接关联。Transformer走的是完全不同的路子。它通过自注意力机制Self-Attention直接把序列中任意两个位置联系起来不管隔着多远都能一步到位计算它们之间的相关性。这等于把“记忆跨度”拉到了序列长度本身。再加上多头注意力机制模型能同时从多个子空间学习不同维度的依赖关系表达能力强了一大截。以电力负荷预测为例晚高峰的负荷和当天上午的气温、前一周同一天的负荷都有关系。LSTM要慢慢迭代才能摸清这种复杂关联而Transformer第一层就能计算出“当前时刻”和“历史关键点”之间的权重分配。1.2 Encoder-Decoder架构与时间序列预测的映射Transformer最早是为机器翻译设计的原文输入进Encoder目标输出进DecoderEncoder把输入编码成语义表示Decoder逐步生成翻译结果。把它套到时间序列预测上其实非常自然Encoder的输入是历史观测序列比如过去168个小时的负荷数据。Decoder的输入是已知的未来片段或零向量占位输出是预测值序列比如未来24小时的负荷。这就形成了标准的Seq2Seq结构历史序列进未来序列出。做多步预测时我们让Decoder一次输出整个预测区间而不是像传统方法那样一步一步滚动预测这样既能保证效率又能让模型同时考虑预测点之间的相互依赖。1.3 一些容易混淆的概念澄清新手经常搞混几个东西这里先说明白Transformer和Self-Attention不是一个概念。Self-Attention是核心机制Transformer是完整架构它由Self-Attention、前馈网络、残差连接、层归一化组合而成。位置编码Positional Encoding是必须的。Self-Attention本身没有顺序感输入顺序打乱后计算结果完全一样。但时间序列里顺序信息极其关键所以必须把位置信息编码后加到输入向量里。多步预测不是只有一种做法。可以直接多输出也可以用滚动预测还可以混合。Transformer天然支持一次输出多个时间步这也是它适合多步预测的原因。1.4 本项目的技术选型整个项目我做了这几个选型决定理由也一并说明框架Pytorch。生态成熟调试方便动态图机制对做研究和实验更友好。数据公开的电力负荷数据集单变量序列预测。先把基础打牢再扩展到多变量。架构标准TransformerEncoder长度和Decoder长度相等。后续如果要换Informer、PatchTST改动接口也方便。训练方式teacher forcing教师强制训练时Decoder每个时间步用真实历史值作为输入推理时才自回归。2. 环境准备与数据预处理2.1 环境依赖与安装建议我这边环境是Python 3.9 Pytorch 2.0 CUDA 11.8各位可以根据自己机器情况调整。核心依赖如下pip install torch2.0.1 pip install numpy pandas matplotlib如果机器有NVIDIA显卡建议装GPU版训练速度快好几倍。没有显卡纯CPU也能跑就是速度慢点把epoch数调小即可。注意Pytorch 2.x在Windows上安装GPU版本时CUDA版本必须在11.8及以上太老的显卡驱动可能不支持。装完用torch.cuda.is_available()验证一下返回True才说明没问题。2.2 数据集说明电力负荷数据我用的是UCI公开的ElectricityLoad数据集里面记录了某地区15分钟粒度的电力负荷值跨度几年。这个数据有几个特点很适合做时间序列预测实验有明显的周期性——日周期、周周期一目了然方便验证模型是否学到了周期模式。有波动和高峰——测试模型对极端值的响应能力。是单变量数据原始模型不需要做特征工程方便入门。下载完后数据是CSV格式大概几百MB里面的字段包括日期时间和负荷值。做实验前我习惯先看一眼数据的形状和值域确保没有异常。2.3 数据预处理三个关键步骤预处理我归纳为三步清洗、归一化、滑动窗口切分。第一步清洗。原始数据里偶尔会有负值或明显异常值比如突然变成0过滤器处理一下。电力负荷一般不会为负数值突变超过10倍基本就是传感器问题我用前向填充法处理。第二步归一化。我测试过的经验是使用MinMaxScaler把数据缩放到[0, 1]区间比StandardScaler更适合负荷数据。原因在于负荷数据整体平稳没有特别大的尖峰。这段逻辑必须和训练数据拟合测试数据只能transform不能重新fit否则会造成数据泄漏。第三步滑动窗口切分。设定一个输入窗口长度如168个点代表一周一个输出窗口长度如24个点代表一天然后按步长滑窗生成训练样本。这一步有两个参数可调window_size 168Encoder输入序列长度pred_len 24Decoder预测序列长度切分后按7:2:1划分为训练集、验证集和测试集注意必须按顺序切不能随机打乱否则时间顺序就乱了预测任务就没意义了。2.4 数据增强的思考时间序列数据不像图像那样容易做增强但也不是完全没有办法。我做实验时试过两种噪声注入在训练输入里加一点高斯噪声模拟传感器误差有一定正则化效果。尺度平移小幅调整输入序列的均值增强模型对不同物理量级数据的适应能力。不过说句实话对时间序列预测来说数据增强收益远不如图像领域明显。如果你的数据量已经足够这个环节可以跳过别为了增强而增强。3. Transformer核心代码实现3.1 数据加载器Dataset与DataLoader实现数据加载是整个项目的地基。我自定义了一个Dataset类作用是让模型可以按索引取到“一对”输入序列目标序列。核心逻辑如下import torch from torch.utils.data import Dataset, DataLoader class TimeSeriesDataset(Dataset): def __init__(self, data, window_size168, pred_len24): self.data data self.window_size window_size self.pred_len pred_len def __len__(self): return len(self.data) - self.window_size - self.pred_len 1 def __getitem__(self, idx): x self.data[idx : idx self.window_size] y self.data[idx self.window_size : idx self.window_size self.pred_len] return torch.FloatTensor(x).unsqueeze(-1), torch.FloatTensor(y).unsqueeze(-1)这里有个值得注意的细节unsqueeze(-1)在最后加了一个维度。因为Transformer的输入要求是三维的(batch_size, seq_len, feature_dim)虽然我们只用了单变量但维度必须保持3D否则后面多头注意力计算会直接报错。然后在训练主程序里创建DataLoaderfrom torch.utils.data import DataLoader train_dataset TimeSeriesDataset(train_data_norm, window_size168, pred_len24) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue)shuffleTrue在训练时是必要的。虽然数据是按时间顺序切分的但如果每批都按顺序取模型会学到“顺序就是特征”这种假规律泛化能力变差。注意验证集和测试集的DataLoader要把shuffle设为False。3.2 位置编码给模型加上“时间感”前面说过Self-Attention本身不区分位置顺序。为了让模型感知“这是第3个时刻那是第150个时刻”需要在输入上叠加位置编码。我实现的是论文里原始的正弦位置编码公式比较稳定import numpy as np import torch import torch.nn as nn import math class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len1000): super().__init__() pe torch.zeros(max_len, d_model) position torch.arange(0, max_len, dtypetorch.float).unsqueeze(1) 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).transpose(0, 1) self.register_buffer(pe, pe) def forward(self, x): return x self.pe[:x.size(0), :]为什么用正弦余弦而不是直接学一个位置向量核心原因是正弦余弦函数外推性好——训练时见过的序列长度是168未来要预测时如果输入变成192正弦编码依旧能给出合理的位置向量而可学习的位置嵌入直接歇菜。此外sin/cos的交替规律本身自带“相对距离”信息对模型捕捉周期性有帮助。有个细节要注意register_buffer而不是普通赋值。这样位置编码会随模型一起迁移到GPU但不会参与梯度更新省显存不说还避免了被当参数训练。3.3 多头注意力实现与维度流转多头注意力是整个Transformer的心脏。原理不复杂把输入切成n_heads个头每个头独立做注意力计算然后把结果拼起来。class MultiHeadAttention(nn.Module): def __init__(self, d_model, n_heads): super().__init__() assert d_model % n_heads 0 self.d_k d_model // n_heads self.n_heads n_heads self.W_q nn.Linear(d_model, d_model) self.W_k nn.Linear(d_model, d_model) self.W_v nn.Linear(d_model, d_model) self.W_o nn.Linear(d_model, d_model) def forward(self, query, key, value, maskNone): batch_size query.size(0) Q self.W_q(query).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2) K self.W_k(key).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2) V self.W_v(value).view(batch_size, -1, self.n_heads, self.d_k).transpose(1, 2) scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) if mask is not None: scores scores.masked_fill(mask 0, -1e9) attn torch.softmax(scores, dim-1) context torch.matmul(attn, V) context context.transpose(1, 2).contiguous().view(batch_size, -1, self.n_heads * self.d_k) return self.W_o(context)这里容易踩的坑是view和transpose后的维度顺序。如果你直接用view(batch_size, seq_len, n_heads, d_k)然后再transpose(1, 2)从内存布局来说是可以的但新手经常忘记先transpose再view导致维度对不上。我习惯的顺序是Linear把特征维度映射到d_modelview拆成(batch, seq_len, n_heads, d_k)transpose(1, 2)变成(batch, n_heads, seq_len, d_k)masked_fill那句是Decoder里用的。因为Decoder在自回归生成时只能看到当前位置之前的输出不能偷看未来所以需要把未来的位置用一个大负数盖住让softmax算出来的权重接近0相当于“盖住”。3.4 Encoder与Decoder完整结构Transformer的Encoder由多头注意力和前馈网络构成每个子层外面套残差连接和层归一化。残差连接能解决深层网络退化问题层归一化则负责稳定每层的输出分布。class EncoderLayer(nn.Module): def __init__(self, d_model, n_heads, d_ff, dropout0.1): super().__init__() self.self_attn MultiHeadAttention(d_model, n_heads) self.feed_forward nn.Sequential( nn.Linear(d_model, d_ff), nn.ReLU(), nn.Linear(d_ff, d_model) ) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.dropout nn.Dropout(dropout) def forward(self, x): # 自注意力子层 attn_out self.self_attn(x, x, x) x self.norm1(x self.dropout(attn_out)) # 前馈子层 ff_out self.feed_forward(x) x self.norm2(x self.dropout(ff_out)) return xDecoder层比Encoder多一个Cross-Attention交叉注意力子层它负责让Decoder在生成预测时去“查阅”Encoder编码的历史信息。这一层是Seq2Seq的核心Encoder输出的语义向量在这里被当作Key和Value而Decoder自身的输出作为Query。有趣的是对纯时间序列预测任务来说Encoder-Decoder结构有时并不是必须的。很多人直接用单独的Encoder把历史序列编码后接一个全连接层直接输出预测值效果也不错。但如果你要做真正复杂的多步预测Decoder的自回归能力还是有优势的它能让预测点之间产生依赖。我最终采用标准Encoder-Decoder结构这样后续如果想迁移到其他时序任务比如异常检测、填补缺失值代码改动最小。3.5 完整模型组装把上面所有模块组合起来就成了一个完整的Transformer时间序列预测模型class TransformerTimeSeries(nn.Module): def __init__(self, d_model64, n_heads4, n_layers3, d_ff128, window_size168, pred_len24): super().__init__() self.input_proj nn.Linear(1, d_model) self.pos_enc PositionalEncoding(d_model, max_lenwindow_size pred_len) self.encoder_layers nn.ModuleList([ EncoderLayer(d_model, n_heads, d_ff) for _ in range(n_layers) ]) self.decoder_layers nn.ModuleList([ DecoderLayer(d_model, n_heads, d_ff) for _ in range(n_layers) ]) self.output_proj nn.Linear(d_model, 1) def forward(self, src, tgt): # src: (batch, src_len, 1), tgt: (batch, tgt_len, 1) src self.input_proj(src) tgt self.input_proj(tgt) # 拼接位置编码 src self.pos_enc(src) tgt self.pos_enc(tgt) for layer in self.encoder_layers: src layer(src) for layer in self.decoder_layers: tgt layer(tgt, src) return self.output_proj(tgt)里面的input_proj是一个$1 \to d_model$的线性层作用是把单变量负荷值映射到高维空间。这个设计对多变量扩展也很友好——如果以后有10个特征直接把nn.Linear(1, d_model)改成nn.Linear(10, d_model)就行。4. 训练流程、损失函数与参数调优4.1 损失函数与优化器选择时间序列预测回归任务损失函数我用的是MSE均方误差criterion nn.MSELoss()MSE对异常值敏感峰值预测不准时损失会被放大这会让模型更努力地去拟合高峰但也会导致对普通时段的拟合稍微粗糙一些。如果你的业务只关心趋势走向不关心精确峰值可以换成MAE。我测试下来MSE在电力负荷数据上整体效果更好所以保留了MSE。优化器用的是AdamW加权重衰减防止过拟合。注意Pytorch里AdamW和Adam是不同的AdamW把权重衰减和应用梯度解耦了效果更稳定。optimizer torch.optim.AdamW(model.parameters(), lr0.001, weight_decay1e-5)4.2 学习率调度warmup 余弦退火Transformer对学习率非常敏感太大训练直接发散太小收敛太慢。我推荐warmup策略——前几个epoch用很小的学习率“热身”后面再放开最后用余弦退火慢慢收敛。def get_lr_scheduler(optimizer, warmup_steps, total_steps): def lr_lambda(step): if step warmup_steps: return step / max(1, warmup_steps) progress (step - warmup_steps) / max(1, total_steps - warmup_steps) return 0.5 * (1 math.cos(math.pi * progress)) return torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)为什么Transformer需要warmup因为训练初期模型参数是随机的如果一开始就用大学习率Self-Attention的权重矩阵会被推到一个很差的方向后面很难拉回来。warmup相当于让模型先“站稳”再加速跑。4.3 训练循环完整实现训练循环我封装成一个函数方便修改和复用def train_epoch(model, train_loader, criterion, optimizer, device): model.train() total_loss 0 for batch_x, batch_y in train_loader: batch_x batch_x.to(device) batch_y batch_y.to(device) # Decoder输入用真实值的前pred_len-1个点 最后一个已知点 decoder_input torch.cat([batch_x[:, -1:, :], batch_y[:, :-1, :]], dim1) optimizer.zero_grad() output model(batch_x, decoder_input) loss criterion(output, batch_y) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() total_loss loss.item() return total_loss / len(train_loader)这段代码里有一个非常关键的实操细节Decoder的输入和输出的对齐问题。训练时我们采用teacher forcing策略Decoder输入是[最后一个已知点, 真实预测值的前pred_len-1个点]而Decoder输出则对应batch_y的全部pred_len个点。这样错开一位模型才能学会“根据当前输入预测下一个点”。clip_grad_norm_这行初学者容易忽略但对Transformer来说极其重要。Self-Attention的梯度范数在训练初期可能非常巨大不做梯度裁剪很容易让训练崩溃。我一般把max_norm设为1.0或0.5。4.4 评估指标不只盯着Loss看训练结束后我习惯同时看RMSE和MAE两个指标。RMSE对大误差敏感能反映模型最差情况MAE则反映平均误差水平。def evaluate(model, test_loader, criterion, device): model.eval() total_loss 0 preds, trues [], [] with torch.no_grad(): for batch_x, batch_y in test_loader: batch_x, batch_y batch_x.to(device), batch_y.to(device) decoder_input torch.cat([batch_x[:, -1:, :], batch_y[:, :-1, :]], dim1) output model(batch_x, decoder_input) loss criterion(output, batch_y) total_loss loss.item() preds.append(output.cpu().numpy()) trues.append(batch_y.cpu().numpy()) preds np.concatenate(preds, axis0) trues np.concatenate(trues, axis0) rmse np.sqrt(np.mean((preds - trues) ** 2)) mae np.mean(np.abs(preds - trues)) return total_loss / len(test_loader), rmse, mae从个人经验来看RMSE比Loss更直观——因为它还原到了原始数据的量纲归一化之前的kW级别可以说“平均差了多少千瓦”。4.5 超参数推荐的“起步套餐”我测试过几组超参数下面这组是通用性最强、最稳的起步配置参数名推荐值备注d_model64嵌入维度数据量小就别用256n_heads4必须能被d_model整除n_layers33层足够再深容易过拟合d_ff128前馈网络隐藏层维度一般是d_model的2倍dropout0.1防止过拟合batch_size64显存不够可以降到32learning_rate0.001配warmup使用window_size168一周的15分钟数据点pred_len24一天epochs60用早停策略防过拟合如果数据量大、特征多比如多变量可以把d_model提到128或256n_layers提到4层以上。但要注意模型参数翻倍后训练时间也差不多翻倍过拟合风险同步上升。5. 训练结果分析与可视化5.1 损失曲线解读三个阶段的信号训练完成后把训练集和验证集的Loss曲线画出来。正常情况会看到三个阶段快速下降期前10个epoch模型从随机状态快速收敛warmup结束后Loss降得很快。缓慢优化期10-40个epochLoss下降速度变慢模型在微调细节。平台期/过拟合期40 epochs训练Loss继续下降但验证Loss开始反弹这时就该用早停机制了。如果验证Loss从第二个epoch就开始上升说明学习率太大了或者模型结构有问题。这时候别急着加数据先调小学习率。5.2 预测效果对比图把测试集上某一段的预测值和真实值画在一起能直观看出模型水平。我用168步历史预测24步图上能清楚看到整体趋势能跟上尤其是平滑的日周期部分。峰值处有滞后模型倾向于把高峰预测得略晚或偏低。这是时间序列预测的普遍问题Transformer也不例外。周期性强的时间段预测准周末和工作日的区分模型能学到。用matplotlib画图代码我放在完整源码里。预测结果和真实值画两条曲线一眼就能看出哪里预测得好、哪里失真。5.3 注意力权重可视化看看模型“关注”了什么Transformer有个额外优势——可以把注意力权重提取出来做可视化看看模型在预测时到底关注历史序列的哪些位置。# 注册hook或者修改forward返回注意力权重 # 简化版直接访问encoder层中self_attn的attn矩阵我观察到的一个有趣现象是模型在预测某一时刻负荷时会重点关注24小时前昨天同一时刻和168小时前上周同一时刻的负荷值。这说明模型确实学到了电力负荷的日周期和周周期模式而不只是死记硬背近期趋势。这种可解释性是LSTM很难提供的。6. 常见问题与排查技巧6.1 典型报错与解决方案问题现象原因解决方案AssertionError: d_model % n_heads ! 0d_model不能被n_heads整除把n_heads调整为能整除d_model的值维度不匹配报错Expected 2D or 3D input输入少了特征维度检查输入是否为(batch, seq_len, feature_dim)三维训练Loss卡住不动学习率太小或模型容量不够调大lr或增加d_model/n_layers验证Loss是NaN学习率太大导致梯度爆炸降低lr开启梯度裁剪预测结果全是常数模型退化/输出被归一化拉平检查数据是否有异常段尝试加长window_sizeGPU显存不足batch_size太大或序列太长减小batch_size或精简d_model6.2 预测结果“慢半拍”的解决方案这是时间序列预测最常见的坑。预测曲线比真实曲线滞后一拍说明模型过度依赖最近时刻的值没有学到真正的动态规律。我有三个经验做法加长输入窗口从168加到336让模型看到更多的历史周期降低对最近时刻的依赖。调整损失权重对预测区间的后半段加大权重强制模型把注意力放到远期。检查数据归一化如果归一化后数据范围压缩过度模型容易走保守路线。6.3 过拟合的判断与处理Transformer参数量大在数据量不足时很容易过拟合。判断标准很直接训练Loss持续下降但验证Loss在某一个点开始上升。处理过拟合的方案按优先级排序增加数据量——最有效但通常最难实现。增大dropout——从0.1提到0.2或0.3效果立竿见影。减小模型容量——把n_layers从3降到2d_model从128降到64。早停机制——监控验证Loss连续patience个epoch没有下降就停止训练。我实现的EarlyStopping类放在源码里用起来很简单传一个patience10即可。6.4 CPU与GPU推理效果差异同一套模型CPU和GPU推理结果应该完全一样。但如果你遇到不一样的情况检查Pytorch的eval()模式和train()模式是否设置对了。train()模式下Dropout和LayerNorm的行为和eval()不同预测时忘记切到eval()会导致结果不稳定而且每跑一遍结果都不一样。7. 从单变量到多变量扩展思路与高级技巧7.1 多变量输入的改造方案如果想把温度、湿度、节假日特征加进来只需要改两处数据预处理阶段把特征拼接成(batch, seq_len, num_features)。把模型的input_proj从nn.Linear(1, d_model)改成nn.Linear(num_features, d_model)。就这么简单。Transformer对多模态特征的处理比LSTM灵活得多因为它可以在注意力层直接学习特征之间的交互关系。7.2 长序列预测时的痛点如果你把预测长度从24提升到168也就是一次预测未来一周你会发现效果明显变差。这不是代码Bug而是Transformer在长序列预测上的已知短板——Self-Attention的计算复杂度是$O(n^2)$序列越长不仅计算开销大注意力分布也会被稀释。针对这个问题有两条技术路线Informer通过ProbSparse注意力机制只让每个Query关注少数重要的Key把复杂度降到$O(n \log n)$。PatchTST把时间序列切成Patch小块以Patch为单位做注意力计算减少序列长度。这两个都是Transformer的时序领域变体等基础版跑通之后非常值得尝试。7.3 部署到生产环境的实时性考虑如果要做实时预测比如每15分钟预测一次未来24小时负荷Transformer推理速度完全能扛住。模型参数量不大的情况下单次推理在CPU上只要几十毫秒。注意两个点推理时的Decoder输入不再使用teacher forcing而是自回归——把上一次预测的输出拼到输入里逐点生成。归一化参数MinMaxScaler必须和训练时一致推理前先加载保存好的scaler对象不能用新数据重新fit。8. 项目总结与个人心得8.1 代码仓库使用说明所有源码都放在我的项目仓库里目录结构如下transformer_time_series/ ├── data/ │ └── electricity_load.csv ├── models/ │ ├── transformer.py │ └── positional_encoding.py ├── utils/ │ ├── dataset.py │ ├── preprocessing.py │ └── metrics.py ├── train.py ├── evaluate.py └── requirements.txt直接运行python train.py就能开始训练训练好的模型会保存在checkpoints/目录下。运行python evaluate.py可以评估模型并生成图表。8.2 个人踩坑记录这次做项目我踩过最大的坑是Decoder输入输出对齐。最开始我把Decoder的输入设成了和输出完全一样结果训练时loss很低一测试一塌糊涂。原因就是模型学会了“把输入抄一遍输出”根本没学到预测能力。后来多加了一步错位问题才解决。另一个坑是学习率。刚开始用了普通的SGD优化器训练速度慢得离谱换AdamW之后才算正常。后来换warmup调度又是另一个提升。8.3 后续优化的方向建议如果你把本文这套代码跑通了下一步我建议按下面几个方向深入尝试把window_size改成336两周看模型是否在周级别周期上有更好的表现。加入节假日特征作为外部变量改造成多变量输入。对比Informer、PatchTST等变体在同样数据上的效果。用AutoML工具如Optuna搜索超参数看一下当前最优配置和我的推荐配置差多少。Transformer在时间序列预测上确实有它的独特优势尤其是长序列和复杂依赖场景。但它也不是万能药——数据量很少比如只有几百个点的时候用简单线性回归或者XGBoost可能效果更好、部署更轻。关键是根据你的数据规模、业务场景和精度要求选择合适的工具。希望这份完整源码和这篇实战讲解能帮你少走一些弯路把更多时间花在真正重要的调优和问题上。本文还有配套的精品资源点击获取
返回列表