基于LSTM的CDN网络流量预测(一)
基于LSTM的CDN网络流量预测
实验目标
- 掌握时序预测中基础的数据分析及训练模型的基本流程;
- 掌握时序预测中基于多线路的单元多步时序预测的基本方法;
- 掌握使用Pytorch进行LSTM模型的构建、训练、保存、加载、预测、统计准确率指标的方法;
案例内容介绍
随着互联网、云业务的迅速发展,企业运转与云服务运维产生的数据与日俱增,在实际生产中,业务的运转往往遵循着相应的规律,时序数据所能带来的价值也日益显现。
本案例是基于CDN(Content Delivery Network)的网络流量预测,CDN是构建在现有网络基础之上的智能虚拟网络,目的是将源站内容分发至最接近用户的节点,使用户可就近取得所需内容,提高用户访问的响应速度和成功率。CDN智能调度中的流量分配算法、95调度算法与错峰调度等算法的有效性高度依赖域名流量的趋势,若能较为准确的预测域名流量的趋势,则可以提升调度算法的效果。与此类似的还有零售预测、供需预测等相关场景。
时间序列预测按数据及场景类别主要可分为单元时序预测(单变量)、多元时序预测、单步预测(预测)、多步预测几类。时间序列预测算法有基于统计的、基于机器学习的和基于深度学习的等等。
本文根据实际场景(单元多步时序预测),针对多线路预测问题,从数据分析到模型预测,采用LSTM,端到端实现CDN网络流量预测模型。
注意事项
- 本案例需使用 GPU 运行,请查看《ModelAtrs JupyterLab 硬件规格使用指南》了解切换硬件规格的方法;
- 如果您是第一次使用 JupyterLab,请查看《ModelAtrs JupyterLab使用指导》了解使用方法;
- 如果您在使用 JupyterLab 过程中碰到报错,请参考《ModelAtrs JupyterLab常见问题解决办法》尝试解决问题。
实验步骤
1. 下载代码和数据集
本案例使用的数据集是来自于脱敏过的CDN网络流量预测项目数据集,因此数据及模型精度较真实效果有点偏差,仅供学习使用
运行下面的代码后可得到network_traffic_forecast目录,里面含有本案例所需的代码和数据集,其data子目录下有4个csv,数据的时间间隔是1分钟,每个区域下面有3个运营商,共31个区域,每个{区域,运营商}对应一条具体的线路,即共31*3条线路,4个csv文件分别对应不同区域流量的4天数据,例如:time_series_1.csv: {0-3}区域,time_series_2.csv: {4-7}区域
import os
import moxing as mox
if not os.path.exists('./network_traffic_predict'):
mox.file.copy('obs://modelarts-labs-bj4-v2/course/modelarts/zjc_team/time_series_forecast/network_traffic_forecast/network_traffic_forecast.zip', './network_traffic_forecast.zip')
os.system('unzip network_traffic_forecast.zip')
if not os.path.exists('./network_traffic_forecast'):
raise Exception('错误!数据不存在!')
INFO:root:Using MoXing-v1.17.3-
INFO:root:Using OBS-Python-SDK-3.20.7
2. 查看数据
构建任何模型之前,都需要先对数据集进行分析,了解数据集的规模、属性名、属性值等情况。因为我们要先了解数据,才能用好数据
2.1 读取csv文件
pandas是常用的python数据分析模块,我们先用它来加载数据集中的csv文件。以time_series_1.csv为例,我们先加载该文件来分析数据的情况
import pandas as pd
df_data = pd.read_csv("./network_traffic_forecast/data/time_series_1.csv")
2.2 查看单个csv文件数据的规模
print('单个csv文件数据的规模,行数:%d, 列数:%d' % (df_data.shape[0], df_data.shape[1]))
单个csv文件数据的规模,行数:68601, 列数:4
2.3 查看前20行数据
使用pandas加载csv后,得到的是一个DataFrame对象,可以理解为一个表格,调用该对象的head()函数,我们查看一下该表格的头20行数据
df_data.head(20)
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
TimePoint | att1 | att2 | Value | |
---|---|---|---|---|
0 | 1592496000 | 0 | 0 | 1.683235e+08 |
1 | 1592496000 | 0 | 1 | 4.024828e+07 |
2 | 1592496000 | 0 | 2 | 1.537997e+08 |
3 | 1592496000 | 1 | 0 | 4.798478e+05 |
4 | 1592496000 | 1 | 1 | 1.437264e+08 |
5 | 1592496000 | 1 | 2 | 3.192533e+07 |
6 | 1592496000 | 2 | 0 | 1.193619e+08 |
7 | 1592496000 | 2 | 1 | 3.457281e+07 |
8 | 1592496000 | 2 | 2 | 2.276732e+07 |
9 | 1592496000 | 3 | 0 | 1.447897e+08 |
10 | 1592496000 | 3 | 1 | 5.181238e+07 |
11 | 1592496000 | 3 | 2 | 2.120722e+08 |
12 | 1592496060 | 0 | 0 | 7.017834e+07 |
13 | 1592496060 | 0 | 1 | 3.913830e+07 |
14 | 1592496060 | 0 | 2 | 5.822768e+05 |
15 | 1592496060 | 1 | 0 | 2.713144e+05 |
16 | 1592496060 | 1 | 1 | 1.095218e+08 |
17 | 1592496060 | 1 | 2 | 7.033603e+05 |
18 | 1592496060 | 2 | 0 | 1.161724e+08 |
19 | 1592496060 | 2 | 1 | 1.995452e+07 |
如上所示是表格的头20行数据,表头是属性名,属性名下面是属性值,各属性含义如下:
属性名 | 属性含义 |
---|---|
TimePoint | Unix时间戳 |
att1 | 区域 |
att2 | 运营商 |
Value | 流量值(GB) |
2.3.1 查看单条线路数据情况
df_data[(df_data['att1'] == 0) & (df_data['att2'] ==0)]
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
TimePoint | att1 | att2 | Value | |
---|---|---|---|---|
0 | 1592496000 | 0 | 0 | 1.683235e+08 |
12 | 1592496060 | 0 | 0 | 7.017834e+07 |
24 | 1592496120 | 0 | 0 | 5.218320e+07 |
36 | 1592496180 | 0 | 0 | 4.973728e+07 |
48 | 1592496240 | 0 | 0 | 2.627043e+07 |
60 | 1592496300 | 0 | 0 | 2.516527e+07 |
72 | 1592496360 | 0 | 0 | 5.241541e+04 |
84 | 1592496420 | 0 | 0 | 5.126166e+02 |
92 | 1592496480 | 0 | 0 | 1.253282e+03 |
104 | 1592496600 | 0 | 0 | 1.975488e+02 |
119 | 1592496720 | 0 | 0 | 6.139325e+02 |
136 | 1592496900 | 0 | 0 | 5.598084e+03 |
147 | 1592496960 | 0 | 0 | 2.088519e+03 |
190 | 1592497320 | 0 | 0 | 8.426582e+02 |
199 | 1592497380 | 0 | 0 | 2.255806e+04 |
210 | 1592497440 | 0 | 0 | 1.048735e+04 |
218 | 1592497500 | 0 | 0 | 4.599218e+03 |
229 | 1592497560 | 0 | 0 | 5.261509e+02 |
237 | 1592497620 | 0 | 0 | 1.098636e+02 |
256 | 1592497740 | 0 | 0 | 1.977122e+02 |
274 | 1592497920 | 0 | 0 | 6.781617e+02 |
282 | 1592497980 | 0 | 0 | 9.741788e+01 |
292 | 1592498040 | 0 | 0 | 5.603992e+03 |
303 | 1592498100 | 0 | 0 | 4.493011e+02 |
313 | 1592498160 | 0 | 0 | 1.461330e+03 |
321 | 1592498220 | 0 | 0 | 2.241611e+03 |
326 | 1592498280 | 0 | 0 | 1.335533e+03 |
331 | 1592498340 | 0 | 0 | 3.136885e+02 |
340 | 1592498460 | 0 | 0 | 3.227209e+02 |
347 | 1592498520 | 0 | 0 | 2.783268e+02 |
... | ... | ... | ... | ... |
68571 | 1592839740 | 0 | 0 | 2.547898e+08 |
68572 | 1592839800 | 0 | 0 | 2.531656e+08 |
68573 | 1592839860 | 0 | 0 | 2.513123e+08 |
68574 | 1592839920 | 0 | 0 | 2.496070e+08 |
68575 | 1592839980 | 0 | 0 | 2.509131e+08 |
68576 | 1592840040 | 0 | 0 | 2.520332e+08 |
68577 | 1592840100 | 0 | 0 | 2.546929e+08 |
68578 | 1592840160 | 0 | 0 | 2.503867e+08 |
68579 | 1592840220 | 0 | 0 | 2.482838e+08 |
68580 | 1592840280 | 0 | 0 | 2.447917e+08 |
68581 | 1592840340 | 0 | 0 | 2.421030e+08 |
68582 | 1592840400 | 0 | 0 | 2.374323e+08 |
68583 | 1592840460 | 0 | 0 | 2.330100e+08 |
68584 | 1592840520 | 0 | 0 | 2.308231e+08 |
68585 | 1592840580 | 0 | 0 | 2.289234e+08 |
68586 | 1592840640 | 0 | 0 | 2.279097e+08 |
68587 | 1592840700 | 0 | 0 | 2.287882e+08 |
68588 | 1592840760 | 0 | 0 | 2.286189e+08 |
68589 | 1592840820 | 0 | 0 | 2.259465e+08 |
68590 | 1592840880 | 0 | 0 | 2.233548e+08 |
68591 | 1592840940 | 0 | 0 | 2.221803e+08 |
68592 | 1592841000 | 0 | 0 | 2.205086e+08 |
68593 | 1592841060 | 0 | 0 | 2.190259e+08 |
68594 | 1592841120 | 0 | 0 | 2.175316e+08 |
68595 | 1592841180 | 0 | 0 | 2.153261e+08 |
68596 | 1592841240 | 0 | 0 | 2.131482e+08 |
68597 | 1592841300 | 0 | 0 | 2.099739e+08 |
68598 | 1592841360 | 0 | 0 | 2.085667e+08 |
68599 | 1592841420 | 0 | 0 | 2.070421e+08 |
68600 | 1592841480 | 0 | 0 | 2.048070e+08 |
5717 rows × 4 columns
如上所示,以att1=0,att2=0 查看单条线路{区域,运营商}数据情况,可以看到数据是1min采样一次,共4天。
2.3.2 查看数据时序性情况
画出每条线路数据曲线图,根据曲线图判断数据时序性
import time
df_data["TimePoint"] = df_data["TimePoint"].apply(lambda x: time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(x))) # 时间戳转换
%matplotlib inline
df_data = df_data.set_index(['TimePoint'])
print(df_data.groupby(["att1","att2"])['Value'].plot(figsize=(20,10)))
df_data = df_data.reset_index(['TimePoint'])
INFO:matplotlib.font_manager:font search path ['/home/ma-user/anaconda3/envs/Pytorch-1.0.0/lib/python3.6/site-packages/matplotlib/mpl-data/fonts/ttf', '/home/ma-user/anaconda3/envs/Pytorch-1.0.0/lib/python3.6/site-packages/matplotlib/mpl-data/fonts/afm', '/home/ma-user/anaconda3/envs/Pytorch-1.0.0/lib/python3.6/site-packages/matplotlib/mpl-data/fonts/pdfcorefonts']
INFO:matplotlib.font_manager:generated new fontManager
att1 att2
0 0 AxesSubplot(0.125,0.125;0.775x0.755)
1 AxesSubplot(0.125,0.125;0.775x0.755)
2 AxesSubplot(0.125,0.125;0.775x0.755)
1 0 AxesSubplot(0.125,0.125;0.775x0.755)
1 AxesSubplot(0.125,0.125;0.775x0.755)
2 AxesSubplot(0.125,0.125;0.775x0.755)
2 0 AxesSubplot(0.125,0.125;0.775x0.755)
1 AxesSubplot(0.125,0.125;0.775x0.755)
2 AxesSubplot(0.125,0.125;0.775x0.755)
3 0 AxesSubplot(0.125,0.125;0.775x0.755)
1 AxesSubplot(0.125,0.125;0.775x0.755)
2 AxesSubplot(0.125,0.125;0.775x0.755)
Name: Value, dtype: object
时间序列数据构成要素:长期趋势,季节变动,循环变动,不规则变动。
- 长期趋势(T)现象在较长时期内受某种根本性因素作用而形成的总的变动趋势。
- 季节变动(S)现象在一年内随着季节的变化而发生的有规律的周期性变动。
- 循环变动(C)现象以若干年为周期所呈现出的波浪起伏形态的有规律的变动。
- 不规则变动(I)是一种无规律可循的变动,包括严格的随机变动和不规则的突发性影响很大的变动两种类型。
分析:
如上图所示,是将数据按照(att1,att2)分组后,每条线路的曲线示意图,图上每条曲线则对应不同的线路,横轴为时间,纵轴为单条线路对应的Value值。根据图中曲线及以上时间序列数据特点,可得出以下结论:
- 数据存在周期性 每天一个周期,同一线路各周期间数据范围大致相同,不存在趋势性,季节性由于数据较少,暂看不出来,
- 共31个区域,每个{区域,运营商}]对应一条具体的线路,即共31*3条线路 每条线路大致趋势相同,但是量级不一样 需要进行归一化
- 数据峰值处波动较大,其余部分较平缓
- 整体数据质量较好,不需要进行数据平滑等使得数据变平稳的操作,后续建模预测未来10min,将主要依赖待预测点前一段时序数据进行预测。
- 数据中无明显脱离整体趋势范围的异常值。
2.4 数据分析
查看数据的统计值及空值情况
df_data.groupby(["att1","att2"]).describe()
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead tr th {
text-align: left;
}
.dataframe thead tr:last-of-type th {
text-align: right;
}
</style>
Value | |||||||||
---|---|---|---|---|---|---|---|---|---|
count | mean | std | min | 25% | 50% | 75% | max | ||
att1 | att2 | ||||||||
0 | 0 | 5717.0 | 2.756998e+08 | 1.471390e+08 | 85.107962 | 1.608420e+08 | 2.940786e+08 | 3.754438e+08 | 6.095700e+08 |
1 | 5686.0 | 5.480488e+07 | 2.756697e+07 | 51.327340 | 3.322805e+07 | 5.950805e+07 | 7.381292e+07 | 1.162919e+08 | |
2 | 5724.0 | 2.188223e+08 | 1.181942e+08 | 65.711383 | 1.333125e+08 | 2.329354e+08 | 2.915056e+08 | 5.042825e+08 | |
1 | 0 | 5715.0 | 1.861563e+07 | 9.069485e+06 | 38.332234 | 1.034681e+07 | 2.209178e+07 | 2.592819e+07 | 3.338421e+07 |
1 | 5725.0 | 1.258266e+08 | 6.321933e+07 | 4.381480 | 6.769589e+07 | 1.459575e+08 | 1.770747e+08 | 2.377960e+08 | |
2 | 5746.0 | 5.218107e+07 | 2.650715e+07 | 4.772777 | 2.804430e+07 | 6.088992e+07 | 7.308530e+07 | 9.835380e+07 | |
2 | 0 | 5732.0 | 1.179337e+08 | 6.204479e+07 | 36.191396 | 6.684485e+07 | 1.292305e+08 | 1.580988e+08 | 2.522959e+08 |
1 | 5696.0 | 3.024132e+07 | 1.488895e+07 | 49.353860 | 1.792033e+07 | 3.356871e+07 | 4.138216e+07 | 5.848370e+07 | |
2 | 5756.0 | 1.024740e+08 | 5.487324e+07 | 146.927647 | 5.985492e+07 | 1.095121e+08 | 1.376453e+08 | 2.203716e+08 | |
3 | 0 | 5708.0 | 1.271339e+08 | 6.343431e+07 | 109.510294 | 7.234194e+07 | 1.381774e+08 | 1.783945e+08 | 2.426037e+08 |
1 | 5711.0 | 4.340486e+07 | 2.045753e+07 | 6.857371 | 2.588623e+07 | 4.660798e+07 | 6.067043e+07 | 7.981218e+07 | |
2 | 5685.0 | 1.991225e+08 | 9.818788e+07 | 1.169489 | 1.234276e+08 | 2.079243e+08 | 2.744293e+08 | 3.993022e+08 |
-
数据在时间列采样上存在缺失值,需要进行缺失值填充
对于count列,不同的att1与att2组合(不同线路)对应的count长度不同,正常情况下,数据有4天,应该是1440*4 = 5760个点,说明数据在时间列采样上存在缺失值,需要进行缺失值填充
查看Value列的最大最小值
由于以上分析中,value的max和min显示并不直观,因此这里再做具体分析
print(df_data['Value'].min())
print(df_data['Value'].max())
1.1694885305466325
609569980.8860668
可以看到最小值是1.1694885305466325,最大值为609569980.8860668,无小于等于0的异常值;
未完待续
- 点赞
- 收藏
- 关注作者
评论(0)