在上一篇文章中,我们使用了统计学中的移动平均方法来预测时间序列。
在这篇文章中,我们使用普通的神经网络和 RNN 来预测时间序列。
数据预处理
首先导入包:
1 2 3 4 import tensorflow as tf import numpy as np import matplotlib.pyplot as plt print(tf.__version__)
接着,生成序列数据:
1 2 3 dataset = tf.data.Dataset.range(10) for val in dataset: print(val.numpy())
输出如下:
使用 window
方法把序列分为窗口。
1 2 3 4 5 6 dataset = tf.data.Dataset.range(10) dataset = dataset.window(5, shift=1) for window_dataset in dataset: for val in window_dataset: print(val.numpy(), end=" ") print()
输出如下:
1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9 6 7 8 9 7 8 9 8 9 9
但是分割的数据中,后面的数据长度小于窗口长度,我们需要把这部分数据删除,设置 drop_remainder=True
。
1 2 3 4 5 6 dataset = tf.data.Dataset.range(10) dataset = dataset.window(5, shift=1, drop_remainder=True) for window_dataset in dataset: for val in window_dataset: print(val.numpy(), end=" ") print()
输出如下:
1 2 3 4 5 6 0 1 2 3 4 1 2 3 4 5 2 3 4 5 6 3 4 5 6 7 4 5 6 7 8 5 6 7 8 9
下一步,我们需要把每个窗口的数据,转换为一个 list。
1 2 3 4 5 dataset = tf.data.Dataset.range(10) dataset = dataset.window(5, shift=1, drop_remainder=True) dataset = dataset.flat_map(lambda window: window.batch(5)) for window in dataset: print(window.numpy())
输出如下:
1 2 3 4 5 6 [0 1 2 3 4] [1 2 3 4 5] [2 3 4 5 6] [3 4 5 6 7] [4 5 6 7 8] [5 6 7 8 9]
然后,把每个窗口前面的数据作为 x,后面最后一个数据作为 y:
1 2 3 4 5 6 dataset = tf.data.Dataset.range(10) dataset = dataset.window(5, shift=1, drop_remainder=True) dataset = dataset.flat_map(lambda window: window.batch(5)) dataset = dataset.map(lambda window: (window[:-1], window[-1:])) for x,y in dataset: print(x.numpy(), y.numpy())
输出如下:
1 2 3 4 5 6 [0 1 2 3] [4] [1 2 3 4] [5] [2 3 4 5] [6] [3 4 5 6] [7] [4 5 6 7] [8] [5 6 7 8] [9]
然后进行 shuffle
。
1 2 3 4 5 6 7 dataset = tf.data.Dataset.range(10) dataset = dataset.window(5, shift=1, drop_remainder=True) dataset = dataset.flat_map(lambda window: window.batch(5)) dataset = dataset.map(lambda window: (window[:-1], window[-1:])) dataset = dataset.shuffle(buffer_size=10) for x,y in dataset: print(x.numpy(), y.numpy())
输出如下:
1 2 3 4 5 6 [3 4 5 6] [7] [0 1 2 3] [4] [5 6 7 8] [9] [2 3 4 5] [6] [4 5 6 7] [8] [1 2 3 4] [5]
最后构建 batch。
1 2 3 4 5 6 7 8 9 10 dataset = tf.data.Dataset.range(10) dataset = dataset.window(5, shift=1, drop_remainder=True) dataset = dataset.flat_map(lambda window: window.batch(5)) dataset = dataset.map(lambda window: (window[:-1], window[-1:])) dataset = dataset.shuffle(buffer_size=10) # dataset = dataset.batch(2).prefetch(2) dataset = dataset.batch(2).prefetch(2) for x,y in dataset: print("x = ", x.numpy()) print("y = ", y.numpy())
输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 x = [[4 5 6 7] [1 2 3 4]] y = [[8] [5]] x = [[5 6 7 8] [3 4 5 6]] y = [[9] [7]] x = [[0 1 2 3] [2 3 4 5]] y = [[4] [6]]
一层神经网络
下面定义各种帮助的函数,并生成数据,划分训练集和验证集:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 def plot_series(time, series, format="-", start=0, end=None): plt.plot(time[start:end], series[start:end], format) plt.xlabel("Time") plt.ylabel("Value") plt.grid(True) def trend(time, slope=0): return slope * time def seasonal_pattern(season_time): """Just an arbitrary pattern, you can change it if you wish""" return np.where(season_time < 0.4, np.cos(season_time * 2 * np.pi), 1 / np.exp(3 * season_time)) def seasonality(time, period, amplitude=1, phase=0): """Repeats the same pattern at each period""" season_time = ((time + phase) % period) / period return amplitude * seasonal_pattern(season_time) def noise(time, noise_level=1, seed=None): rnd = np.random.RandomState(seed) return rnd.randn(len(time)) * noise_level time = np.arange(4 * 365 + 1, dtype="float32") baseline = 10 series = trend(time, 0.1) baseline = 10 amplitude = 40 slope = 0.05 noise_level = 5 # Create the series series = baseline + trend(time, slope) + seasonality(time, period=365, amplitude=amplitude) # Update with noise series += noise(time, noise_level, seed=42) # 划分训练集和验证集 split_time = 1000 time_train = time[:split_time] x_train = series[:split_time] time_valid = time[split_time:] x_valid = series[split_time:] # 设置超参数 window_size = 20 batch_size = 32 shuffle_buffer_size = 1000
将上面数据预处理的代码封装为一个函数:
1 2 3 4 5 6 7 def windowed_dataset(series, window_size, batch_size, shuffle_buffer): dataset = tf.data.Dataset.from_tensor_slices(series) dataset = dataset.window(window_size + 1, shift=1, drop_remainder=True) dataset = dataset.flat_map(lambda window: window.batch(window_size + 1)) dataset = dataset.shuffle(shuffle_buffer).map(lambda window: (window[:-1], window[-1])) dataset = dataset.batch(batch_size).prefetch(1) return dataset
下面进行数据预处理,并构建一个简单的只有一层的模型进行训练:
1 2 3 4 5 6 7 8 9 10 dataset = windowed_dataset(x_train, window_size, batch_size, shuffle_buffer_size) print(dataset) # 这里使用 l0 向量保存层,是为了下一步能够输出这层的权重 l0 = tf.keras.layers.Dense(1, input_shape=[window_size]) model = tf.keras.models.Sequential([l0]) model.compile(loss="mse", optimizer=tf.keras.optimizers.SGD(lr=1e-6, momentum=0.9)) model.fit(dataset,epochs=100,verbose=0) print("Layer weights {}".format(l0.get_weights()))
输出如下:
第一个 array
表示系数,第二个 array
只有一个数,表示 bias。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Layer weights [array([[-0.07050379], [ 0.01968649], [ 0.03672196], [ 0.08268202], [-0.07870597], [-0.01931396], [-0.04015162], [ 0.06981006], [ 0.03004084], [ 0.02451279], [-0.08928804], [ 0.00215667], [ 0.02927431], [ 0.01889405], [-0.06870657], [ 0.17018564], [-0.03161251], [ 0.19858538], [ 0.2544437 ], [ 0.44778714]], dtype=float32), array([0.01451531], dtype=float32)]
下面对验证集进行预测:
其中,series[time:time + window_size][np.newaxis]
是在前面增加一个 batch_size
维度,把形状为 [window_size]
的数据,变为 [1, window_size]
。
model.predict
的结果是一个2 维的 list,第一个维度是 batch_size
,第二个维度是输出神经元的个数,这里形状是 [1, 1]
。
因此 np.array(forecast)
是一个 3 维数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 forecast = [] for time in range(len(series) - window_size): forecast.append(model.predict(series[time:time + window_size][np.newaxis])) # 由于 forecast 比 series 少 window_size 个元素,因此这里取 split_time-window_size。 forecast = forecast[split_time-window_size:] # np.array(forecast) 的形状是:[length, 1,1] results = np.array(forecast)[:, 0, 0] plt.figure(figsize=(10, 6)) plot_series(time_valid, x_valid) plot_series(time_valid, results)
图显示如下:
计算 MAE 如下:
1 tf.keras.metrics.mean_absolute_error(x_valid, results).numpy()
把网络结构改为 2 层
1 2 3 4 5 6 7 8 9 10 11 dataset = windowed_dataset(x_train, window_size, batch_size, shuffle_buffer_size) model = tf.keras.models.Sequential([ tf.keras.layers.Dense(10, input_shape=[window_size], activation="relu"), tf.keras.layers.Dense(10, activation="relu"), tf.keras.layers.Dense(1) ]) model.compile(loss="mse", optimizer=tf.keras.optimizers.SGD(lr=1e-6, momentum=0.9)) model.fit(dataset,epochs=100,verbose=0)
计算得到的 MAE 是 5.004041。
寻找最佳学习率
接下来,使用 LearningRateScheduler
寻找最佳学习率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 dataset = windowed_dataset(x_train, window_size, batch_size, shuffle_buffer_size) model = tf.keras.models.Sequential([ tf.keras.layers.Dense(10, input_shape=[window_size], activation="relu"), tf.keras.layers.Dense(10, activation="relu"), tf.keras.layers.Dense(1) ]) lr_schedule = tf.keras.callbacks.LearningRateScheduler( lambda epoch: 1e-8 * 10**(epoch / 20)) optimizer = tf.keras.optimizers.SGD(lr=1e-8, momentum=0.9) model.compile(loss="mse", optimizer=optimizer) history = model.fit(dataset, epochs=100, callbacks=[lr_schedule], verbose=0)
画出 loss 曲线变化图,X 轴是学习率,Y 轴是 loss。
1 2 3 lrs = 1e-8 * (10 ** (np.arange(100) / 20)) plt.semilogx(lrs, history.history["loss"]) plt.axis([1e-8, 1e-3, 0, 300])
loss 曲线变化图如下所示:
可以看出,学习率大概在 \(8 \times 10^{-6}\) 时,比较平稳且低,因此下面是用学习率 \(8 \times 10^{-6}\) 进行训练。
1 2 3 4 5 6 7 8 9 10 11 12 window_size = 30 dataset = windowed_dataset(x_train, window_size, batch_size, shuffle_buffer_size) model = tf.keras.models.Sequential([ tf.keras.layers.Dense(10, activation="relu", input_shape=[window_size]), tf.keras.layers.Dense(10, activation="relu"), tf.keras.layers.Dense(1) ]) optimizer = tf.keras.optimizers.SGD(lr=8e-6, momentum=0.9) model.compile(loss="mse", optimizer=optimizer) history = model.fit(dataset, epochs=500, verbose=0)
画出loss 曲线变化图
1 2 3 4 loss = history.history['loss'] epochs = range(len(loss)) plt.plot(epochs, loss, 'b', label='Training Loss') plt.show()
loss 曲线变化图如下所示:
第一眼看上去,loss 在前面几个 epoch 之后就达到了收敛,后面的训练几乎没有下降。其实这有可能是因为前面几个 epoch 的 loss 尺度太大,导致后面 epoch 的 loss 看起来微乎其微。
我们下面看看去掉前面 10 个 epoch,再画出 loss 曲线变化图。
1 2 3 4 5 6 # Plot all but the first 10 loss = history.history['loss'] epochs = range(10, len(loss)) plot_loss = loss[10:] plt.plot(epochs, plot_loss, 'b', label='Training Loss') plt.show()
loss 曲线变化图如下所示:
可以看出,loss 的确是在震荡。
最后对验证集进行预测。
1 2 3 4 5 6 7 8 9 10 11 12 forecast = [] for time in range(len(series) - window_size): forecast.append(model.predict(series[time:time + window_size][np.newaxis])) forecast = forecast[split_time-window_size:] results = np.array(forecast)[:, 0, 0] plt.figure(figsize=(10, 6)) plot_series(time_valid, x_valid) plot_series(time_valid, results)
计算 MAE。
1 tf.keras.metrics.mean_absolute_error(x_valid, results).numpy()
得到结果为 5.2556496。
RNN
下面使用 RNN 来进行预测。
简介
首先来看下 RNN 的输入形状是 [batch_size, time_step, feature_dim]
。
假设输入的形状是 [4,30,1]
,表示 batch_size
为 4,时间序列长度为 30,特征维度是 1。那么在每个时间步的输入数据形状是 [batch_size, feature_dim]
(4,1),输出的数据形状是 [batch_size, units]
(4, 3),其中 units
表示这层 RNN 的神经元个数。所有数据拼接起来的输出形状是 [batch_size, time_step, units]
。
在普通的 RNN 中每个时间步输入的 state H
和上一个时间步的输出 Y
是一样的。
如果设置了 return_sequences=false
(这是 keras RNN 的默认参数),那么只会返回最后一个时间步的输出,形状是 [batch_size, units]
,每个时间序列被转换为一个长度为 units
的向量表示。
如果你有两层 RNN ,那么最后一层之前的 RNN 都需要设置 return_sequences=true
,因为所有时间步的输出,都需要输入到下一层 RNN。
如果我们使用 2 层 RNN。
那么第一层 RNN 需要设置 input_shape
,你可以设置 [batch_size, time_step, feature_dim]
,或者设置 [time_step, feature_dim]
。
比如下面设置 input_shape=[None,1]
,不包括 batch_size
,表示 batch_size
可以是任意的,而 time_step
为 None,表示 time_step
也是任意的,最后的 feature_dim
为 1。
1 2 3 4 5 model = tf.keras.models.Sequential([ tf.keras.layers.SimpleRNN(32, return_sequences=True, input_shape=[None,1]), tf.keras.layers.SimpleRNN(20), tf.keras.layers.Dense(1), ])
网络结构图如下所示:
如果我们把第 2 层 RNN 也设置 return_sequences=True
:
1 2 3 4 5 model = tf.keras.models.Sequential([ tf.keras.layers.SimpleRNN(32, return_sequences=True, input_shape=[None,1]), tf.keras.layers.SimpleRNN(20, return_sequences=True), tf.keras.layers.Dense(1), ])
那么结构如下所示:
同一个 Dense 层会应用到每个时间步,输出的数据长度和输入的数据长度是一样的,这种模型叫做 Sequence2Sequence Model
。
Lambda 层
keras 提供了 Lambda 层让我们实现任意的自定义层来扩展 keras 的能力。
这里,我们使用 Lambda 层来完成 2 个数据处理。
首先,在 windowed_dataset
函数中,我们返回的数据是 2 个维度的 [batch_size, time_step]
,而 RNN 的输入数据形状是 3 个维度 [batch_size, time_step, feature_dim]
。
因此我们在 RNN 层之前,使用 Lambda
层来扩展数据的维度,其中 input_shape=[None]
表示时间序列的长度 time_step
可以是任意的(而不是指 batch_size
)。
其次,我们的数据范围长度是 [-100, 100]
之间,而 RNN 层使用 Tanh
激活函数,输出的数据尺度是 [-1,1]
,大约相差 100 倍。因此,我们在最后添加一个 Lmabda
层,将所有数据乘以 100,这种做法有助于收敛。
1 2 3 4 5 6 7 8 model = tf.keras.models.Sequential([ tf.keras.layers.Lambda(lambda x: tf.expand_dims(x, axis=-1), input_shape=[None]), tf.keras.layers.SimpleRNN(32, return_sequences=True, input_shape=[None,1]), tf.keras.layers.SimpleRNN(20, return_sequences=True), tf.keras.layers.Dense(1), tf.keras.layers.Lambda(lambda x: x * 100.0) ])
实战
首先定义 RNN 模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 tf.keras.backend.clear_session() tf.random.set_seed(51) np.random.seed(51) train_set = windowed_dataset(x_train, window_size, batch_size=128, shuffle_buffer=shuffle_buffer_size) model = tf.keras.models.Sequential([ tf.keras.layers.Lambda(lambda x: tf.expand_dims(x, axis=-1), input_shape=[None]), tf.keras.layers.SimpleRNN(40, return_sequences=True), tf.keras.layers.SimpleRNN(40), tf.keras.layers.Dense(1), tf.keras.layers.Lambda(lambda x: x * 100.0) ])
然后寻找最佳学习率
1 2 3 4 5 6 7 8 9 lr_schedule = tf.keras.callbacks.LearningRateScheduler( lambda epoch: 1e-8 * 10**(epoch / 20)) optimizer = tf.keras.optimizers.SGD(lr=1e-8, momentum=0.9) model.compile(loss=tf.keras.losses.Huber(), optimizer=optimizer, metrics=["mae"]) history = model.fit(train_set, epochs=100, callbacks=[lr_schedule]) plt.semilogx(history.history["lr"], history.history["loss"]) plt.axis([1e-8, 1e-4, 0, 30])
loss 随着学习率变化图如下:
可以看出,在 学习率为 \(10^{-5}\) 时,loss 比较小且平稳,因此下面是用这个学习率来训练模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 tf.keras.backend.clear_session() tf.random.set_seed(51) np.random.seed(51) tf.keras.backend.clear_session() dataset = windowed_dataset(x_train, window_size, batch_size, shuffle_buffer_size) model = tf.keras.models.Sequential([ tf.keras.layers.Lambda(lambda x: tf.expand_dims(x, axis=-1), input_shape=[None]), tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32, return_sequences=True)), tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32)), tf.keras.layers.Dense(1), tf.keras.layers.Lambda(lambda x: x * 100.0) ]) model.compile(loss="mse", optimizer=tf.keras.optimizers.SGD(lr=1e-5, momentum=0.9),metrics=["mae"]) history = model.fit(dataset,epochs=500,verbose=0)
对验证集进行预测并画图:
1 2 3 4 5 6 7 8 9 10 11 12 forecast = [] results = [] for time in range(len(series) - window_size): forecast.append(model.predict(series[time:time + window_size][np.newaxis])) forecast = forecast[split_time-window_size:] results = np.array(forecast)[:, 0, 0] plt.figure(figsize=(10, 6)) plot_series(time_valid, x_valid) plot_series(time_valid, results)
计算 MAE
1 tf.keras.metrics.mean_absolute_error(x_valid, results).numpy()
MAE 为 8.514286。
可以看到,结果不如之前普通的神经网络,你可以尝试添加多层 RNN,并修改超参数来看看能不能提降低 MAE。
CNN
下面,使用 CNN 来构建模型。
首先定义模型预测的函数:
1 2 3 4 5 6 7 def model_forecast(model, series, window_size): ds = tf.data.Dataset.from_tensor_slices(series) ds = ds.window(window_size, shift=1, drop_remainder=True) ds = ds.flat_map(lambda w: w.batch(window_size)) ds = ds.batch(32).prefetch(1) forecast = model.predict(ds) return forecast
其中 input_shape=[30, 1]
表示输入的每个时间序列的形状是 [30, 1]
,也可以设置为 [None, 1]
。
注意最后一个 LSTM
层的 return_sequences=True
,意味着最后输出的数据形状和 input_shape
一样,也是 [30, 1]
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 tf.keras.backend.clear_session() tf.random.set_seed(51) np.random.seed(51) window_size = 30 train_set = windowed_dataset(x_train, window_size, batch_size=128, shuffle_buffer=shuffle_buffer_size) # padding="causal" 表示保持卷积后的时间序列长度不变 model = tf.keras.models.Sequential([ tf.keras.layers.Conv1D(filters=32, kernel_size=5, strides=1, padding="causal", activation="relu", input_shape=[30, 1]), tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32, return_sequences=True)), tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32, return_sequences=True)), tf.keras.layers.Dense(1), tf.keras.layers.Lambda(lambda x: x * 200) ]) model.summary()
模型结构如下,注意最后的输出的数据形状是 (None, 30, 1)
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv1d (Conv1D) (None, 30, 32) 192 _________________________________________________________________ bidirectional (Bidirectional (None, 30, 64) 16640 _________________________________________________________________ bidirectional_1 (Bidirection (None, 30, 64) 24832 _________________________________________________________________ dense (Dense) (None, 30, 1) 65 _________________________________________________________________ lambda (Lambda) (None, 30, 1) 0 ================================================================= Total params: 41,729 Trainable params: 41,729 Non-trainable params: 0 _________________________________________________________________
用不同的学习率训练模型,找出最佳学习率。
1 2 3 4 5 6 7 lr_schedule = tf.keras.callbacks.LearningRateScheduler( lambda epoch: 1e-8 * 10**(epoch / 20)) optimizer = tf.keras.optimizers.SGD(lr=1e-8, momentum=0.9) model.compile(loss=tf.keras.losses.Huber(), optimizer=optimizer, metrics=["mae"]) history = model.fit(train_set, epochs=100, callbacks=[lr_schedule])
画出 loss 随着学习率变化的曲线。
1 2 plt.semilogx(history.history["lr"], history.history["loss"]) plt.axis([1e-8, 1e-4, 0, 30])
最佳学习率是 \(10^{-5}\) ,下面使用这个学习率进行训练。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 tf.keras.backend.clear_session() tf.random.set_seed(51) np.random.seed(51) #batch_size = 16 dataset = windowed_dataset(x_train, window_size, batch_size, shuffle_buffer_size) model = tf.keras.models.Sequential([ tf.keras.layers.Conv1D(filters=32, kernel_size=3, strides=1, padding="causal", activation="relu", input_shape=[None, 1]), tf.keras.layers.LSTM(32, return_sequences=True), tf.keras.layers.LSTM(32, return_sequences=True), tf.keras.layers.Dense(1), tf.keras.layers.Lambda(lambda x: x * 200) ]) optimizer = tf.keras.optimizers.SGD(lr=1e-5, momentum=0.9) model.compile(loss=tf.keras.losses.Huber(), optimizer=optimizer, metrics=["mae"]) history = model.fit(dataset,epochs=500)
使用模型进行预测
1 2 3 4 5 # 添加一个 feature_dim 的维度 rnn_forecast = model_forecast(model, series[..., np.newaxis], window_size) # 由于模型输出的数据形状是 (1432, 30, 1),对应 [size, time_step, feature_dim] # 因此 time_step 取最后一个,feature_dim 取第一个 rnn_forecast = rnn_forecast[split_time - window_size:-1, -1, 0]
画出验证集的真实值和预测值
1 2 3 plt.figure(figsize=(10, 6)) plot_series(time_valid, x_valid) plot_series(time_valid, rnn_forecast)
计算 MAE:
1 tf.keras.metrics.mean_absolute_error(x_valid, rnn_forecast).numpy()
得到 MAE 是 4.963412。
真实案例
下面使用我们所学习的方法来完成 kaggle 上的太阳黑子数量预测,地址:https://www.kaggle.com/robervalt/sunspots 。
课程提供了数据集的下载地址:https://storage.googleapis.com/laurencemoroney-blog.appspot.com/Sunspots.csv 。
首先导入库:
1 2 import tensorflow as tf print(tf.__version__)
下载数据:
1 !wget --no-check-certificate https://storage.googleapis.com/laurencemoroney-blog.appspot.com/Sunspots.csv -O sunspots.csv
读取数据并进行可视化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import csv time_step = [] sunspots = [] with open('sunspots.csv') as csvfile: reader = csv.reader(csvfile, delimiter=',') next(reader) for row in reader: sunspots.append(float(row[2])) time_step.append(int(row[0])) series = np.array(sunspots) time = np.array(time_step) plt.figure(figsize=(10, 6)) plot_series(time, series)
如下所示:
划分训练集和验证集:
1 2 3 4 5 6 7 8 9 split_time = 3000 time_train = time[:split_time] x_train = series[:split_time] time_valid = time[split_time:] x_valid = series[split_time:] window_size = 30 batch_size = 32 shuffle_buffer_size = 1000
定义窗口划分函数和预测的工具函数。
这里把 添加一个 feature_dim 的维度
的功能放到了 windowed_dataset
函数里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def windowed_dataset(series, window_size, batch_size, shuffle_buffer): # 这里首先添加一个 feature_dim 的维度 series = tf.expand_dims(series, axis=-1) ds = tf.data.Dataset.from_tensor_slices(series) ds = ds.window(window_size + 1, shift=1, drop_remainder=True) ds = ds.flat_map(lambda w: w.batch(window_size + 1)) ds = ds.shuffle(shuffle_buffer) ds = ds.map(lambda w: (w[:-1], w[1:])) return ds.batch(batch_size).prefetch(1) def model_forecast(model, series, window_size): ds = tf.data.Dataset.from_tensor_slices(series) ds = ds.window(window_size, shift=1, drop_remainder=True) ds = ds.flat_map(lambda w: w.batch(window_size)) ds = ds.batch(32).prefetch(1) forecast = model.predict(ds) return forecast
定义模型并调整不同的学习率进行训练,这里注意 Lambda 层放大了 400 倍,因为原始数据尺度是 0 到 400。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 tf.keras.backend.clear_session() tf.random.set_seed(51) np.random.seed(51) window_size = 64 batch_size = 256 train_set = windowed_dataset(x_train, window_size, batch_size, shuffle_buffer_size) print(train_set) print(x_train.shape) model = tf.keras.models.Sequential([ tf.keras.layers.Conv1D(filters=32, kernel_size=5, strides=1, padding="causal", activation="relu", input_shape=[None, 1]), tf.keras.layers.LSTM(64, return_sequences=True), tf.keras.layers.LSTM(64, return_sequences=True), tf.keras.layers.Dense(30, activation="relu"), tf.keras.layers.Dense(10, activation="relu"), tf.keras.layers.Dense(1), # 这里注意 Lambda 层放大了 400 倍,因为原始数据尺度是 0 到 400 tf.keras.layers.Lambda(lambda x: x * 400) ]) lr_schedule = tf.keras.callbacks.LearningRateScheduler( lambda epoch: 1e-8 * 10**(epoch / 20)) optimizer = tf.keras.optimizers.SGD(lr=1e-8, momentum=0.9) model.compile(loss=tf.keras.losses.Huber(), optimizer=optimizer, metrics=["mae"]) history = model.fit(train_set, epochs=100, callbacks=[lr_schedule])
画出 loss 随着学习率变化的曲线图。
最佳学习率是 \(10^{-5}\) 。
下面使用这个学习率进行训练:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 tf.keras.backend.clear_session() tf.random.set_seed(51) np.random.seed(51) train_set = windowed_dataset(x_train, window_size=60, batch_size=100, shuffle_buffer=shuffle_buffer_size) model = tf.keras.models.Sequential([ tf.keras.layers.Conv1D(filters=60, kernel_size=5, strides=1, padding="causal", activation="relu", input_shape=[None, 1]), tf.keras.layers.LSTM(60, return_sequences=True), tf.keras.layers.LSTM(60, return_sequences=True), tf.keras.layers.Dense(30, activation="relu"), tf.keras.layers.Dense(10, activation="relu"), tf.keras.layers.Dense(1), tf.keras.layers.Lambda(lambda x: x * 400) ]) optimizer = tf.keras.optimizers.SGD(lr=1e-5, momentum=0.9) model.compile(loss=tf.keras.losses.Huber(), optimizer=optimizer, metrics=["mae"]) history = model.fit(train_set,epochs=500)
进行预测:
1 2 rnn_forecast = model_forecast(model, series[..., np.newaxis], window_size) rnn_forecast = rnn_forecast[split_time - window_size:-1, -1, 0]
画出验证集的真实值和预测值:
1 2 3 plt.figure(figsize=(10, 6)) plot_series(time_valid, x_valid) plot_series(time_valid, rnn_forecast)
计算 MAE
1 tf.keras.metrics.mean_absolute_error(x_valid, rnn_forecast).numpy()
得到 MAE 是 15.40074。