Python深度学习实例--利用循环神经网络进行温度预测(时间序列预测)

1. 数据集介绍

在本节的例子中,将使用一个天气时间序列数据集,它由德国耶拿的马克思 • 普朗克生物地球化学研究所的气象站记录。在这个数据集中,每 10 分钟记录 14 个不同的量(比如气温、气压、湿度、风向等),其中包含多年的记录。原始数据可追溯到 2003 年,但本例仅使用 2009—2016 年的数据。这个数据集非常适合用来学习处理数值型时间序列。我们将会用这个数据集来构建模型,输入最近的一些数据(几天的数据点),可以预测 24 小时之后的气温。下载并解压数据。

cd ~/Downloads
mkdir jena_climate
cd jena_climate
wget https://s3.amazonaws.com/keras-datasets/jena_climate_2009_2016.csv.zip 
unzip jena_climate_2009_2016.csv.zip

观察耶拿天气数据

import numpy as np
import pandas as pd
import matplotlib as plt
df=pd.read_csv("D:\Downloads\jena_climate_2009_2016.csv") #数据集下载的地址
df.tail()

输出:
在这里插入图片描述
从输出可以看出,共有 420 551 行数据(每行是一个时间步,记录了一个日期和 14 个与天气有关的值),[“Date Time”,“p (mbar)”, “T (degC)”, “Tpot (K)”, “Tdew (degC)”, “rh (%)”, “VPmax (mbar)”, “VPact (mbar)”, “VPdef (mbar)”, “sh (g/kg)”, “H2OC (mmol/mol)”, “rho (g/m**3)”, “wv (m/s)”, “max. wv(m/s)”。 “wd (deg)”]
下面将420551行数据转换成一个Numpy数组,并绘制温度数据序列。

float_data=df.values
float_data.shape  #(420551, 14)
from matplotlib import pyplot as plt
temp = float_data[:, 1]  # temperature (in degrees Celsius)
print(temp) #[-8.02 -8.41 -8.51 ... -3.16 -4.23 -4.82]
plt.plot(range(len(temp)), temp)
plt.show()

输出:

(420551, 14)
[-8.02 -8.41 -8.51 ... -3.16 -4.23 -4.82]

在这里插入图片描述

2. 准备数据

这个问题的数学表述如下:一个时间步是 10 分钟,每 steps 个时间步采样一次数据,给定过去 lookback 个时间步之内的数据,能否预测 delay 个时间步之后的温度?因此,需要完成以下两件事。
1、需要对每个时间序列分别做标准化,让它们在相似的范围内都取较小的值。
2、编写一个 Python 生成器,以当前的浮点数数组作为输入,并从最近的数据中生成数据批量,同时生成未来的目标温度。
数据标准化

#使用前200 000 个时间步作为训练数据,所以只对这部分数据计算平均值和标准差。
mean = float_data[:200000].mean(axis=0)  # 按照列进行标准化
float_data -= mean
std = float_data[:200000].std(axis=0)
float_data /= std

编写可以生成时间序列样本及标签的生成器,生成器的参数如下。
data:浮点数数据组成的原始数组。
lookback:输入数据应该包括过去多少个时间步。
delay:目标应该在未来多少个时间步之后。
min_index 和 max_index:data 数组中的索引,用于界定需要抽取哪些时间步。这有助于保存一部分数据用于验证、另一部分用于测试。
shuffle:是打乱样本,还是按顺序抽取样本。
batch_size:每个批量的样本数。
step:数据采样的周期(单位:时间步)。将其设为 6,为的是每小时抽取一个数据点。

def generator(data, lookback, delay, min_index, max_index,
              shuffle=False, batch_size=128, step=6):
    if max_index is None:#判别是否为测试数据生成器
        max_index = len(data) - delay - 1
    i = min_index + lookback #lookback=720
    while 1:#一直循环
        if shuffle:#判断是否为训练数据生成器
            rows = np.random.randint(
                min_index + lookback, max_index, size=batch_size)#随机生成128个范围在min_index + lookback和max_index之间的乱数
        else:#验证数据生成器
            if i + batch_size >= max_index: #判断是否超出总数据的大小
                i = min_index + lookback
            rows = np.arange(i, min(i + batch_size, max_index))#按顺序生成128个范围在i和i + batch_size的数字
            i += len(rows)
#训练、验证、测试数据生成共用下面的代码
        samples = np.zeros((len(rows),
                           lookback // step,
                           data.shape[-1]))#构建形状为(128,120,14)的三维张量存放样本
        targets = np.zeros((len(rows),))#存放标签
        for j, row in enumerate(rows):#以训练数据生成器为例,将上面随机生成的128个乱数进行enumerate
            indices = range(rows[j] - lookback, rows[j], step)#step=6,第一次循环, rows[j]为随机生成的128个乱数的第1个,生成120个索引。若rows[j]为720,则生成的120个索引为【0、6、12...720】
            samples[j] = data[indices]#按照生成的120个索引将原始数据装入构建的三维samples中
            targets[j] = data[rows[j] + delay][1]#装入标签
        yield samples, targets#这段代码比较复杂,建议仔细分析。

准备训练生成器、验证生成器和测试生成器

lookback = 1440
step = 6
delay = 144
batch_size = 128
train_gen = generator(float_data,
 lookback=lookback,
 delay=delay,
 min_index=0,
 max_index=200000,
 shuffle=True,
 step=step,
 batch_size=batch_size)
val_gen = generator(float_data,
 lookback=lookback,
 delay=delay,
 min_index=200001,
 max_index=300000,
 step=step,
 batch_size=batch_size)
test_gen = generator(float_data,
 lookback=lookback,
 delay=delay,
 min_index=300001,
 max_index=None,
 step=step,
 batch_size=batch_size)
val_steps = (300000 - 200001 - lookback) //batch_size #为了查看整个验证集,需要从 val_gen 中抽取多少次
test_steps = (len(float_data) - 300001 - lookback) //batch_size #为了查看整个测试集,需要从test_gen 中抽取多少次

3. 使用循环卷积网络-GRU

使用 Chung 等人在 2014 年开发的 GRU 层 ,而不是上一节介绍的 LSTM 层。门控循环单元(GRU,gated recurrent unit)层的工作原理与 LSTM 相同。但它做了一些简化,因此运行的计算代价更低(虽然表示能力可能不如 LSTM)。机器学习中到处可以见到这种计算代价与表示能力之间的折中。

from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop

model = Sequential()
model.add(layers.GRU(32, input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))
model.summary()
model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,
                              steps_per_epoch=500,
                              epochs=20,
                              validation_data=val_gen,
                              validation_steps=val_steps)

输出:
在这里插入图片描述
画出训练损失和验证损失

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(loss))

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

输出:
在这里插入图片描述
MAE 约为 0.265(在开始显著过拟合之前),反标准化转换成温度的平均绝对误差为 2.35℃,这个结果可能仍有改进的空间。

4. 使用循环dropout来降低过拟合

从训练和验证曲线中可以明显看出,模型出现过拟合:几轮过后,训练损失和验证损失就开始显著偏离。我们已经学过降低过拟合的一种经典技术——dropout,即将某一层的输入单元随机设为 0,其目的是打破该层训练数据中的偶然相关性。但在循环网络中如何正确地使用dropout,这并不是一个简单的问题。人们早就知道,在循环层前面应用 dropout,这种正则化会妨碍学习过程,而不是有所帮助。2015 年,在关于贝叶斯深度学习的博士论文中 ,Yarin Gal 确定了在循环网络中使用 dropout 的正确方法:对每个时间步应该使用相同的 dropout 掩码(dropout mask,相同模式的舍弃单元),而不是让 dropout 掩码随着时间步的增加而随机变化。此外,为了对 GRU、LSTM 等循环层得到的表示做正则化,应该将不随时间变化的 dropout 掩码应用于层
的内部循环激活(叫作循环 dropout 掩码)。对每个时间步使用相同的 dropout 掩码,可以让网络沿着时间正确地传播其学习误差,而随时间随机变化的 dropout 掩码则会破坏这个误差信号,并且不利于学习过程。Yarin Gal 使用 Keras 开展这项研究,并帮助将这种机制直接内置到 Keras 循环层中。Keras的每个循环层都有两个与 dropout 相关的参数:一个是 dropout,它是一个浮点数,指定该层输入单元的 dropout 比率;另一个是 recurrent_dropout,指定循环单元的 dropout 比率。向 GRU 层中添dropout 和循环 dropout,看一下这么做对过拟合的影响。因为使用 dropout正则化的网络总是需要更长的时间才能完全收敛,所以网络训练轮次增加为原来的 2 倍。

#需要在GPU上训练
from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop

model = Sequential()
model.add(layers.GRU(32,
                     dropout=0.2,
                     recurrent_dropout=0.2,
                     input_shape=(None, float_data.shape[-1])))
model.add(layers.Dense(1))

model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,
                              steps_per_epoch=500,
                              epochs=40,
                              validation_data=val_gen,
                              validation_steps=val_steps)

输出:
在这里插入图片描述
画出训练损失和验证损失

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(loss))

plt.figure()

plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()

plt.show()

输出:
在这里插入图片描述
如上图所示,前 30 个轮次不再过拟合。

5. 循环层堆叠

模型不再过拟合,但似乎遇到了性能瓶颈,所以我们应该考虑增加网络容量。增加网络容量的通常做法是增加每层单元数或增加层数。循环层堆叠(recurrent layer stacking)是构建更加强大的循环网络的经典方法。

from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop

model = Sequential()
model.add(layers.GRU(32,
                     dropout=0.1,
                     recurrent_dropout=0.5,
                     return_sequences=True,
                     input_shape=(None, float_data.shape[-1])))
model.add(layers.GRU(64, activation='relu',
                     dropout=0.1, 
                     recurrent_dropout=0.5))
model.add(layers.Dense(1))
model.summary()
model.compile(optimizer=RMSprop(), loss='mae')
history = model.fit_generator(train_gen,
                              steps_per_epoch=500,
                              epochs=40,
                              validation_data=val_gen,
                              validation_steps=val_steps)

输出:
在这里插入图片描述
画出训练损失和验证损失
在这里插入图片描述

如上图所示,可以看到,添加一层的确对结果有所改进,但并不显著。我们可以得出两个结论。
1、因为过拟合仍然不是很严重,所以可以放心地增大每层的大小,以进一步改进验证损失。但这么做的计算成本很高。
2、添加一层后模型并没有显著改进,所以你可能发现,提高网络能力的回报在逐渐减小。
最后一种介绍一种方法叫作双向 RNN(bidirectional RNN)。双向 RNN 是一种常见的RNN 变体,它在某些任务上的性能比普通 RNN 更好。它常用于自然语言处理,可谓深度学习对自然语言处理的瑞士军刀。
RNN 特别依赖于顺序或时间,RNN 按顺序处理输入序列的时间步,而打乱时间步或反转时间步会完全改变 RNN 从序列中提取的表示。正是由于这个原因,如果顺序对问题很重要(比如温度预测问题),RNN 的表现会很好。双向 RNN 利用了 RNN 的顺序敏感性:它包含两个普通 RNN,比如 GRU 层和 LSTM 层,每个 RNN分别沿一个方向对输入序列进行处理(时间正序和时间逆序),然后将它们的表示合并在一起。通过沿这两个方向处理序列,双向RNN 能够捕捉到可能被单向 RNN 忽略的模式。感兴趣的可以自己去实践一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值