LSTM时间序列预测实战:滑动窗口、差分归一化与多步预测闭环
简介本资源是一份面向Python初学者与数据科学入门者的LSTM时间序列预测实战教程聚焦股票价格、销售趋势、气象预报等典型场景系统讲解如何用深度学习解决时序建模问题。压缩包共15个文件391KB含6个核心Python脚本如train_lstm.py、train_lstm_multivariate.py实现单变量/多变量预测、4张关键流程图与结构示意图jpg、2份Markdown说明文档中英文README、2个真实时序数据集CSV及1个.gitignore配置文件覆盖数据预处理、滑动窗口构造、标准化、模型搭建、训练验证与反向还原全流程。已有39844人学习下载资源结构清晰、代码即开即用附带多变量预测扩展实现与输入数组/CSV双模式测试脚本便于读者理解LSTM输入格式设计逻辑并快速迁移至自有业务数据。1. 时间序列预测不是“把数据喂给LSTM就完事”一个能跑通、能调参、能上线的Python实现闭环你是不是也试过下载了网上一堆标着“LSTM时间序列预测”的Python脚本一跑就报错ValueError: Input 0 is incompatible with layer lstm_1: expected ndim3, found ndim2或者训练完loss掉得飞快但预测曲线却平得像尺子量过这不是你代码写错了——是绝大多数公开资源根本没告诉你LSTM对输入形状、滑动窗口长度、归一化方式、状态重置逻辑这四点极其敏感漏掉任意一个模型就从“智能预测”退化成“高级平均器”。这份资源不是教学Demo而是一个经过某高校实验室在电力负荷、工业传感器、气象数据三类真实时序任务中反复验证的最小可运行闭环从原始CSV读入→滑动窗口构造→Min-Max差分双层归一化→LSTM状态管理→多步滚动预测→反归一化还原。它不依赖任何云服务或私有API纯本地PyTorch/TensorFlow双后端支持所有参数都带中文注释连lookback_window24这种值背后对应“用过去24小时预测下一小时”都写清楚。适合正在做课程设计、毕设、或需要快速验证业务指标趋势的工程师——别再被玄学调参拖垮进度了。2. LSTM为什么必须用滑动窗口从数学本质到代码实现的三层拆解2.1 时间序列的“记忆断层”问题为什么不能直接喂原始序列LSTM的本质是处理有序依赖关系但它无法理解“第100个点和第1个点之间隔着99个时间步”这种全局时序语义。如果你把整段1000点的序列直接reshape成(1000, 1)丢进LSTM模型会错误地认为第1点和第1000点是相邻时刻因为batch维度被压缩了。真实场景中我们关心的是“基于最近N个观测值预测未来M个值”这个N就是滑动窗口长度lookback window。它不是超参数调优的起点而是问题定义的第一步。比如预测服务器CPU使用率若采样间隔是5分钟那么lookback_window12意味着用过去1小时的数据预测下一时刻——这个值必须由业务周期决定而非随意设为64或128。提示窗口长度选太小如lookback5模型学不到长期模式选太大如lookback500不仅显存爆炸还会引入大量无关噪声。经验法则是取业务周期的整数倍再加20%冗余。例如风电功率日周期为288点5分钟/点则lookback350比lookback256更合理。2.2 构造滑动窗口的两种实现NumPy切片 vs. Pandas.shift()核心逻辑是对长度为T的序列y生成(T-lookback, lookback, 1)的三维数组X和(T-lookback, 1)的y_true。下面给出两种生产环境常用写法区别在于内存占用与可读性import numpy as np import pandas as pd def create_dataset_numpy(data, lookback): NumPy版内存高效适合大数据集 data: (T,) 一维数组 return: X (T-lookback, lookback, 1), y (T-lookback, 1) X, y [], [] for i in range(lookback, len(data)): # 取前lookback个点作为输入当前点作为标签 X.append(data[i-lookback:i].reshape(-1, 1)) y.append(data[i]) return np.array(X), np.array(y).reshape(-1, 1) # 示例1000点序列窗口50 → 输出X.shape(950, 50, 1) data np.random.randn(1000) X_np, y_np create_dataset_numpy(data, lookback50) print(fNumPy版输出形状: X{X_np.shape}, y{y_np.shape})这段代码的关键在.reshape(-1, 1)它强制将每个窗口内的50个标量转为(50, 1)列向量确保LSTM输入是三维张量。如果漏掉.reshape(-1, 1)X会是(950, 50)二维数组后续model.fit()必然报错expected ndim3。def create_dataset_pandas(data, lookback): Pandas版可读性强支持多列特征扩展 data: pd.Series 或 pd.DataFrame df pd.DataFrame(data) # 创建滞后特征df[t-1], df[t-2], ..., df[t-lookback] for i in range(1, lookback 1): df[ft-{i}] df[0].shift(i) df.dropna(inplaceTrue) # 删除含NaN的行前lookback行 # 输入列t-1 到 t-lookback标签列t即原序列当前值 feature_cols [ft-{i} for i in range(1, lookback 1)] X_pd df[feature_cols].values.reshape(-1, lookback, 1) y_pd df[0].values.reshape(-1, 1) return X_pd, y_pd # 示例等效于NumPy版但后续加温度、湿度特征只需改df[0]为df[[cpu,temp,hum]] X_pd, y_pd create_dataset_pandas(pd.Series(data), lookback50) print(fPandas版输出形状: X{X_pd.shape}, y{y_pd.shape})Pandas版的优势在于shift()天然支持多变量。比如你要同时用CPU、内存、磁盘IO预测负载只需传入pd.DataFrame({cpu:cpu_data, mem:mem_data, disk:disk_data})shift()会自动为每列生成滞后项最后feature_cols包含所有变量的滞后特征这才是工业级时序建模的起点。2.3 窗口长度与预测步长的耦合关系为什么单步预测不能直接外推很多初学者以为训练好单步LSTM预测t1后只要把预测值不断喂回模型就能实现多步预测t2,t3...。这是危险的因为每次预测都携带误差误差会指数级累积类似蝴蝶效应模型从未见过“自己预测的值”作为输入分布偏移严重。正确做法是训练时就构造多步目标。例如预测未来3小时每5分钟一采样共36步则y不再是(T-lookback, 1)而是(T-lookback, 36)def create_multistep_dataset(data, lookback, forecast_steps): 多步预测窗口构造 data: (T,) 数组 forecast_steps: 要预测的步数如363小时 return: X (T-lookback-forecast_steps1, lookback, 1), y (T-lookback-forecast_steps1, forecast_steps) X, y [], [] # 注意最后一个有效窗口起始位置是 T-lookback-forecast_steps for i in range(lookback, len(data) - forecast_steps 1): X.append(data[i-lookback:i].reshape(-1, 1)) # 取接下来forecast_steps个点作为多步标签 y.append(data[i:iforecast_steps]) return np.array(X), np.array(y) X_multi, y_multi create_multistep_dataset(data, lookback50, forecast_steps36) print(f多步版输出: X{X_multi.shape}, y{y_multi.shape}) # X(915,50,1), y(915,36)这里len(data) - forecast_steps 1是关键边界——如果忽略1最后一行数据会被截断。这个细节在Kaggle竞赛中常导致线下验证和线上提交结果不一致血泪经验。3. 归一化不是“套个MinMaxScaler就完事”差分缩放双保险的工程实践3.1 为什么单靠MinMaxScaler会失效看一个真实翻车案例某公司用LSTM预测产线良率原始数据范围是[92.3, 99.7]。他们直接用MinMaxScaler(feature_range(0,1))归一化训练loss降到0.001但部署后预测值全在0.95±0.01区间震荡完全无法捕捉92.3→93.1这种关键下跌。原因在于MinMaxScaler只解决量纲问题不解决趋势漂移。当序列存在缓慢上升/下降趋势如设备老化导致良率逐年下降归一化后的数据仍保留该趋势而LSTM对长期趋势的建模能力极弱容易过拟合短期波动。3.2 差分Differencing让序列变平稳的物理意义差分的本质是计算变化量而非绝对值。对序列y_t一阶差分Δy_t y_t - y_{t-1}。它把“预测良率值”转化为“预测良率变化量”大幅降低趋势干扰。更重要的是差分具有明确物理意义Δy_t 0表示良率提升Δy_t 0表示恶化这对产线告警系统至关重要。def apply_differencing(data, order1): 对序列进行order阶差分 data: (T,) 数组 return: diff_data (T-order,) 数组以及用于还原的首order个原始值 diff_data data.copy() history [] # 存储每次差分前的首元素用于逆差分 for _ in range(order): history.append(diff_data[0]) diff_data np.diff(diff_data) # 一阶差分 return diff_data, history # 原始数据模拟缓慢上升趋势 raw_data np.linspace(92.0, 99.0, 1000) np.random.normal(0, 0.2, 1000) diff_data, history apply_differencing(raw_data, order1) print(f原始数据范围: [{raw_data.min():.2f}, {raw_data.max():.2f}]) print(f差分后范围: [{diff_data.min():.2f}, {diff_data.max():.2f}]) # 范围缩小10倍以上注意np.diff()会减少长度order1时输出长度为T-1。history列表存储了差分过程中丢失的首元素这是逆差分还原预测值的必要条件。3.3 差分MinMaxScaler的串联流程与代码实现正确顺序是先差分 → 再归一化。如果反过来归一化后的数据再差分会放大归一化引入的数值误差。from sklearn.preprocessing import MinMaxScaler def preprocess_pipeline(data, lookback, forecast_steps, diff_order1, scaler_range(0,1)): 完整预处理流水线 # 步骤1差分 diff_data, history apply_differencing(data, orderdiff_order) # 步骤2归一化仅对差分后数据 scaler MinMaxScaler(feature_rangescaler_range) scaled_diff scaler.fit_transform(diff_data.reshape(-1, 1)).flatten() # 步骤3构造滑动窗口对scaled_diff操作 X, y create_multistep_dataset(scaled_diff, lookback, forecast_steps) return X, y, scaler, history, diff_order # 执行预处理 X_proc, y_proc, scaler, history, diff_order preprocess_pipeline( raw_data, lookback50, forecast_steps36, diff_order1 ) print(f预处理后X形状: {X_proc.shape}, y形状: {y_proc.shape}) print(f归一化器范围: {scaler.data_min_[0]:.3f} ~ {scaler.data_max_[0]:.3f})这里scaler.fit_transform().flatten()确保输出是一维数组避免后续create_multistep_dataset出错。scaler对象必须保存下来因为预测后要先用scaler.inverse_transform()还原差分值再用history做逆差分。4. LSTM模型构建与训练状态管理、损失函数、早停策略的硬核配置4.1 PyTorch版LSTM为什么必须手动管理hidden stateTensorFlow/Keras的return_sequencesTrue隐藏了状态管理细节但在PyTorch中LSTM的hidden state和cell state必须显式传递。否则模型会默认每次forward都重置状态导致无法学习长期依赖。import torch import torch.nn as nn class LSTMModel(nn.Module): def __init__(self, input_size1, hidden_size50, num_layers2, output_size1, dropout0.2): super().__init__() self.hidden_size hidden_size self.num_layers num_layers self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue, dropoutdropout if num_layers 1 else 0) self.fc nn.Linear(hidden_size, output_size) def forward(self, x, hiddenNone): # x: (batch, seq_len, features) batch_size x.size(0) if hidden is None: # 初始化hidden state(num_layers, batch, hidden_size) h0 torch.zeros(self.num_layers, batch_size, self.hidden_size) c0 torch.zeros(self.num_layers, batch_size, self.hidden_size) hidden (h0, c0) lstm_out, hidden self.lstm(x, hidden) # lstm_out: (batch, seq_len, hidden_size) # 取最后一个时间步的输出seq_len维度末尾 last_output lstm_out[:, -1, :] # (batch, hidden_size) out self.fc(last_output) # (batch, output_size) return out, hidden # 实例化模型 model LSTMModel(input_size1, hidden_size64, num_layers2, output_size36) print(f模型参数量: {sum(p.numel() for p in model.parameters())})关键点hidden参数允许外部传入状态实现跨batch状态延续如在线学习lstm_out[:, -1, :]取最后一个时间步因为我们要预测未来值而非整个序列output_size36直接对应多步预测的36个点避免循环预测。4.2 损失函数选择MAE vs. MSE vs. Huber Loss的场景适配MSE均方误差对异常值敏感适合数据干净、需惩罚大误差的场景如金融风控MAE平均绝对误差鲁棒性强适合含脉冲噪声的工业传感器数据Huber LossMSE与MAE的结合体当误差小于δ时用MSE大于δ时用MAEδ0.5是常用起点。import torch.nn.functional as F def huber_loss(pred, target, delta0.5): PyTorch Huber Loss实现 error pred - target abs_error torch.abs(error) quadratic torch.clamp(abs_error, maxdelta) linear abs_error - quadratic return torch.mean(0.5 * quadratic ** 2 delta * linear) # 训练循环中使用 criterion lambda pred, target: huber_loss(pred, target, delta0.3) optimizer torch.optim.Adam(model.parameters(), lr0.001) # 示例模拟一次前向传播 dummy_x torch.randn(32, 50, 1) # batch32, seq50, feat1 dummy_y torch.randn(32, 36) # batch32, forecast36 pred, _ model(dummy_x) loss criterion(pred, dummy_y) print(fHuber Loss值: {loss.item():.4f})注意Huber Loss的delta值需根据归一化后数据的典型误差范围设定。若scaler将数据缩放到[0,1]delta0.1意味着对小于10%的误差用平方惩罚大于10%的用线性惩罚。4.3 早停Early Stopping与学习率衰减防止过拟合的双重保险早停不是简单监控val_loss而是要设置耐心值patience和最小增量min_delta。否则模型可能在val_loss微小波动时就停止错过最佳点。class EarlyStopping: def __init__(self, patience10, min_delta0.001, verboseFalse): self.patience patience self.min_delta min_delta self.verbose verbose self.counter 0 self.best_score None self.early_stop False def __call__(self, val_loss): score -val_loss # 因为我们要最大化score最小化loss if self.best_score is None: self.best_score score elif score self.best_score self.min_delta: self.counter 1 if self.verbose: print(fEarlyStopping counter: {self.counter} out of {self.patience}) if self.counter self.patience: self.early_stop True else: self.best_score score self.counter 0 # 使用示例 early_stopping EarlyStopping(patience15, min_delta0.0005, verboseTrue) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience5, verboseTrue ) # 在训练循环中 for epoch in range(100): # ... train_step ... val_loss validate(model, val_loader) scheduler.step(val_loss) # 学习率衰减 early_stopping(val_loss) # 早停检查 if early_stopping.early_stop: print(Early stopping triggered) breakReduceLROnPlateau比固定学习率衰减更智能只有当val_loss连续5个epoch不下降时才将lr乘以0.5。这避免了在训练初期盲目降lr。5. 预测结果还原与评估从归一化值到业务指标的完整映射5.1 逆差分Inverse Differencing三步还原法预测值是归一化后的差分值需按顺序执行逆归一化用训练时保存的scaler还原为差分尺度逆差分用history列表逐步累加还原为原始尺度对齐时间轴注意差分导致的索引偏移。def inverse_transform_prediction(scaler, pred_scaled, history, diff_order1): 将归一化预测值还原为原始尺度 pred_scaled: (N, forecast_steps) 归一化后的预测差分值 history: 差分时保存的首元素列表 # 步骤1逆归一化 → 得到差分值 pred_diff scaler.inverse_transform(pred_scaled) # 步骤2逆差分核心 # 初始化还原数组长度forecast_steps diff_order pred_original np.zeros(pred_diff.shape[0] diff_order) # 填充history中的初始值差分前的原始起点 for i in range(diff_order): pred_original[i] history[i] # 逐点累加pred_original[t] pred_original[t-1] pred_diff[t-diff_order] for t in range(diff_order, len(pred_original)): pred_original[t] pred_original[t-1] pred_diff[t-diff_order] # 返回预测的forecast_steps个点跳过history部分 return pred_original[diff_order:] # 示例还原单个样本的36步预测 sample_pred_scaled np.random.rand(1, 36) # 模拟模型输出 pred_original inverse_transform_prediction( scaler, sample_pred_scaled, history, diff_order1 ) print(f还原后预测长度: {len(pred_original)}, 值域: [{pred_original.min():.2f}, {pred_original.max():.2f}])这里pred_original[diff_order:]是关键——diff_order1时pred_original[0]是history存的起点真正预测的是pred_original[1:]这36个点。5.2 多步预测评估指标不只是RMSE还要看方向一致性工业场景中预测值的方向准确性上涨/下跌判断往往比绝对误差更重要。因此除RMSE、MAE外必须计算指标公式业务意义Direction Accuracy (DA)100% × (正确方向数 / 总步数)预测趋势是否与实际一致RMSE√(Σ(y_true - y_pred)² / N)绝对误差大小MAPE100% × Σy_true - y_preddef evaluate_forecast(y_true, y_pred): y_true, y_pred: (N, forecast_steps) 二维数组 return: dict of metrics n_samples, n_steps y_true.shape metrics {} # RMSE MAE mse np.mean((y_true - y_pred) ** 2) mae np.mean(np.abs(y_true - y_pred)) metrics[RMSE] np.sqrt(mse) metrics[MAE] mae # Direction Accuracy比较相邻点变化符号 # y_true_dir[i,j] sign(y_true[i,j] - y_true[i,j-1])j0 y_true_dir np.sign(np.diff(y_true, axis1)) # (N, forecast_steps-1) y_pred_dir np.sign(np.diff(y_pred, axis1)) da np.mean(y_true_dir y_pred_dir) * 100 metrics[Direction_Accuracy_%] da # MAPE过滤y_true为0的点 non_zero_mask y_true ! 0 mape np.mean(np.abs((y_true[non_zero_mask] - y_pred[non_zero_mask]) / y_true[non_zero_mask])) * 100 metrics[MAPE_%] mape return metrics # 示例评估 y_true_sample np.random.randn(10, 36) 95.0 # 模拟真实值 y_pred_sample y_true_sample np.random.randn(10, 36) * 0.5 # 加噪声预测 results evaluate_forecast(y_true_sample, y_pred_sample) for k, v in results.items(): print(f{k}: {v:.3f})np.diff(..., axis1)沿列方向时间步求差得到变化方向。np.sign()将正负变化转为1/-1/0直接比较相等性即可统计方向准确率。5.3 预测可视化带置信区间的滚动预测图单点预测不够业务需要知道不确定性。这里用分位数回归思想训练三个模型分别预测10%、50%、90%分位数构成预测区间。import matplotlib.pyplot as plt def plot_forecast_with_uncertainty(y_true, y_pred_lower, y_pred_median, y_pred_upper, lookback50, forecast_steps36): 绘制带置信区间的预测图 y_true: (T,) 真实序列 y_pred_*: (N, forecast_steps) 预测结果N为预测次数如滚动预测的起始点数 # 取最后一次预测最远的未来 last_true y_true[-forecast_steps:] last_lower y_pred_lower[-1] last_median y_pred_median[-1] last_upper y_pred_upper[-1] plt.figure(figsize(12, 5)) plt.plot(range(len(y_true)-forecast_steps, len(y_true)), last_true, labelTrue, colorblack, linewidth2) plt.plot(range(len(y_true)-forecast_steps, len(y_true)), last_median, labelMedian, colorblue, linestyle--) plt.fill_between(range(len(y_true)-forecast_steps, len(y_true)), last_lower, last_upper, alpha0.3, colorblue, label10%-90% CI) plt.xlabel(Time Step) plt.ylabel(Value) plt.title(fLSTM Forecast with Uncertainty (lookback{lookback}, forecast{forecast_steps})) plt.legend() plt.grid(True) plt.show() # 示例调用需先训练三个模型 # plot_forecast_with_uncertainty(raw_data, lower_preds, median_preds, upper_preds)图中灰色区域是10%-90%置信区间若真实值频繁突破该区间说明模型不确定性估计不足需增加dropout或集成多个模型。6. 避坑指南LSTM时间序列预测中五个必踩的“经典陷阱”6.1 陷阱1测试集泄露Test Set Leakage——你以为的“未来”其实是“过去”现象模型在测试集上RMSE低至0.01但上线后预测完全失效。原因在构造滑动窗口前对整个数据集含测试集做了归一化或差分。这导致测试集信息“泄漏”到训练过程模型实际是在拟合已知分布而非预测未知值。解决严格按时间顺序划分数据——先切分train/val/test再对训练集单独拟合scaler和差分history测试集只能用训练集参数做transform绝不能fit。代码中必须有scaler.fit(train_data)和scaler.transform(test_data)的明确分离。6.2 陷阱2窗口边界错位Window Boundary Misalignment——预测起点对不上现象预测曲线整体右移1个时间步或与真实值完全不重叠。原因滑动窗口构造时X[i]对应y[i]的索引计算错误。常见错误是for i in range(lookback, len(data))但y.append(data[i])这导致X[0]用data[0:lookback]预测data[lookback]而X[1]用data[1:lookback1]预测data[lookback1]看似正确但若forecast_steps1y[i]应为data[i:iforecast_steps]此时i的上限必须是len(data)-forecast_steps否则越界。解决在create_multistep_dataset中强制校验iforecast_steps len(data)并在循环内加assertfor i in range(lookback, len(data) - forecast_steps 1): assert i forecast_steps len(data), fIndex overflow at i{i} X.append(...) y.append(data[i:iforecast_steps])6.3 陷阱3LSTM输入维度混乱Input Dimension Confusion——三维变二维的静默失败现象训练不报错但loss不下降预测值全为常数。原因X本应是(N, lookback, features)但误写成(N, lookback*features)。例如对单变量数据X.reshape(-1, lookback)生成二维数组LSTM接收后自动视为(batch, features)lookback被吞掉。解决在model.fit()前打印X.shape并校验print(fX shape before training: {X.shape}) assert len(X.shape) 3, fX must be 3D, got {len(X.shape)}D assert X.shape[1] lookback, fX second dim must be lookback{lookback}, got {X.shape[1]}6.4 陷阱4状态重置时机错误Hidden State Reset Timing——跨batch的记忆污染现象训练loss震荡剧烈验证集性能忽高忽低。原因在每个batch结束时未重置LSTM hidden state导致前一个batch的状态污染下一个batch。尤其在batch_firstFalsePyTorch默认时更易发生。解决在每个epoch开始时重置或在dataloader迭代中显式管理for epoch in range(num_epochs): hidden None # 每个epoch重置 for batch_idx, (x_batch, y_batch) in enumerate(train_loader): pred, hidden model(x_batch, hidden) # ... loss计算 ... # 关键每个batch后detach hidden防止梯度爆炸 if hidden is not None: hidden tuple(h.detach() for h in hidden)hidden.detach()切断计算图避免反向传播时追溯到上个batch。6.5 陷阱5多步预测的“自回归幻觉”Autoregressive Illusion——用假数据喂模型现象多步预测曲线平滑但严重偏离真实值误差随步长指数增长。原因误以为训练好的单步模型可通过“预测→喂回→再预测”实现多步但模型从未见过自己预测的值分布偏移Distribution Shift导致失败。解决坚持直接多步训练Direct Multi-step Forecasting。若必须用单步模型应采用多输出头Multi-head OutputLSTM最后一层接36个独立全连接层每个输出一步共享LSTM特征提取器。这比循环预测稳定得多。7. 从“能跑通”到“可交付”一个生产就绪的预测服务封装技巧7.1 模型序列化保存scaler、history、模型权重的原子操作训练完的模型不能只存.pt文件必须打包所有依赖组件。我一般用joblib保存scaler和historytorch.save存模型再用zip打包import joblib import torch def save_model_artifacts(model, scaler, history, diff_order, path_prefixlstm_model): 保存完整模型资产 # 保存PyTorch模型 torch.save({ model_state_dict: model.state_dict(), input_size: 1, hidden_size: 64, num_layers: 2, output_size: 36, }, f{path_prefix}_weights.pt) # 保存预处理组件 joblib.dump(scaler, f{path_prefix}_scaler.pkl) joblib.dump({history: history, diff_order: diff_order}, f{path_prefix}_preproc.pkl) # 打包成zip便于交付 import zipfile with zipfile.ZipFile(f{path_prefix}.zip, w) as zf: zf.write(f{path_prefix}_weights.pt) zf.write(f{path_prefix}_scaler.pkl) zf.write(f{path_prefix}_preproc.pkl) print(f模型资产已打包至 {path_prefix}.zip) # 调用保存 save_model_artifacts(model, scaler, history, diff_order1)交付时只给.zip对方解压后即可加载避免版本冲突。7.2 预测服务APIFlask轻量封装支持单点/批量预测from flask import Flask, request, jsonify import numpy as np import torch import joblib app Flask(__name__) # 全局加载模型启动时执行 model LSTMModel(input_size1, hidden_size64, num_layers2, output_size36) checkpoint torch.load(lstm_model_weights.pt) model.load_state_dict(checkpoint[model_state_dict]) model.eval() # 设为评估模式 scaler joblib.load(lstm_model_scaler.pkl) preproc joblib.load(lstm_model_preproc.pkl) history preproc[history] diff_order preproc[diff_order] app.route(/predict, methods[POST]) def predict(): try: data request.json # data: {history_sequence: [92.1, 92.3, ...], forecast_steps: 36} hist_seq np.array(data[history_sequence]) forecast_steps data.get(forecast_steps, 36) # 预处理差分 → 归一化 → 构造窗口 diff_hist, _ apply_differencing(hist_seq, orderdiff_order) scaled_diff scaler.transform(diff_hist.reshape(-1, 1)).flatten() # 构造单样本输入(1, lookback, 1) lookback 50 if len(scaled_diff) lookback: raise ValueError(fHistory length {len(scaled_diff)} lookback {lookback}) X_input scaled_diff[-lookback:].reshape(1, lookback, 1) # 预测 with torch.no_grad(): X_tensor torch.tensor(X_input, dtypetorch.float32) pred_scaled, _ model(X_tensor) # p a hrefhttps://download.csdn.net/download/weixin_43471818/11166952 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p