☰
LSTM预测网络流量实战:从数据预处理到调参避坑
2026/10/7 9:52:14 网站建设 项目流程

简介:一套基于长短期记忆网络(LSTM)的循环神经网络预测演示,主要面向需要快速上手深度学习序列建模的初学者,以及希望利用人工智能技术进行网络流量趋势预估、带宽告警预判的运维与开发人员。压缩包仅2KB大小,文件总数1个,格式为Python脚本,代码内在逻辑覆盖了从原始流量数据清洗、异常值剔除、归一化处理,到以滑动窗口切分训练样本、搭建LSTM网络、设置损失函数与优化器,再到模型训练、验证集评估和后续预测的完整流程。目前已有486人学习下载该资源。学习该脚本,不但能理解LSTM输入门、遗忘门、输出门各自的作用,建立对循环神经网络记忆机制的直观认识,还可以掌握在Keras框架下配置均方误差、平均绝对误差等指标来量化预测偏差的实际技巧,并了解学习率、批次大小、单元数量等超参数对模型性能的潜在影响。对于课程设计、毕业设计或生产环境中的初步流量预测尝试,这份小而精的代码都是一个不错的起点。

1. 循环神经网络预测网络流量:LSTM 适合什么,不适合什么

做网络运维的人都有过这种经历:流量曲线平时挺平稳,某个时刻突然拉满,等看清是哪个出口在跑,业务已经受了影响。网络流量预测就是为了在那一刻之前给出提示,而 LSTM 这类循环神经网络恰好是最常用的手段——它把过去几十个小时的流量当作有序序列,而不是一堆互不相干的点,去推断下一个时刻的值。这份「循环神经网络预测.py」就是这样一个用 Python 写好的流量预测实现:从异常值清洗、归一化,到 Keras 搭 LSTM、训练和评估,完整走了一遍。它适合两类人:想上手 LSTM 时间序列预测、不想从论文读起的开发者,以及需要快速验证流量峰值的运维工程师。读完你至少能回答三个问题:数据怎么切成序列、LSTM 层怎么搭、预测结果凭什么相信。

2. 数据预处理与切片:把流量序列变成 LSTM 能吃的形状

2.1 流量数据的三个特征,决定预处理怎么选

网络流量数据和一般表格数据不一样,它有三个特征会直接影响后续每一步。

第一是强自相关。这一刻的流量和上一刻高度相关,白天和深夜有明显的日内节奏。这意味着我们不能像前馈 BP 网络那样把每个时间点当成独立特征喂进去——这才是循环神经网络在这个场景的立足点。第二是尖峰。某条链路的抖动、扫描流量、设备误报,会在一两个采样点里把数值拉得很大,如果不先处理,后面 MinMaxScaler 会把整个序列压到很窄的区间里,模型学到的基本是尖峰的影子。第三是周期性。小时级数据有 24 小时日周期,跨周还有 168 小时周期。预处理不改周期,但 window 参数必须和周期对齐,否则模型永远看不到「昨天同一时刻发生了什么」。这一节先把前两个问题处理掉,第 6 章再讲怎么用自相关图确认周期。

2.2 清洗与归一化:滚动中位数比均值更抗尖峰

import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler df = pd.read_csv('traffic.csv', parse_dates=['ts'], index_col='ts') raw = df['traffic'].astype(float) # 流量尖峰会拉偏均值和标准差,用滚动中位数 + MAD 做稳健去尖峰 window = 24 # 小时粒度下的一天;5 分钟粒度改成 288 median = raw.rolling(window, center=True, min_periods=6).median() mad = (raw - median).abs().rolling(window, center=True, min_periods=6).median() series = raw.mask((raw - median).abs() > 3 * 1.4826 * mad, median) series = series.fillna(series.median()) # 前 80% 做训练段,scaler 只在这段上 fit,避免测试信息提前进入 n_train = int(len(series) * 0.8) train_vals = series.iloc[:n_train].values.reshape(-1, 1) scaler = MinMaxScaler(feature_range=(0, 1)) scaler.fit(train_vals) scaled = scaler.transform(series.values.reshape(-1, 1))

这段代码的核心是用滚动中位数而不是滚动均值去估计序列的局部水平。原因很直接:均值对尖峰敏感,一个异常点就能把窗口均值拉高,接着把正常值误判成异常;中位数只取决于排序位置,几个离谱值移动不了它。MAD(中位数绝对偏差)同理,是对离散度的稳健估计。阈值里的 1.4826 是把 MAD 折算成正态标准差的常数,3 倍阈值是一个常用的稳健离群点判定标准。

参数说明:window=24 对应小时粒度的一天,数据变成 5 分钟粒度就改成 288,否则统计基准对不上;min_periods=6 允许序列开头结尾在窗口不足时用部分数据计算,避免头部全变成 NaN;处理方式是 mask 替换而不是直接删除,删除会让时间索引出现空洞,后续切片时要额外对齐,没有好处。

为什么用 MinMaxScaler 而不是 StandardScaler?LSTM 内部用 tanh 和 sigmoid 激活,输入落到 0~1 之间最顺。StandardScaler 是零均值单位方差,流量序列局部波动大时 z-score 会甩出绝对值很大的离群值,tanh 容易饱和,收敛变慢。MinMax 到 (0,1) 是这类时序例子的常见做法,唯一缺点是它对尖峰敏感,所以上一步清洗必须放在缩放之前。

scaler 只 fit 训练段,这是新手最容易忽略的点。如果对全量序列做 fit_transform,测试段的最大最小值参与了归一化参数估计,验证时指标会虚高,真上线就现原形。这是数据泄露的一种,具体现象在第 5 章会讲到。

2.3 序列切片:window 决定模型看到多长的过去

def create_sequences(data, window=24, horizon=1): X, y = [], [] for i in range(window, len(data) - horizon + 1): X.append(data[i - window:i, 0]) y.append(data[i + horizon - 1, 0]) return np.array(X), np.array(y) X, y = create_sequences(scaled, window=24, horizon=1) split = int(len(X) * 0.8) X_train, X_test = X[:split], X[split:] y_train, y_test = y[:split], y[split:] print(X_train.shape, y_train.shape) # 分别是 (样本数, 24, 1) 和 (样本数,)

create_sequences 做的事情很直白:从第 window 个点开始,每取一个长度为 window 的滑动窗口作为 X,目标 y 取窗口结束之后的第 horizon 个点。两个参数别弄混:window 是「模型看多长的过去」,horizon 是「预测未来多远」。

window 取 24,模型只看昨天一天;取 168 就能看到上周同一时刻。太小会丢周期信息,模型只能学短时惯性;太大则输入维度过高,单层 LSTM 的记忆容量未必装得下。horizon 默认 1 就是预测下一个时间步,这也是摘要里说的「目标变量通常是序列的下一个时间步的流量」。想预测未来 3 小时,可以把 horizon 改成 3 直接预测第 3 小时,也可以改成 seq2seq 结构输出长度为 3 的未来序列。后者属于另一族结构,本项目的默认实现是前者,先把单步做稳再扩展。

切分时用X[:split]而不是按原始时间戳切,目的是保证训练和测试在样本维度上连续。LSTM 对训练样本的先后不敏感,但测试集样本一旦乱序,后面把预测结果按时间画图时会对不上。

注意:window 不是越大越好。能切出的样本数约等于原始点数减 window 再减 horizon,window 超过数据总量的十分之一时,训练样本会明显不够用。经验上,样本数至少要是 LSTM 单元数的 10 倍以上,想加大 window 就得先攒够历史数据。

3. 构建 LSTM 模型:input_shape、return_sequences 和训练参数怎么定

3.1 先理解 LSTM 的输入输出形状

Keras 里 LSTM 层的输入是三维张量 (batch_size, timesteps, features)。第一次接触的人通常在这里犯迷糊:为什么二维表格不能直接喂,(24,1) 中间的 1 到底是什么。

batch_size 由 fit 的 batch_size 参数决定,input_shape 里不用写;timesteps 对应第 2 章的 window,LSTM 按这个步数展开内部循环;features 是每个时间点有几个数值字段,只预测流量就是 1,如果同时喂带宽、丢包率、连接数就是 3。模型定义里input_shape=(window, 1)省略了 batch 维度,告诉模型每个样本是「window 行、1 列」。这个形状约定和一维卷积神经网络一致,一维卷积也是 (window, features),并不是 LSTM 特有的东西。

一个容易忽略的细节是训练时的样本顺序。时间序列的相邻样本之间高度重叠(滑动窗口间隔为 1),如果在样本维度做 shuffle,验证集的分布就会偏离真实预测场景。Keras 的 fit 默认 shuffle=True,在时序回归任务里我习惯显式传 False,让训练和验证的样本都保持原始时间顺序。

3.2 return_sequences 是单步和多步预测的分水岭

return_sequences=False 表示 LSTM 只输出最后一个时间步的隐状态,输出形状变成 (batch_size, units)。对「预测下一个值」这种单输出任务,这个配置是对的。改成 True 则每个时间步都输出隐状态,形状变成 (batch_size, timesteps, units)。它用在两个地方:堆叠多层 LSTM 时,前面几层要把完整序列传给下一层;以及需要输出未来多个时间步的时候。

这里的对比值得多说一句。BP 前馈神经网络做流量预测,常规做法是把过去 24 小时拼成 24 个特征喂给全连接层,时间顺序在特征拼好后其实就丢失了,模型只能靠权重硬记。LSTM 按时间步展开、权重跨时间共享,序列结构天然保留。这也是为什么同样预测流量,循环网络通常比同等规模的前馈网络更稳。

3.3 模型定义、编译与训练

from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model = Sequential() model.add(LSTM(64, input_shape=(window, 1), return_sequences=False)) model.add(Dropout(0.2)) model.add(Dense(1)) model.compile(optimizer='adam', loss='mse') model.summary() early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True) history = model.fit( X_train, y_train, validation_split=0.1, epochs=100, batch_size=32, shuffle=False, callbacks=[early_stop], verbose=1 )

参数说明:LSTM(64) 表示 64 个单元,这是小时级单特征序列的稳妥起步值。数据更复杂可以加到 128,但超过 128 而样本不够,过拟合会很快找上门。数据量够不够的判断标准,就是 2.3 里说的样本数不小于单元数的 10 倍。Dropout(0.2) 在 LSTM 输出后随机丢弃 20% 的连接,流量数据训练 loss 常常很漂亮、验证集却很惨,Dropout 是成本最低的防过拟合手段。Dense(1) 输出层用默认的线性激活,千万不要在回归输出层加 relu,否则预测值被截断在正区间,反归一化后整体曲线下边界会抬高。

loss='mse' 的选型有讲究:均方误差对大误差惩罚更重。流量运维关心的是峰值时刻的准确度,峰值偏 20% 和低谷偏 20% 的代价完全不同,mse 会把优化重心压向那些大偏差样本。Adam 优化器默认学习率 1e-3,规模不大的时序项目足够;如果训练初期 loss 抖动剧烈,先降到 3e-4,不要急着动网络结构。

EarlyStopping 的 patience=10 意味着连续 10 个 epoch 验证集没有改善就停止,并自动恢复验证集最好的权重。epochs 设 100 只是上限,实际训练通常用不到。fit 返回的 history 里最值得盯的两个序列是 loss 和 val_loss:前几个 epoch 两者一起降是正常,LSTM 在前几轮主要在学习序列的均值水平;真正区分模型优劣的是后面 20 轮 val_loss 还能不能继续下探。如果 val_loss 在 30 轮内平坦不动,说明容量不够,往上加 units;如果平坦之后突然跌一截,多半是运气成分,换随机种子会现原形。另外提醒一句,这是回归任务,不要去找 accuracy,流量预测里没有准确率这个概念,只有误差。

4. 评估与调参:MSE、MAE 怎么选,超参数先动哪个

4.1 评估指标的选法不能只看 loss 数值

训练时打印的 loss 是归一化空间里的值。0~1 之间 MSE 永远很小,0.002 看着很漂亮,反归一化到真实流量单位(Mbps)之后可能误差几百兆,根本不是一回事。所以评估要分两层:训练过程用归一化 loss 看相对下降趋势;结论评估必须回到原始流量单位。

MSE 和 MAE 的本质差别是对大误差的惩罚强度。MSE 对大偏差加倍惩罚,个别峰值预测失准时它的数值会很难看;MAE 是绝对误差的均值,直观、单位好解释,但对极端点不敏感。两者没有谁绝对更好,取决于你汇报的对象。流量场景我一般两个都算:MSE 用来发现「有没有个别点错得离谱」,MAE 用来判断「整体误差能不能接受」。MAPE 不推荐做全天指标,原因见第 6 章。

指标公式含义流量场景的坑什么时候看
MSE误差平方的均值对峰值误差过于敏感,一个离谱点拉高整体检查灾难性预测
MAE绝对误差的均值直观可解释,但会被大量小误差稀释业务口径主指标
MAPE百分比误差均值深夜流量接近 0 时百分比爆炸只统计白天高峰时段

4.2 反归一化后算一次真正的误差

from sklearn.metrics import mean_squared_error, mean_absolute_error pred = model.predict(X_test) pred_inv = scaler.inverse_transform(pred) y_test_inv = scaler.inverse_transform(y_test.reshape(-1, 1)) mse = mean_squared_error(y_test_inv, pred_inv) mae = mean_absolute_error(y_test_inv, pred_inv) print(f"MSE={mse:.3f} MAE={mae:.3f}")

predict 出来的 pred 是二维数组 (样本数, 1),inverse_transform 要求输入二维,所以 y_test 也要 reshape 成同样形状。这是用 scikit-learn 归一化器最常碰到的细节错误,忘了 reshape 会直接抛维度错误。很多初版实现直接拿归一化空间的 loss 当结论,这是我最想劝改掉的做法。你要向同事汇报的是「明天高峰流量大概 8.2Gbps,误差 ±0.4Gbps」,不是「loss 0.0031」。在原始单位上算的 MAE 才是能写进报告的数字。

4.3 调参顺序:先形状,再结构,最后学习率

调参最忌讳一上来就动学习率。深度学习调参表面看是玄学,实际顺序是固定的:先定数据形态,再调模型宽度,最后碰学习率。

第一步用 ACF 图固定 window(第 6 章会讲怎么画),horizon 按业务需求定,这两项定死之后不要反复横跳。第二步调 units,从 32 试到 128,宽度不够时训练和验证 loss 都高,宽度过头时训练 loss 继续降、验证 loss 反升。第三步调学习率,Adam 默认 1e-3 如果出锯齿状曲线,就降到 3e-4 或 1e-4,学习率对结果的影响往往比 units 更剧烈,所以放在结构之后最后碰。第四步才是 batch_size,16/32/64 之间的差异通常很小,但 loss 抖动剧烈时调大 batch 能明显稳住。

参数常见取值调整优先级说明
window24 / 168最先定死用 ACF 确认周期,别拍脑袋
units32 / 64 / 128第二样本少优先 32
dropout0 ~ 0.4过拟合时调验证 loss 掉头就开
learning rate1e-3 / 3e-4第三曲线锯齿就降
batch_size16 / 32 / 64最后影响小但能稳曲线

5. LSTM 流量预测常见问题排查:五条实测踩坑记录

如果你打算拿到源码就直接跑数据,下面五条是我按实际踩坑频率排的。每一条都先描述现象,再给原因,最后是可操作的解决路径,照着排查比自己瞎试快得多。

5.1 预测结果是一条水平线

现象:训练 loss 很低,但把预测和真实流量画在一起,预测几乎是一条水平直线,数值贴近序列均值,所有尖峰都没预测到。

原因:两种常见情况。一是归一化后的目标序列方差太小,训练段流量本身就平稳,模型发现输出均值比认真预测的 loss 还低,直接躺平;二是 window 设太长、units 又太少,长期依赖超出单层 LSTM 的记忆容量,模型退化成只输出偏置。

解决:先检查归一化后序列的方差,小于 0.01 就考虑对序列做一阶差分,把预测目标从流量值改成流量的变化量,预测完再累加回去;方差正常就把 window 从 168 缩短到 24~72,units 提到 64 起步。改完重训,水平线基本能破。

5.2 预测曲线比真实曲线「晚一步」

现象:预测值和真实值画在一起,形状几乎一样,但整体向右平移了一个时间步,看起来像把上一时刻的真实值抄了过来。

原因:这是强自相关序列的经典陷阱。t 时刻的流量和 t-1 时刻高度相关,模型发现「输出等于上一个输入」已经把 loss 压得很低,它就不需要再去学周期和趋势。这种模型单步看很准,滚动多步时误差迅速累积,曲线快速失真。

解决:第一,检查切片代码,确认 y 取的是窗口结束之后的未来值,而不是窗口的最后一位——X 和 y 重叠一位时模型就是在抄答案;第二,把 horizon 设成 3 或 5,强制模型学会跨越中间步的映射;第三,用第 6 章的 walk-forward 回测看滚动多步误差,如果第 2 步误差就翻倍,基本可以断定模型在复制上一个值。

5.3 训练 loss 一直降,验证 loss 却在涨

现象:训练集 loss 一路走低,验证集先降后升,标准的过拟合曲线。

原因:流量数据即使清洗过,仍有一些局部不规则抖动。LSTM 单元数偏多时,模型会把训练样本里的噪声细节背下来。早期没有 EarlyStopping 的初版项目最容易栽在这里——跑完 100 个 epoch,模型早就开始恶化,代码还在继续算。

解决:把 EarlyStopping 加到 callbacks,patience 给 10;LSTM 输出后加 Dropout,0.2 起步,验证 loss 掉头就加到 0.4;units 从 128 回退到 64。三个手段按顺序来,大多数过拟合能压住。

5.4 训练一开始 loss 就是 NaN

现象:第一个 epoch 的 loss 直接显示 nan,之后所有 epoch 全部无效。

原因:最常见的是数据里有 NaN 没清干净。流量采集断点、CSV 空行都会留 NaN,MinMaxScaler 遇到 NaN 会把整列带坏,模型反向传播时梯度直接失效。其次是学习率偏大,个别离群值导致梯度爆炸,权重被推到 NaN。

解决:清洗阶段加一行series.isna().sum()检查,有缺失先填充;归一化之后验一遍方差,确认没有 NaN 残留。数据干净还是 NaN,就把 Adam 学习率降到 1e-4 再试。这个坑几乎每个跑 LSTM 的人都撞过,属于入门必修课。

5.5 周末和节假日的预测明显偏掉

现象:工作日预测还行,一到周末曲线整体不对,峰值的位置和量级都偏离。

原因:流量有「工作日峰值、周末平缓」的双模式。window 只取 24 时模型看不到「今天是周几」;window 取 168 虽然带进了一整周信息,但单层 LSTM 对「周一和周日不同」这种远距离对比并不擅长,信息被中间大量工作日样本稀释。

解决:我自己的做法是拆模型:工作日一个模型、周末一个模型,分别切序列分别训练,效果立竿见影。另一个常见做法是把「星期几」编码成一个特征列,和流量一起拼成 (window, 2) 输入,模型结构不用动,input_shape 的第二个数改成 2 就行。

6. 把预测从曲线变成结论:回测、ACF 与误差口径的验证技巧

模型训练完、误差也算了,最后一步是确认它真的能用于业务。拿到这份「循环神经网络预测.py」跑通之后,我建议再走三道验证,这三道也是我踩过多次坑之后固定下来的习惯。

第一道是 walk-forward 回测。用训练好的模型,沿着时间轴一步一步预测:每预测一步,把真实观测值滚进窗口继续下一步。这样评估的是「每一步都有真实数据时」的单步预测能力,对应日常监控告警场景。如果做容量规划,就需要另一套评估:把预测值当作输入继续往前推,评估多步自回归能力。两个指标必须分开看,混在一起会得出完全相反的结论——单步很准、多步全崩的情况在流量预测里太常见了。

第二道是用 ACF 自相关图定 window。把清洗后的序列丢给 plot_acf 画一次,横轴是滞后步数,纵轴是自相关系数。流量序列在滞后 24、48、168 处出现明显峰值,说明存在日周期和周年周期,window 至少取 24,想同时看到上周同一时刻就取 168。用图定 window 比反复试参数靠谱得多,不用再交「周期看不见」的学费。

from statsmodels.graphics.tsaplots import plot_acf import matplotlib.pyplot as plt plot_acf(series, lags=24 * 7) plt.show()

图里出现周期性峰值的滞后位置,就是周期的可信长度。峰值越高,代表相隔这么远的两个时刻相关性越强,window 把这段距离包进去才有意义。

第三道是误差口径分时段报。MAPE 在全天范围基本不可用,因为深夜流量接近 0,很小的绝对误差也会算出巨大的百分比。我习惯白天高峰时段单独算 MAPE,全天用 MAE,峰值时段再补一个 MSE,分三个口径汇报。不然一张全天 MAPE 90% 的图,足以让整个项目被误判——其实那只是深夜几个点的误差在放大。

从那以后,我每次跑这类流量预测都强制自己先画 ACF 定 window,再检查切片里 X 和 y 有没有重叠,最后用反归一化后的分时段指标下结论。这套流程看起来只多花十分钟,但能拦住绝大多数翻车现场。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询