基于LSTM预测未来一天的网络流量

作者: 学无止境1980 | 来源:发表于2019-07-04 11:56 被阅读54次

实验笔记:网络流量预测

概述

需要对未来的网络流量大小进行预测,故进行此实验,尝试网络流量预测的可行性。该笔记完整记录了我在整个实验过程中做的所有工作,包括进行过的尝试和得到的结果。

通过尝试使用神经网络对未来流量进行预测,并对比直接复现一天前流量数据和一周前流量数据所能达到的预测效果,得出的结论是:
loss_{yesterday} > loss_{nn\_predict} > loss_{last\_week}

其中 loss 为定义的损失函数, loss_{yesterday} 表示复现一天前的数据作为预测结果的误差, loss_{nn\_predict} 表示使用神经网络模型对未来流量进行预测的误差, loss_{last\_week} 表示复现一周前的数据作为预测结果的误差。

损失函数值越小,误差越小,说明预测得越精确。故,最终的预测效果准确程度,由高到低,可按如下排序:

复现一周前流量数据 > 使用神经网络模型预测 > 复现一天前流量数据

STEP 1:获取数据源

预测未来网络流量,我们需要先分析历史流量数据,并从中发现规律。

在服务器上有其他同事导出好的历史流量数据,这些数据通过 pythonmarshal 库,使用 marshal.dump 函数导出成文件。导出文件的命名规则为 bu_flow %Y-%m-%d.mar ,其内容为这一天各个业务下各个机房的流量数据。譬如文件 bu_flow 2018-12-01.mar ,即表示2018年12月1日这天,各个业务下各个机房的流量数据。在我进行实验时,服务器上有从2018年9月1日起共300天左右的 bu_flow 文件。

使用 pythonmarshal 库的 marshal.load 函数,可以加载这些导出的数据文件。加载后,将得到一个字典 dict 。该字典的关键词是一个三元组 (oc_id, bu_id, level) ,值则是一个长度为1440的 list ,表示这一天跑着 bu_idlevel 对应的业务的 oc_id 对应的机房,每一分钟的流量大小(一天共 24 * 60 = 1440 分钟)。我们只需要将bu_id和level相同的流量数据累加起来,即可获得每一个业务当天的流量汇总数据。

一个业务的流量受到各种突发事件的影响,比如:

  1. 手游新版本的发布
  2. 电视剧突然火遍全网
  3. 大型赛事的直播

我随机选取了一些业务作为我关注的对象,无论是流量较大的大业务,还是流量较小的小业务,因为受到突发事件的影响,其流量数据变化的规律性都不明显,随机性较强。想要预测某一业务的流量变化,可以说是非常难实现的。

既然难以做到对业务的流量预测,我只好转变思路,适当放粗粒度,改为对某一运营商下所有流量进行预测。运营商下的流量包含各种各样的业务的流量,从宏观角度来看,其受各种突发事件的影响更小。所以,从理论上来说,某一运营商下所有流量的变化规律性也应更明显。

要计算某一运营商下所有流量数据,可以先获取各机房的流量数据,然后将属于该运营商的机房的流量数据累加起来。在本次实验中,我选择关注电信(isp_id为1)运营商的所有流量数据,电信属于较大的运营商,其流量较多。使用SQL语句 SELECT value FROM ******** WHERE isp_id=1 AND day="%Y-%m-%d"; 即可从数据库中查出某一天内所有属于电信运营商的流量数据,再累加即可。查询出来的原始数据的采样间隔大多数情况下为10s,这里需要先对原始数据进行一下处理,每分钟仅保留一个数据点,该点的值为这一分钟内流量大小的平均值。

这一步骤的关键代码参见附录 <a href="#code_section_1">Code Section 1</a> 。

上面的步骤将得到一个长度为 503 * 1440list (2018年1月15至2019年6月1日共503天,每天共1440分钟)。该 list 中的值表示电信运营商某一天某一分钟的所有流量的总大小,以MB为单位。

我们还可以继续对得到的流量数据进行平滑处理,每一小时仅保留一个数据点,该点的值为这一小时内的最大流量。处理完毕后,得到的 list 长度将从 503 * 1440 缩减至 503 * 24

可用 pythonmatplotlib 库绘图,直观地输出数据,代码参见附录 <a href="#code_section_2">Code Section 2</a> 。

绘制出来的图表如下:

md_cut1.png

这里可能还不能很清楚地看出流量数据变化的规律性,我们可以仅输出最后30天的流量数据,效果如下:

md_cut2.png

可以明显看到以一天为周期的流量变化情况。至此,我们寻找到了一个比较适合接下来的预测工作的流量数据源。接下来将基于该数据源作为历史数据,尝试预测未来的电信运营商的所有流量总和的变化情况。

STEP 2:制定策略

对于未来网络流量大小的预测,总的来说有两种方法:

  1. 基于统计的方法。可以通过统计历史流量数据,运用统计的方法总结规律,实现对未来流量的预测。由于流量数据的周期规律性,我决定分别尝试复现前一天或上周同天的数据,作为接下来一天的流量预测结果,并观察预测效果。

  2. 基于机器学习的方法。预测未来的网络流量,其本质即是对随时间变化的序列进行预测,而这可以通过机器学习的方法实现。对随时间变化的序列进行预测,比较常用的模型是 LSTM(Long Short-Term Memory,长短期记忆)。 LSTMRNN (Recurrent Neural Network,循环神经网络)的一种,传统 RNN 的内部结构如下图:

    md_cut3.png

    LSTM 的内部结构就复杂得多了:

    md_cut4.png

    LSTM 由3个门——遗忘门、输入门、输出门来保护和控制每一个单元的状态。LSTM 的主要参数为 units ,其表示单元输出的一维向量的长度。

STEP 3:定义评价标准

我们需要一个评价标准,用于根据实际发生的流量数据,评价预测出来的流量数据的准确程度。我将实验目标先暂定为预测接下来一天的流量数据(共24个数据点),定义损失函数 loss 为预测出的24个数据点的值与实际发生的流量的24个数据点的值的均方误差(MSE,mean square error):

md_cut5.png

该损失函数可以作为评价预测准确度的标准,损失函数值越小,则预测得越精确。尽管对于“95调度”而言,均方误差并不是一个足够合适的评价标准(因为“95调度”不关心预测的每一个数据点值的准确,仅关心所有数据点的值分布的比例的准确),但是在这里我还是先采用了均方误差作为评价标准,而针对“95调度”的流量预测将在后续实验中进行。

STEP 4:建立神经网络模型

确定了要使用 LSTM ,还需要思考神经网络的具体细节。在这里我采用了三层 LSTM 的网络结构,具体连接方式如下:

  1. 第一层 LSTM 接收时间步长 timesteps 为24,数据维度 data_dim 为3的输入。其中,第一维表示从一天前开始的接下来的24个数据点的值;第二维表示从两天前开始的接下来的24个数据点的值;第三维表示从一周前开始的接下来的24个数据点的值。
  2. 第二层 LSTM 以第一层 LSTM 的输出为输入。
  3. 第三层 LSTM 以第二层 LSTM 的输出为输入。
  4. 第三层 LSTM 的输出作为输入,连接到一个全连接层。全连接层的输出是一个长度为24的一维向量,为最终的输出结果,表示预测的未来一天内24个数据点的值。

定义模型的代码参见附录 <a href="#code_section_3">Code Section 3</a> 。

在代码中选取 data_dim = 3timesteps = 24 ,是权衡输入数据多少和包含历史信息长短的结果。输入数据多少和包含历史信息长短,对于预测工作有着如下影响:

  1. 输入数据太多,将导致计算复杂,训练神经网络时速度较慢。
  2. 输入数据太少,容易导致包含的历史信息较短,难以反映出周期规律。

而在我的代码中的这种采用三维输入数据的设计,其时间步长不长,输入数据并不多,训练神经网络时速度较快。并且,这种设计在时间步长仅为24的输入数据内,还包含了上周同期的流量数据信息。上周同期的流量数据信息,对于流量预测工作是一个很重要的参考,因为流量变化除了以一天为周期(人们白天工作、晚上休息),还以一周为周期(周一至周五是工作日,而周六周日放假),我们可以很明显看出周末的流量与工作日的流量的不同。

对于这个神经网络模型,还有许多可以调整的地方,比如:

  1. 在第三层 LSTM 和全连接层中间增加一层 Dropout 层防止过拟合
  2. 调整各 LSTM 层的 units 参数大小
  3. 使用不同的优化器 optimizer

上述修改我都尝试过,但它们对最终预测效果往往没有太大影响,当设置不当时,甚至会让预测效果更差。

STEP 5:训练神经网络模型

读入数据源后,还需要依次进行如下处理:

  1. 先对数据进行归一化,这样有利于提升梯度下降的速度。

  2. 为了让数据组数能刚好整除 batch_size ,舍弃掉一组数据。

    在训练模型或使用模型进行预测时,需要利用到远至一周前的数据点,来预测远至一天后的数据点,时间跨度为八天,共涉及到 8 * 24 个数据点。我们的数据源共有 503 * 24 个数据点,故共可形成 (503 * 24 - 8 * 24 + 1) 组数据。舍弃掉一组数据后,数据组数刚好整除 batch_size

  3. 对剩下的 (503 * 24 - 8 * 24) 组数据进行分割,将最后的 7*24 组数据作为测试集,而前面的所有组数据都作为训练集。

    训练集数据用于训练神经网络模型,而测试集数据则用于评价当前神经网络的预测准确度。每一轮训练完成后,测试集数据都会被放入模型中跑一遍,将预测的结果和测试集中的实际结果比较,计算损失函数的值并输出(val_loss)。

对数据源进行处理并使用处理后的数据训练神经网络模型,代码参见附录 <a href="#code_section_4">Code Section 4</a> 。

运行代码,可以看到,每一轮训练后,测试集的损失函数值 val_loss 都有所下降:

md_cut6.png

经过30轮训练后,val_loss 的值徘徊在0.0215左右。

md_cut7.png

STEP 6:比较优劣

前文说过,基于统计的方法也可以实现对网络流量的预测。那么,基于统计的方法和基于机器学习的方法相比,孰优孰劣呢?在这一步,我将针对最后一天的24个数据点,使用训练出的神经网络模型进行预测。与此同时,我将直接简单地复现前一天的24个数据点和一周前同期的24个数据点作为对这最后一天的24个数据点的预测,并观察这三种方法的预测结果与真实情况的差异。

这一工作的代码,参见附录 <a href="#code_section_5">Code Section 5</a> 。

在代码中通过 matplotlib 绘制出的图表如下:

md_cut8.png

从图表中可以直观地看出,在三种预测方法中,复现上周流量数据作为预测最贴合实际发生的流量数据。

从更数值化一些的视角来看,代码中通过 print 函数分别输出了三个预测方法的均方误差值,分别为:

  • loss_{nn\_predict} = 0.0379768424590701
  • loss_{yesterday} = 0.10520508337927204
  • loss_{last\_week} = 0.004095131203424123

由上面的数值我们可以得出结论,使用神经网络模型对未来网络流量进行预测,其效果好于直接复现前一天的网络流量数据,但差于复现一周前的网络流量数据。

附录

<a name="code_section_1">Code Section 1</a>
date = datetime.date(2018, 1, 15)
while date <= datetime.date(2019, 6, 1):
    sql = "SELECT value FROM sz_sync_oc_flow WHERE isp_id=1 AND day='%s';" % date.strftime("%Y-%m-%d")
    print(sql)
    fluxSum = [0] * 1440
    result = db.query(sql)
    for row in result:
        a = [0] * (6*1440)
        first = True
        for perFlux in row['value'].split('|'):
            flux = perFlux.split(',')
            assert int(flux[1]) % 10 == 0
            if first:
                startTs = int(flux[0])
                first = False
            k = (int(flux[0]) - startTs) // 10
            for i in flux[2:]:
                for j in range(int(flux[1]) // 10):
                    if k >= 6*1440:
                        break
                    a[k] = int(i)
                    k += 1
        b = [sum(a[i:i+6])/6 for i in range(0, 24*60*6, 6)]
        for i in range(1440):
            fluxSum[i] += b[i]
    with open('sz_isp_1_flow/%s.mar' % date.strftime("%Y-%m-%d"), 'wb') as f:
        marshal.dump(fluxSum, f)
    date += datetime.timedelta(days=1)

date = datetime.date(2018, 1, 15)
data = []
while date <= datetime.date(2019, 6, 1):
    with open('sz_isp_1_flow/%s.mar' % date.strftime('%Y-%m-%d'), 'rb') as f:
        a = marshal.load(f)
        print(date)
    data.extend(a)
    date += datetime.timedelta(days=1)                                                                                                           
with open('sum.mar', 'wb') as f:
    marshal.dump(data, f)
<a name="code_section_2">Code Section 2</a>
import matplotlib.pyplot as plt
import marshal

# Get data: 
index = list(range(503*24))
data = []
with open('sum.mar', 'rb') as f:
    d = marshal.load(f)
data = [max(d[i: i+60]) for i in range(0, 503*1440, 60)]
plt.figure()
plt.plot(index, data)
plt.xlabel('hour')
plt.ylabel('flow (MB)')
plt.title("ISP_ID = 1")
plt.show()
<a name="code_section_3">Code Section 3</a>
from keras.models import Sequential
from keras.layers import LSTM, Dense
from keras.optimizers import Adam

data_dim = 3
timesteps = 24
batch_size = 8

# 期望输入数据尺寸: (batch_size, timesteps, data_dim)
model = Sequential()
model.add(LSTM(32, return_sequences=True, stateful=True, 
               batch_input_shape=(batch_size, timesteps, data_dim)))
model.add(LSTM(64, return_sequences=True, stateful=True))
model.add(LSTM(96, stateful=True))
model.add(Dense(24))

model.compile(loss='mean_squared_error', optimizer=Adam())
<a name="code_section_4">Code Section 4</a>
import marshal
with open('sum.mar', 'rb') as f:
    d = marshal.load(f)
import numpy as np
d2 = np.array([max(d[i:i+60]) for i in range(0, 503*1440, 60)])
d = (d2 - d2.mean()) / d2.std() # 归一化

x = np.zeros((503*24-7*24-24+1, timesteps, 3))
y = np.zeros((503*24-7*24-24+1, 24))
for i in range(7*24, 503*24-24+1):
    for j in range(timesteps):
        x[i-7*24, j, 0] = d[i-24+j]
        x[i-7*24, j, 1] = d[i-2*24+j]
        x[i-7*24, j, 2] = d[i-7*24+j]
    y[i-7*24, :] = d[i: i+24]
x_train = x[1: -7*24, : , : ] # 舍弃第1组数据,使得数据组数能整除batch_size
y_train = y[1: -7*24, :]
x_val = x[-7*24: , : , : ] # 取最后的7*24组数据作为测试集
y_val = y[-7*24: , : ]

model.fit(x_train, y_train, batch_size=batch_size,
          epochs=30, shuffle=False, validation_data=(x_val, y_val))
<a name="code_section_5">Code Section 5</a>
x_predict = x[-1: , : , : ]
y_predict = model.predict_on_batch(x_predict)

predict = y_predict[0, : ] # 使用训练后的神经网络模型的预测值
real = y_val[-1, : ] # 真实值
yesterday = d[503*24-2*24: 503*24-24] # 复现一天前的值
last_week = d[503*24-8*24: 503*24-7*24] # 复现一周前的值

import matplotlib as mpl
from matplotlib import pyplot as plt
plt.figure()
plt.plot(predict, label='predict')
plt.plot(real, label='real')
plt.plot(yesterday, label='yesterday')
plt.plot(last_week, label='last_week')
plt.legend()
plt.show()

print(((predict - real) ** 2).mean()) # loss_nn_predict
print(((yesterday - real) ** 2).mean()) # loss_yesterday
print(((last_week - real) ** 2).mean()) # loss_last_week

相关文章

网友评论

    本文标题:基于LSTM预测未来一天的网络流量

    本文链接:https://www.haomeiwen.com/subject/ygulhctx.html