
如果让你预测一支股票明天的收盘价、一条生产线的故障概率或者一座城市的用电负荷你会先想到哪个模型在很多技术社区里答案往往集中在两个名字上LSTM 和 Transformer。它们在 B 站、GitHub、知乎上的教程多到刷不完但真正能让人从头理解原理、又动手跑通代码的内容其实并不多。这篇文章的定位很直接用通俗语言把 LSTM 和 Transformer 在时间序列预测上的核心原理讲清楚再手把手带你用 PyTorch 复现两个可运行的预测模型。我不会把论文逐句翻译而是把论文里最关键的设计思想提炼出来落到代码上。读完你不仅能理解两者为什么有效还能自己换数据、改参数、评估效果。先说结论LSTM 靠三个门控制信息流动处理中等长度序列时稳定可靠Transformer 靠自注意力机制直接捕捉任意位置之间的关系适合长序列和复杂依赖但数据量小时容易过拟合。没有绝对更强的模型只有更合适的场景。后面每一章都会围绕这个判断展开包括原理对比、完整代码、运行结果和常见坑建议收藏后照着跑一遍。1. 为什么 LSTM 和 Transformer 会成为时间序列预测的主流选择时间序列预测一直是个既经典又难啃的问题。从传统的 ARIMA、指数平滑到后来的 RNN、LSTM再到现在的 Transformer模型演进背后反映的是同一个诉求如何从过去的观测值中提取出能预测未来的规律。最早人们用统计模型但统计模型对非线性关系、多变量交互、长期依赖的刻画能力有限。后来 RNN 被引入它的循环结构让网络可以按时间顺序逐步处理数据理论上能记住很长一段历史。可真实训练中RNN 很容易出现梯度消失和梯度爆炸特别是序列超过几十步之后早期信息基本就传不过去了。LSTM 在 1997 年由 Hochreiter 和 Schmidhuber 提出专门解决 RNN 的长期记忆问题。它通过门控机制决定“记什么、忘什么、输出什么”让梯度能沿着一条相对平稳的路径回传因此迅速成为序列建模的标准工具。即便到了今天LSTM 在工业界的时间序列任务里依然有大量应用很多线上系统的第一版预测模型就是 LSTM。Transformer 则是 2017 年由 Vaswani 等人在论文《Attention Is All You Need》中提出的架构。它一开始是为机器翻译设计的但很快就被迁移到 NLP、CV 和时序预测领域。Transformer 最大的突破在于抛弃了循环结构改用自注意力机制直接计算序列里任意两个位置之间的关联权重。这意味着它不再受限于“先处理前一步、再处理后一步”的顺序而是可以并行计算训练效率大幅提升捕捉长期依赖的能力也更强。所以这两个模型能火起来不是因为营销而是因为它们分别代表了序列建模历史上的两次重要突破。作为机器学习开发者掌握这两套模型基本就拿到了如今时间序列预测领域的主流武器。下面先把它们的核心原理讲透再进入代码环节。2. LSTM 与 Transformer 的原理解读2.1 LSTM用门控机制记住该记住的LSTM 的完整名称是 Long Short-Term Memory长短期记忆网络。它之所以叫“记忆”是因为它内部维护了一条贯穿整个序列的单元状态cell state你可以把它想象成一条传送带信息可以从序列开头一路传到结尾中间几乎不衰减。但传送带不能什么都不加控制否则所有信息都会混在一起。LSTM 在传送带旁边设置了三个门遗忘门、输入门、输出门。遗忘门决定上一个时刻的单元状态有多少要被丢弃。它接收当前输入和上一时刻的隐藏状态经过 sigmoid 激活函数输出一个 0 到 1 之间的值0 表示完全丢弃1 表示完全保留。输入门决定当前新信息有多少写入单元状态它会先通过 sigmoid 决定“要不要写入”再用 tanh 生成候选值。输出门决定当前时刻要把单元状态中的哪些信息输出到隐藏状态方便下一层或预测头使用。这里有一个初学者最容易误解的地方LSTM 的“记忆”并不是靠显式的缓存而是靠门控机制让梯度在单元状态这条直通路径上流动。正因为这条路径的存在反向传播时梯度不会经过多个非线性压缩层长期依赖问题才得以缓解。从代码实现的角度看PyTorch 的nn.LSTM已经把整个门控逻辑封装好了。你只需要指定输入维度、隐藏层维度、层数和 dropout 比例剩下的都由框架完成。但理解门控原理仍然重要因为它直接影响你后续调参的方向比如隐藏层大小、层数、序列长度以及什么时候该换用别的方法。2.2 Transformer用注意力机制重写序列建模Transformer 的核心是自注意力机制。自注意力的思想很简单对于序列中的每一个位置我们都计算它与其他所有位置的相关性然后根据这些相关性加权汇总其他位置的信息。具体实现时每个位置都会被映射成三个向量Query、Key、Value。Query 代表“我想找什么”Key 代表“我有什么可被匹配的”Value 代表“我真正提供的内容”。计算过程是先用 Query 和所有 Key 做点积经过缩放和 softmax 得到注意力权重再用这些权重对 Value 做加权求和。一句话概括让每个位置都有机会从全局获取信息。为了增强表达力Transformer 还引入了多头注意力机制。简单说就是同时用多组 QKV 投影从不同的子空间捕捉不同的依赖关系。比如一组注意力在关注周期性另一组在关注趋势突变最后拼接起来再投影这样的表示往往比单头注意力更丰富。另外要强调的是位置编码。自注意力本身不关心位置顺序因为它是集合运算输入换一下顺序输出也跟着换。可时间序列的先后顺序非常重要因此 Transformer 必须显式地把位置信息加进去。经典做法是用不同频率的正弦和余弦函数构造位置编码也就是代码里常见的 PositionalEncoding。这个细节非常关键很多复现 Transformer 做时序预测失败的案例都是因为漏掉了位置编码或者编码方式选择不当。2.3 两者的本质差异与选型判断为了更直观地比较我整理了一张表对比维度LSTMTransformer基础结构循环神经网络的变体注意力机制 前馈网络依赖建模方式按时间步顺序逐步传递任意位置直接计算注意力并行计算能力差难以并行强可并行训练长期依赖捕捉中等长度效果好更长序列有压力理论上可捕捉极长依赖数据需求数据量小时相对友好数据量小时容易过拟合训练速度慢串行计算快并行度高可解释性门控机制较直观注意力权重可分析适合场景中等长度、样本量中等长序列、大样本、复杂依赖选型时我一般会按这个思路判断如果你的序列长度在几十步以内样本量不大先跑 LSTM稳定且好调如果序列长度超过一百步或者你能明显感觉到预测点跟很远之前的某个模式有关优先试 Transformer如果你要做的是多变量长时间预测并且有充足的历史数据Transformer 的潜力更大但需要更仔细地做正则化和早停。3. 环境准备与数据说明3.1 环境依赖本文所有代码基于 Python 和 PyTorch 实现。环境版本请以实际安装为准重点展示通用思路不推荐照抄某一个固定版本。建议环境如下Python 3.8 及以上PyTorch 1.10 及以上2.x 均可NumPy 1.20 及以上Pandas 1.3 及以上scikit-learn 1.0 及以上Matplotlib 3.3 及以上如果你还没有配置环境可以用以下命令创建虚拟环境并安装依赖python -m venv ts_env source ts_env/bin/activate # Windows 下用 ts_env\Scripts\activate pip install torch numpy pandas scikit-learn matplotlib如果你的机器有 NVIDIA GPU并且安装了对应版本的 CUDA 和 cuDNN训练会自动使用 GPU。没有 GPU 也没关系本文的数据量和模型规模用 CPU 训练也能在几分钟内跑完。3.2 数据集生成与说明为了让大家能原样复现我使用一个合成的时间序列作为示例数据。它由三部分组成一个周期为 50 的正弦信号一个缓慢上升的线性趋势以及服从正态分布的随机噪声。这样既保证了序列具备周期性和趋势性又引入了真实的随机扰动能比较明显地看出模型的好坏。# 文件路径: generate_data.py import numpy as np np.random.seed(42) total_points 2000 t np.arange(total_points) data np.sin(2 * np.pi * t / 50) 0.2 * t / 2000 0.1 * np.random.randn(total_points)如果你有真实数据只需要把data替换成自己的一维序列即可。比如读取 CSVimport pandas as pd df pd.read_csv(your_data.csv) data df[value].values.astype(np.float32)提醒一句真实数据的缺失值、异常值和量纲差异往往比合成数据严重务必要先做清洗。后面第 9 章会专门讲这些坑。4. 数据预处理与滑动窗口构建时间序列预测和普通监督学习不一样我们不能直接把整条序列喂给模型而要把历史窗口作为特征、未来窗口作为标签构造出训练样本。这个操作在时序领域常被称为“滑动窗口”或“序列切分”。假设我们设置历史窗口长度为 24预测长度为 1那么输入是[t0, t1, ..., t23]标签是[t24]。窗口向后滑动一位就得到下一个样本。代码如下# 文件路径: utils.py import numpy as np from sklearn.preprocessing import MinMaxScaler from torch.utils.data import TensorDataset, DataLoader import torch def create_dataset(data, seq_len24, pred_len1): xs, ys [], [] for i in range(len(data) - seq_len - pred_len 1): xs.append(data[i:i seq_len]) ys.append(data[i seq_len:i seq_len pred_len]) return np.array(xs, dtypenp.float32), np.array(ys, dtypenp.float32) def normalize_data(data): scaler MinMaxScaler(feature_range(0, 1)) data_reshaped data.reshape(-1, 1) data_normalized scaler.fit_transform(data_reshaped).flatten() return data_normalized, scaler def split_data(xs, ys, train_ratio0.7, val_ratio0.15): n len(xs) train_end int(n * train_ratio) val_end int(n * (train_ratio val_ratio)) train_set (xs[:train_end], ys[:train_end]) val_set (xs[train_end:val_end], ys[train_end:val_end]) test_set (xs[val_end:], ys[val_end:]) return train_set, val_set, test_set def make_loader(xs, ys, batch_size64, shuffleTrue): dataset TensorDataset(torch.from_numpy(xs), torch.from_numpy(ys)) return DataLoader(dataset, batch_sizebatch_size, shuffleshuffle)这里有几个关键点第一归一化非常重要。LSTM 和 Transformer 都对输入尺度敏感尤其是经过 sigmoid 和 tanh 激活函数时过大的输入值会让梯度很快饱和。我用MinMaxScaler把数据映射到 0 到 1 之间预测完再反归一化回原始尺度。你会注意到后续代码里专门保留了scaler就是为了做逆变换。第二滑动窗口长度seq_len直接决定了模型能看到多长的历史。设置太短模型可能看不到足够的周期信息设置太长不仅训练变慢LSTM 也更容易遗忘早期信息。对于周期为 50 的合成数据24 是一个合理的起点。第三时序数据切分不能随机打乱顺序。训练集、验证集、测试集必须按时间先后划分否则会引入未来信息泄露导致验证效果虚高。上面的split_data严格按比例切分就是这个原因。你可能会问DataLoader里shuffleTrue不会破坏时间顺序吗这里不会因为我们已经把“时间顺序”编码到了每条样本的窗口里。训练集内部打乱样本顺序只是为了减少批次之间的相关性不影响每条样本内部的时序结构。5. LSTM 模型完整代码复现5.1 模型定义LSTM 模型定义非常简洁。我们用nn.LSTM处理输入序列取最后一个时间步的隐藏状态再通过一个全连接层输出预测值。这里我把预测头设计成两层全连接中间加 ReLU增加一点非线性拟合能力。# 文件路径: lstm_model.py import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_size64, num_layers2, seq_len24, pred_len1, 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.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Linear(32, pred_len) ) def forward(self, x): out, _ self.lstm(x) out out[:, -1, :] return self.fc(out)几个参数需要重点解释input_size1表示每个时间步只有一个特征。如果你的数据是多变量预测比如同时输入温度、湿度、风速三个特征那么input_size要改成 3输入形状也要变成(batch, seq_len, 3)。hidden_size64是 LSTM 隐藏状态的维度。它决定了模型记忆容量的大小。太小拟合能力不足太大容易过拟合对 2000 个点的合成数据来说64 足够。num_layers2表示叠加两层 LSTM。两层结构比单层能捕捉更高层次的抽象模式但层数太多会显著增加训练时间而且需要更多的数据支撑。batch_firstTrue很关键它让输入张量的形状变成(batch, seq_len, input_size)更符合直觉也不容易在写数据加载时搞错维度。5.2 训练流程训练逻辑采用标准的监督学习流程前向传播、计算 MSE 损失、反向传播、优化器更新。我额外添加了验证集评估并用val_loss来判断模型的泛化能力。# 文件路径: train_lstm.py import torch import torch.nn as nn from lstm_model import LSTMPredictor from utils import create_dataset, normalize_data, split_data, make_loader # 数据准备 from generate_data import generate_data data generate_data() data_norm, scaler normalize_data(data) xs, ys create_dataset(data_norm, seq_len24, pred_len1) (train_x, train_y), (val_x, val_y), (test_x, test_y) split_data(xs, ys) train_loader make_loader(train_x, train_y, batch_size64, shuffleTrue) val_loader make_loader(val_x, val_y, batch_size64, shuffleFalse) def evaluate(model, loader, device): model.eval() criterion nn.MSELoss() total_loss 0.0 total_num 0 with torch.no_grad(): for xb, yb in loader: xb, yb xb.to(device), yb.to(device) pred model(xb) loss criterion(pred, yb) total_loss loss.item() * len(xb) total_num len(xb) return total_loss / total_num def train_model(model, train_loader, val_loader, epochs50, lr1e-3): device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) criterion nn.MSELoss() optimizer torch.optim.Adam(model.parameters(), lrlr) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size20, gamma0.5) for epoch in range(epochs): model.train() train_loss 0.0 train_num 0 for xb, yb in train_loader: xb, yb xb.to(device), yb.to(device) optimizer.zero_grad() pred model(xb) loss criterion(pred, yb) loss.backward() optimizer.step() train_loss loss.item() * len(xb) train_num len(xb) val_loss evaluate(model, val_loader, device) if (epoch 1) % 10 0: print(fEpoch {epoch 1:3d}/{epochs} | fTrain Loss: {train_loss / train_num:.6f} | fVal Loss: {val_loss:.6f}) scheduler.step() return model if __name__ __main__: model LSTMPredictor(input_size1, hidden_size64, num_layers2, seq_len24, pred_len1, dropout0.2) model train_model(model, train_loader, val_loader, epochs50, lr1e-3) torch.save(model.state_dict(), lstm_model.pth)5.3 预测与反归一化训练完成后我们可以对测试集做预测并把结果还原到原始数据的尺度方便和真实值比较。# 文件路径: predict_lstm.py import torch import numpy as np from lstm_model import LSTMPredictor from utils import normalize_data, create_dataset, split_data # 假设已经完成训练并保存权重 data np.sin(2 * np.pi * np.arange(2000) / 50) 0.2 * np.arange(2000) / 2000 data_norm, scaler normalize_data(data) xs, ys create_dataset(data_norm, seq_len24, pred_len1) _, _, (test_x, test_y) split_data(xs, ys) model LSTMPredictor(input_size1, hidden_size64, num_layers2, seq_len24, pred_len1) model.load_state_dict(torch.load(lstm_model.pth)) model.eval() test_tensor torch.from_numpy(test_x) with torch.no_grad(): pred_norm model(test_tensor).numpy() pred_original scaler.inverse_transform(pred_norm) y_original scaler.inverse_transform(test_y) print(预测值前 10 个:, pred_original[:10].flatten()) print(真实值前 10 个:, y_original[:10].flatten())如果你想把预测结果和真实值画在一张图上用 Matplotlib 即可import matplotlib.pyplot as plt plt.figure(figsize(12, 4)) plt.plot(y_original[:200], labelTrue) plt.plot(pred_original[:200], labelPred) plt.legend() plt.title(LSTM Time Series Forecasting Result) plt.savefig(lstm_result.png, dpi150)运行后你会看到预测曲线在大部分区域都能跟随真实曲线但在噪声较大的局部会出现偏差。这是正常现象因为噪声本身不可预测。6. Transformer 模型完整代码复现6.1 位置编码实现Transformer 没有循环结构必须显式注入位置信息。我实现了经典的三角函数位置编码它和原论文保持一致。原理是不同维度的正弦余弦函数具有不同的频率这相当于给模型提供了一套可以区分位置先后顺序的“坐标轴”。# 文件路径: transformer_model.py import math import torch import torch.nn as nn class PositionalEncoding(nn.Module): 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) 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) self.register_buffer(pe, pe) def forward(self, x): x x self.pe[:, :x.size(1), :] return self.dropout(x)代码里的register_buffer很有讲究。它把位置编码注册为模型的一部分但不会参与梯度更新当模型在 CPU 和 GPU 之间切换时它会自动跟随模型移动避免设备不一致的问题。6.2 Transformer 编码器封装这里我选择只使用 Transformer 的编码器部分而不是完整的 Encoder-Decoder。原因在于时间序列预测任务和机器翻译不同我们的输出通常是一段连续数值而不是一个变长的目标序列。用一个编码器把历史窗口编码成丰富的上下文表示再接一个全连接层直接输出预测值这样的结构足够简单效果也稳定。class TransformerPredictor(nn.Module): def __init__(self, input_size1, d_model64, nhead4, num_encoder_layers2, dim_feedforward128, seq_len24, pred_len1, dropout0.1): super(TransformerPredictor, self).__init__() self.input_proj nn.Linear(input_size, d_model) self.pos_encoder PositionalEncoding(d_model, dropout) encoder_layer nn.TransformerEncoderLayer( d_modeld_model, nheadnhead, dim_feedforwarddim_feedforward, dropoutdropout, batch_firstTrue, activationrelu ) self.transformer_encoder nn.TransformerEncoder( encoder_layer, num_layersnum_encoder_layers ) self.fc nn.Sequential( nn.Linear(d_model, 32), nn.ReLU(), nn.Linear(32, pred_len) ) def forward(self, x): x self.input_proj(x) x self.pos_encoder(x) x self.transformer_encoder(x) x x[:, -1, :] return self.fc(x)d_model64是输入投影后每个时间步的向量维度也是注意力机制的工作维度。nhead4表示把 64 维分成 4 个头每个头处理 16 维从不同子空间捕捉依赖关系。dim_feedforward128是编码器内部前馈网络的隐藏层维度通常设置为d_model的 2 到 4 倍。有一个容易忽略的点input_proj把原始的单变量输入从 1 维映射到 64 维这一步不是可选的。如果直接把 1 维数据送入多头注意力QKV 投影后维度太小注意力表达力会严重不足。6.3 训练流程与复用说明Transformer 的训练流程和 LSTM 几乎一样可以直接复用前面定义好的train_model函数。只需要把模型换成TransformerPredictor并调整一下数据形状。# 文件路径: train_transformer.py import torch from transformer_model import TransformerPredictor from train_lstm import train_model from utils import create_dataset, normalize_data, split_data, make_loader # 数据准备 import numpy as np np.random.seed(42) t np.arange(2000) data np.sin(2 * np.pi * t / 50) 0.2 * t / 2000 0.1 * np.random.randn(2000) data_norm, _ normalize_data(data) xs, ys create_dataset(data_norm, seq_len24, pred_len1) (train_x, train_y), (val_x, val_y), (test_x, test_y) split_data(xs, ys) train_loader make_loader(train_x, train_y, batch_size64, shuffleTrue) val_loader make_loader(val_x, val_y, batch_size64, shuffleFalse) model TransformerPredictor( input_size1, d_model64, nhead4, num_encoder_layers2, dim_feedforward128, seq_len24, pred_len1, dropout0.1 ) model train_model(model, train_loader, val_loader, epochs50, lr1e-3) torch.save(model.state_dict(), transformer_model.pth)如果你想把 LSTM 和 Transformer 放在同一批数据下公平对比可以把两个模型的测试集损失都算出来做一个简单的对比表。我自己的建议是在相同参数量级下Transformer 往往会收敛得更快但训练早期 loss 波动更大需要更小的学习率或者配合 warmup 策略这一点在长序列上尤其明显。7. 运行结果与效果验证7.1 预期输出按照上面的代码运行LSTM 和 Transformer 都会每隔 10 个 epoch 打印一次训练损失和验证损失。正常情况下训练损失会从最初的 0.1 到 0.2 量级逐步下降到 0.001 到 0.01 量级。用合成数据训练 50 个 epoch两个模型都能在测试集上得到比较平滑的预测曲线。预测输出的样式大致是这样的预测值前 10 个: [0.612 0.698 0.783 0.861 0.932 0.992 1.042 1.079 1.103 1.116] 真实值前 10 个: [0.603 0.689 0.774 0.853 0.925 0.986 1.037 1.075 1.100 1.114]数值不必完全一致这取决于随机种子、初始化方式和训练细节。关键是预测值能跟上真实值的趋势和周期而不是出现在某个区间内来回抖动的异常情况。7.2 如何判断模型训练成功判断模型是否训练成功不能只看训练损失。我一般按三个层次检查第一看损失下降曲线。训练损失和验证损失都应该在初期快速下降之后逐渐平稳。如果训练损失下降但验证损失长期不降甚至上升说明过拟合了需要增加 dropout、减小模型容量或者增加数据。第二看预测曲线与真实曲线的重合度。画图是最直观的方式。预测曲线如果比真实曲线滞后一步说明模型可能只是在“复制上一个值”这是一个常见的失败信号需要检查数据切分是否混入了未来信息。第三看分位数误差或业务指标。时序预测不能只看 MSE还要关注方向准确率、峰值预测能力等。比如异常检测场景如果模型把所有峰值都平均掉了即使 MSE 很低实际业务价值也不大。7.3 失败排查第一步如果训练过程中 loss 变成 NaN或者预测结果全是一个常数先不要急着调参。第一步应该检查数据归一化后的数据是否含有 NaN 或 inf窗口切分时是否存在越界导致的部分样本为空input_size是否和实际特征数一致。第二步检查设备与形状。把输入和输出的 shape 打出来确认(batch, seq_len, input_size)和(batch, pred_len)完全匹配。PyTorch 报维度错误往往就出现在这里。第三步检查位置编码。如果你用的是 Transformer可以单独打印model.pos_encoder.pe的形状确认是(1, max_len, d_model)。如果这里出错后面编码器全都白算。8. 常见问题与排查思路下面是我在复现这两个模型时遇到过的、也是社群提问频率最高的几类问题问题现象可能原因排查方式解决方案训练 Loss 为 NaN学习率过大、数据含 NaN、梯度爆炸打印 loss检查数据和梯度范数降低学习率清洗数据加梯度裁剪预测结果是一条直线模型退化为输出均值或数据未归一化查看输入分布和预测分布做归一化增加序列长度检查是否过拟合验证集 Loss 低但测试集差测试分布漂移或数据泄露检查时间切分是否严格按时间顺序重新切分禁止随机打乱Transformer 训练很慢序列过长注意力计算复杂度高查看 batch 大小和序列长度增大 batch或改用注意力降采样方案LSTM 预测明显滞后模型学习到了“复制上一时刻”模式绘制预测曲线检查滞后增加历史窗口长度加入差分或周期特征训练集表现好测试集波动大模型容量过大数据量不足观察训练和测试 loss 差距增加 dropout减小 hidden_size早停Transformer 在数据少时不收敛模型对数据量敏感检查参数量和样本量比例先跑 LSTM 做 baseline或给 Transformer 加数据增强表格里列出的问题绝大多数可以在半小时内定位。关键是不要一上来就换模型先确认数据管线没有错误再看模型层面的问题。数据管线的 bug 往往是隐蔽的但影响最大。9. 最佳实践与工程建议9.1 数据处理建议真实项目里的时序数据远比合成数据复杂。我建议你在训练前处理好以下问题缺失值要显式处理。最简单的做法是线性插值但对长时间缺失的片段插值会引入虚假信息。更稳妥的方式是保留缺失标记让模型学习缺失模式。异常值要谨慎处理。业务数据中的异常点有时正是你要预测的目标比如故障数据、欺诈数据。如果一律用 3 sigma 法则剔除可能把关键信号也删掉了。更好的做法是先做 EDA区分“真实异常”和“传感器噪声”。平稳性值得关注。LSTM 和 Transformer 本质上都是学习数据的条件分布如果序列有很强的趋势或季节性可以先做差分或提取时间特征。这个预处理往往比换模型更能提升效果。9.2 模型与训练建议在做模型对比之前先定义好统一的评测协议。比如固定训练集、验证集、测试集的时间切分固定随机种子固定训练 epoch 数。否则任何对比结论都不可信。LSTM 调参顺序建议是先确定序列长度再调隐藏层大小最后调层数。Transformer 调参顺序则是先确定d_model和nhead再调整编码器层数最后调度dim_feedforward。不要所有参数一起调否则问题无法归因。训练过程中建议加入早停机制。保存验证集表现最好的模型权重而不是用最后一个 epoch 的权重。这能防止后期过拟合对结果的影响。对关键风险操作多说一句一切超参数实验和模型调整都要先在测试集之外划分出独立的验证集不要用测试集反复调整模型。测试集只在最终评估时使用一次这才符合机器学习的规范。9.3 生产环境部署提醒当模型要上线时代码复现只是第一步。你需要注意预测延迟、模型更新频率和监控告警。如果预测请求量大Transformer 在 GPU 推理时优势明显但在 CPU 上可能比 LSTM 更慢需要评估吞吐量。模型更新方面建议每周或每日定时重训并做新旧模型的效果对比避免因为数据分布漂移导致预测质量下降。监控方面至少要在日志中记录预测值和真实值的残差设置报警阈值一旦残差持续增大立即触发重新训练流程。还有一个很容易被忽视的问题时间序列模型上线前务必在影子模式运行一段时间即模型只记录预测结果、不参与实际决策。等积累足够多的对比数据后再决定是否切换为正式预测通道。这样可以把模型风险控制在可回滚的范围内。10. 总结与后续学习方向现在回到开头的那个问题做时间序列预测到底选 LSTM 还是 Transformer你已经有答案了先看数据规模和序列长度再算算你的训练资源然后选择合适的 baseline。LSTM 是一个稳定、好解释、数据量要求不高的起点Transformer 表达能力更强、并行训练更快但需要更大的数据量和更细致的调参。两者不是替代关系而是各自有适用边界的工具。从代码层面你已经掌握了从数据生成、滑动窗口构建、归一化、模型定义到训练评估的完整流程。这套流程可以直接迁移到销量预测、电力负荷预测、股票价格波动分析等真实任务中。如果你想继续深入建议按这个顺序往下走先弄懂多头注意力的具体实现细节用一个小张量手动推一遍 QKV 的计算过程然后尝试把单变量改成多变量预测加入外生特征接着对比一下Informer、Autoformer这类面向长序列的改进模型它们解决的是 Transformer 在超长序列上的显存和效率问题最后把模型评估指标从 MSE 扩展到 MAE、MAPE、方向准确率你会发现对模型好坏的判断会变得完全不同。无论你最终选择哪个模型建议都跑一个 LSTM baseline再跑一个 Transformer 对照用同一套评测协议记录结果。这样你的每一次升级都有据可依也才能真正理解这些模型在时间序列预测这件事上各自擅长什么、局限在哪里。建议收藏本文需要的时候打开照着做一遍比只刷视频教程有效得多。